diff --git a/vm/array.go b/vm/array.go index 88cd529fa..828e2d8af 100644 --- a/vm/array.go +++ b/vm/array.go @@ -21,1470 +21,1466 @@ type ArrayObject struct { } // Class methods -------------------------------------------------------- -func builtinArrayClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitNoMethodError(sourceLine, "new", receiver) - }, +var builtinArrayClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitNoMethodError(sourceLine, "new", receiver) }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinArrayInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Retrieves an object in an array using Integer index. - // The index starts from 0. It returns `null` if the given index is bigger than its size. - // - // ```ruby - // a = [1, 2, 3, "a", "b", "c"] - // a[0] #=> 1 - // a[3] #=> "a" - // a[10] #=> nil - // a[-1] #=> "c" - // a[-3] #=> "a" - // a[-7] #=> nil - // - // # Double indexing, second argument specifies the count of the elements - // a[1, 3] #=> [2, 3, "a"] - // a[1, 0] #=> [] <-- Zero count is empty - // a[1, 5] #=> [2, 3, "a", "b", "c"] - // a[1, 10] #=> [2, 3, "a", "b", "c"] - // a[-3, 2] #=> ["a", "b"] - // a[-3, 5] #=> ["a", "b", "c"] - // a[5, 1] #=> ["c"] - // a[6, 1] #=> [] - // a[7, 1] #=> nil - // - // Special case 1: - // a[6] #=> nil - // a[6, 1] #=> [] <-- Not nil! - // a[7, 1] #=> nil <-- Because it is really out of the edge of the array - // - // Special case 2: Second argument is negative - // This behaviour is different from Ruby itself, in Ruby, it returns "nil". - // However, in Goby, it raises error because there cannot be negative count values. - // - // a[1, -1] #=> ArgumentError: Expect second argument to be positive value. got: -1 - // a[-4, -3] #=> ArgumentError: Expect second argument to be positive value. got: -3 - // - // Special case 3: First argument is negative and exceed the array length - // a[-6, 1] #=> [1] - // a[-6, 0] #=> [] - // a[-7, 1] #=> ArgumentError: Index value -7 too small for array. minimum: -6 - // a[-7, 0] #=> ArgumentError: Index value -7 too small for array. minimum: -6 - // ``` - // - // Note: - // * The notations such as `a.[](1)` or `a.[] 1` are unsupported. - // * `Range` object is unsupported for now. - // - // @param index [Integer], (count [Integer]) - // @return [Array] - Name: "[]", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - arr := receiver.(*ArrayObject) - return arr.index(t, args, sourceLine) - }, +var builtinArrayInstanceMethods = []*BuiltinMethodObject{ + { + // Retrieves an object in an array using Integer index. + // The index starts from 0. It returns `null` if the given index is bigger than its size. + // + // ```ruby + // a = [1, 2, 3, "a", "b", "c"] + // a[0] #=> 1 + // a[3] #=> "a" + // a[10] #=> nil + // a[-1] #=> "c" + // a[-3] #=> "a" + // a[-7] #=> nil + // + // # Double indexing, second argument specifies the count of the elements + // a[1, 3] #=> [2, 3, "a"] + // a[1, 0] #=> [] <-- Zero count is empty + // a[1, 5] #=> [2, 3, "a", "b", "c"] + // a[1, 10] #=> [2, 3, "a", "b", "c"] + // a[-3, 2] #=> ["a", "b"] + // a[-3, 5] #=> ["a", "b", "c"] + // a[5, 1] #=> ["c"] + // a[6, 1] #=> [] + // a[7, 1] #=> nil + // + // Special case 1: + // a[6] #=> nil + // a[6, 1] #=> [] <-- Not nil! + // a[7, 1] #=> nil <-- Because it is really out of the edge of the array + // + // Special case 2: Second argument is negative + // This behaviour is different from Ruby itself, in Ruby, it returns "nil". + // However, in Goby, it raises error because there cannot be negative count values. + // + // a[1, -1] #=> ArgumentError: Expect second argument to be positive value. got: -1 + // a[-4, -3] #=> ArgumentError: Expect second argument to be positive value. got: -3 + // + // Special case 3: First argument is negative and exceed the array length + // a[-6, 1] #=> [1] + // a[-6, 0] #=> [] + // a[-7, 1] #=> ArgumentError: Index value -7 too small for array. minimum: -6 + // a[-7, 0] #=> ArgumentError: Index value -7 too small for array. minimum: -6 + // ``` + // + // Note: + // * The notations such as `a.[](1)` or `a.[] 1` are unsupported. + // * `Range` object is unsupported for now. + // + // @param index [Integer], (count [Integer]) + // @return [Array] + Name: "[]", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + arr := receiver.(*ArrayObject) + return arr.index(t, args, sourceLine) }, - { - // Repetition β€” returns a new array built by just concatenating the specified number of copies of `self`. - // - // ```ruby - // a = [1, 2, 3] - // a * 2 #=> [1, 2, 3, 1, 2, 3] - // ``` - // - // * The index should be a positive or zero Integer object. - // * Ruby's syntax such as `[1, 2, 3] * ','` are unsupported. Use `#join` instead. - // - // @param zero or positive integer [Integer] - // @return [Array] - Name: "*", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Repetition β€” returns a new array built by just concatenating the specified number of copies of `self`. + // + // ```ruby + // a = [1, 2, 3] + // a * 2 #=> [1, 2, 3, 1, 2, 3] + // ``` + // + // * The index should be a positive or zero Integer object. + // * Ruby's syntax such as `[1, 2, 3] * ','` are unsupported. Use `#join` instead. + // + // @param zero or positive integer [Integer] + // @return [Array] + Name: "*", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - arr := receiver.(*ArrayObject) + arr := receiver.(*ArrayObject) - copiesNumber, ok := args[0].(*IntegerObject) + copiesNumber, ok := args[0].(*IntegerObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) + } - return arr.concatenateCopies(t, copiesNumber) + return arr.concatenateCopies(t, copiesNumber) - }, }, - { - // Concatenation: returns a new array by just concatenating the two arrays. - // - // ```ruby - // a = [1, 2] - // b + [3, 4] #=> [1, 2, 3, 4] - // ``` - // - // @param array [Array] - // @return [Array] - Name: "+", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Concatenation: returns a new array by just concatenating the two arrays. + // + // ```ruby + // a = [1, 2] + // b + [3, 4] #=> [1, 2, 3, 4] + // ``` + // + // @param array [Array] + // @return [Array] + Name: "+", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - otherArrayArg := args[0] - otherArray, ok := otherArrayArg.(*ArrayObject) + otherArrayArg := args[0] + otherArray, ok := otherArrayArg.(*ArrayObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.ArrayClass, args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.ArrayClass, args[0].Class().Name) + } - selfArray := receiver.(*ArrayObject) + selfArray := receiver.(*ArrayObject) - newArrayElements := append(selfArray.Elements, otherArray.Elements...) + newArrayElements := append(selfArray.Elements, otherArray.Elements...) - newArray := t.vm.InitArrayObject(newArrayElements) + newArray := t.vm.InitArrayObject(newArrayElements) - return newArray - }, + return newArray }, - { - // Assigns one or more values to an array. It requires one or two indices and a value as argument. - // The first index should be Integer, and the second index should be zero or positive integer. - // The array will expand if the assigned index is bigger than the current size of self. - // Returns the assigned value. - // The gaps will be filled with `nil`, but such operations should be avoided. - // - // ```ruby - // a = [] - // a[0] = 10 #=> 10 - // a[3] = 20 #=> 20 - // a #=> [10, nil, nil, 20] - // a[-2] = 5 #=> [10, nil, 5, 20] - // - // # Double indexing, second argument specify the count of the arguments - // a = [1, 2, 3, 4, 5] - // a[2, 3] = [:a, :b, :c] # <-- Common case: overridden - // a #=> [1, 2, "a", "b", "c"] - // - // a = [1, 2, 3, 4, 5] - // a[4, 4] = [:a, :b, :c] # <- Exceeded case: the array will be expanded and `5` will be overridden - // a #=> [1, 2, 3, 4, "a", "b", "c"] - // - // a = [1, 2, 3, 4, 5] - // a[5, 1] = [:a, :b, :c] # <-- Edge case: insertion - // a #=> [1, 2, 3, 4, 5, "a", "b", "c"] - // - // a = [1, 2, 3, 4, 5] - // a[8, 123] = [:a, :b, :c] # <-- Weak array case: the gaps will be filled with `nil` but the tailing ones not - // a #=> [1, 2, 3, 4, 5, nil, nil, nil, "a", "b", "c"] - // - // a = [1, 2, 3, 4, 5] - // a[3, 0] = [:a, :b, :c] # <-- Insertion case: the second index `0` is to insert there - // a #=> [1, 2, 3, "a", "b", "c", 4, 5] - // - // a = [1, 2, 3, 4, 5] - // a[0, 3] = 12345 # <-- Assign non-array value case - // a #=> [12345, 4, 5] - // - // a = [1, 2, 3, 4, 5] - // a[-3, 2] = [:a, :b, :c] # <-- Negative index assign case - // a #=> [1, 2, "a", "b", "c", 5] - // - // a = [1, 2, 3, 4, 5] - // a[-5, 3] = [:a, :b, :c] # <-- Negative index edge case - // a #=> ["a", "b", "c", 4, 5] - // - // a = [1, 2, 3, 4, 5] - // a[-5, 4] = [:a, :b, :c] # <-- Negative index exceeded case: `4` will be destroyed - // a #=> ["a", "b", "c", 5] - // - // a = [1, 2, 3, 4, 5] - // a[-5, 5] = [:a, :b, :c] # <-- Negative index exceeded case: `4, 5` will be destroyed - // a #=> ["a", "b", "c"] - // - // a = [1, 2, 3, 4, 5] - // a[-6, 4] = [:a, :b, :c] # <-- Invalid: Negative index too small case - // # ArgumentError: Index value -6 too small for array. minimum: -5 - // - // a = [1, 2, 3, 4, 5] - // a[6, -4] = [9, 8, 7] # <-- Weak array assignment with negative count case - // # ArgumentError: Expect second argument to be positive. got: -4 - // ``` - // - // Note that passing multiple values to the method is unavailable. - // - // @param index [Integer], object [Object] - // @param index [Integer], count [Integer], object [Object] - // @return [Array] - Name: "[]=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - // First argument is an index: there exists two cases which will be described in the following code - aLen := len(args) - if aLen < 2 || aLen > 3 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 2, 3, aLen) + }, + { + // Assigns one or more values to an array. It requires one or two indices and a value as argument. + // The first index should be Integer, and the second index should be zero or positive integer. + // The array will expand if the assigned index is bigger than the current size of self. + // Returns the assigned value. + // The gaps will be filled with `nil`, but such operations should be avoided. + // + // ```ruby + // a = [] + // a[0] = 10 #=> 10 + // a[3] = 20 #=> 20 + // a #=> [10, nil, nil, 20] + // a[-2] = 5 #=> [10, nil, 5, 20] + // + // # Double indexing, second argument specify the count of the arguments + // a = [1, 2, 3, 4, 5] + // a[2, 3] = [:a, :b, :c] # <-- Common case: overridden + // a #=> [1, 2, "a", "b", "c"] + // + // a = [1, 2, 3, 4, 5] + // a[4, 4] = [:a, :b, :c] # <- Exceeded case: the array will be expanded and `5` will be overridden + // a #=> [1, 2, 3, 4, "a", "b", "c"] + // + // a = [1, 2, 3, 4, 5] + // a[5, 1] = [:a, :b, :c] # <-- Edge case: insertion + // a #=> [1, 2, 3, 4, 5, "a", "b", "c"] + // + // a = [1, 2, 3, 4, 5] + // a[8, 123] = [:a, :b, :c] # <-- Weak array case: the gaps will be filled with `nil` but the tailing ones not + // a #=> [1, 2, 3, 4, 5, nil, nil, nil, "a", "b", "c"] + // + // a = [1, 2, 3, 4, 5] + // a[3, 0] = [:a, :b, :c] # <-- Insertion case: the second index `0` is to insert there + // a #=> [1, 2, 3, "a", "b", "c", 4, 5] + // + // a = [1, 2, 3, 4, 5] + // a[0, 3] = 12345 # <-- Assign non-array value case + // a #=> [12345, 4, 5] + // + // a = [1, 2, 3, 4, 5] + // a[-3, 2] = [:a, :b, :c] # <-- Negative index assign case + // a #=> [1, 2, "a", "b", "c", 5] + // + // a = [1, 2, 3, 4, 5] + // a[-5, 3] = [:a, :b, :c] # <-- Negative index edge case + // a #=> ["a", "b", "c", 4, 5] + // + // a = [1, 2, 3, 4, 5] + // a[-5, 4] = [:a, :b, :c] # <-- Negative index exceeded case: `4` will be destroyed + // a #=> ["a", "b", "c", 5] + // + // a = [1, 2, 3, 4, 5] + // a[-5, 5] = [:a, :b, :c] # <-- Negative index exceeded case: `4, 5` will be destroyed + // a #=> ["a", "b", "c"] + // + // a = [1, 2, 3, 4, 5] + // a[-6, 4] = [:a, :b, :c] # <-- Invalid: Negative index too small case + // # ArgumentError: Index value -6 too small for array. minimum: -5 + // + // a = [1, 2, 3, 4, 5] + // a[6, -4] = [9, 8, 7] # <-- Weak array assignment with negative count case + // # ArgumentError: Expect second argument to be positive. got: -4 + // ``` + // + // Note that passing multiple values to the method is unavailable. + // + // @param index [Integer], object [Object] + // @param index [Integer], count [Integer], object [Object] + // @return [Array] + Name: "[]=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + + // First argument is an index: there exists two cases which will be described in the following code + aLen := len(args) + if aLen < 2 || aLen > 3 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 2, 3, aLen) + } + + i := args[0] + index, ok := i.(*IntegerObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) + } + + indexValue := index.value + arr := receiver.(*ArrayObject) + + // + // Second argument: the length of successive array values (zero or positive Integer) + // Third argument: the assignment value (object) + if aLen == 3 { + // Negative index value too small + if indexValue < 0 { + if arr.normalizeIndex(index) == -1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.TooSmallIndexValue, indexValue, -arr.Len()) + } + indexValue = arr.normalizeIndex(index) } - i := args[0] - index, ok := i.(*IntegerObject) + c := args[1] + count, ok := c.(*IntegerObject) + // Second argument must be an integer if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[1].Class().Name) } - indexValue := index.value - arr := receiver.(*ArrayObject) - - // - // Second argument: the length of successive array values (zero or positive Integer) - // Third argument: the assignment value (object) - if aLen == 3 { - // Negative index value too small - if indexValue < 0 { - if arr.normalizeIndex(index) == -1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.TooSmallIndexValue, indexValue, -arr.Len()) - } - indexValue = arr.normalizeIndex(index) - } - - c := args[1] - count, ok := c.(*IntegerObject) - - // Second argument must be an integer - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[1].Class().Name) - } - - countValue := count.value - // Second argument must be a positive value - if countValue < 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.NegativeSecondValue, count.value) - } - - a := args[2] - assignedValue, isArray := a.(*ArrayObject) - - // Expand the array with nil; the second index is unnecessary in the case - if indexValue >= arr.Len() { + countValue := count.value + // Second argument must be a positive value + if countValue < 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.NegativeSecondValue, count.value) + } - for arr.Len() < indexValue { - arr.Elements = append(arr.Elements, NULL) - } + a := args[2] + assignedValue, isArray := a.(*ArrayObject) - if isArray { - arr.Elements = append(arr.Elements, assignedValue.Elements...) - } else { - arr.Elements = append(arr.Elements, a) - } - return a - } + // Expand the array with nil; the second index is unnecessary in the case + if indexValue >= arr.Len() { - endValue := indexValue + countValue - // the case the addition of index and count is too large - if endValue > arr.Len() { - endValue = arr.Len() + for arr.Len() < indexValue { + arr.Elements = append(arr.Elements, NULL) } - arr.Elements = append(arr.Elements[:indexValue], arr.Elements[endValue:]...) - - // If assigned value is an array, then splat the array and push each element to the receiver - // following the first and second indices if isArray { - arr.Elements = append(arr.Elements[:indexValue], append(assignedValue.Elements, arr.Elements[indexValue:]...)...) + arr.Elements = append(arr.Elements, assignedValue.Elements...) } else { - arr.Elements = append(arr.Elements[:indexValue], append([]Object{a}, arr.Elements[indexValue:]...)...) + arr.Elements = append(arr.Elements, a) } - return a } - // - // Second argument is the assignment value (object) - - // Negative index value condition - if indexValue < 0 { - if len(arr.Elements) < -indexValue { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.TooSmallIndexValue, indexValue, -arr.Len()) - } - arr.Elements[len(arr.Elements)+indexValue] = args[1] - return arr.Elements[len(arr.Elements)+indexValue] + endValue := indexValue + countValue + // the case the addition of index and count is too large + if endValue > arr.Len() { + endValue = arr.Len() } - // Expand the array - if len(arr.Elements) < (indexValue + 1) { - newArr := make([]Object, indexValue+1) - copy(newArr, arr.Elements) - for i := len(arr.Elements); i <= indexValue; i++ { - newArr[i] = NULL - } - arr.Elements = newArr + arr.Elements = append(arr.Elements[:indexValue], arr.Elements[endValue:]...) + + // If assigned value is an array, then splat the array and push each element to the receiver + // following the first and second indices + if isArray { + arr.Elements = append(arr.Elements[:indexValue], append(assignedValue.Elements, arr.Elements[indexValue:]...)...) + } else { + arr.Elements = append(arr.Elements[:indexValue], append([]Object{a}, arr.Elements[indexValue:]...)...) } - arr.Elements[indexValue] = args[1] + return a + } - return arr.Elements[indexValue] + // + // Second argument is the assignment value (object) - }, - }, - { - // A predicate method. - // Evaluates the given block and returns `true` if the block ever returns a value. - // Returns `false` if the evaluated block returns `false` or `nil`. - // - // ```ruby - // a = [1, 2, 3] - // - // a.any? do |e| - // e == 2 - // end #=> true - // a.any? do |e| - // e - // end #=> true - // a.any? do |e| - // e == 5 - // end #=> false - // a.any? do |e| - // nil - // end #=> false - // - // a = [] - // - // a.any? do |e| - // true - // end #=> false - // ``` - // - // @param block [Block] - // @return [Boolean] - Name: "any?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - arr := receiver.(*ArrayObject) - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + // Negative index value condition + if indexValue < 0 { + if len(arr.Elements) < -indexValue { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.TooSmallIndexValue, indexValue, -arr.Len()) } + arr.Elements[len(arr.Elements)+indexValue] = args[1] + return arr.Elements[len(arr.Elements)+indexValue] + } - if blockIsEmpty(blockFrame) { - return FALSE + // Expand the array + if len(arr.Elements) < (indexValue + 1) { + newArr := make([]Object, indexValue+1) + copy(newArr, arr.Elements) + for i := len(arr.Elements); i <= indexValue; i++ { + newArr[i] = NULL } + arr.Elements = newArr + } - if len(arr.Elements) == 0 { - t.callFrameStack.pop() - } + arr.Elements[indexValue] = args[1] - for _, obj := range arr.Elements { - result := t.builtinMethodYield(blockFrame, obj) + return arr.Elements[indexValue] - if result.Target.isTruthy() { - return TRUE - } - } + }, + }, + { + // A predicate method. + // Evaluates the given block and returns `true` if the block ever returns a value. + // Returns `false` if the evaluated block returns `false` or `nil`. + // + // ```ruby + // a = [1, 2, 3] + // + // a.any? do |e| + // e == 2 + // end #=> true + // a.any? do |e| + // e + // end #=> true + // a.any? do |e| + // e == 5 + // end #=> false + // a.any? do |e| + // nil + // end #=> false + // + // a = [] + // + // a.any? do |e| + // true + // end #=> false + // ``` + // + // @param block [Block] + // @return [Boolean] + Name: "any?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + arr := receiver.(*ArrayObject) + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + if blockIsEmpty(blockFrame) { return FALSE + } - }, - }, - { - // Retrieves an object in an array using the given index. - // The index is 0-based; `nil` is returned when trying to access the index out of bounds. - // - // ```ruby - // a = [1, 2, 3] - // a.at(0) #=> 1 - // a.at(10) #=> nil - // a.at(-2) #=> 2 - // a.at(-4) #=> nil - // ``` - // - // @param index [Integer] - // @return [Object] - Name: "at", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + if len(arr.Elements) == 0 { + t.callFrameStack.pop() + } - arr := receiver.(*ArrayObject) - return arr.index(t, args, sourceLine) + for _, obj := range arr.Elements { + result := t.builtinMethodYield(blockFrame, obj) - }, - }, - { - // Removes all elements in the array and returns an empty array. - // - // ```ruby - // a = [1, 2, 3] - // a.clear #=> [] - // a #=> [] - // ``` - // - // @return [Array] - Name: "clear", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + if result.Target.isTruthy() { + return TRUE } + } - arr := receiver.(*ArrayObject) - arr.Elements = []Object{} + return FALSE - return arr - }, }, - { - // Concatenation: returns a new array by just concatenating the arrays. - // Empty or multiple arrays can be taken. - // - // ```ruby - // a = [1, 2, 3] - // a.concat([4, 5, 6]) - // a #=> [1, 2, 3, 4, 5, 6] - // - // [1, 2, 3].concat([]) #=> [1, 2, 3] - // - // [1, 2, 3].concat([4, 5], [6, 7], []) #=> [1, 2, 3, 4, 5, 6, 7] - // ``` - // - // @param array [Array] - // @return [Array] - Name: "concat", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - arr := receiver.(*ArrayObject) - - for _, arg := range args { - addAr, ok := arg.(*ArrayObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.ArrayClass, arg.Class().Name) - } - - for _, el := range addAr.Elements { - arr.Elements = append(arr.Elements, el) - } - } + }, + { + // Retrieves an object in an array using the given index. + // The index is 0-based; `nil` is returned when trying to access the index out of bounds. + // + // ```ruby + // a = [1, 2, 3] + // a.at(0) #=> 1 + // a.at(10) #=> nil + // a.at(-2) #=> 2 + // a.at(-4) #=> nil + // ``` + // + // @param index [Integer] + // @return [Object] + Name: "at", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - return arr + arr := receiver.(*ArrayObject) + return arr.index(t, args, sourceLine) - }, }, - { - // If no block is given, just returns the count of the elements within the array. - // If a block is given, evaluate each element of the array by the given block, - // and then return the count of elements that return `true` by the block. - // - // ```ruby - // a = [1, 2, 3, 4, 5] - // - // a.count do |e| - // e * 2 > 3 - // end - // #=> 4 - // ``` - // - // @param - // @param block [Block] - // @return [Integer] - Name: "count", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen > 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) - } + }, + { + // Removes all elements in the array and returns an empty array. + // + // ```ruby + // a = [1, 2, 3] + // a.clear #=> [] + // a #=> [] + // ``` + // + // @return [Array] + Name: "clear", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - arr := receiver.(*ArrayObject) - var count int - if blockFrame != nil { - if blockIsEmpty(blockFrame) { - return t.vm.InitIntegerObject(0) - } - if len(arr.Elements) == 0 { - t.callFrameStack.pop() - } + arr := receiver.(*ArrayObject) + arr.Elements = []Object{} - for _, obj := range arr.Elements { - result := t.builtinMethodYield(blockFrame, obj) - if result.Target.isTruthy() { - count++ - } - } + return arr + }, + }, + { + // Concatenation: returns a new array by just concatenating the arrays. + // Empty or multiple arrays can be taken. + // + // ```ruby + // a = [1, 2, 3] + // a.concat([4, 5, 6]) + // a #=> [1, 2, 3, 4, 5, 6] + // + // [1, 2, 3].concat([]) #=> [1, 2, 3] + // + // [1, 2, 3].concat([4, 5], [6, 7], []) #=> [1, 2, 3, 4, 5, 6, 7] + // ``` + // + // @param array [Array] + // @return [Array] + Name: "concat", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + arr := receiver.(*ArrayObject) - return t.vm.InitIntegerObject(count) - } + for _, arg := range args { + addAr, ok := arg.(*ArrayObject) - if aLen == 0 { - return t.vm.InitIntegerObject(len(arr.Elements)) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.ArrayClass, arg.Class().Name) } - arg := args[0] - findInt, findIsInt := arg.(*IntegerObject) - findString, findIsString := arg.(*StringObject) - findBoolean, findIsBoolean := arg.(*BooleanObject) - - for i := 0; i < len(arr.Elements); i++ { - el := arr.Elements[i] - switch el := el.(type) { - case *IntegerObject: - if findIsInt && findInt.equal(el) { - count++ - } - case *StringObject: - if findIsString && findString.equal(el) { - count++ - } - case *BooleanObject: - if findIsBoolean && findBoolean.equal(el) { - count++ - } - } + for _, el := range addAr.Elements { + arr.Elements = append(arr.Elements, el) } + } - return t.vm.InitIntegerObject(count) + return arr - }, }, - { - // Deletes the element pointed by the given index. - // Returns the removed element. - // The method is destructive and the self is mutated. - // The index is 0-based; `nil` is returned when using an out-of-bounds index. - // - // ```ruby - // a = ["a", "b", "c"] - // a.delete_at(1) #=> "b" - // a.delete_at(-1) #=> "c" - // a #=> ["a"] - // ``` - // - // @param index [Integer] - // @return [Object] - Name: "delete_at", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + }, + { + // If no block is given, just returns the count of the elements within the array. + // If a block is given, evaluate each element of the array by the given block, + // and then return the count of elements that return `true` by the block. + // + // ```ruby + // a = [1, 2, 3, 4, 5] + // + // a.count do |e| + // e * 2 > 3 + // end + // #=> 4 + // ``` + // + // @param + // @param block [Block] + // @return [Integer] + Name: "count", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen > 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + } + + arr := receiver.(*ArrayObject) + var count int + if blockFrame != nil { + if blockIsEmpty(blockFrame) { + return t.vm.InitIntegerObject(0) } - - i := args[0] - index, ok := i.(*IntegerObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) + if len(arr.Elements) == 0 { + t.callFrameStack.pop() } - arr := receiver.(*ArrayObject) - normalizedIndex := arr.normalizeIndex(index) + for _, obj := range arr.Elements { + result := t.builtinMethodYield(blockFrame, obj) + if result.Target.isTruthy() { + count++ + } + } - if normalizedIndex == -1 { - return NULL + return t.vm.InitIntegerObject(count) + } + + if aLen == 0 { + return t.vm.InitIntegerObject(len(arr.Elements)) + } + + arg := args[0] + findInt, findIsInt := arg.(*IntegerObject) + findString, findIsString := arg.(*StringObject) + findBoolean, findIsBoolean := arg.(*BooleanObject) + + for i := 0; i < len(arr.Elements); i++ { + el := arr.Elements[i] + switch el := el.(type) { + case *IntegerObject: + if findIsInt && findInt.equal(el) { + count++ + } + case *StringObject: + if findIsString && findString.equal(el) { + count++ + } + case *BooleanObject: + if findIsBoolean && findBoolean.equal(el) { + count++ + } } + } - // delete and slice + return t.vm.InitIntegerObject(count) - deletedValue := arr.Elements[normalizedIndex] + }, + }, + { + // Deletes the element pointed by the given index. + // Returns the removed element. + // The method is destructive and the self is mutated. + // The index is 0-based; `nil` is returned when using an out-of-bounds index. + // + // ```ruby + // a = ["a", "b", "c"] + // a.delete_at(1) #=> "b" + // a.delete_at(-1) #=> "c" + // a #=> ["a"] + // ``` + // + // @param index [Integer] + // @return [Object] + Name: "delete_at", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - arr.Elements = append(arr.Elements[:normalizedIndex], arr.Elements[normalizedIndex+1:]...) + i := args[0] + index, ok := i.(*IntegerObject) - return deletedValue + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) + } - }, - }, - { - // Returns the value from the nested array, specified by one or more indices, - // Returns `nil` if one of the intermediate values are `nil`. - // - // ```Ruby - // [1 , 2].dig(-2) #=> 1 - // [[], 2].dig(0, 1) #=> nil - // [[], 2].dig(0, 1, 2) #=> nil - // [[1, 2, [3, [8, [9]]]], 4, 5].dig(0, 2, 1, 1, 0) #=> 9 - // [1, 2].dig(0, 1) #=> TypeError: Expect target to be Diggable - // ``` - // - // @param index [Integer]... - // @return [Object] - Name: "dig", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) < 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, len(args)) - } + arr := receiver.(*ArrayObject) + normalizedIndex := arr.normalizeIndex(index) - array := receiver.(*ArrayObject) - value := array.dig(t, args, sourceLine) + if normalizedIndex == -1 { + return NULL + } - return value + // delete and slice - }, - }, - { - // Loops through each element in the array, with the given block. - // Returns self. - // A block literal is required. - // - // ```ruby - // a = ["a", "b", "c"] - // - // b = a.each do |e| - // puts(e + e) - // end - // #=> "aa" - // #=> "bb" - // #=> "cc" - // puts b - // #=> ["a", "b", "c"] - // ``` - // - // @param block literal - // @return [Array] - Name: "each", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + deletedValue := arr.Elements[normalizedIndex] - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } + arr.Elements = append(arr.Elements[:normalizedIndex], arr.Elements[normalizedIndex+1:]...) - arr := receiver.(*ArrayObject) - if blockIsEmpty(blockFrame) { - return arr - } + return deletedValue - // If it's an empty array, pop the block's call frame - if len(arr.Elements) == 0 { - t.callFrameStack.pop() - } + }, + }, + { + // Returns the value from the nested array, specified by one or more indices, + // Returns `nil` if one of the intermediate values are `nil`. + // + // ```Ruby + // [1 , 2].dig(-2) #=> 1 + // [[], 2].dig(0, 1) #=> nil + // [[], 2].dig(0, 1, 2) #=> nil + // [[1, 2, [3, [8, [9]]]], 4, 5].dig(0, 2, 1, 1, 0) #=> 9 + // [1, 2].dig(0, 1) #=> TypeError: Expect target to be Diggable + // ``` + // + // @param index [Integer]... + // @return [Object] + Name: "dig", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) < 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, len(args)) + } - for _, obj := range arr.Elements { - t.builtinMethodYield(blockFrame, obj) - } - return arr + array := receiver.(*ArrayObject) + value := array.dig(t, args, sourceLine) + + return value - }, }, - // Works like #each, but passes the index of the element instead of the element itself. + }, + { + // Loops through each element in the array, with the given block. // Returns self. // A block literal is required. // // ```ruby - // a = [:apple, :orange, :grape, :melon] + // a = ["a", "b", "c"] // - // b = a.each_index do |i| - // puts(i*i) + // b = a.each do |e| + // puts(e + e) // end - // #=> 0 - // #=> 1 - // #=> 4 - // #=> 9 + // #=> "aa" + // #=> "bb" + // #=> "cc" // puts b // #=> ["a", "b", "c"] // ``` // // @param block literal // @return [Array] - { - Name: "each_index", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - arr := receiver.(*ArrayObject) - if blockIsEmpty(blockFrame) { - return arr - } + Name: "each", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + arr := receiver.(*ArrayObject) + if blockIsEmpty(blockFrame) { + return arr + } - // If it's an empty array, pop the block's call frame - if len(arr.Elements) == 0 { - t.callFrameStack.pop() - } + // If it's an empty array, pop the block's call frame + if len(arr.Elements) == 0 { + t.callFrameStack.pop() + } - for i := range arr.Elements { - t.builtinMethodYield(blockFrame, t.vm.InitIntegerObject(i)) - } - return arr + for _, obj := range arr.Elements { + t.builtinMethodYield(blockFrame, obj) + } + return arr - }, }, - { - // A predicate method. - // Returns if the array"s length is 0 or not. - // - // ```ruby - // [1, 2, 3].empty? #=> false - // [].empty? #=> true - // [[]].empty? #=> false - // ``` - // - // @return [Boolean] - Name: "empty?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + // Works like #each, but passes the index of the element instead of the element itself. + // Returns self. + // A block literal is required. + // + // ```ruby + // a = [:apple, :orange, :grape, :melon] + // + // b = a.each_index do |i| + // puts(i*i) + // end + // #=> 0 + // #=> 1 + // #=> 4 + // #=> 9 + // puts b + // #=> ["a", "b", "c"] + // ``` + // + // @param block literal + // @return [Array] + { + Name: "each_index", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + arr := receiver.(*ArrayObject) + if blockIsEmpty(blockFrame) { + return arr + } - arr := receiver.(*ArrayObject) + // If it's an empty array, pop the block's call frame + if len(arr.Elements) == 0 { + t.callFrameStack.pop() + } - if arr.Len() == 0 { - return TRUE - } - - return FALSE + for i := range arr.Elements { + t.builtinMethodYield(blockFrame, t.vm.InitIntegerObject(i)) + } + return arr - }, }, - { - // Returns the first element of the array. - // If a count 'n' is provided as an argument, it returns the array of the first n elements. - // - // ```ruby - // [1, 2, 3].first #=> 1 - // [:apple, :orange, :grape, :melon].first #=> "apple" - // [:apple, :orange, :grape, :melon].first(2) #=> ["apple", "orange"] - // ``` - // - // @param count [Integer] - // @return [Object] - Name: "first", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen > 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) - } - - arr := receiver.(*ArrayObject) - arrLength := len(arr.Elements) - if arrLength == 0 { - return NULL - } - - if aLen == 0 { - return arr.Elements[0] - } + }, + { + // A predicate method. + // Returns if the array"s length is 0 or not. + // + // ```ruby + // [1, 2, 3].empty? #=> false + // [].empty? #=> true + // [[]].empty? #=> false + // ``` + // + // @return [Boolean] + Name: "empty?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - arg, ok := args[0].(*IntegerObject) + arr := receiver.(*ArrayObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) - } + if arr.Len() == 0 { + return TRUE + } - if arg.value < 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.NegativeValue, arg.value) - } + return FALSE - if arrLength > arg.value { - return t.vm.InitArrayObject(arr.Elements[:arg.value]) - } - return arr + }, + }, + { + // Returns the first element of the array. + // If a count 'n' is provided as an argument, it returns the array of the first n elements. + // + // ```ruby + // [1, 2, 3].first #=> 1 + // [:apple, :orange, :grape, :melon].first #=> "apple" + // [:apple, :orange, :grape, :melon].first(2) #=> ["apple", "orange"] + // ``` + // + // @param count [Integer] + // @return [Object] + Name: "first", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen > 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + } + + arr := receiver.(*ArrayObject) + arrLength := len(arr.Elements) + if arrLength == 0 { + return NULL + } + + if aLen == 0 { + return arr.Elements[0] + } + + arg, ok := args[0].(*IntegerObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) + } + + if arg.value < 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.NegativeValue, arg.value) + } + + if arrLength > arg.value { + return t.vm.InitArrayObject(arr.Elements[:arg.value]) + } + return arr - }, }, - { - // Returns a new array that is a one-dimensional flattening of self. - // - // ```ruby - // a = [ 1, 2, 3 ] - // b = [ 4, 5, 6, [7, 8] ] - // c = [ a, b, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] - // c.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - // - // [[[1, 2], [[[3, 4]], [5, 6]]]].flatten - // #=> [1, 2, 3, 4, 5, 6] - // ``` - // - // @return [Array] - Name: "flatten", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns a new array that is a one-dimensional flattening of self. + // + // ```ruby + // a = [ 1, 2, 3 ] + // b = [ 4, 5, 6, [7, 8] ] + // c = [ a, b, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] + // c.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + // + // [[[1, 2], [[[3, 4]], [5, 6]]]].flatten + // #=> [1, 2, 3, 4, 5, 6] + // ``` + // + // @return [Array] + Name: "flatten", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - arr := receiver.(*ArrayObject) - newElements := arr.flatten() + arr := receiver.(*ArrayObject) + newElements := arr.flatten() - return t.vm.InitArrayObject(newElements) + return t.vm.InitArrayObject(newElements) - }, }, - { - // Returns a new hash from the element of the receiver (array) as keys, and generates respective values of hash from the keys by using the block provided. - // The method can take a default value, and a block is required. - // `index_with` is equivalent to `receiver.map do |e| e, e._do_something end.to_h` - // Ref: https://github.com/rails/rails/pull/32523 - // - // ```ruby - // ary = [:Mon, :Tue, :Wed, :Thu, :Fri, :Sat, :Sun] - // ary.index_with("weekday") do |d| - // if d == :Sat || d == :Sun - // "off day" - // end - // end - // #=> {Mon: "weekday", - // Tue: "weekday" - // Wed: "weekday" - // Thu: "weekday" - // Fri: "weekday" - // Sat: "off day" - // Sun: "off day" - // } - // ``` - // - // @param optional default value [Object], block - // @return [Hash] - Name: "index_with", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - a := receiver.(*ArrayObject) - // If it's an empty array, pop the block's call frame - if len(a.Elements) == 0 { - t.callFrameStack.pop() - } - - hash := make(map[string]Object) - switch len(args) { - case 0: - for _, obj := range a.Elements { - hash[obj.ToString()] = t.builtinMethodYield(blockFrame, obj).Target - } - case 1: - arg := args[0] - for _, obj := range a.Elements { - switch b := t.builtinMethodYield(blockFrame, obj).Target; b.(type) { - case *NullObject: - hash[obj.ToString()] = arg - default: - hash[obj.ToString()] = b - } + }, + { + // Returns a new hash from the element of the receiver (array) as keys, and generates respective values of hash from the keys by using the block provided. + // The method can take a default value, and a block is required. + // `index_with` is equivalent to `receiver.map do |e| e, e._do_something end.to_h` + // Ref: https://github.com/rails/rails/pull/32523 + // + // ```ruby + // ary = [:Mon, :Tue, :Wed, :Thu, :Fri, :Sat, :Sun] + // ary.index_with("weekday") do |d| + // if d == :Sat || d == :Sun + // "off day" + // end + // end + // #=> {Mon: "weekday", + // Tue: "weekday" + // Wed: "weekday" + // Thu: "weekday" + // Fri: "weekday" + // Sat: "off day" + // Sun: "off day" + // } + // ``` + // + // @param optional default value [Object], block + // @return [Hash] + Name: "index_with", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + a := receiver.(*ArrayObject) + // If it's an empty array, pop the block's call frame + if len(a.Elements) == 0 { + t.callFrameStack.pop() + } + + hash := make(map[string]Object) + switch len(args) { + case 0: + for _, obj := range a.Elements { + hash[obj.ToString()] = t.builtinMethodYield(blockFrame, obj).Target + } + case 1: + arg := args[0] + for _, obj := range a.Elements { + switch b := t.builtinMethodYield(blockFrame, obj).Target; b.(type) { + case *NullObject: + hash[obj.ToString()] = arg + default: + hash[obj.ToString()] = b } - default: - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, len(args)) } + default: + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, len(args)) + } - return t.vm.InitHashObject(hash) + return t.vm.InitHashObject(hash) - }, }, - { - // Returns a string by concatenating each element to string, separated by given separator. - // If the array is nested, they will be flattened and then concatenated. - // If separator is nil, it uses empty string. - // - // ```ruby - // [ 1, 2, 3 ].join #=> "123" - // [[:h, :e, :l], [[:l], :o]].join #=> "hello" - // [[:hello],{k: :v}].join #=> 'hello{ k: "v" }' - // [ 1, 2, 3 ].join("-") #=> "1-2-3" - // ``` - // - // @param separator [String] - // @return [String] - Name: "join", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen < 0 || aLen > 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 0, 1, aLen) - } - - var sep string - if aLen == 0 { - sep = "" - } else { - arg, ok := args[0].(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + }, + { + // Returns a string by concatenating each element to string, separated by given separator. + // If the array is nested, they will be flattened and then concatenated. + // If separator is nil, it uses empty string. + // + // ```ruby + // [ 1, 2, 3 ].join #=> "123" + // [[:h, :e, :l], [[:l], :o]].join #=> "hello" + // [[:hello],{k: :v}].join #=> 'hello{ k: "v" }' + // [ 1, 2, 3 ].join("-") #=> "1-2-3" + // ``` + // + // @param separator [String] + // @return [String] + Name: "join", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen < 0 || aLen > 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 0, 1, aLen) + } + + var sep string + if aLen == 0 { + sep = "" + } else { + arg, ok := args[0].(*StringObject) - sep = arg.value + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) } - arr := receiver.(*ArrayObject) - elements := []string{} - for _, e := range arr.flatten() { - elements = append(elements, e.ToString()) - } + sep = arg.value + } - return t.vm.InitStringObject(strings.Join(elements, sep)) + arr := receiver.(*ArrayObject) + elements := []string{} + for _, e := range arr.flatten() { + elements = append(elements, e.ToString()) + } + + return t.vm.InitStringObject(strings.Join(elements, sep)) - }, }, - { - // Returns the last element of the array. - // If a count 'n' is provided as an argument, it returns the array of the last n elements. - // - // ```ruby - // [1, 2, 3].last #=> 3 - // [:apple, :orange, :grape, :melon].last #=> "melon" - // [:apple, :orange, :grape, :melon].last(2) #=> ["grape", "melon"] - // ``` - // - // @param count [Integer] - // @return [Object] - Name: "last", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen > 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) - } + }, + { + // Returns the last element of the array. + // If a count 'n' is provided as an argument, it returns the array of the last n elements. + // + // ```ruby + // [1, 2, 3].last #=> 3 + // [:apple, :orange, :grape, :melon].last #=> "melon" + // [:apple, :orange, :grape, :melon].last(2) #=> ["grape", "melon"] + // ``` + // + // @param count [Integer] + // @return [Object] + Name: "last", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen > 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + } - arr := receiver.(*ArrayObject) - arrLength := len(arr.Elements) + arr := receiver.(*ArrayObject) + arrLength := len(arr.Elements) - if aLen == 0 { - return arr.Elements[arrLength-1] - } + if aLen == 0 { + return arr.Elements[arrLength-1] + } - arg, ok := args[0].(*IntegerObject) + arg, ok := args[0].(*IntegerObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) + } - if arg.value < 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.NegativeValue, arg.value) - } + if arg.value < 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.NegativeValue, arg.value) + } - if arrLength > arg.value { - return t.vm.InitArrayObject(arr.Elements[arrLength-arg.value : arrLength]) - } - return arr + if arrLength > arg.value { + return t.vm.InitArrayObject(arr.Elements[arrLength-arg.value : arrLength]) + } + return arr - }, }, - { - // Returns the length of the array. - // The method does not take a block literal and is just to check the length of the array. - // - // ```ruby - // [1, 2, 3].length #=> 3 - // ``` - // - // @return [Integer] - Name: "length", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns the length of the array. + // The method does not take a block literal and is just to check the length of the array. + // + // ```ruby + // [1, 2, 3].length #=> 3 + // ``` + // + // @return [Integer] + Name: "length", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - arr := receiver.(*ArrayObject) - return t.vm.InitIntegerObject(arr.Len()) + arr := receiver.(*ArrayObject) + return t.vm.InitIntegerObject(arr.Len()) - }, }, - { - // Loops through each element with the given block literal, and then returns the yielded elements as an array. - // A block literal is required. - // - // ```ruby - // a = ["a", "b", "c"] - // - // a.map do |e| - // e + e - // end - // #=> ["aa", "bb", "cc"] - // - // ------------------------- - // - // a = [:apple, :orange, :lemon, :grape].map do |i| - // i + "s" - // end - // puts a - // #=> ["apples", "oranges", "lemons", "grapes"] - // ``` - // - // @param block literal - // @return [Array] - Name: "map", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - arr := receiver.(*ArrayObject) - var elements = make([]Object, len(arr.Elements)) - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } + }, + { + // Loops through each element with the given block literal, and then returns the yielded elements as an array. + // A block literal is required. + // + // ```ruby + // a = ["a", "b", "c"] + // + // a.map do |e| + // e + e + // end + // #=> ["aa", "bb", "cc"] + // + // ------------------------- + // + // a = [:apple, :orange, :lemon, :grape].map do |i| + // i + "s" + // end + // puts a + // #=> ["apples", "oranges", "lemons", "grapes"] + // ``` + // + // @param block literal + // @return [Array] + Name: "map", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + arr := receiver.(*ArrayObject) + var elements = make([]Object, len(arr.Elements)) - // If it's an empty array, pop the block's call frame - if len(arr.Elements) == 0 { - t.callFrameStack.pop() - } + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } - if blockIsEmpty(blockFrame) { - for i := 0; i < len(arr.Elements); i++ { - elements[i] = NULL - } - } else { - for i, obj := range arr.Elements { - result := t.builtinMethodYield(blockFrame, obj) - elements[i] = result.Target - } + // If it's an empty array, pop the block's call frame + if len(arr.Elements) == 0 { + t.callFrameStack.pop() + } + + if blockIsEmpty(blockFrame) { + for i := 0; i < len(arr.Elements); i++ { + elements[i] = NULL } + } else { + for i, obj := range arr.Elements { + result := t.builtinMethodYield(blockFrame, obj) + elements[i] = result.Target + } + } - return t.vm.InitArrayObject(elements) + return t.vm.InitArrayObject(elements) - }, }, - { - // A destructive method. - // Removes the last element in the array and returns it. - // - // ```ruby - // a = [1, 2, 3] - // a.pop #=> 3 - // a #=> [1, 2] - // ``` - // - // @return [Object] - Name: "pop", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // A destructive method. + // Removes the last element in the array and returns it. + // + // ```ruby + // a = [1, 2, 3] + // a.pop #=> 3 + // a #=> [1, 2] + // ``` + // + // @return [Object] + Name: "pop", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - arr := receiver.(*ArrayObject) - return arr.pop() + arr := receiver.(*ArrayObject) + return arr.pop() - }, }, - { - // A destructive method. - // Appends the given object to the array and returns the array. - // One or more arguments can be passed to the method. - // If no argument have been given, nothing will be added to the array, - // and returns the unchanged array. - // Even `nil` or empty strings `""` will be added to the array. - // - // ```ruby - // a = [1, 2, 3] - // a.push(4) #=> [1, 2, 3, 4] - // a.push(5, 6, 7) #=> [1, 2, 3, 4, 5, 6, 7] - // a.push #=> [1, 2, 3, 4, 5, 6, 7] - // a #=> [1, 2, 3, 4, 5, 6, 7] - // a.push(nil, "") #=> [1, 2, 3, 4, 5, 6, 7, nil, ""] - // ``` - // - // @param object [Object]... - // @return [Array] - Name: "push", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - arr := receiver.(*ArrayObject) - return arr.push(args) - - }, + }, + { + // A destructive method. + // Appends the given object to the array and returns the array. + // One or more arguments can be passed to the method. + // If no argument have been given, nothing will be added to the array, + // and returns the unchanged array. + // Even `nil` or empty strings `""` will be added to the array. + // + // ```ruby + // a = [1, 2, 3] + // a.push(4) #=> [1, 2, 3, 4] + // a.push(5, 6, 7) #=> [1, 2, 3, 4, 5, 6, 7] + // a.push #=> [1, 2, 3, 4, 5, 6, 7] + // a #=> [1, 2, 3, 4, 5, 6, 7] + // a.push(nil, "") #=> [1, 2, 3, 4, 5, 6, 7, nil, ""] + // ``` + // + // @param object [Object]... + // @return [Array] + Name: "push", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + + arr := receiver.(*ArrayObject) + return arr.push(args) + }, - { - // Accumulates the given argument and the results from evaluating each elements - // with the first block parameter of the given block. - // Takes one block with two block arguments (less than two block arguments are meaningless). - // The first block argument is to succeed the initial value or previous result, - // and the second block arguments is to enumerate the elements of the array. - // You can also pass an argument as an initial value. - // If you do not pass an argument, the first element of collection is used as an initial value. - // - // ```ruby - // a = [1, 2, 7] - // - // a.reduce do |sum, n| - // sum + n - // end - // #=> 10 - // - // a.reduce(10) do |sum, n| - // sum + n - // end - // #=> 20 - // - // a = ["this", "is", "a", "test!"] - // a.reduce("Yes, ") do |prev, s| - // prev + s + " " - // end - // #=> "Yes, this is a test! " - // ``` - // - // @param initial value [Object], block literal with two block parameters - // @return [Object] - Name: "reduce", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen > 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) - } - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } + }, + { + // Accumulates the given argument and the results from evaluating each elements + // with the first block parameter of the given block. + // Takes one block with two block arguments (less than two block arguments are meaningless). + // The first block argument is to succeed the initial value or previous result, + // and the second block arguments is to enumerate the elements of the array. + // You can also pass an argument as an initial value. + // If you do not pass an argument, the first element of collection is used as an initial value. + // + // ```ruby + // a = [1, 2, 7] + // + // a.reduce do |sum, n| + // sum + n + // end + // #=> 10 + // + // a.reduce(10) do |sum, n| + // sum + n + // end + // #=> 20 + // + // a = ["this", "is", "a", "test!"] + // a.reduce("Yes, ") do |prev, s| + // prev + s + " " + // end + // #=> "Yes, this is a test! " + // ``` + // + // @param initial value [Object], block literal with two block parameters + // @return [Object] + Name: "reduce", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen > 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + } + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + // If it's an empty array, pop the block's call frame + arr := receiver.(*ArrayObject) + if len(arr.Elements) == 0 { + t.callFrameStack.pop() + } + + if blockIsEmpty(blockFrame) { + return NULL + } + + var prev Object + var start int + switch aLen { + case 0: + prev = arr.Elements[0] + start = 1 + case 1: + prev = args[0] + start = 0 + } + + for i := start; i < len(arr.Elements); i++ { + result := t.builtinMethodYield(blockFrame, prev, arr.Elements[i]) + prev = result.Target + } + + return prev - // If it's an empty array, pop the block's call frame - arr := receiver.(*ArrayObject) - if len(arr.Elements) == 0 { - t.callFrameStack.pop() - } + }, + }, + { + // Returns a new array containing selfβ€˜s elements in reverse order. Not destructive. + // + // ```ruby + // a = [1, 2, 7] + // + // a.reverse #=> [7, 2, 1] + // ``` + // + // @return [Array] + Name: "reverse", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - if blockIsEmpty(blockFrame) { - return NULL - } + arr := receiver.(*ArrayObject) + return arr.reverse() - var prev Object - var start int - switch aLen { - case 0: - prev = arr.Elements[0] - start = 1 - case 1: - prev = args[0] - start = 0 - } + }, + }, + { + // Behaves as the same as #each, but traverses self in reverse order. + // Returns self. + // A block literal is required. + // + // ```ruby + // a = [:a, :b, :c] + // + // a.reverse_each do |e| + // puts(e + e) + // end + // #=> "cc" + // #=> "bb" + // #=> "aa" + // ``` + // + // @param block literal + // @return [Array] + Name: "reverse_each", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + arr := receiver.(*ArrayObject) + if blockIsEmpty(blockFrame) { + return arr + } - for i := start; i < len(arr.Elements); i++ { - result := t.builtinMethodYield(blockFrame, prev, arr.Elements[i]) - prev = result.Target - } + // If it's an empty array, pop the block's call frame + if len(arr.Elements) == 0 { + t.callFrameStack.pop() + } - return prev + reversedArr := arr.reverse() - }, - }, - { - // Returns a new array containing selfβ€˜s elements in reverse order. Not destructive. - // - // ```ruby - // a = [1, 2, 7] - // - // a.reverse #=> [7, 2, 1] - // ``` - // - // @return [Array] - Name: "reverse", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + for _, obj := range reversedArr.Elements { + t.builtinMethodYield(blockFrame, obj) + } - arr := receiver.(*ArrayObject) - return arr.reverse() + return reversedArr - }, }, - { - // Behaves as the same as #each, but traverses self in reverse order. - // Returns self. - // A block literal is required. - // - // ```ruby - // a = [:a, :b, :c] - // - // a.reverse_each do |e| - // puts(e + e) - // end - // #=> "cc" - // #=> "bb" - // #=> "aa" - // ``` - // - // @param block literal - // @return [Array] - Name: "reverse_each", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + }, + { + // Returns a new rotated array from the self. + // The method is not destructive. + // If zero `0` is passed, it returns a new array that has been rotated 1 time to left (default). + // If an optional positive integer `n` is passed, it returns a new array that has been rotated `n` times to left. + // + // ```ruby + // a = [:a, :b, :c, :d] + // + // a.rotate #=> ["b", "c", "d", "a"] + // a.rotate(2) #=> ["c", "d", "a", "b"] + // a.rotate(3) #=> ["d", "a", "b", "c"] + // ``` + // + // If an optional negative integer `-n` is passed, it returns a new array that has been rotated `n` times to right. + // + // ```ruby + // a = [:a, :b, :c, :d] + // + // a.rotate(-1) #=> ["d", "a", "b", "c"] + // ``` + // + // @param index [Integer] + // @return [Array] + Name: "rotate", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen > 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + } + + var rotate int + arr := receiver.(*ArrayObject) + rotArr := t.vm.InitArrayObject(arr.Elements) + + if aLen == 0 { + rotate = 1 + } else { + arg, ok := args[0].(*IntegerObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) } + rotate = arg.value + } - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + if rotate < 0 { + for i := 0; i > rotate; i-- { + el := rotArr.pop() + rotArr.unshift([]Object{el}) } - - arr := receiver.(*ArrayObject) - if blockIsEmpty(blockFrame) { - return arr + } else { + for i := 0; i < rotate; i++ { + el := rotArr.shift() + rotArr.push([]Object{el}) } + } - // If it's an empty array, pop the block's call frame - if len(arr.Elements) == 0 { - t.callFrameStack.pop() - } + return rotArr - reversedArr := arr.reverse() + }, + }, + { + // Loops through each element with the given block literal that contains conditional expressions. + // Returns a new array that contains elements that have been evaluated as `true` by the block. + // A block literal is required. + // + // ```ruby + // a = [1, 2, 3, 4, 5] + // + // a.select do |e| + // e + 1 > 3 + // end + // #=> [3, 4, 5] + // ``` + // + // @param conditional block literal + // @return [Array] + Name: "select", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - for _, obj := range reversedArr.Elements { - t.builtinMethodYield(blockFrame, obj) - } + arr := receiver.(*ArrayObject) + var elements []Object - return reversedArr + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } - }, - }, - { - // Returns a new rotated array from the self. - // The method is not destructive. - // If zero `0` is passed, it returns a new array that has been rotated 1 time to left (default). - // If an optional positive integer `n` is passed, it returns a new array that has been rotated `n` times to left. - // - // ```ruby - // a = [:a, :b, :c, :d] - // - // a.rotate #=> ["b", "c", "d", "a"] - // a.rotate(2) #=> ["c", "d", "a", "b"] - // a.rotate(3) #=> ["d", "a", "b", "c"] - // ``` - // - // If an optional negative integer `-n` is passed, it returns a new array that has been rotated `n` times to right. - // - // ```ruby - // a = [:a, :b, :c, :d] - // - // a.rotate(-1) #=> ["d", "a", "b", "c"] - // ``` - // - // @param index [Integer] - // @return [Array] - Name: "rotate", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen > 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) - } + if blockIsEmpty(blockFrame) { + return t.vm.InitArrayObject(elements) + } - var rotate int - arr := receiver.(*ArrayObject) - rotArr := t.vm.InitArrayObject(arr.Elements) + // If it's an empty array, pop the block's call frame + if len(arr.Elements) == 0 { + t.callFrameStack.pop() + } - if aLen == 0 { - rotate = 1 - } else { - arg, ok := args[0].(*IntegerObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) - } - rotate = arg.value + for _, obj := range arr.Elements { + result := t.builtinMethodYield(blockFrame, obj) + if result.Target.isTruthy() { + elements = append(elements, obj) } + } - if rotate < 0 { - for i := 0; i > rotate; i-- { - el := rotArr.pop() - rotArr.unshift([]Object{el}) - } - } else { - for i := 0; i < rotate; i++ { - el := rotArr.shift() - rotArr.push([]Object{el}) - } - } + return t.vm.InitArrayObject(elements) + + }, + }, + { + // A destructive method. + // Removes the first element from the array and returns the removed element. + // + // ```ruby + // a = [1, 2, 3] + // a.shift #=> 1 + // a #=> [2, 3] + // ``` + // + // @return [Object] + Name: "shift", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - return rotArr + arr := receiver.(*ArrayObject) + return arr.shift() - }, }, - { - // Loops through each element with the given block literal that contains conditional expressions. - // Returns a new array that contains elements that have been evaluated as `true` by the block. - // A block literal is required. - // - // ```ruby - // a = [1, 2, 3, 4, 5] - // - // a.select do |e| - // e + 1 > 3 - // end - // #=> [3, 4, 5] - // ``` - // - // @param conditional block literal - // @return [Array] - Name: "select", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Return a sorted array + // + // ```ruby + // a = [3, 2, 1] + // a.sort #=> [1, 2, 3] + // ``` + // + // @return [Object] + Name: "sort", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 argument. got=%d", len(args)) + } + + arr := receiver.(*ArrayObject) + newArr := arr.copy().(*ArrayObject) + sort.Sort(newArr) + return newArr - arr := receiver.(*ArrayObject) - var elements []Object + }, + }, + { + // Returns the result of interpreting ary as an array of [key value] array pairs. + // Note that the keys should always be String or symbol literals (using symbol literal is preferable). + // Each value can be any objects. + // + // ```ruby + // ary = [[:john, [:guitar, :harmonica]], [:paul, :base], [:george, :guitar], [:ringo, :drum]] + // ary.to_h + // #=> { john: ["guitar", "harmonica"], paul: "base", george: "guitar", ringo: "drum" } + // ``` + // + // @return [Hash] + Name: "to_h", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + ary := receiver.(*ArrayObject) - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } + hash := make(map[string]Object) + if len(ary.Elements) == 0 { + return t.vm.InitHashObject(hash) + } - if blockIsEmpty(blockFrame) { - return t.vm.InitArrayObject(elements) + for i, el := range ary.Elements { + kv, ok := el.(*ArrayObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, "Expect the Array's element #%d to be Array. got: %s", i, el.Class().Name) } - // If it's an empty array, pop the block's call frame - if len(arr.Elements) == 0 { - t.callFrameStack.pop() + if len(kv.Elements) != 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect element #%d to have 2 elements as a key-value pair. got: %s", i, kv.ToString()) } - for _, obj := range arr.Elements { - result := t.builtinMethodYield(blockFrame, obj) - if result.Target.isTruthy() { - elements = append(elements, obj) - } + k := kv.Elements[0] + if _, ok := k.(*StringObject); !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, "Expect the key in the Array's element #%d to be String. got: %s", i, k.Class().Name) } - return t.vm.InitArrayObject(elements) + hash[k.ToString()] = kv.Elements[1] - }, - }, - { - // A destructive method. - // Removes the first element from the array and returns the removed element. - // - // ```ruby - // a = [1, 2, 3] - // a.shift #=> 1 - // a #=> [2, 3] - // ``` - // - // @return [Object] - Name: "shift", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + } - arr := receiver.(*ArrayObject) - return arr.shift() + return t.vm.InitHashObject(hash) - }, }, - { - // Return a sorted array - // - // ```ruby - // a = [3, 2, 1] - // a.sort #=> [1, 2, 3] - // ``` - // - // @return [Object] - Name: "sort", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 argument. got=%d", len(args)) - } - - arr := receiver.(*ArrayObject) - newArr := arr.copy().(*ArrayObject) - sort.Sort(newArr) - return newArr + }, + { + // A destructive method. + // Inserts one or more arguments at the first position of the array, and then returns the self. + // + // ```ruby + // a = [1, 2] + // a.unshift(0) #=> [0, 1, 2] + // a #=> [0, 1, 2] + // a.unshift(:hello, :goby) #=> ["hello", "goby", 0, 1, 2] + // a #=> ["hello", "goby", 0, 1, 2] + // ``` + // + // @param element [Object] + // @return [Array] + Name: "unshift", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + arr := receiver.(*ArrayObject) + return arr.unshift(args) - }, }, - { - // Returns the result of interpreting ary as an array of [key value] array pairs. - // Note that the keys should always be String or symbol literals (using symbol literal is preferable). - // Each value can be any objects. - // - // ```ruby - // ary = [[:john, [:guitar, :harmonica]], [:paul, :base], [:george, :guitar], [:ringo, :drum]] - // ary.to_h - // #=> { john: ["guitar", "harmonica"], paul: "base", george: "guitar", ringo: "drum" } - // ``` - // - // @return [Hash] - Name: "to_h", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - ary := receiver.(*ArrayObject) - - hash := make(map[string]Object) - if len(ary.Elements) == 0 { - return t.vm.InitHashObject(hash) - } - - for i, el := range ary.Elements { - kv, ok := el.(*ArrayObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, "Expect the Array's element #%d to be Array. got: %s", i, el.Class().Name) - } - - if len(kv.Elements) != 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect element #%d to have 2 elements as a key-value pair. got: %s", i, kv.ToString()) - } - - k := kv.Elements[0] - if _, ok := k.(*StringObject); !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, "Expect the key in the Array's element #%d to be String. got: %s", i, k.Class().Name) - } + }, + { + // Returns a new array that contains the elements pointed by zero or more indices given. + // If no arguments have been passed, an empty array `[]` will be returned. + // If the index is out of range, `nil` is used as the element. + // + // ```ruby + // a = ["a", "b", "c"] + // a.values_at(1) #=> ["b"] + // a.values_at(-1, 3) #=> ["c", nil] + // a.values_at() #=> [] + // ``` + // + // @param index [Integer]... + // @return [Array] + Name: "values_at", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + arr := receiver.(*ArrayObject) + var elements = make([]Object, len(args)) - hash[k.ToString()] = kv.Elements[1] + for i, arg := range args { + index, ok := arg.(*IntegerObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, arg.Class().Name) } - return t.vm.InitHashObject(hash) - - }, - }, - { - // A destructive method. - // Inserts one or more arguments at the first position of the array, and then returns the self. - // - // ```ruby - // a = [1, 2] - // a.unshift(0) #=> [0, 1, 2] - // a #=> [0, 1, 2] - // a.unshift(:hello, :goby) #=> ["hello", "goby", 0, 1, 2] - // a #=> ["hello", "goby", 0, 1, 2] - // ``` - // - // @param element [Object] - // @return [Array] - Name: "unshift", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - arr := receiver.(*ArrayObject) - return arr.unshift(args) - - }, - }, - { - // Returns a new array that contains the elements pointed by zero or more indices given. - // If no arguments have been passed, an empty array `[]` will be returned. - // If the index is out of range, `nil` is used as the element. - // - // ```ruby - // a = ["a", "b", "c"] - // a.values_at(1) #=> ["b"] - // a.values_at(-1, 3) #=> ["c", nil] - // a.values_at() #=> [] - // ``` - // - // @param index [Integer]... - // @return [Array] - Name: "values_at", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - arr := receiver.(*ArrayObject) - var elements = make([]Object, len(args)) - - for i, arg := range args { - index, ok := arg.(*IntegerObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, arg.Class().Name) - } - - if index.value >= len(arr.Elements) { - elements[i] = NULL - } else if index.value < 0 && -index.value > len(arr.Elements) { - elements[i] = NULL - } else if index.value < 0 { - elements[i] = arr.Elements[len(arr.Elements)+index.value] - } else { - elements[i] = arr.Elements[index.value] - } + if index.value >= len(arr.Elements) { + elements[i] = NULL + } else if index.value < 0 && -index.value > len(arr.Elements) { + elements[i] = NULL + } else if index.value < 0 { + elements[i] = arr.Elements[len(arr.Elements)+index.value] + } else { + elements[i] = arr.Elements[index.value] } + } - return t.vm.InitArrayObject(elements) + return t.vm.InitArrayObject(elements) - }, }, - } + }, } // Internal functions =================================================== @@ -1501,8 +1497,8 @@ func (vm *VM) InitArrayObject(elements []Object) *ArrayObject { func (vm *VM) initArrayClass() *RClass { ac := vm.initializeClass(classes.ArrayClass) - ac.setBuiltinMethods(builtinArrayInstanceMethods(), false) - ac.setBuiltinMethods(builtinArrayClassMethods(), true) + ac.setBuiltinMethods(builtinArrayInstanceMethods, false) + ac.setBuiltinMethods(builtinArrayClassMethods, true) vm.libFiles = append(vm.libFiles, "array.gb") vm.libFiles = append(vm.libFiles, "array_enumerator.gb") vm.libFiles = append(vm.libFiles, "lazy_enumerator.gb") diff --git a/vm/block.go b/vm/block.go index 21e0ba821..561a43e17 100644 --- a/vm/block.go +++ b/vm/block.go @@ -53,75 +53,71 @@ type BlockObject struct { } // 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) - }, +var builtinBlockClassMethods = []*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 - // #=> - // 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 - }, +var builtinBlockInstanceMethods = []*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 + // #=> + // 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 =================================================== @@ -130,8 +126,8 @@ func builtinBlockInstanceMethods() []*BuiltinMethodObject { func (vm *VM) initBlockClass() *RClass { class := vm.initializeClass(classes.BlockClass) - class.setBuiltinMethods(builtinBlockClassMethods(), true) - class.setBuiltinMethods(builtinBlockInstanceMethods(), false) + class.setBuiltinMethods(builtinBlockClassMethods, true) + class.setBuiltinMethods(builtinBlockInstanceMethods, false) return class } diff --git a/vm/boolean.go b/vm/boolean.go index a73159979..2340c7119 100644 --- a/vm/boolean.go +++ b/vm/boolean.go @@ -26,15 +26,13 @@ var ( ) // Class methods -------------------------------------------------------- -func builtinBooleanClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitNoMethodError(sourceLine, "new", receiver) - }, +var builtinBooleanClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitNoMethodError(sourceLine, "new", receiver) }, - } + }, } // Internal functions =================================================== @@ -43,7 +41,7 @@ func builtinBooleanClassMethods() []*BuiltinMethodObject { func (vm *VM) initBoolClass() *RClass { b := vm.initializeClass(classes.BooleanClass) - b.setBuiltinMethods(builtinBooleanClassMethods(), true) + b.setBuiltinMethods(builtinBooleanClassMethods, true) TRUE = &BooleanObject{value: true, BaseObj: &BaseObj{class: b}} FALSE = &BooleanObject{value: false, BaseObj: &BaseObj{class: b}} diff --git a/vm/channel.go b/vm/channel.go index 175ebd9a6..49216a3a9 100644 --- a/vm/channel.go +++ b/vm/channel.go @@ -81,160 +81,156 @@ const ( ) // Class methods -------------------------------------------------------- -func builtinChannelClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Creates an instance of `Channel` class, taking no arguments. - // - // ```ruby - // c = Channel.new - // c.class #=> Channel - // ``` - // - // @return [Channel] - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - c := &ChannelObject{BaseObj: &BaseObj{class: t.vm.TopLevelClass(classes.ChannelClass)}, Chan: make(chan int, chOpen)} - return c - }, +var builtinChannelClassMethods = []*BuiltinMethodObject{ + { + // Creates an instance of `Channel` class, taking no arguments. + // + // ```ruby + // c = Channel.new + // c.class #=> Channel + // ``` + // + // @return [Channel] + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + c := &ChannelObject{BaseObj: &BaseObj{class: t.vm.TopLevelClass(classes.ChannelClass)}, Chan: make(chan int, chOpen)} + return c }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinChannelInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Just to close and the channel to declare no more objects will be sent. - // Channel is not like files, and you don't need to call `close` explicitly unless - // you definitely need to notify that no more objects will be sent, - // Well, you can call `#close` against the same channel twice or more, which is redundant. - // (Go's channel cannot do that) - // See https://tour.golang.org/concurrency/4 - // - // ```ruby - // c = Channel.new - // - // 1001.times do |i| - // thread do - // c.deliver(i) - // end - // end - // - // r = 0 - // 1001.times do - // r = r + c.receive - // end - // - // c.close # close the channel - // - // puts(r) - // ``` - // - // If you call `close` twice against the same channel, an error is returned. - // - // It takes no argument. - // - // @return [Null] - Name: "close", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } +var builtinChannelInstanceMethods = []*BuiltinMethodObject{ + { + // Just to close and the channel to declare no more objects will be sent. + // Channel is not like files, and you don't need to call `close` explicitly unless + // you definitely need to notify that no more objects will be sent, + // Well, you can call `#close` against the same channel twice or more, which is redundant. + // (Go's channel cannot do that) + // See https://tour.golang.org/concurrency/4 + // + // ```ruby + // c = Channel.new + // + // 1001.times do |i| + // thread do + // c.deliver(i) + // end + // end + // + // r = 0 + // 1001.times do + // r = r + c.receive + // end + // + // c.close # close the channel + // + // puts(r) + // ``` + // + // If you call `close` twice against the same channel, an error is returned. + // + // It takes no argument. + // + // @return [Null] + Name: "close", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - c := receiver.(*ChannelObject) + c := receiver.(*ChannelObject) - if c.ChannelState == chClosed { - return t.vm.InitErrorObject(errors.ChannelCloseError, sourceLine, errors.ChannelIsClosed) - } - c.ChannelState = chClosed + if c.ChannelState == chClosed { + return t.vm.InitErrorObject(errors.ChannelCloseError, sourceLine, errors.ChannelIsClosed) + } + c.ChannelState = chClosed - close(receiver.(*ChannelObject).Chan) - receiver = nil - return NULL - }, + close(receiver.(*ChannelObject).Chan) + receiver = nil + return NULL }, - { - // Sends an object to the receiver (channel), then returns the object. - // Note that the method suspends the process until the object is actually received. - // Thus if you call `deliver` outside thread, the main process would suspend. - // Note that you don't need to send dummy object just to resume; use `close` instead. - // - // ```ruby - // c = Channel.new - // - // i = 0 - // thread do - // i += 1 - // c.deliver(i) # sends `i` to channel `c` - // end - // - // c.receive # receives `i` - // ``` - // - // If you call `deliver` against the closed channel, an error is returned. - // - // It takes 1 argument. - // - // @param object [Object] - // @return [Object] - Name: "deliver", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Sends an object to the receiver (channel), then returns the object. + // Note that the method suspends the process until the object is actually received. + // Thus if you call `deliver` outside thread, the main process would suspend. + // Note that you don't need to send dummy object just to resume; use `close` instead. + // + // ```ruby + // c = Channel.new + // + // i = 0 + // thread do + // i += 1 + // c.deliver(i) # sends `i` to channel `c` + // end + // + // c.receive # receives `i` + // ``` + // + // If you call `deliver` against the closed channel, an error is returned. + // + // It takes 1 argument. + // + // @param object [Object] + // @return [Object] + Name: "deliver", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - c := receiver.(*ChannelObject) + c := receiver.(*ChannelObject) - if c.ChannelState == chClosed { - return t.vm.InitErrorObject(errors.ChannelCloseError, sourceLine, errors.ChannelIsClosed) - } + if c.ChannelState == chClosed { + return t.vm.InitErrorObject(errors.ChannelCloseError, sourceLine, errors.ChannelIsClosed) + } - id := t.vm.channelObjectMap.storeObj(args[0]) - c.Chan <- id + id := t.vm.channelObjectMap.storeObj(args[0]) + c.Chan <- id - return args[0] - }, + return args[0] }, - { - // Receives objects from other threads' `deliver` method, then returns it. - // The method works as if the channel would receive objects perpetually from outside. - // Note that the method suspends the process until it actually receives something via `deliver`. - // Thus if you call `receive` outside thread, the main process would suspend. - // This also means you can resume a code by using the `receive` method. - // - // ```ruby - // c = Channel.new - // - // thread do - // puts(c.receive) # prints the object received from other threads. - // f("thread") - // end - // ``` - // - // If you call `receive` against the closed channel, an error is returned. - // - // It takes no arguments. - // - // @return [Object] - Name: "receive", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Receives objects from other threads' `deliver` method, then returns it. + // The method works as if the channel would receive objects perpetually from outside. + // Note that the method suspends the process until it actually receives something via `deliver`. + // Thus if you call `receive` outside thread, the main process would suspend. + // This also means you can resume a code by using the `receive` method. + // + // ```ruby + // c = Channel.new + // + // thread do + // puts(c.receive) # prints the object received from other threads. + // f("thread") + // end + // ``` + // + // If you call `receive` against the closed channel, an error is returned. + // + // It takes no arguments. + // + // @return [Object] + Name: "receive", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - c := receiver.(*ChannelObject) + c := receiver.(*ChannelObject) - if c.ChannelState == chClosed { - return t.vm.InitErrorObject(errors.ChannelCloseError, sourceLine, errors.ChannelIsClosed) - } + if c.ChannelState == chClosed { + return t.vm.InitErrorObject(errors.ChannelCloseError, sourceLine, errors.ChannelIsClosed) + } - num := <-c.Chan + num := <-c.Chan - return t.vm.channelObjectMap.retrieveObj(num) - }, + return t.vm.channelObjectMap.retrieveObj(num) }, - } + }, } // Internal functions =================================================== @@ -243,8 +239,8 @@ func builtinChannelInstanceMethods() []*BuiltinMethodObject { func (vm *VM) initChannelClass() *RClass { class := vm.initializeClass(classes.ChannelClass) - class.setBuiltinMethods(builtinChannelClassMethods(), true) - class.setBuiltinMethods(builtinChannelInstanceMethods(), false) + class.setBuiltinMethods(builtinChannelClassMethods, true) + class.setBuiltinMethods(builtinChannelInstanceMethods, false) return class } diff --git a/vm/class.go b/vm/class.go index 554bddaac..97f38167c 100644 --- a/vm/class.go +++ b/vm/class.go @@ -74,986 +74,1020 @@ func ExternalClass(name, path string, classMethods, instanceMethods map[string]M } // Class's class methods -func builtinClassCommonClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Creates and returns a new anonymous class from a receiver. - // You can use any classes you defined as the receiver: - // - // ```ruby - // class Foo - // end - // a = Foo.new - // ``` - // - // Note that the built-in classes such as String are not open for creating instances - // and you can't call `new` against them. - // - // ```ruby - // a = String.new # => error - // ``` - // @param class [Class] Receiver - // @return [Object] Created object - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - class, ok := receiver.(*RClass) +var builtinClassCommonClassMethods = []*BuiltinMethodObject{ + { + // Creates and returns a new anonymous class from a receiver. + // You can use any classes you defined as the receiver: + // + // ```ruby + // class Foo + // end + // a = Foo.new + // ``` + // + // Note that the built-in classes such as String are not open for creating instances + // and you can't call `new` against them. + // + // ```ruby + // a = String.new # => error + // ``` + // @param class [Class] Receiver + // @return [Object] Created object + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + class, ok := receiver.(*RClass) - if !ok { - return t.vm.InitNoMethodError(sourceLine, "new", receiver) - } + if !ok { + return t.vm.InitNoMethodError(sourceLine, "new", receiver) + } - instance := class.initializeInstance() - initMethod := class.lookupMethod("initialize") + instance := class.initializeInstance() + initMethod := class.lookupMethod("initialize") - if initMethod != nil { - instance.InitializeMethod = initMethod.(*MethodObject) - } + if initMethod != nil { + instance.InitializeMethod = initMethod.(*MethodObject) + } - return instance - }, + return instance }, - } + }, } // Class methods -------------------------------------------------------- -func builtinModuleCommonClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Returns an array that contains ancestor classes/modules of the receiver, - // left to right. - // - // ```ruby - // String.ancestors #=> [String, Object] - // - // module Foo - // def bar - // 42 - // end - // end - // - // class Bar - // include Foo - // end - // - // Bar.ancestors - // #=> [Bar, Foo, Object] - // - // # you need `#singleton_class` to show the 'extended' modules - // class Baz - // extend Foo - // end - // - // Baz.singleton_class.ancestors - // #=> [#, Foo, #, Class, Object] - // Baz.ancestors # Foo is hidden - // #=> [Baz, Object] - // ``` - // - // @param class [Class] Receiver - // @return [Array] - Name: "ancestors", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - c, ok := receiver.(*RClass) +var builtinModuleCommonClassMethods = []*BuiltinMethodObject{ + { + // Returns an array that contains ancestor classes/modules of the receiver, + // left to right. + // + // ```ruby + // String.ancestors #=> [String, Object] + // + // module Foo + // def bar + // 42 + // end + // end + // + // class Bar + // include Foo + // end + // + // Bar.ancestors + // #=> [Bar, Foo, Object] + // + // # you need `#singleton_class` to show the 'extended' modules + // class Baz + // extend Foo + // end + // + // Baz.singleton_class.ancestors + // #=> [#, Foo, #, Class, Object] + // Baz.ancestors # Foo is hidden + // #=> [Baz, Object] + // ``` + // + // @param class [Class] Receiver + // @return [Array] + Name: "ancestors", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + c, ok := receiver.(*RClass) - if !ok { - return t.vm.InitNoMethodError(sourceLine, "#ancestors", receiver) - } + if !ok { + return t.vm.InitNoMethodError(sourceLine, "#ancestors", receiver) + } - a := c.ancestors() - ancestors := make([]Object, len(a)) - for i := range a { - ancestors[i] = a[i] - } - return t.vm.InitArrayObject(ancestors) - }, + a := c.ancestors() + ancestors := make([]Object, len(a)) + for i := range a { + ancestors[i] = a[i] + } + return t.vm.InitArrayObject(ancestors) }, - { - // Returns true if self is an ancestor of another class/module. - // - // ```ruby - // Object > Array #=> true - // Array > Object #=> false - // Object > Object #=> false - // ``` - // - // @param module [Class] - // @return [Boolean, Null] - Name: ">", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - c, ok := receiver.(*RClass) + }, + { + // Returns true if self is an ancestor of another class/module. + // + // ```ruby + // Object > Array #=> true + // Array > Object #=> false + // Object > Object #=> false + // ``` + // + // @param module [Class] + // @return [Boolean, Null] + Name: ">", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + c, ok := receiver.(*RClass) - if !ok { - return t.vm.InitNoMethodError(sourceLine, "#>", receiver) - } + if !ok { + return t.vm.InitNoMethodError(sourceLine, "#>", receiver) + } - module, ok := args[0].(*RClass) + module, ok := args[0].(*RClass) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) + } - if c == module { - return FALSE - } + if c == module { + return FALSE + } - if module.alreadyInherit(c) { - return TRUE - } + if module.alreadyInherit(c) { + return TRUE + } - if c.alreadyInherit(module) { - return FALSE - } - return NULL - }, + if c.alreadyInherit(module) { + return FALSE + } + return NULL }, - { - // Returns true if self is an ancestor or same class/module of another. - // - // ```ruby - // Object >= Array #=> true - // Array >= Object #=> false - // Object >= Object #=> true - // ``` - // - // @param module [Class] - // @return [Boolean, Null] - Name: ">=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - c, ok := receiver.(*RClass) + }, + { + // Returns true if self is an ancestor or same class/module of another. + // + // ```ruby + // Object >= Array #=> true + // Array >= Object #=> false + // Object >= Object #=> true + // ``` + // + // @param module [Class] + // @return [Boolean, Null] + Name: ">=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + c, ok := receiver.(*RClass) - if !ok { - return t.vm.InitNoMethodError(sourceLine, "#>=", receiver) - } + if !ok { + return t.vm.InitNoMethodError(sourceLine, "#>=", receiver) + } - module, ok := args[0].(*RClass) + module, ok := args[0].(*RClass) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) + } - if c == module { - return TRUE - } + if c == module { + return TRUE + } - if module.alreadyInherit(c) { - return TRUE - } + if module.alreadyInherit(c) { + return TRUE + } - if c.alreadyInherit(module) { - return FALSE - } - return NULL - }, + if c.alreadyInherit(module) { + return FALSE + } + return NULL }, - { - // Returns true if another class/module is an ancestor of self. - // - // ```ruby - // Object < Array #=> false - // Array < Object #=> true - // Object < Object #=> false - // ``` - // - // @param module [Class] - // @return [Boolean, Null] - Name: "<", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - c, ok := receiver.(*RClass) + }, + { + // Returns true if another class/module is an ancestor of self. + // + // ```ruby + // Object < Array #=> false + // Array < Object #=> true + // Object < Object #=> false + // ``` + // + // @param module [Class] + // @return [Boolean, Null] + Name: "<", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + c, ok := receiver.(*RClass) - if !ok { - return t.vm.InitNoMethodError(sourceLine, "#<", receiver) - } + if !ok { + return t.vm.InitNoMethodError(sourceLine, "#<", receiver) + } - module, ok := args[0].(*RClass) + module, ok := args[0].(*RClass) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) + } - if c == module { - return FALSE - } + if c == module { + return FALSE + } - if module.alreadyInherit(c) { - return FALSE - } + if module.alreadyInherit(c) { + return FALSE + } - if c.alreadyInherit(module) { - return TRUE - } - return NULL - }, + if c.alreadyInherit(module) { + return TRUE + } + return NULL }, - { - // Returns true if another is an ancestor or same class/module of self. - // - // ```ruby - // Object <= Array #=> false - // Array <= Object #=> true - // Object <= Object #=> true - // ``` - // - // @param module [Class] - // @return [Boolean, Null] - Name: "<=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - c, ok := receiver.(*RClass) + }, + { + // Returns true if another is an ancestor or same class/module of self. + // + // ```ruby + // Object <= Array #=> false + // Array <= Object #=> true + // Object <= Object #=> true + // ``` + // + // @param module [Class] + // @return [Boolean, Null] + Name: "<=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + c, ok := receiver.(*RClass) - if !ok { - return t.vm.InitNoMethodError(sourceLine, "#<=", receiver) - } + if !ok { + return t.vm.InitNoMethodError(sourceLine, "#<=", receiver) + } - module, ok := args[0].(*RClass) + module, ok := args[0].(*RClass) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) + } - if c == module { - return TRUE - } + if c == module { + return TRUE + } - if module.alreadyInherit(c) { - return FALSE - } + if module.alreadyInherit(c) { + return FALSE + } - if c.alreadyInherit(module) { - return TRUE - } - return NULL - }, - }, - { - // Creates instance variables and corresponding methods that return the value of - // each instance variable and assign an argument to each instance variable. - // Only string literal can be used for now. - // - // ```ruby - // class Foo - // attr_accessor("bar", "buz") - // end - // ``` - // is equivalent to: - // - // ```ruby - // class Foo - // def bar - // @bar - // end - // def buz - // @buz - // end - // def bar=(val) - // @bar = val - // end - // def buz=(val) - // @buz = val - // end - // end - // ``` - // - // @param *args [String] One or more quoted method names for 'getter/setter' - // @return [Null] - Name: "attr_accessor", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - r := receiver.(*RClass) - r.setAttrAccessor(args) - - return r - }, - }, - { - // Creates instance variables and corresponding methods that return the value of each - // instance variable. - // - // Only string literal can be used for now. - // - // ```ruby - // class Foo - // attr_reader("bar", "buz") - // end - // ``` - // is equivalent to: - // - // ```ruby - // class Foo - // def bar - // @bar - // end - // def buz - // @buz - // end - // end - // ``` - // - // @param *args [String] One or more quoted method names for 'getter' - // @return [Null] - Name: "attr_reader", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - r := receiver.(*RClass) - r.setAttrReader(args) - - return r - }, - }, - { - // Creates instance variables and corresponding methods that assign an argument to each - // instance variable. No return value. - // - // Only string literal can be used for now. - // - // ```ruby - // class Foo - // attr_writer("bar", "buz") - // end - // ``` - // is equivalent to: - // - // ```ruby - // class Foo - // def bar=(val) - // @bar = val - // end - // def buz=(val) - // @buz = val - // end - // end - // ``` - // - // @param *args [String] One or more quoted method names for 'setter' - // @return [Null] - Name: "attr_writer", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - r := receiver.(*RClass) - r.setAttrWriter(args) - - return r - }, + if c.alreadyInherit(module) { + return TRUE + } + return NULL }, - { - Name: "constants", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - var constantNames []string - var objs []Object - r := receiver.(*RClass) - - for n := range r.constants { - constantNames = append(constantNames, n) - } - sort.Strings(constantNames) - - for _, cn := range constantNames { - objs = append(objs, t.vm.InitStringObject(cn)) - } - - return t.vm.InitArrayObject(objs) + }, + { + // Creates instance variables and corresponding methods that return the value of + // each instance variable and assign an argument to each instance variable. + // Only string literal can be used for now. + // + // ```ruby + // class Foo + // attr_accessor("bar", "buz") + // end + // ``` + // is equivalent to: + // + // ```ruby + // class Foo + // def bar + // @bar + // end + // def buz + // @buz + // end + // def bar=(val) + // @bar = val + // end + // def buz=(val) + // @buz = val + // end + // end + // ``` + // + // @param *args [String] One or more quoted method names for 'getter/setter' + // @return [Null] + Name: "attr_accessor", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + r := receiver.(*RClass) + r.setAttrAccessor(args) - }, + return r }, - // Inserts a module as a singleton class to make the module's methods class methods. - // You can see the extended module by using `singleton_class.ancestors` + }, + { + // Creates instance variables and corresponding methods that return the value of each + // instance variable. + // + // Only string literal can be used for now. // // ```ruby - // String.ancestors #=> [String, Object] + // class Foo + // attr_reader("bar", "buz") + // end + // ``` + // is equivalent to: // - // module Foo + // ```ruby + // class Foo // def bar - // 42 + // @bar + // end + // def buz + // @buz // end // end + // ``` // - // class Bar - // extend Foo - // end + // @param *args [String] One or more quoted method names for 'getter' + // @return [Null] + Name: "attr_reader", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + r := receiver.(*RClass) + r.setAttrReader(args) + + return r + }, + }, + { + // Creates instance variables and corresponding methods that assign an argument to each + // instance variable. No return value. // - // Bar.bar #=> 42 + // Only string literal can be used for now. // - // Bar.singleton_class.ancestors - // #=> [#, Foo, #, Class, Object] + // ```ruby + // class Foo + // attr_writer("bar", "buz") + // end + // ``` + // is equivalent to: // - // Bar.ancestors # Foo is hidden - // #=> [Bar, Object] + // ```ruby + // class Foo + // def bar=(val) + // @bar = val + // end + // def buz=(val) + // @buz = val + // end + // end // ``` // - // @param module [Class] Module name to extend + // @param *args [String] One or more quoted method names for 'setter' // @return [Null] - { - Name: "extend", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - var class *RClass - module, ok := args[0].(*RClass) - - if !ok || !module.isModule { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) - } + Name: "attr_writer", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + r := receiver.(*RClass) + r.setAttrWriter(args) + + return r + }, + }, + { + Name: "constants", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + var constantNames []string + var objs []Object + r := receiver.(*RClass) - class = receiver.SingletonClass() + for n := range r.constants { + constantNames = append(constantNames, n) + } + sort.Strings(constantNames) - if class.alreadyInherit(module) { - return class - } + for _, cn := range constantNames { + objs = append(objs, t.vm.InitStringObject(cn)) + } - module.superClass = class.superClass - class.superClass = module + return t.vm.InitArrayObject(objs) - return class - }, }, - { - // Includes a module for mixin, which inherits only methods and constants from the module. - // The included module is inserted into the path of the inheritance tree, between the class - // and the superclass so that the methods of the module is prioritized to superclasses. - // - // The order of `include` affects: the modules that included later are prioritized. - // If multiple modules include the same methods, the method will only come from - // the last included module. - // - // ```ruby - // module Foo - // def ten - // 10 - // end - // end - // - // module Bar - // def ten - // "ten" - // end - // end - // - // class Baz - // include(Foo) - // include(Bar) # method `ten` is only included from this module - // end - // - // Baz.ancestors - // [Baz, Bar, Foo, Object] # Bar is prioritized to Foo - // - // a = Baz.new - // puts(a.ten) # => ten # overridden - // ``` - // - // **Note**: - // - // You cannot use string literal, or pass two or more arguments to `include`. - // - // ```ruby - // include("Foo") # => error - // include(Foo, Bar) # => error - // ``` - // - // @param module [Class] Module name to include - // @return [Null] - Name: "include", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - var class *RClass - module, ok := args[0].(*RClass) - - if !ok || !module.isModule { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) - } - - switch r := receiver.(type) { - case *RClass: - class = r - default: - class = r.SingletonClass() - } + }, + // Inserts a module as a singleton class to make the module's methods class methods. + // You can see the extended module by using `singleton_class.ancestors` + // + // ```ruby + // String.ancestors #=> [String, Object] + // + // module Foo + // def bar + // 42 + // end + // end + // + // class Bar + // extend Foo + // end + // + // Bar.bar #=> 42 + // + // Bar.singleton_class.ancestors + // #=> [#, Foo, #, Class, Object] + // + // Bar.ancestors # Foo is hidden + // #=> [Bar, Object] + // ``` + // + // @param module [Class] Module name to extend + // @return [Null] + { + Name: "extend", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + var class *RClass + module, ok := args[0].(*RClass) - if class.alreadyInherit(module) { - return class - } + if !ok || !module.isModule { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) + } - module.superClass = class.superClass - class.superClass = module + class = receiver.SingletonClass() + if class.alreadyInherit(module) { return class - }, + } + + module.superClass = class.superClass + class.superClass = module + + return class }, - // Activates `method_missing` method in ancestor class. - // You need to call the method when you are trying to use a user-defined `method_missing` in one of the ancestor classes. - // This makes `method_missing` safer and more trackable. + }, + { + // Includes a module for mixin, which inherits only methods and constants from the module. + // The included module is inserted into the path of the inheritance tree, between the class + // and the superclass so that the methods of the module is prioritized to superclasses. + // + // The order of `include` affects: the modules that included later are prioritized. + // If multiple modules include the same methods, the method will only come from + // the last included module. // // ```ruby - // class Foo - // def method_missing(name) + // module Foo + // def ten // 10 // end // end // - // class Bar < Foo + // module Bar + // def ten + // "ten" + // end // end // - // Bar.new.bar #=> NoMethodError + // class Baz + // include(Foo) + // include(Bar) # method `ten` is only included from this module + // end + // + // Baz.ancestors + // [Baz, Bar, Foo, Object] # Bar is prioritized to Foo + // + // a = Baz.new + // puts(a.ten) # => ten # overridden // ``` // - // ```ruby - // class Foo - // def method_missing(name) - // 10 - // end - // end + // **Note**: // - // class Bar < Foo - // inherits_method_missing # needs this for activation - // end + // You cannot use string literal, or pass two or more arguments to `include`. // - // Bar.new.bar #=> 10 + // ```ruby + // include("Foo") # => error + // include(Foo, Bar) # => error // ``` // - // @return [Class] - { - Name: "inherits_method_missing", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - var class *RClass - - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + // @param module [Class] Module name to include + // @return [Null] + Name: "include", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + var class *RClass + module, ok := args[0].(*RClass) - switch r := receiver.(type) { - case *RClass: - class = r - default: - class = r.SingletonClass() - } + if !ok || !module.isModule { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "a module", args[0].Class().Name) + } - class.inheritsMethodMissing = true + switch r := receiver.(type) { + case *RClass: + class = r + default: + class = r.SingletonClass() + } + if class.alreadyInherit(module) { return class - }, + } + + module.superClass = class.superClass + class.superClass = module + + return class }, - { - // Returns the name of the class (receiver). - // - // ```ruby - // puts(Array.name) # => Array - // puts(Class.name) # => Class - // puts(Object.name) # => Object - // ``` - // @param class [Class] Receiver - // @return [String] Converted receiver name - Name: "name", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + // Activates `method_missing` method in ancestor class. + // You need to call the method when you are trying to use a user-defined `method_missing` in one of the ancestor classes. + // This makes `method_missing` safer and more trackable. + // + // ```ruby + // class Foo + // def method_missing(name) + // 10 + // end + // end + // + // class Bar < Foo + // end + // + // Bar.new.bar #=> NoMethodError + // ``` + // + // ```ruby + // class Foo + // def method_missing(name) + // 10 + // end + // end + // + // class Bar < Foo + // inherits_method_missing # needs this for activation + // end + // + // Bar.new.bar #=> 10 + // ``` + // + // @return [Class] + { + Name: "inherits_method_missing", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + var class *RClass - n, ok := receiver.(*RClass) + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - if !ok { - return t.vm.InitNoMethodError(sourceLine, "#name", receiver) - } + switch r := receiver.(type) { + case *RClass: + class = r + default: + class = r.SingletonClass() + } + + class.inheritsMethodMissing = true - name := n.ReturnName() - nameString := t.vm.InitStringObject(name) - return nameString - }, + return class }, - { - // A predicate class method that returns `true` if the object has an ability to respond to the method, otherwise `false`. - // Note that signs like `+` or `?` should be String literal. - // - // ```ruby - // Class.respond_to? "respond_to?" #=> true - // Class.respond_to? :numerator #=> false - // ``` - // - // @param [String] - // @return [Boolean] - Name: "respond_to?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Returns the name of the class (receiver). + // + // ```ruby + // puts(Array.name) # => Array + // puts(Class.name) # => Class + // puts(Object.name) # => Object + // ``` + // @param class [Class] Receiver + // @return [String] Converted receiver name + Name: "name", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - arg, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, arg.Class().Name) - } + n, ok := receiver.(*RClass) - r := receiver - if r.findMethod(arg.value) == nil { - return FALSE - } - return TRUE - }, + if !ok { + return t.vm.InitNoMethodError(sourceLine, "#name", receiver) + } + + name := n.ReturnName() + nameString := t.vm.InitStringObject(name) + return nameString }, - { - // Returns the superclass object of the receiver. - // - // ```ruby - // puts(Array.superclass) # => - // puts(String.superclass) # => - // - // class Foo;end - // class Bar < Foo - // end - // puts(Foo.superclass) # => - // puts(Bar.superclass) # => - // ``` - // - // **Note**: the following is not supported: - // - // - Class class - // - // - Object class - // - // - instance objects or object literals - // - // ```ruby - // puts("string".superclass) # => error - // puts(Class.superclass) # => error - // puts(Object.superclass) # => error - // ``` - // @param class [Class] Receiver - // @return [Object] Superclass object of the receiver - Name: "superclass", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // A predicate class method that returns `true` if the object has an ability to respond to the method, otherwise `false`. + // Note that signs like `+` or `?` should be String literal. + // + // ```ruby + // Class.respond_to? "respond_to?" #=> true + // Class.respond_to? :numerator #=> false + // ``` + // + // @param [String] + // @return [Boolean] + Name: "respond_to?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - c, ok := receiver.(*RClass) + arg, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, arg.Class().Name) + } - if !ok { - return t.vm.InitNoMethodError(sourceLine, "#superclass", receiver) - } + r := receiver + if r.findMethod(arg.value) == nil { + return FALSE + } + return TRUE + }, + }, + { + // Returns the superclass object of the receiver. + // + // ```ruby + // puts(Array.superclass) # => + // puts(String.superclass) # => + // + // class Foo;end + // class Bar < Foo + // end + // puts(Foo.superclass) # => + // puts(Bar.superclass) # => + // ``` + // + // **Note**: the following is not supported: + // + // - Class class + // + // - Object class + // + // - instance objects or object literals + // + // ```ruby + // puts("string".superclass) # => error + // puts(Class.superclass) # => error + // puts(Object.superclass) # => error + // ``` + // @param class [Class] Receiver + // @return [Object] Superclass object of the receiver + Name: "superclass", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - superClass := c.returnSuperClass() + c, ok := receiver.(*RClass) - if superClass == nil { - return NULL - } + if !ok { + return t.vm.InitNoMethodError(sourceLine, "#superclass", receiver) + } + + superClass := c.returnSuperClass() - return superClass - }, + if superClass == nil { + return NULL + } + + return superClass }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinClassCommonInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // General method for comparing equalty of the objects - // - // ```ruby - // 123 == 123 # => true - // 123 == "123" # => false - // - // # Hash will not concern about the key-value pair order - // { a: 1, b: 2 } == { a: 1, b: 2 } # => true - // { a: 1, b: 2 } == { b: 2, a: 1 } # => true - // - // # Hash key will be override if the key duplicated - // { a: 1, b: 2 } == { a: 2, b: 2, a: 1 } # => true - // { a: 1, b: 2 } == { a: 1, b: 2, a: 2 } # => false - // - // # Array will concern about the order of the elements - // [1, 2, 3] == [1, 2, 3] # => true - // [1, 2, 3] == [3, 2, 1] # => false - // ``` - // - // @return [@boolean] - Name: "==", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - className := receiver.Class().Name - compareClassName := args[0].Class().Name - - if className == compareClassName && reflect.DeepEqual(receiver, args[0]) { - return TRUE - } - return FALSE - }, - }, - { - // Inverts the boolean value. Any objects other than `nil` and `false` are `true`, thus returns `false`. - // - // ```ruby - // !true # => false - // !false # => true - // !nil # => true - // !555 # => false - // ``` - // - // @param object [Object] object that return boolean value to invert - // @return [Object] Inverted boolean value - Name: "!", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - rightValue, ok := receiver.(*BooleanObject) - if !ok { - return FALSE - } - - if rightValue.value { - return FALSE - } - return TRUE +var builtinClassCommonInstanceMethods = []*BuiltinMethodObject{ + { + // General method for comparing equalty of the objects + // + // ```ruby + // 123 == 123 # => true + // 123 == "123" # => false + // + // # Hash will not concern about the key-value pair order + // { a: 1, b: 2 } == { a: 1, b: 2 } # => true + // { a: 1, b: 2 } == { b: 2, a: 1 } # => true + // + // # Hash key will be override if the key duplicated + // { a: 1, b: 2 } == { a: 2, b: 2, a: 1 } # => true + // { a: 1, b: 2 } == { a: 1, b: 2, a: 2 } # => false + // + // # Array will concern about the order of the elements + // [1, 2, 3] == [1, 2, 3] # => true + // [1, 2, 3] == [3, 2, 1] # => false + // ``` + // + // @return [@boolean] + Name: "==", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + className := receiver.Class().Name + compareClassName := args[0].Class().Name - }, - }, - { - // General method for comparing inequality of the objects - // - // ```ruby - // 123 != 123 # => false - // 123 != "123" # => true - // - // # Hash will not concern about the key-value pair order - // { a: 1, b: 2 } != { a: 1, b: 2 } # => false - // { a: 1, b: 2 } != { b: 2, a: 1 } # => false - // - // # Hash key will be override if the key duplicated - // { a: 1, b: 2 } != { a: 2, b: 2, a: 1 } # => false - // { a: 1, b: 2 } != { a: 1, b: 2, a: 2 } # => true - // - // # Array will concern about the order of the elements - // [1, 2, 3] != [1, 2, 3] # => false - // [1, 2, 3] != [3, 2, 1] # => true - // ``` - // - // @return [Boolean] - Name: "!=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - className := receiver.Class().Name - compareClassName := args[0].Class().Name - - if className == compareClassName && reflect.DeepEqual(receiver, args[0]) { - return FALSE - } + if className == compareClassName && reflect.DeepEqual(receiver, args[0]) { return TRUE - }, + } + return FALSE }, - { - // Returns true if a block is given in the current context and `yield` is ready to call. - // - // **Note:** The method name does not end with '?' because the sign is unavailable in Goby for now. - // - // ```ruby - // class File - // def self.open(filename, mode, perm) - // file = new(filename, mode, perm) - // - // if block_given? - // yield(file) - // end - // - // file.close - // end - // end - // ``` - // - // @param n/a [] - // @return [Boolean] true/false - Name: "block_given?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - cf := t.callFrameStack.callFrames[t.callFrameStack.pointer-2] - - if cf.BlockFrame() == nil { - return FALSE - } + }, + { + // Inverts the boolean value. Any objects other than `nil` and `false` are `true`, thus returns `false`. + // + // ```ruby + // !true # => false + // !false # => true + // !nil # => true + // !555 # => false + // ``` + // + // @param object [Object] object that return boolean value to invert + // @return [Object] Inverted boolean value + Name: "!", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return TRUE + rightValue, ok := receiver.(*BooleanObject) + if !ok { + return FALSE + } + + if rightValue.value { + return FALSE + } + return TRUE - }, }, - { - // Returns the class of the object. Receiver cannot be omitted. - // - // FYI: You can convert the class into String with `#name`. - // - // ```ruby - // puts(100.class) # => - // puts(100.class.name) # => Integer - // puts("123".class) # => - // puts("123".class.name) # => String - // ``` - // - // @param object [Object] Receiver (required) - // @return [Class] The class of the receiver - Name: "class", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return receiver.Class() - - }, + }, + { + // General method for comparing inequality of the objects + // + // ```ruby + // 123 != 123 # => false + // 123 != "123" # => true + // + // # Hash will not concern about the key-value pair order + // { a: 1, b: 2 } != { a: 1, b: 2 } # => false + // { a: 1, b: 2 } != { b: 2, a: 1 } # => false + // + // # Hash key will be override if the key duplicated + // { a: 1, b: 2 } != { a: 2, b: 2, a: 1 } # => false + // { a: 1, b: 2 } != { a: 1, b: 2, a: 2 } # => true + // + // # Array will concern about the order of the elements + // [1, 2, 3] != [1, 2, 3] # => false + // [1, 2, 3] != [3, 2, 1] # => true + // ``` + // + // @return [Boolean] + Name: "!=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + className := receiver.Class().Name + compareClassName := args[0].Class().Name + + if className == compareClassName && reflect.DeepEqual(receiver, args[0]) { + return FALSE + } + return TRUE }, - // Exits from the interpreter, returning the specified exit code (if any). + }, + { + // Returns true if a block is given in the current context and `yield` is ready to call. // - // The method itself formally returns nil, although it's not usable. + // **Note:** The method name does not end with '?' because the sign is unavailable in Goby for now. // // ```ruby - // exit # exits with status code 0 - // exit(1) # exits with status code 1 + // class File + // def self.open(filename, mode, perm) + // file = new(filename, mode, perm) + // + // if block_given? + // yield(file) + // end + // + // file.close + // end + // end // ``` // - // @param [Integer] exit code (optional), defaults to 0 - // @return nil - { - Name: "exit", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - switch aLen { - case 0: - os.Exit(0) - case 1: - exitCode, ok := args[0].(*IntegerObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) - } + // @param n/a [] + // @return [Boolean] true/false + Name: "block_given?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + cf := t.callFrameStack.callFrames[t.callFrameStack.pointer-2] - os.Exit(exitCode.value) - default: - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) - } + if cf.BlockFrame() == nil { + return FALSE + } - return NULL + return TRUE - }, }, - { - // Returns true if Object class is equal to the input argument class - // - // ```ruby - // "Hello".is_a?(String) # => true - // 123.is_a?(Integer) # => true - // [1, true, "String"].is_a?(Array) # => true - // { a: 1, b: 2 }.is_a?(Hash) # => true - // "Hello".is_a?(Integer) # => false - // 123.is_a?(Range) # => false - // (2..4).is_a?(Hash) # => false - // nil.is_a?(Integer) # => false - // ``` - // - // @param n/a [] - // @return [Boolean] - Name: "is_a?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Returns the class of the object. Receiver cannot be omitted. + // + // FYI: You can convert the class into String with `#name`. + // + // ```ruby + // puts(100.class) # => + // puts(100.class.name) # => Integer + // puts("123".class) # => + // puts("123".class.name) # => String + // ``` + // + // @param object [Object] Receiver (required) + // @return [Class] The class of the receiver + Name: "class", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return receiver.Class() - c := args[0] - gobyClass, ok := c.(*RClass) + }, + }, + // Exits from the interpreter, returning the specified exit code (if any). + // + // The method itself formally returns nil, although it's not usable. + // + // ```ruby + // exit # exits with status code 0 + // exit(1) # exits with status code 1 + // ``` + // + // @param [Integer] exit code (optional), defaults to 0 + // @return nil + { + Name: "exit", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + switch aLen { + case 0: + os.Exit(0) + case 1: + exitCode, ok := args[0].(*IntegerObject) if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.ClassClass, c.Class().Name) + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) } - receiverClass := receiver.Class() - - for { - if receiverClass.Name == gobyClass.Name { - return TRUE - } + os.Exit(exitCode.value) + default: + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + } - if receiverClass.Name == classes.ObjectClass { - break - } + return NULL - receiverClass = receiverClass.superClass - } - return FALSE - }, }, - // Checks if the class of the instance has been activated with `inherits_method_missing`. + }, + { + // Returns true if Object class is equal to the input argument class // // ```ruby - // class Bar - // inherits_method_missing - // end - // - // Bar.new.inherits_method_missing? #=> true + // "Hello".is_a?(String) # => true + // 123.is_a?(Integer) # => true + // [1, true, "String"].is_a?(Array) # => true + // { a: 1, b: 2 }.is_a?(Hash) # => true + // "Hello".is_a?(Integer) # => false + // 123.is_a?(Range) # => false + // (2..4).is_a?(Hash) # => false + // nil.is_a?(Integer) # => false // ``` // + // @param n/a [] // @return [Boolean] - { - Name: "inherits_method_missing?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + Name: "is_a?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + c := args[0] + gobyClass, ok := c.(*RClass) - if receiver.Class().inheritsMethodMissing { + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.ClassClass, c.Class().Name) + } + + receiverClass := receiver.Class() + + for { + if receiverClass.Name == gobyClass.Name { return TRUE } - return FALSE + if receiverClass.Name == classes.ObjectClass { + break + } - }, + receiverClass = receiverClass.superClass + } + return FALSE }, - // Evaluates the given block or Block object. - // The evaluation is performed within the context of the receiver. - // - // The variable `self` in the block or the Block object is set to the receiver - // while the code is executing, which allows the code access to the receiver's - // instance variables and private methods. - // - // No other arguments can be taken. - // - // ```ruby - // string = "String" - // string.instance_eval do - // def new_method - // self.reverse - // end - // end - // string.new_method #=> "gnirtS" - // ``` - // - // ```ruby - // block = Block.new do - // def new_method - // self.reverse - // end - // end - // string = "String" - // string.instance_eval(block) - // - // string.new_method #=> "gnirtS" - // ``` - // - // @param block [Block] - // @return [Object] - { - Name: "instance_eval", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - switch aLen { - case 0: - case 1: - if args[0].Class().Name == classes.BlockClass { - blockObj := args[0].(*BlockObject) - blockFrame = newNormalCallFrame(blockObj.instructionSet, blockObj.instructionSet.filename, sourceLine) - blockFrame.ep = blockObj.ep - blockFrame.self = receiver - blockFrame.isBlock = true - } else { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.BlockClass, args[0].Class().Name) - } - default: - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) - } + }, + // Checks if the class of the instance has been activated with `inherits_method_missing`. + // + // ```ruby + // class Bar + // inherits_method_missing + // end + // + // Bar.new.inherits_method_missing? #=> true + // ``` + // + // @return [Boolean] + { + Name: "inherits_method_missing?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - if blockFrame == nil { - return receiver - } + if receiver.Class().inheritsMethodMissing { + return TRUE + } - if blockIsEmpty(blockFrame) { - return receiver - } + return FALSE + + }, + }, + // Evaluates the given block or Block object. + // The evaluation is performed within the context of the receiver. + // + // The variable `self` in the block or the Block object is set to the receiver + // while the code is executing, which allows the code access to the receiver's + // instance variables and private methods. + // + // No other arguments can be taken. + // + // ```ruby + // string = "String" + // string.instance_eval do + // def new_method + // self.reverse + // end + // end + // string.new_method #=> "gnirtS" + // ``` + // + // ```ruby + // block = Block.new do + // def new_method + // self.reverse + // end + // end + // string = "String" + // string.instance_eval(block) + // + // string.new_method #=> "gnirtS" + // ``` + // + // @param block [Block] + // @return [Object] + { + Name: "instance_eval", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + switch aLen { + case 0: + case 1: + if args[0].Class().Name == classes.BlockClass { + blockObj := args[0].(*BlockObject) + blockFrame = newNormalCallFrame(blockObj.instructionSet, blockObj.instructionSet.filename, sourceLine) + blockFrame.ep = blockObj.ep + blockFrame.self = receiver + blockFrame.isBlock = true + } else { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.BlockClass, args[0].Class().Name) + } + default: + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + } + + if blockFrame == nil { + return receiver + } + + if blockIsEmpty(blockFrame) { + return receiver + } - blockFrame.self = receiver - result := t.builtinMethodYield(blockFrame) + blockFrame.self = receiver + result := t.builtinMethodYield(blockFrame) - return result.Target + return result.Target - }, }, - // Returns the value of the instance variable. - // Only string literal with `@` is supported. + }, + // Returns the value of the instance variable. + // Only string literal with `@` is supported. + // + // ```ruby + // class Foo + // def initialize + // @bar = 99 + // end + // end + // + // a = Foo.new + // a.instance_variable_get("@bar") #=> 99 + // ``` + // + // @param string [String] + // @return [Object], value + { + Name: "instance_variable_get", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + arg, isStr := args[0].(*StringObject) + + if !isStr { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } + + obj, ok := receiver.InstanceVariableGet(arg.value) + + if !ok { + return NULL + } + + return obj + }, + }, + { + // Updates the specified instance variable with the value provided + // Only string literal with `@` is supported for specifying an instance variable. // // ```ruby // class Foo @@ -1063,471 +1097,431 @@ func builtinClassCommonInstanceMethods() []*BuiltinMethodObject { // end // // a = Foo.new - // a.instance_variable_get("@bar") #=> 99 + // a.instance_variable_set("@bar", 42) // ``` // - // @param string [String] - // @return [Object], value - { - Name: "instance_variable_get", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + // @param string [String], value [Object] + // @return [Object] value + Name: "instance_variable_set", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) + } - arg, isStr := args[0].(*StringObject) + argName, isStr := args[0].(*StringObject) + obj := args[1] - if !isStr { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + if !isStr { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - obj, ok := receiver.InstanceVariableGet(arg.value) + receiver.InstanceVariableSet(argName.value, obj) - if !ok { - return NULL - } + return obj - return obj - }, }, - { - // Updates the specified instance variable with the value provided - // Only string literal with `@` is supported for specifying an instance variable. - // - // ```ruby - // class Foo - // def initialize - // @bar = 99 - // end - // end - // - // a = Foo.new - // a.instance_variable_set("@bar", 42) - // ``` - // - // @param string [String], value [Object] - // @return [Object] value - Name: "instance_variable_set", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) - } - - argName, isStr := args[0].(*StringObject) - obj := args[1] - - if !isStr { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + }, + // Returns an array that contains the method names of the receiver. + // + // ```ruby + // Class.methods + // ["ancestors", "attr_accessor", "attr_reader", "attr_writer", "extend", "include", "name", "new", "superclass", "!", "!=", "==", "block_given?", "class", "instance_variable_get", "instance_variable_set", "is_a?", "methods", "nil?", "puts", "require", "require_relative", "send", "singleton_class", "sleep", "thread", "to_s"] + // ``` + // + // @param class [Class] Receiver + // @return [Array] + { + Name: "methods", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + methods := []Object{} + set := map[string]interface{}{} + klasses := receiver.Class().ancestors() + if receiver.SingletonClass() != nil { + klasses = append([]*RClass{receiver.SingletonClass()}, klasses...) + } + for _, klass := range klasses { + for _, name := range klass.Methods.names() { + if set[name] == nil { + set[name] = true + methods = append(methods, t.vm.InitStringObject(name)) + } } + } + return t.vm.InitArrayObject(methods) - receiver.InstanceVariableSet(argName.value, obj) - - return obj - - }, }, - // Returns an array that contains the method names of the receiver. + }, + { + // Returns true if Object is nil // // ```ruby - // Class.methods - // ["ancestors", "attr_accessor", "attr_reader", "attr_writer", "extend", "include", "name", "new", "superclass", "!", "!=", "==", "block_given?", "class", "instance_variable_get", "instance_variable_set", "is_a?", "methods", "nil?", "puts", "require", "require_relative", "send", "singleton_class", "sleep", "thread", "to_s"] + // 123.nil? # => false + // "String".nil? # => false + // { a: 1, b: 2 }.nil? # => false + // (3..5).nil? # => false + // nil.nil? # => true (See the implementation of Null#nil? in vm/null.go file) // ``` // - // @param class [Class] Receiver - // @return [Array] - { - Name: "methods", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - methods := []Object{} - set := map[string]interface{}{} - klasses := receiver.Class().ancestors() - if receiver.SingletonClass() != nil { - klasses = append([]*RClass{receiver.SingletonClass()}, klasses...) - } - for _, klass := range klasses { - for _, name := range klass.Methods.names() { - if set[name] == nil { - set[name] = true - methods = append(methods, t.vm.InitStringObject(name)) - } - } - } - return t.vm.InitArrayObject(methods) + // @param n/a [] + // @return [Boolean] + Name: "nil?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + return FALSE - }, }, - { - // Returns true if Object is nil - // - // ```ruby - // 123.nil? # => false - // "String".nil? # => false - // { a: 1, b: 2 }.nil? # => false - // (3..5).nil? # => false - // nil.nil? # => true (See the implementation of Null#nil? in vm/null.go file) - // ``` - // - // @param n/a [] - // @return [Boolean] - Name: "nil?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - return FALSE + }, + { + // Returns object's unique id from Go's `receiver.id()` + // @param n/a [] + // @return [Integer] Object's address + Name: "object_id", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitIntegerObject(receiver.id()) - }, - }, - { - // Returns object's unique id from Go's `receiver.id()` - // @param n/a [] - // @return [Integer] Object's address - Name: "object_id", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitIntegerObject(receiver.id()) - - }, }, - { - // Print an object, without the newline, converting into String if needed. - // - // ```ruby - // print("foo", "bar") - // # => foobar - // ``` - // - // @param *args [Class] String literals, or other objects that can be converted into String. - // @return [Null] - Name: "print", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - for _, arg := range args { - fmt.Print(arg.ToString()) - } + }, + { + // Print an object, without the newline, converting into String if needed. + // + // ```ruby + // print("foo", "bar") + // # => foobar + // ``` + // + // @param *args [Class] String literals, or other objects that can be converted into String. + // @return [Null] + Name: "print", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + for _, arg := range args { + fmt.Print(arg.ToString()) + } - return NULL + return NULL - }, }, - { - // Puts string literals or objects into stdout with a tailing line feed, converting into String - // if needed. - // - // ```ruby - // puts("foo", "bar") - // # => foo - // # => bar - // puts("baz", String.name) - // # => baz - // # => String - // puts("foo" + "bar") - // # => foobar - // ``` - // TODO: interpolation is needed to be implemented. - // - // @param *args [Class] String literals, or other objects that can be converted into String. - // @return [Null] - Name: "puts", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - for _, arg := range args { - fmt.Println(arg.ToString()) - } + }, + { + // Puts string literals or objects into stdout with a tailing line feed, converting into String + // if needed. + // + // ```ruby + // puts("foo", "bar") + // # => foo + // # => bar + // puts("baz", String.name) + // # => baz + // # => String + // puts("foo" + "bar") + // # => foobar + // ``` + // TODO: interpolation is needed to be implemented. + // + // @param *args [Class] String literals, or other objects that can be converted into String. + // @return [Null] + Name: "puts", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + for _, arg := range args { + fmt.Println(arg.ToString()) + } - return NULL + return NULL - }, }, - { - Name: "raise", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - switch aLen { - case 0: - return t.vm.InitErrorObject(errors.InternalError, sourceLine, "") - case 1: - return t.vm.InitErrorObject(errors.InternalError, sourceLine, "'%s'", args[0].ToString()) - case 2: - errorClass, ok := args[0].(*RClass) - - if !ok { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, "a class", args[0].Class().Name) - } + }, + { + Name: "raise", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + switch aLen { + case 0: + return t.vm.InitErrorObject(errors.InternalError, sourceLine, "") + case 1: + return t.vm.InitErrorObject(errors.InternalError, sourceLine, "'%s'", args[0].ToString()) + case 2: + errorClass, ok := args[0].(*RClass) - return t.vm.InitErrorObject(errorClass.Name, sourceLine, "'%s'", args[1].ToString()) + if !ok { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, "a class", args[0].Class().Name) } - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 2, aLen) + return t.vm.InitErrorObject(errorClass.Name, sourceLine, "'%s'", args[1].ToString()) + } + + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 2, aLen) - }, }, - { - // A predicate class method that returns `true` if the object has an ability to respond to the method, otherwise `false`. - // Note that signs like `+` or `?` should be String literal. - // - // ```ruby - // 1.respond_to? :to_i #=> true - // "string".respond_to? "+" #=> true - // 1.respond_to? :numerator #=> false - // ``` - // - // @param [String] - // @return [Boolean] - Name: "respond_to?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // A predicate class method that returns `true` if the object has an ability to respond to the method, otherwise `false`. + // Note that signs like `+` or `?` should be String literal. + // + // ```ruby + // 1.respond_to? :to_i #=> true + // "string".respond_to? "+" #=> true + // 1.respond_to? :numerator #=> false + // ``` + // + // @param [String] + // @return [Boolean] + Name: "respond_to?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - arg, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, arg.Class().Name) - } + arg, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, arg.Class().Name) + } - r := receiver - if r.findMethod(arg.value) == nil { - return FALSE - } - return TRUE + r := receiver + if r.findMethod(arg.value) == nil { + return FALSE + } + return TRUE - }, }, - { - // Loads the given Goby library name without extension (mainly for modules), returning `true` - // if successful and `false` if the feature is already loaded. - // - // ```ruby - // require("db") - // File.extname("foo.rb") - // ``` - // - // TBD: the load paths for `require` - // - // @param filename [String] Quoted file name of the library, without extension - // @return [Boolean] Result of loading module - Name: "require", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Loads the given Goby library name without extension (mainly for modules), returning `true` + // if successful and `false` if the feature is already loaded. + // + // ```ruby + // require("db") + // File.extname("foo.rb") + // ``` + // + // TBD: the load paths for `require` + // + // @param filename [String] Quoted file name of the library, without extension + // @return [Boolean] Result of loading module + Name: "require", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - switch args[0].(type) { - case *StringObject: - libName := args[0].(*StringObject).value - initFunc, ok := standardLibraries[libName] + switch args[0].(type) { + case *StringObject: + libName := args[0].(*StringObject).value + initFunc, ok := standardLibraries[libName] + if !ok { + externalClassLock.Lock() + loaders, ok := externalClasses[libName] + externalClassLock.Unlock() if !ok { - externalClassLock.Lock() - loaders, ok := externalClasses[libName] - externalClassLock.Unlock() - if !ok { - err := t.execGobyLib(libName + ".gb") - if err != nil { - return t.vm.InitErrorObject(errors.IOError, sourceLine, errors.CantLoadFile, libName) - } + err := t.execGobyLib(libName + ".gb") + if err != nil { + return t.vm.InitErrorObject(errors.IOError, sourceLine, errors.CantLoadFile, libName) } - initFunc = func(v *VM) { - for _, l := range loaders { - l(v) - } + } + initFunc = func(v *VM) { + for _, l := range loaders { + l(v) } } + } - initFunc(t.vm) + initFunc(t.vm) - return TRUE - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.CantRequireNonString, args[0].(Object).Class().Name) - } + return TRUE + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.CantRequireNonString, args[0].(Object).Class().Name) + } - }, }, - { - // Loads the Goby library (mainly for modules) from the given local path plus name - // without extension from the current directory, returning `true` if successful, - // and `false` if the feature is already loaded. - // - // ```ruby - // require_relative("../test_fixtures/require_test/foo") - // fifty = Foo.bar(5) - // ``` - // - // @param path/name [String] Quoted file path to library plus name, without extension - // @return [Boolean] Result of loading module - Name: "require_relative", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + }, + { + // Loads the Goby library (mainly for modules) from the given local path plus name + // without extension from the current directory, returning `true` if successful, + // and `false` if the feature is already loaded. + // + // ```ruby + // require_relative("../test_fixtures/require_test/foo") + // fifty = Foo.bar(5) + // ``` + // + // @param path/name [String] Quoted file path to library plus name, without extension + // @return [Boolean] Result of loading module + Name: "require_relative", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + switch args[0].(type) { + case *StringObject: + callerDir := path.Dir(t.vm.currentFilePath()) + filePath := args[0].(*StringObject).value + filePath = path.Join(callerDir, filePath) + filePath += ".gb" + + if t.execFile(filePath) != nil { + return t.vm.InitErrorObject(errors.IOError, sourceLine, errors.CantLoadFile, args[0].(*StringObject).value) } - switch args[0].(type) { - case *StringObject: - callerDir := path.Dir(t.vm.currentFilePath()) - filePath := args[0].(*StringObject).value - filePath = path.Join(callerDir, filePath) - filePath += ".gb" + return TRUE + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.CantRequireNonString, args[0].(Object).Class().Name) + } - if t.execFile(filePath) != nil { - return t.vm.InitErrorObject(errors.IOError, sourceLine, errors.CantLoadFile, args[0].(*StringObject).value) - } + }, + }, + // Invoke the specified instance method or class method. + // - Method name should be either a symbol or String (required). + // - You can pass one or more arguments (option). + // - A block can also be provided (option). + // + // + // ```ruby + // class Foo + // def self.bar + // 10 + // end + // end + // + // Foo.send(:bar) #=> 10 + // + // class Math + // def self.add(x, y) + // x + y + // end + // end + // + // Math.send(:add, 10, 15) #=> 25 + // + // class Foo + // def bar(x, y) + // yield x, y + // end + // end + // a = Foo.new + // + // a.send(:bar, 7, 8) do |i, j| i * j; end #=> 56 + // ``` + // + // @param name [String/symbol], args [Object], block + // @return [Object] + { + Name: "send", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) == 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, 0) + } - return TRUE - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.CantRequireNonString, args[0].(Object).Class().Name) - } + name, ok := args[0].(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } + + t.sendMethod(name.value, len(args)-1, blockFrame, sourceLine) + + return t.Stack.top().Target - }, }, - // Invoke the specified instance method or class method. - // - Method name should be either a symbol or String (required). - // - You can pass one or more arguments (option). - // - A block can also be provided (option). - // + }, + { + // Returns the singleton class object of the receiver class. // // ```ruby // class Foo - // def self.bar - // 10 - // end // end // - // Foo.send(:bar) #=> 10 - // - // class Math - // def self.add(x, y) - // x + y - // end - // end + // Foo.singleton_class + // #=> # + // Foo.singleton_class.ancestors + // #=> [#, #, Class, Object] + // ``` // - // Math.send(:add, 10, 15) #=> 25 + // @param class [Class] receiver + // @return [Object] singleton class + Name: "singleton_class", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + r := receiver + if r.SingletonClass() == nil { + id := t.vm.InitIntegerObject(r.id()) + singletonClass := t.vm.createRClass(fmt.Sprintf("#>", r.Class().Name, id.ToString())) + singletonClass.isSingleton = true + return singletonClass + } + return receiver.SingletonClass() + + }, + }, + { + // Suspends the current thread for duration (sec). // - // class Foo - // def bar(x, y) - // yield x, y - // end - // end - // a = Foo.new + // **Note:** currently, parameter cannot be omitted, and only Integer can be specified. // - // a.send(:bar, 7, 8) do |i, j| i * j; end #=> 56 + // ```ruby + // a = sleep(2) + // puts(a) # => 2 // ``` // - // @param name [String/symbol], args [Object], block - // @return [Object] - { - Name: "send", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) == 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, 0) - } + // @param sec [Integer] time to wait in sec + // @return [Integer] actual time slept in sec + Name: "sleep", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - name, ok := args[0].(*StringObject) + int, ok := args[0].(*IntegerObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + if ok { + seconds := int.value + time.Sleep(time.Duration(seconds) * time.Second) + return int + } - t.sendMethod(name.value, len(args)-1, blockFrame, sourceLine) + float, ok := args[0].(*FloatObject) - return t.Stack.top().Target + if ok { + nanoseconds := int64(float.value * float64(time.Second/time.Nanosecond)) + time.Sleep(time.Duration(nanoseconds) * time.Nanosecond) + return float + } - }, - }, - { - // Returns the singleton class object of the receiver class. - // - // ```ruby - // class Foo - // end - // - // Foo.singleton_class - // #=> # - // Foo.singleton_class.ancestors - // #=> [#, #, Class, Object] - // ``` - // - // @param class [Class] receiver - // @return [Object] singleton class - Name: "singleton_class", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - r := receiver - if r.SingletonClass() == nil { - id := t.vm.InitIntegerObject(r.id()) - singletonClass := t.vm.createRClass(fmt.Sprintf("#>", r.Class().Name, id.ToString())) - singletonClass.isSingleton = true - return singletonClass - } - return receiver.SingletonClass() + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) - }, }, - { - // Suspends the current thread for duration (sec). - // - // **Note:** currently, parameter cannot be omitted, and only Integer can be specified. - // - // ```ruby - // a = sleep(2) - // puts(a) # => 2 - // ``` - // - // @param sec [Integer] time to wait in sec - // @return [Integer] actual time slept in sec - Name: "sleep", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - int, ok := args[0].(*IntegerObject) + }, + { + Name: "thread", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } - if ok { - seconds := int.value - time.Sleep(time.Duration(seconds) * time.Second) - return int - } + newT := t.vm.newThread() - float, ok := args[0].(*FloatObject) + go func() { + newT.builtinMethodYield(blockFrame, args...) + }() - if ok { - nanoseconds := int64(float.value * float64(time.Second/time.Nanosecond)) - time.Sleep(time.Duration(nanoseconds) * time.Nanosecond) - return float - } + // We need to pop this frame from main thread manually, + // because the block's 'leave' instruction is running on other process + t.callFrameStack.pop() - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) + return NULL - }, }, - { - Name: "thread", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - newT := t.vm.newThread() - - go func() { - newT.builtinMethodYield(blockFrame, args...) - }() - - // We need to pop this frame from main thread manually, - // because the block's 'leave' instruction is running on other process - t.callFrameStack.pop() - - return NULL + }, + { + // Returns object's string representation. + // @param n/a [] + // @return [String] Object's string representation. + Name: "to_s", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitStringObject(receiver.ToString()) - }, }, - { - // Returns object's string representation. - // @param n/a [] - // @return [String] Object's string representation. - Name: "to_s", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitStringObject(receiver.ToString()) - - }, - }, - } + }, } // Internal functions =================================================== @@ -1599,7 +1593,7 @@ func initModuleClass(classClass *RClass) *RClass { moduleClass.class = classClass moduleClass.singletonClass = moduleSingletonClass - moduleClass.setBuiltinMethods(builtinModuleCommonClassMethods(), true) + moduleClass.setBuiltinMethods(builtinModuleCommonClassMethods, true) return moduleClass } @@ -1624,7 +1618,7 @@ func initClassClass() *RClass { classClass.class = classClass classClass.singletonClass = classSingletonClass - classClass.setBuiltinMethods(builtinClassCommonClassMethods(), true) + classClass.setBuiltinMethods(builtinClassCommonClassMethods, true) return classClass } @@ -1652,8 +1646,8 @@ func initObjectClass(c *RClass) *RClass { objectClass.pseudoSuperClass = objectClass c.superClass.inherits(objectClass) - objectClass.setBuiltinMethods(builtinClassCommonInstanceMethods(), true) - objectClass.setBuiltinMethods(builtinClassCommonInstanceMethods(), false) + objectClass.setBuiltinMethods(builtinClassCommonInstanceMethods, true) + objectClass.setBuiltinMethods(builtinClassCommonInstanceMethods, false) return objectClass } diff --git a/vm/concurrent_array.go b/vm/concurrent_array.go index 1694f08f7..8b5c37e92 100644 --- a/vm/concurrent_array.go +++ b/vm/concurrent_array.go @@ -58,44 +58,30 @@ type ConcurrentArrayObject struct { } // Class methods -------------------------------------------------------- -func builtinConcurrentArrayClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - - switch aLen { - case 0: - return t.vm.initConcurrentArrayObject([]Object{}) - case 1: - arg := args[0] - arrayArg, ok := arg.(*ArrayObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.ArrayClass, arg.Class().Name) - } - - return t.vm.initConcurrentArrayObject(arrayArg.Elements) - default: - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) - } +var builtinConcurrentArrayClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) - }, - }, - } -} + switch aLen { + case 0: + return t.vm.initConcurrentArrayObject([]Object{}) + case 1: + arg := args[0] + arrayArg, ok := arg.(*ArrayObject) -// Instance methods ----------------------------------------------------- -func builtinConcurrentArrayInstanceMethods() []*BuiltinMethodObject { - methodDefinitions := []*BuiltinMethodObject{} + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.ArrayClass, arg.Class().Name) + } - for methodName, requireWriteLock := range ConcurrentArrayMethodsForwardingTable { - methodFunction := DefineForwardedConcurrentArrayMethod(methodName, requireWriteLock) - methodDefinitions = append(methodDefinitions, methodFunction) - } + return t.vm.initConcurrentArrayObject(arrayArg.Elements) + default: + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + } - return methodDefinitions + }, + }, } // Internal functions =================================================== @@ -116,8 +102,15 @@ func initConcurrentArrayClass(vm *VM) { concurrent := vm.loadConstant("Concurrent", true) array := vm.initializeClass(classes.ArrayClass) - array.setBuiltinMethods(builtinConcurrentArrayInstanceMethods(), false) - array.setBuiltinMethods(builtinConcurrentArrayClassMethods(), true) + var arrayMethodDefinitions = []*BuiltinMethodObject{} + + for methodName, requireWriteLock := range ConcurrentArrayMethodsForwardingTable { + methodFunction := DefineForwardedConcurrentArrayMethod(methodName, requireWriteLock) + arrayMethodDefinitions = append(arrayMethodDefinitions, methodFunction) + } + + array.setBuiltinMethods(arrayMethodDefinitions, false) + array.setBuiltinMethods(builtinConcurrentArrayClassMethods, true) concurrent.setClassConstant(array) } diff --git a/vm/concurrent_hash.go b/vm/concurrent_hash.go index 0395d74eb..405b3aa5b 100644 --- a/vm/concurrent_hash.go +++ b/vm/concurrent_hash.go @@ -33,261 +33,257 @@ type ConcurrentHashObject struct { } // Class methods -------------------------------------------------------- -func builtinConcurrentHashClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen > 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) - } - - if aLen == 0 { - return t.vm.initConcurrentHashObject(make(map[string]Object)) - } - - arg := args[0] - hashArg, ok := arg.(*HashObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.HashClass, arg.Class().Name) - } - - return t.vm.initConcurrentHashObject(hashArg.Pairs) - - }, +var builtinConcurrentHashClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen > 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + } + + if aLen == 0 { + return t.vm.initConcurrentHashObject(make(map[string]Object)) + } + + arg := args[0] + hashArg, ok := arg.(*HashObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.HashClass, arg.Class().Name) + } + + return t.vm.initConcurrentHashObject(hashArg.Pairs) + }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinConcurrentHashInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Retrieves the value (object) that corresponds to the key specified. - // When a key doesn't exist, `nil` is returned, or the default, if set. - // - // ```Ruby - // h = Concurrent::Hash.new({ a: 1, b: "2" }) - // h['a'] #=> 1 - // h['b'] #=> "2" - // h['c'] #=> nil - // ``` - // - // @return [Object] - Name: "[]", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - i := args[0] - key, ok := i.(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, i.Class().Name) - } - - h := receiver.(*ConcurrentHashObject) - - value, ok := h.internalMap.Load(key.value) - - if !ok { - return NULL - } - - return value.(Object) - - }, - }, - { - // Associates the value given by `value` with the key given by `key`. - // Returns the `value`. - // - // ```Ruby - // h = Concurrent::Hash.new{ a: 1, b: "2" }) - // h['a'] = 2 #=> 2 - // h #=> { a: 2, b: "2" } - // ``` - // - // @return [Object] The value - Name: "[]=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - // First arg is index - // Second arg is assigned value - if len(args) != 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) - } - - k := args[0] - key, ok := k.(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, k.Class().Name) - } - - h := receiver.(*ConcurrentHashObject) - h.internalMap.Store(key.value, args[1]) - - return args[1] - - }, +var builtinConcurrentHashInstanceMethods = []*BuiltinMethodObject{ + { + // Retrieves the value (object) that corresponds to the key specified. + // When a key doesn't exist, `nil` is returned, or the default, if set. + // + // ```Ruby + // h = Concurrent::Hash.new({ a: 1, b: "2" }) + // h['a'] #=> 1 + // h['b'] #=> "2" + // h['c'] #=> nil + // ``` + // + // @return [Object] + Name: "[]", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + i := args[0] + key, ok := i.(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, i.Class().Name) + } + + h := receiver.(*ConcurrentHashObject) + + value, ok := h.internalMap.Load(key.value) + + if !ok { + return NULL + } + + return value.(Object) + }, - { - // Remove the key from the hash if key exist. - // - // ```Ruby - // h = Concurrent::Hash.new({ a: 1, b: 2, c: 3 }) - // h.delete("b") # => NULL - // h # => { a: 1, c: 3 } - // ``` - // - // @return [NULL] - Name: "delete", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - h := receiver.(*ConcurrentHashObject) - d := args[0] - deleteKeyObject, ok := d.(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, d.Class().Name) - } - - h.internalMap.Delete(deleteKeyObject.value) + }, + { + // Associates the value given by `value` with the key given by `key`. + // Returns the `value`. + // + // ```Ruby + // h = Concurrent::Hash.new{ a: 1, b: "2" }) + // h['a'] = 2 #=> 2 + // h #=> { a: 2, b: "2" } + // ``` + // + // @return [Object] The value + Name: "[]=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + // First arg is index + // Second arg is assigned value + if len(args) != 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) + } + + k := args[0] + key, ok := k.(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, k.Class().Name) + } + + h := receiver.(*ConcurrentHashObject) + h.internalMap.Store(key.value, args[1]) + + return args[1] - return NULL + }, + }, + { + // Remove the key from the hash if key exist. + // + // ```Ruby + // h = Concurrent::Hash.new({ a: 1, b: 2, c: 3 }) + // h.delete("b") # => NULL + // h # => { a: 1, c: 3 } + // ``` + // + // @return [NULL] + Name: "delete", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + h := receiver.(*ConcurrentHashObject) + d := args[0] + deleteKeyObject, ok := d.(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, d.Class().Name) + } + + h.internalMap.Delete(deleteKeyObject.value) + + return NULL - }, }, - { - // Calls block once for each key in the hash (in sorted key order), passing the - // key-value pair as parameters. - // Note that iteration is not deterministic under all circumstances; see - // https://golang.org/pkg/sync/#Map. - // - // ```Ruby - // h = Concurrent::Hash.new({ b: "2", a: 1 }) - // h.each do |k, v| - // puts k.to_s + "->" + v.to_s - // end - // # => a->1 - // # => b->2 - // ``` - // - // @return [Hash] self - Name: "each", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - hash := receiver.(*ConcurrentHashObject) - framePopped := false - - iterator := func(key, value interface{}) bool { - keyObject := t.vm.InitStringObject(key.(string)) - - t.builtinMethodYield(blockFrame, keyObject, value.(Object)) - - framePopped = true - - return true - } - - hash.internalMap.Range(iterator) - - if !framePopped { - t.callFrameStack.pop() - } - - return hash - - }, + }, + { + // Calls block once for each key in the hash (in sorted key order), passing the + // key-value pair as parameters. + // Note that iteration is not deterministic under all circumstances; see + // https://golang.org/pkg/sync/#Map. + // + // ```Ruby + // h = Concurrent::Hash.new({ b: "2", a: 1 }) + // h.each do |k, v| + // puts k.to_s + "->" + v.to_s + // end + // # => a->1 + // # => b->2 + // ``` + // + // @return [Hash] self + Name: "each", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + hash := receiver.(*ConcurrentHashObject) + framePopped := false + + iterator := func(key, value interface{}) bool { + keyObject := t.vm.InitStringObject(key.(string)) + + t.builtinMethodYield(blockFrame, keyObject, value.(Object)) + + framePopped = true + + return true + } + + hash.internalMap.Range(iterator) + + if !framePopped { + t.callFrameStack.pop() + } + + return hash + }, - { - // Returns true if the key exist in the hash. - // - // ```Ruby - // h = Concurrent::Hash.new({ a: 1, b: "2" }) - // h.has_key?("a") # => true - // h.has_key?("e") # => false - // ``` - // - // @return [Boolean] - Name: "has_key?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - h := receiver.(*ConcurrentHashObject) - i := args[0] - input, ok := i.(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, i.Class().Name) - } - - if _, ok := h.internalMap.Load(input.value); ok { - return TRUE - } - - return FALSE - - }, + }, + { + // Returns true if the key exist in the hash. + // + // ```Ruby + // h = Concurrent::Hash.new({ a: 1, b: "2" }) + // h.has_key?("a") # => true + // h.has_key?("e") # => false + // ``` + // + // @return [Boolean] + Name: "has_key?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + h := receiver.(*ConcurrentHashObject) + i := args[0] + input, ok := i.(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, i.Class().Name) + } + + if _, ok := h.internalMap.Load(input.value); ok { + return TRUE + } + + return FALSE + }, - { - // Returns json that is corresponding to the hash. - // Basically just like Hash#to_json in Rails but currently doesn't support options. - // - // ```Ruby - // h = Concurrent::Hash.new({ a: 1, b: 2 }) - // h.to_json #=> {"a":1,"b":2} - // ``` - // - // @return [String] - Name: "to_json", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - r := receiver.(*ConcurrentHashObject) - return t.vm.InitStringObject(r.ToJSON(t)) - - }, + }, + { + // Returns json that is corresponding to the hash. + // Basically just like Hash#to_json in Rails but currently doesn't support options. + // + // ```Ruby + // h = Concurrent::Hash.new({ a: 1, b: 2 }) + // h.to_json #=> {"a":1,"b":2} + // ``` + // + // @return [String] + Name: "to_json", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*ConcurrentHashObject) + return t.vm.InitStringObject(r.ToJSON(t)) + }, - { - // Returns json that is corresponding to the hash. - // Basically just like Hash#to_json in Rails but currently doesn't support options. - // - // ```Ruby - // h = Concurrent::Hash.new({ a: 1, b: "2"}) - // h.to_s #=> "{ a: 1, b: \"2\" }" - // ``` - // - // @return [String] - Name: "to_s", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - h := receiver.(*ConcurrentHashObject) - return t.vm.InitStringObject(h.ToString()) - - }, + }, + { + // Returns json that is corresponding to the hash. + // Basically just like Hash#to_json in Rails but currently doesn't support options. + // + // ```Ruby + // h = Concurrent::Hash.new({ a: 1, b: "2"}) + // h.to_s #=> "{ a: 1, b: \"2\" }" + // ``` + // + // @return [String] + Name: "to_s", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + h := receiver.(*ConcurrentHashObject) + return t.vm.InitStringObject(h.ToString()) + }, - } + }, } // Internal functions =================================================== @@ -314,8 +310,8 @@ func initConcurrentHashClass(vm *VM) { concurrent := vm.loadConstant("Concurrent", true) hash := vm.initializeClass(classes.HashClass) - hash.setBuiltinMethods(builtinConcurrentHashInstanceMethods(), false) - hash.setBuiltinMethods(builtinConcurrentHashClassMethods(), true) + hash.setBuiltinMethods(builtinConcurrentHashInstanceMethods, false) + hash.setBuiltinMethods(builtinConcurrentHashClassMethods, true) concurrent.setClassConstant(hash) } diff --git a/vm/concurrent_rw_lock.go b/vm/concurrent_rw_lock.go index ae00cadfc..3656dde25 100644 --- a/vm/concurrent_rw_lock.go +++ b/vm/concurrent_rw_lock.go @@ -28,194 +28,190 @@ type ConcurrentRWLockObject struct { } // Class methods -------------------------------------------------------- -func builtinConcurrentRWLockClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - return t.vm.initConcurrentRWLockObject() - - }, +var builtinConcurrentRWLockClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + return t.vm.initConcurrentRWLockObject() + }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinConcurrentRWLockInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Acquires a read lock. - // - // ```Ruby - // lock = Concurrent::RWLock.new - // lock.acquire_read_lock - // # critical section - // lock.release_read_lock - // - // @return [nil] - // ``` - Name: "acquire_read_lock", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - lockObject := receiver.(*ConcurrentRWLockObject) - - lockObject.mutex.RLock() - - return NULL - - }, +var builtinConcurrentRWLockInstanceMethods = []*BuiltinMethodObject{ + { + // Acquires a read lock. + // + // ```Ruby + // lock = Concurrent::RWLock.new + // lock.acquire_read_lock + // # critical section + // lock.release_read_lock + // + // @return [nil] + // ``` + Name: "acquire_read_lock", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + lockObject := receiver.(*ConcurrentRWLockObject) + + lockObject.mutex.RLock() + + return NULL + }, - { - // Acquires a write lock. - // - // ```Ruby - // lock = Concurrent::RWLock.new - // lock.acquire_write_lock - // # critical section - // lock.release_write_lock - // - // @return [nil] - // ``` - Name: "acquire_write_lock", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - lockObject := receiver.(*ConcurrentRWLockObject) - - lockObject.mutex.Lock() - - return NULL - - }, + }, + { + // Acquires a write lock. + // + // ```Ruby + // lock = Concurrent::RWLock.new + // lock.acquire_write_lock + // # critical section + // lock.release_write_lock + // + // @return [nil] + // ``` + Name: "acquire_write_lock", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + lockObject := receiver.(*ConcurrentRWLockObject) + + lockObject.mutex.Lock() + + return NULL + }, - { - // Releases a read lock. - // - // ```Ruby - // lock = Concurrent::RWLock.new - // lock.acquire_read_lock - // # critical section - // lock.release_read_lock - // - // @return [nil] - // ``` - Name: "release_read_lock", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - lockObject := receiver.(*ConcurrentRWLockObject) - - lockObject.mutex.RUnlock() - - return NULL - - }, + }, + { + // Releases a read lock. + // + // ```Ruby + // lock = Concurrent::RWLock.new + // lock.acquire_read_lock + // # critical section + // lock.release_read_lock + // + // @return [nil] + // ``` + Name: "release_read_lock", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + lockObject := receiver.(*ConcurrentRWLockObject) + + lockObject.mutex.RUnlock() + + return NULL + }, - { - // Releases a write lock. - // - // ```Ruby - // lock = Concurrent::RWLock.new - // lock.acquire_write_lock - // # critical section - // lock.release_write_lock - // - // @return [nil] - // ``` - Name: "release_write_lock", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - lockObject := receiver.(*ConcurrentRWLockObject) - - lockObject.mutex.Unlock() - - return NULL - - }, + }, + { + // Releases a write lock. + // + // ```Ruby + // lock = Concurrent::RWLock.new + // lock.acquire_write_lock + // # critical section + // lock.release_write_lock + // + // @return [nil] + // ``` + Name: "release_write_lock", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + lockObject := receiver.(*ConcurrentRWLockObject) + + lockObject.mutex.Unlock() + + return NULL + }, - { - // Executes the block with a read lock. - // The lock is freed upon exiting the block. - // - // ```Ruby - // lock = Concurrent::RWLock.new - // lock.with_read_lock do - // # critical section - // end - // - // @return [Object] the yielded value of the block. - // ``` - Name: "with_read_lock", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - lockObject := receiver.(*ConcurrentRWLockObject) - - lockObject.mutex.RLock() - - blockReturnValue := t.builtinMethodYield(blockFrame).Target - - lockObject.mutex.RUnlock() - - return blockReturnValue - - }, + }, + { + // Executes the block with a read lock. + // The lock is freed upon exiting the block. + // + // ```Ruby + // lock = Concurrent::RWLock.new + // lock.with_read_lock do + // # critical section + // end + // + // @return [Object] the yielded value of the block. + // ``` + Name: "with_read_lock", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + lockObject := receiver.(*ConcurrentRWLockObject) + + lockObject.mutex.RLock() + + blockReturnValue := t.builtinMethodYield(blockFrame).Target + + lockObject.mutex.RUnlock() + + return blockReturnValue + }, - { - // Executes the block with a write lock. - // The lock is freed upon exiting the block. - // - // ```Ruby - // lock = Concurrent::RWLock.new - // lock.with_write_lock do - // # critical section - // end - // - // @return [Object] the yielded value of the block. - // ``` - Name: "with_write_lock", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - lockObject := receiver.(*ConcurrentRWLockObject) - - lockObject.mutex.Lock() - - blockReturnValue := t.builtinMethodYield(blockFrame).Target - - lockObject.mutex.Unlock() - - return blockReturnValue - - }, + }, + { + // Executes the block with a write lock. + // The lock is freed upon exiting the block. + // + // ```Ruby + // lock = Concurrent::RWLock.new + // lock.with_write_lock do + // # critical section + // end + // + // @return [Object] the yielded value of the block. + // ``` + Name: "with_write_lock", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + lockObject := receiver.(*ConcurrentRWLockObject) + + lockObject.mutex.Lock() + + blockReturnValue := t.builtinMethodYield(blockFrame).Target + + lockObject.mutex.Unlock() + + return blockReturnValue + }, - } + }, } // Internal functions =================================================== @@ -236,8 +232,8 @@ func initConcurrentRWLockClass(vm *VM) { concurrentModule := vm.loadConstant("Concurrent", true) lockClass := vm.initializeClass("RWLock") - lockClass.setBuiltinMethods(builtinConcurrentRWLockInstanceMethods(), false) - lockClass.setBuiltinMethods(builtinConcurrentRWLockClassMethods(), true) + lockClass.setBuiltinMethods(builtinConcurrentRWLockInstanceMethods, false) + lockClass.setBuiltinMethods(builtinConcurrentRWLockClassMethods, true) concurrentModule.setClassConstant(lockClass) } diff --git a/vm/decimal.go b/vm/decimal.go index fd6082302..451b4bfbf 100644 --- a/vm/decimal.go +++ b/vm/decimal.go @@ -45,511 +45,507 @@ type DecimalObject struct { } // Class methods -------------------------------------------------------- -func builtinDecimalClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitNoMethodError(sourceLine, "#new", receiver) - - }, +var builtinDecimalClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitNoMethodError(sourceLine, "#new", receiver) + }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinDecimalInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Returns the sum of self and a numeric. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // - // ```Ruby - // "1.1".to_d + "2.1".to_d # => 3.2 - // "1.1".to_d + 2 # => 3.2 - // "1.1".to_d + "2.1".to_f - // # => 3.200000000000000088817841970012523233890533447265625 - // ``` - // - // @return [Decimal] - Name: "+", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - operation := func(leftValue *Decimal, rightValue *Decimal) *Decimal { - return new(Decimal).Add(leftValue, rightValue) - } +var builtinDecimalInstanceMethods = []*BuiltinMethodObject{ + { + // Returns the sum of self and a numeric. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // + // ```Ruby + // "1.1".to_d + "2.1".to_d # => 3.2 + // "1.1".to_d + 2 # => 3.2 + // "1.1".to_d + "2.1".to_f + // # => 3.200000000000000088817841970012523233890533447265625 + // ``` + // + // @return [Decimal] + Name: "+", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + operation := func(leftValue *Decimal, rightValue *Decimal) *Decimal { + return new(Decimal).Add(leftValue, rightValue) + } + + return receiver.(*DecimalObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - return receiver.(*DecimalObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - - }, }, - { - // Returns the subtraction of a decimal from self. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // - // ```Ruby - // ("1.5".to_d) - "1.1".to_d # => 0.4 - // ("1.5".to_d) - 1 # => 0.5 - // ("1.5".to_d) - "1.1".to_f # => 0.4 - // #=> 0.399999999999999911182158029987476766109466552734375 - // ``` - // - // @return [Decimal] - Name: "-", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - operation := func(leftValue *Decimal, rightValue *Decimal) *Decimal { - return new(Decimal).Sub(leftValue, rightValue) - } + }, + { + // Returns the subtraction of a decimal from self. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // + // ```Ruby + // ("1.5".to_d) - "1.1".to_d # => 0.4 + // ("1.5".to_d) - 1 # => 0.5 + // ("1.5".to_d) - "1.1".to_f # => 0.4 + // #=> 0.399999999999999911182158029987476766109466552734375 + // ``` + // + // @return [Decimal] + Name: "-", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + operation := func(leftValue *Decimal, rightValue *Decimal) *Decimal { + return new(Decimal).Sub(leftValue, rightValue) + } + + return receiver.(*DecimalObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - return receiver.(*DecimalObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - - }, }, - { - // Returns self multiplying a decimal. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // - // ```Ruby - // "2.5".to_d * "10.1".to_d # => 25.25 - // "2.5".to_d * 10 # => 25 - // "2.5".to_d * "10.1".to_f - // #=> 25.24999999999999911182158029987476766109466552734375 - // ``` - // - // @return [Decimal] - Name: "*", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - operation := func(leftValue *Decimal, rightValue *Decimal) *Decimal { - return new(Decimal).Mul(leftValue, rightValue) - } + }, + { + // Returns self multiplying a decimal. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // + // ```Ruby + // "2.5".to_d * "10.1".to_d # => 25.25 + // "2.5".to_d * 10 # => 25 + // "2.5".to_d * "10.1".to_f + // #=> 25.24999999999999911182158029987476766109466552734375 + // ``` + // + // @return [Decimal] + Name: "*", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + operation := func(leftValue *Decimal, rightValue *Decimal) *Decimal { + return new(Decimal).Mul(leftValue, rightValue) + } + + return receiver.(*DecimalObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - return receiver.(*DecimalObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - - }, }, - { - // Returns self squaring a decimal. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // Note that the calculation is via float64 (math.Pow) for now. - // - // ```Ruby - // "4.0".to_d ** "2.5".to_d # => 32 - // "4.0".to_d ** 2 # => 16 - // "4.0".to_d ** "2.5".to_f # => 32 - // "4.0".to_d ** "2.1".to_d - // #=> 18.379173679952561570871694129891693592071533203125 - // "4.0".to_d ** "2.1".to_f - // #=> 18.379173679952561570871694129891693592071533203125 - // ``` - // - // @return [Decimal] - Name: "**", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - operation := func(leftValue *Decimal, rightValue *Decimal) *Decimal { - l, _ := leftValue.Float64() - r, _ := rightValue.Float64() - return new(Decimal).SetFloat64(math.Pow(l, r)) - } + }, + { + // Returns self squaring a decimal. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // Note that the calculation is via float64 (math.Pow) for now. + // + // ```Ruby + // "4.0".to_d ** "2.5".to_d # => 32 + // "4.0".to_d ** 2 # => 16 + // "4.0".to_d ** "2.5".to_f # => 32 + // "4.0".to_d ** "2.1".to_d + // #=> 18.379173679952561570871694129891693592071533203125 + // "4.0".to_d ** "2.1".to_f + // #=> 18.379173679952561570871694129891693592071533203125 + // ``` + // + // @return [Decimal] + Name: "**", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + operation := func(leftValue *Decimal, rightValue *Decimal) *Decimal { + l, _ := leftValue.Float64() + r, _ := rightValue.Float64() + return new(Decimal).SetFloat64(math.Pow(l, r)) + } + + return receiver.(*DecimalObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - return receiver.(*DecimalObject).arithmeticOperation(t, args[0], operation, sourceLine, false) + }, + }, + { + // Returns self divided by a decimal. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // + // ```Ruby + // "7.5".to_d / "3.1".to_d.fraction # => 75/31 + // "7.5".to_d / "3.1".to_d + // # => 2.419354838709677419354838709677419354838709677419354838709677 + // "7.5".to_d / 3 # => 2.5 + // "7.5".to_d / "3.1".to_f + // #=> 2.419354838709677350038104601967335570360611893758448172620333 + // ``` + // + // @return [Decimal] + Name: "/", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + decimalOperation := func(leftValue *Decimal, rightValue *Decimal) *Decimal { + return new(Decimal).Quo(leftValue, rightValue) + } + + return receiver.(*DecimalObject).arithmeticOperation(t, args[0], decimalOperation, sourceLine, true) - }, }, - { - // Returns self divided by a decimal. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // - // ```Ruby - // "7.5".to_d / "3.1".to_d.fraction # => 75/31 - // "7.5".to_d / "3.1".to_d - // # => 2.419354838709677419354838709677419354838709677419354838709677 - // "7.5".to_d / 3 # => 2.5 - // "7.5".to_d / "3.1".to_f - // #=> 2.419354838709677350038104601967335570360611893758448172620333 - // ``` - // - // @return [Decimal] - Name: "/", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - decimalOperation := func(leftValue *Decimal, rightValue *Decimal) *Decimal { - return new(Decimal).Quo(leftValue, rightValue) + }, + { + // Returns if self is larger than a decimal. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // + // ```Ruby + // a = "3.14".to_d + // b = "3.16".to_d + // a > b # => false + // b > a # => true + // a > 3 # => true + // a > 4 # => false + // a > "3.1".to_f # => true + // a > "3.2".to_f # => false + // ``` + // + // @return [Boolean] + Name: ">", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { + if leftValue.Cmp(rightValue) == 1 { + return true } - return receiver.(*DecimalObject).arithmeticOperation(t, args[0], decimalOperation, sourceLine, true) + return false + } - }, - }, - { - // Returns if self is larger than a decimal. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // - // ```Ruby - // a = "3.14".to_d - // b = "3.16".to_d - // a > b # => false - // b > a # => true - // a > 3 # => true - // a > 4 # => false - // a > "3.1".to_f # => true - // a > "3.2".to_f # => false - // ``` - // - // @return [Boolean] - Name: ">", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { - if leftValue.Cmp(rightValue) == 1 { - return true - } + return receiver.(*DecimalObject).numericComparison(t, args[0], decimalOperation, sourceLine) + }, + }, + { + // Returns if self is larger than or equals to a Numeric. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // + // ```Ruby + // a = "3.14".to_d + // b = "3.16".to_d + // e = "3.14".to_d + // a >= b # => false + // b >= a # => true + // a >= e # => true + // a >= 3 # => true + // a >= 4 # => false + // a >= "3.1".to_f # => true + // a >= "3.2".to_f # => false + // ``` + // + // @return [Boolean] + Name: ">=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { + switch leftValue.Cmp(rightValue) { + case 1, 0: + return true + default: return false } + } - return receiver.(*DecimalObject).numericComparison(t, args[0], decimalOperation, sourceLine) + return receiver.(*DecimalObject).numericComparison(t, args[0], decimalOperation, sourceLine) - }, }, - { - // Returns if self is larger than or equals to a Numeric. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // - // ```Ruby - // a = "3.14".to_d - // b = "3.16".to_d - // e = "3.14".to_d - // a >= b # => false - // b >= a # => true - // a >= e # => true - // a >= 3 # => true - // a >= 4 # => false - // a >= "3.1".to_f # => true - // a >= "3.2".to_f # => false - // ``` - // - // @return [Boolean] - Name: ">=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { - switch leftValue.Cmp(rightValue) { - case 1, 0: - return true - default: - return false - } + }, + { + // Returns if self is smaller than a Numeric. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // + // ```Ruby + // a = "3.14".to_d + // b = "3.16".to_d + // a < b # => true + // b < a # => false + // a < 3 # => false + // a < 4 # => true + // a < "3.1".to_f # => false + // a < "3.2".to_f # => true + // ``` + // + // @return [Boolean] + Name: "<", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { + if leftValue.Cmp(rightValue) == -1 { + return true } - return receiver.(*DecimalObject).numericComparison(t, args[0], decimalOperation, sourceLine) + return false + } - }, - }, - { - // Returns if self is smaller than a Numeric. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // - // ```Ruby - // a = "3.14".to_d - // b = "3.16".to_d - // a < b # => true - // b < a # => false - // a < 3 # => false - // a < 4 # => true - // a < "3.1".to_f # => false - // a < "3.2".to_f # => true - // ``` - // - // @return [Boolean] - Name: "<", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { - if leftValue.Cmp(rightValue) == -1 { - return true - } + return receiver.(*DecimalObject).numericComparison(t, args[0], decimalOperation, sourceLine) + }, + }, + { + // Returns if self is smaller than or equals to a decimal. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // + // ```Ruby + // a = "3.14".to_d + // b = "3.16".to_d + // e = "3.14".to_d + // a <= b # => true + // b <= a # => false + // a <= e # => false + // a <= 3 # => false + // a <= 4 # => true + // a <= "3.1".to_f # => false + // a <= "3.2".to_f # => true + // ``` + // + // @return [Boolean] + Name: "<=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { + switch leftValue.Cmp(rightValue) { + case -1, 0: + return true + default: return false } + } - return receiver.(*DecimalObject).numericComparison(t, args[0], decimalOperation, sourceLine) - - }, + return receiver.(*DecimalObject).numericComparison(t, args[0], decimalOperation, sourceLine) }, - { - // Returns if self is smaller than or equals to a decimal. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // - // ```Ruby - // a = "3.14".to_d - // b = "3.16".to_d - // e = "3.14".to_d - // a <= b # => true - // b <= a # => false - // a <= e # => false - // a <= 3 # => false - // a <= 4 # => true - // a <= "3.1".to_f # => false - // a <= "3.2".to_f # => true - // ``` - // - // @return [Boolean] - Name: "<=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { - switch leftValue.Cmp(rightValue) { - case -1, 0: - return true - default: - return false - } - } + }, + { + // Returns 1 if self is larger than a Numeric, -1 if smaller. Otherwise 0. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // x < y: -1 + // x == y: 0 (including -0 == 0, -Infinity == +Infinity and vice versa) + // x > y: 1 + // + // ```Ruby + // "1.5".to_d <=> 3 # => -1 + // "1.0".to_d <=> 1 # => 0 + // "3.5".to_d <=> 1 # => 1 + // ``` + // + // @return [Integer] + Name: "<=>", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + decimalOperation := func(leftValue *Decimal, rightValue *Decimal) int { + return leftValue.Cmp(rightValue) + } + + return receiver.(*DecimalObject).rocketComparison(t, args[0], decimalOperation, sourceLine) - return receiver.(*DecimalObject).numericComparison(t, args[0], decimalOperation, sourceLine) - }, }, - { - // Returns 1 if self is larger than a Numeric, -1 if smaller. Otherwise 0. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // x < y: -1 - // x == y: 0 (including -0 == 0, -Infinity == +Infinity and vice versa) - // x > y: 1 - // - // ```Ruby - // "1.5".to_d <=> 3 # => -1 - // "1.0".to_d <=> 1 # => 0 - // "3.5".to_d <=> 1 # => 1 - // ``` - // - // @return [Integer] - Name: "<=>", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - decimalOperation := func(leftValue *Decimal, rightValue *Decimal) int { - return leftValue.Cmp(rightValue) + }, + { + // Returns if self is equal to an Object. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // If the Object is not a Numeric the result is always false. + // + // ```Ruby + // "1.0".to_d == 3 # => false + // "1.0".to_d == 1 # => true + // "1.0".to_d == "1".to_d # => true + // "1.0".to_d == "1".to_f # => false + // "1.0".to_d == "1.0".to_f # => false + // "1.0".to_d == 'str' # => false + // "1.0".to_d == Array # => false + // ``` + // + // @return [Boolean] + Name: "==", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { + if leftValue.Cmp(rightValue) == 0 { + return true } - return receiver.(*DecimalObject).rocketComparison(t, args[0], decimalOperation, sourceLine) - - }, - }, - { - // Returns if self is equal to an Object. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // If the Object is not a Numeric the result is always false. - // - // ```Ruby - // "1.0".to_d == 3 # => false - // "1.0".to_d == 1 # => true - // "1.0".to_d == "1".to_d # => true - // "1.0".to_d == "1".to_f # => false - // "1.0".to_d == "1.0".to_f # => false - // "1.0".to_d == 'str' # => false - // "1.0".to_d == Array # => false - // ``` - // - // @return [Boolean] - Name: "==", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { - if leftValue.Cmp(rightValue) == 0 { - return true - } + return false + } - return false - } + return receiver.(*DecimalObject).equalityTest(t, args[0], decimalOperation, true, sourceLine) - return receiver.(*DecimalObject).equalityTest(t, args[0], decimalOperation, true, sourceLine) - - }, }, - { - // Returns if self is not equal to an Object. - // If the second term is integer or float, they are converted into decimal and then perform calculation. - // If the Object is not a Numeric the result is always false. - // - // ```Ruby - // "1.0".to_d != 3 # => false - // "1.0".to_d != 1 # => true - // "1.0".to_d != "1".to_d # => true - // "1.0".to_d != "1".to_f # => false - // "1.0".to_d != "1.0".to_f # => false - // "1.0".to_d != 'str' # => false - // "1.0".to_d != Array # => false - // ``` - // - // @return [Boolean] - Name: "!=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { - if leftValue.Cmp(rightValue) != 0 { - return true - } - - return false + }, + { + // Returns if self is not equal to an Object. + // If the second term is integer or float, they are converted into decimal and then perform calculation. + // If the Object is not a Numeric the result is always false. + // + // ```Ruby + // "1.0".to_d != 3 # => false + // "1.0".to_d != 1 # => true + // "1.0".to_d != "1".to_d # => true + // "1.0".to_d != "1".to_f # => false + // "1.0".to_d != "1.0".to_f # => false + // "1.0".to_d != 'str' # => false + // "1.0".to_d != Array # => false + // ``` + // + // @return [Boolean] + Name: "!=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + decimalOperation := func(leftValue *Decimal, rightValue *Decimal) bool { + if leftValue.Cmp(rightValue) != 0 { + return true } - return receiver.(*DecimalObject).equalityTest(t, args[0], decimalOperation, false, sourceLine) + return false + } + + return receiver.(*DecimalObject).equalityTest(t, args[0], decimalOperation, false, sourceLine) - }, }, - { - // Returns a string with fraction format of the decimal. - // If the denominator is 1, '/1` is omitted. - // Minus sign will be preserved. - // (Actually, the internal rational number is always deducted) - // - // ```Ruby - // a = "-355/113".to_d - // a.reduction #=> -355/113 - // b = "-331/1".to_d - // b.reduction #=> -331 - // ``` - // - // @return [String] - Name: "reduction", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitStringObject(receiver.(*DecimalObject).value.RatString()) - - }, + }, + { + // Returns a string with fraction format of the decimal. + // If the denominator is 1, '/1` is omitted. + // Minus sign will be preserved. + // (Actually, the internal rational number is always deducted) + // + // ```Ruby + // a = "-355/113".to_d + // a.reduction #=> -355/113 + // b = "-331/1".to_d + // b.reduction #=> -331 + // ``` + // + // @return [String] + Name: "reduction", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitStringObject(receiver.(*DecimalObject).value.RatString()) + }, - { - // Returns the denominator of the decimal value which contains Go's big.Rat type. - // The value is Decimal. - // The value does not contain a minus sign. - // - // ```Ruby - // a = "-355/113".to_d - // a.denominator #=> 113 - // ``` - // - // @return [Decimal] - Name: "denominator", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.initDecimalObject(new(Decimal).SetInt(receiver.(*DecimalObject).value.Denom())) - - }, + }, + { + // Returns the denominator of the decimal value which contains Go's big.Rat type. + // The value is Decimal. + // The value does not contain a minus sign. + // + // ```Ruby + // a = "-355/113".to_d + // a.denominator #=> 113 + // ``` + // + // @return [Decimal] + Name: "denominator", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.initDecimalObject(new(Decimal).SetInt(receiver.(*DecimalObject).value.Denom())) + }, - { - // Returns a string with fraction format of the decimal. - // Even though the denominator is 1, fraction format is used. - // Minus sign will be preserved. - // - // ```Ruby - // a = "-355/113".to_d - // a.fraction #=> -355/113 - // b = "-331/1".to_d - // b.fraction #=> -331/1 - // ``` - // - // @return [String] - Name: "fraction", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitStringObject(receiver.(*DecimalObject).value.String()) - - }, + }, + { + // Returns a string with fraction format of the decimal. + // Even though the denominator is 1, fraction format is used. + // Minus sign will be preserved. + // + // ```Ruby + // a = "-355/113".to_d + // a.fraction #=> -355/113 + // b = "-331/1".to_d + // b.fraction #=> -331/1 + // ``` + // + // @return [String] + Name: "fraction", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitStringObject(receiver.(*DecimalObject).value.String()) + }, - { - // Inverses the numerator and the denominator of the decimal and returns it. - // Minus sign will move to the new numerator. - // - // ```Ruby - // a = "-355/113".to_d - // a.inverse.fraction #=> -113/355 - // ``` - // - // @return [Decimal] - Name: "inverse", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - d := receiver.(*DecimalObject).value - return t.vm.initDecimalObject(d.Inv(d)) - - }, + }, + { + // Inverses the numerator and the denominator of the decimal and returns it. + // Minus sign will move to the new numerator. + // + // ```Ruby + // a = "-355/113".to_d + // a.inverse.fraction #=> -113/355 + // ``` + // + // @return [Decimal] + Name: "inverse", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + d := receiver.(*DecimalObject).value + return t.vm.initDecimalObject(d.Inv(d)) + }, - { - // Returns the numerator of the decimal value which contains Go's big.Rat type. - // The value is Decimal. - // The value can contain a minus sign. - // - // ```Ruby - // a = "-355/113".to_d - // a.numerator #=> -355 - // ``` - // - // @return [Decimal] - Name: "numerator", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.initDecimalObject(new(Decimal).SetInt(receiver.(*DecimalObject).value.Num())) - - }, + }, + { + // Returns the numerator of the decimal value which contains Go's big.Rat type. + // The value is Decimal. + // The value can contain a minus sign. + // + // ```Ruby + // a = "-355/113".to_d + // a.numerator #=> -355 + // ``` + // + // @return [Decimal] + Name: "numerator", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.initDecimalObject(new(Decimal).SetInt(receiver.(*DecimalObject).value.Num())) + }, - { - // Returns an array with two Decimal elements: numerator and denominator. - // - // ```ruby - // "129.30928304982039482039842".to_d.to_a - // # => [6465464152491019741019921, 50000000000000000000000] - // ``` - // - // @return [Array] - Name: "to_a", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - n := receiver.(*DecimalObject).value.Num() - d := receiver.(*DecimalObject).value.Denom() - elems := []Object{} - - elems = append(elems, t.vm.initDecimalObject(new(Decimal).SetInt(n))) - elems = append(elems, t.vm.initDecimalObject(new(Decimal).SetInt(d))) - - return t.vm.InitArrayObject(elems) - - }, + }, + { + // Returns an array with two Decimal elements: numerator and denominator. + // + // ```ruby + // "129.30928304982039482039842".to_d.to_a + // # => [6465464152491019741019921, 50000000000000000000000] + // ``` + // + // @return [Array] + Name: "to_a", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + + n := receiver.(*DecimalObject).value.Num() + d := receiver.(*DecimalObject).value.Denom() + elems := []Object{} + + elems = append(elems, t.vm.initDecimalObject(new(Decimal).SetInt(n))) + elems = append(elems, t.vm.initDecimalObject(new(Decimal).SetInt(d))) + + return t.vm.InitArrayObject(elems) + }, - { - // Returns Float object from Decimal object. - // In most case the number of digits in Float is shorter than the one in Decimal. - // - // ```Ruby - // a = "355/113".to_d - // a.to_s # => 3.1415929203539823008849557522123893805309734513274336283185840 - // a.to_f # => 3.1415929203539825 - // ``` - // - // @return [Float] - Name: "to_f", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.initFloatObject(receiver.(*DecimalObject).FloatValue()) - - }, + }, + { + // Returns Float object from Decimal object. + // In most case the number of digits in Float is shorter than the one in Decimal. + // + // ```Ruby + // a = "355/113".to_d + // a.to_s # => 3.1415929203539823008849557522123893805309734513274336283185840 + // a.to_f # => 3.1415929203539825 + // ``` + // + // @return [Float] + Name: "to_f", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.initFloatObject(receiver.(*DecimalObject).FloatValue()) + }, - { - // Returns the truncated Integer object from Decimal object. - // - // ```Ruby - // a = "355/113".to_d - // a.to_i # => 3 - // ``` - // - // @return [Integer] - Name: "to_i", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitIntegerObject(receiver.(*DecimalObject).IntegerValue()) - - }, + }, + { + // Returns the truncated Integer object from Decimal object. + // + // ```Ruby + // a = "355/113".to_d + // a.to_i # => 3 + // ``` + // + // @return [Integer] + Name: "to_i", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitIntegerObject(receiver.(*DecimalObject).IntegerValue()) + }, - { - // Returns the float-converted decimal value with a string style. - // Maximum digit under the dots is 60. - // This is just to print the final value should not be used for recalculation. - // - // ```Ruby - // a = "355/113".to_d - // a.to_s # => 3.1415929203539823008849557522123893805309734513274336283185840 - // ``` - // - // @return [String] - Name: "to_s", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitStringObject(receiver.(*DecimalObject).ToString()) - - }, + }, + { + // Returns the float-converted decimal value with a string style. + // Maximum digit under the dots is 60. + // This is just to print the final value should not be used for recalculation. + // + // ```Ruby + // a = "355/113".to_d + // a.to_s # => 3.1415929203539823008849557522123893805309734513274336283185840 + // ``` + // + // @return [String] + Name: "to_s", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitStringObject(receiver.(*DecimalObject).ToString()) + }, - } + }, } // Internal functions =================================================== @@ -565,8 +561,8 @@ func (vm *VM) initDecimalObject(value *Decimal) *DecimalObject { func (vm *VM) initDecimalClass() *RClass { dc := vm.initializeClass(classes.DecimalClass) - dc.setBuiltinMethods(builtinDecimalInstanceMethods(), false) - dc.setBuiltinMethods(builtinDecimalClassMethods(), true) + dc.setBuiltinMethods(builtinDecimalInstanceMethods, false) + dc.setBuiltinMethods(builtinDecimalClassMethods, true) return dc } diff --git a/vm/file.go b/vm/file.go index 5dc72bb43..56d1a9d64 100644 --- a/vm/file.go +++ b/vm/file.go @@ -34,436 +34,432 @@ var fileModeTable = map[string]int{ } // Class methods -------------------------------------------------------- -func builtinFileClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Returns the last element from path. - // - // ```ruby - // File.basename("/home/goby/plugin/loop.gb") # => loop.gb - // ``` - // @param filePath [String] - // @return [String] - Name: "basename", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } +var builtinFileClassMethods = []*BuiltinMethodObject{ + { + // Returns the last element from path. + // + // ```ruby + // File.basename("/home/goby/plugin/loop.gb") # => loop.gb + // ``` + // @param filePath [String] + // @return [String] + Name: "basename", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - fn, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + fn, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - return t.vm.InitStringObject(filepath.Base(fn.value)) + return t.vm.InitStringObject(filepath.Base(fn.value)) - }, }, - { - // Changes the mode of the file. - // Return number of files. - // - // ```ruby - // File.chmod(0755, "test.sh") # => 1 - // File.chmod(0755, "goby", "../test.sh") # => 2 - // ``` - // @param fileName [String] - // @return [Integer] - Name: "chmod", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) < 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 2, len(args)) + }, + { + // Changes the mode of the file. + // Return number of files. + // + // ```ruby + // File.chmod(0755, "test.sh") # => 1 + // File.chmod(0755, "goby", "../test.sh") # => 2 + // ``` + // @param fileName [String] + // @return [Integer] + Name: "chmod", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) < 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 2, len(args)) + } + + mod, ok := args[0].(*IntegerObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.IntegerClass, args[0].Class().Name) + } + + if !os.FileMode(mod.value).IsRegular() { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.InvalidChmodNumber, mod.value) + } + + for i := 1; i < len(args); i++ { + fn, ok := args[i].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, i+1, classes.StringClass, args[0].Class().Name) } - mod, ok := args[0].(*IntegerObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.IntegerClass, args[0].Class().Name) + if !filepath.IsAbs(fn.value) { + fn.value = filepath.Join(t.vm.fileDir, fn.value) } - if !os.FileMode(mod.value).IsRegular() { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.InvalidChmodNumber, mod.value) + err := os.Chmod(fn.value, os.FileMode(uint32(mod.value))) + if err != nil { + return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) } + } - for i := 1; i < len(args); i++ { - fn, ok := args[i].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, i+1, classes.StringClass, args[0].Class().Name) - } + return t.vm.InitIntegerObject(len(args) - 1) - if !filepath.IsAbs(fn.value) { - fn.value = filepath.Join(t.vm.fileDir, fn.value) - } + }, + }, + // Deletes the specified files. + // Return the number of deleted files. + // The number of the argument can be zero, but deleting non-existent files causes an error. + // + // ```ruby + // File.delete("test.sh") # => 1 + // File.delete("test.sh", "test2.sh") # => 2 + // File.delete() # => 0 + // File.delete("non-existent.txt") # => + // ``` + // @param fileName [String] + // @return [Integer] + { + Name: "delete", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + for i, arg := range args { + fn, ok := arg.(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, i+1, classes.StringClass, args[i].Class().Name) + } + err := os.Remove(fn.value) - err := os.Chmod(fn.value, os.FileMode(uint32(mod.value))) - if err != nil { - return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) - } + if err != nil { + return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) } + } - return t.vm.InitIntegerObject(len(args) - 1) + return t.vm.InitIntegerObject(len(args)) - }, }, - // Deletes the specified files. - // Return the number of deleted files. - // The number of the argument can be zero, but deleting non-existent files causes an error. + }, + // Determines if the specified file. + // + // ```ruby + // File.exist?("test.sh") # => false + // File.open("test.sh, "w", 0755) + // File.exist?("test.sh") # => true + // ``` + // @param fileName [String] + // @return [Boolean] + { + Name: "exist?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + fn, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } + _, err := os.Stat(fn.value) + + if err != nil { + return FALSE + } + + return TRUE + + }, + }, + { + // Returns the extension part of file. // // ```ruby - // File.delete("test.sh") # => 1 - // File.delete("test.sh", "test2.sh") # => 2 - // File.delete() # => 0 - // File.delete("non-existent.txt") # => + // File.extname("loop.gb") # => .gb // ``` + // // @param fileName [String] - // @return [Integer] - { - Name: "delete", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - for i, arg := range args { - fn, ok := arg.(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, i+1, classes.StringClass, args[i].Class().Name) - } - err := os.Remove(fn.value) + // @return [String] + Name: "extname", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - if err != nil { - return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) - } - } + fn, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - return t.vm.InitIntegerObject(len(args)) + return t.vm.InitStringObject(filepath.Ext(fn.value)) - }, }, - // Determines if the specified file. + }, + { + // Returns the string with joined elements. + // Arguments can be zero. // // ```ruby - // File.exist?("test.sh") # => false - // File.open("test.sh, "w", 0755) - // File.exist?("test.sh") # => true + // File.join("home", "goby", "plugin") # => home/goby/plugin // ``` + // // @param fileName [String] - // @return [Boolean] - { - Name: "exist?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - fn, ok := args[0].(*StringObject) + // @return [String] + Name: "join", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + var e []string + for i := 0; i < len(args); i++ { + next, ok := args[i].(*StringObject) if !ok { return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) } - _, err := os.Stat(fn.value) - if err != nil { - return FALSE - } + e = append(e, next.value) + } - return TRUE + return t.vm.InitStringObject(filepath.Join(e...)) - }, }, - { - // Returns the extension part of file. - // - // ```ruby - // File.extname("loop.gb") # => .gb - // ``` - // - // @param fileName [String] - // @return [String] - Name: "extname", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - fn, ok := args[0].(*StringObject) + }, + { + // Finds the file with given fileName and initializes a file object with it. + // File permissions can be specified at the second or third argument. + // + // ```ruby + // File.new("./samples/server.gb") + // + // File.new("../test_fixtures/file_test/size.gb", "r") + // + // File.new("../test_fixtures/file_test/size.gb", "r", 0755) + // ``` + // @param fileName [String] + // @return [File] + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen < 1 || aLen > 3 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 1, 3, aLen) + } + + fn, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.StringClass, args[0].Class().Name) + } + + mod := syscall.O_RDONLY + perm := os.FileMode(0755) + if aLen >= 2 { + m, ok := args[1].(*StringObject) if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, args[1].Class().Name) } - return t.vm.InitStringObject(filepath.Ext(fn.value)) - - }, - }, - { - // Returns the string with joined elements. - // Arguments can be zero. - // - // ```ruby - // File.join("home", "goby", "plugin") # => home/goby/plugin - // ``` - // - // @param fileName [String] - // @return [String] - Name: "join", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - var e []string - for i := 0; i < len(args); i++ { - next, ok := args[i].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } - - e = append(e, next.value) + md, ok := fileModeTable[m.value] + if !ok { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Unknown file mode: %s", m.value) } - return t.vm.InitStringObject(filepath.Join(e...)) - - }, - }, - { - // Finds the file with given fileName and initializes a file object with it. - // File permissions can be specified at the second or third argument. - // - // ```ruby - // File.new("./samples/server.gb") - // - // File.new("../test_fixtures/file_test/size.gb", "r") - // - // File.new("../test_fixtures/file_test/size.gb", "r", 0755) - // ``` - // @param fileName [String] - // @return [File] - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen < 1 || aLen > 3 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 1, 3, aLen) + if md == syscall.O_RDWR || md == syscall.O_WRONLY { + os.Create(fn.value) } - fn, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.StringClass, args[0].Class().Name) - } + mod = md + perm = os.FileMode(0755) - mod := syscall.O_RDONLY - perm := os.FileMode(0755) - if aLen >= 2 { - m, ok := args[1].(*StringObject) + if aLen == 3 { + p, ok := args[2].(*IntegerObject) if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, args[1].Class().Name) + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 3, classes.IntegerClass, args[2].Class().Name) } - md, ok := fileModeTable[m.value] - if !ok { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Unknown file mode: %s", m.value) - } - - if md == syscall.O_RDWR || md == syscall.O_WRONLY { - os.Create(fn.value) + if !os.FileMode(p.value).IsRegular() { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.InvalidChmodNumber, p.value) } - mod = md - perm = os.FileMode(0755) - - if aLen == 3 { - p, ok := args[2].(*IntegerObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 3, classes.IntegerClass, args[2].Class().Name) - } - - if !os.FileMode(p.value).IsRegular() { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.InvalidChmodNumber, p.value) - } - - perm = os.FileMode(p.value) - } + perm = os.FileMode(p.value) } + } - f, err := os.OpenFile(fn.value, mod, perm) + f, err := os.OpenFile(fn.value, mod, perm) - if err != nil { - return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) - } + if err != nil { + return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) + } - // TODO: Refactor this class retrieval mess - fo := &FileObject{File: f, BaseObj: &BaseObj{class: t.vm.TopLevelClass(classes.FileClass)}} + // TODO: Refactor this class retrieval mess + fo := &FileObject{File: f, BaseObj: &BaseObj{class: t.vm.TopLevelClass(classes.FileClass)}} - return fo + return fo - }, }, - { - // Returns size of file in bytes. - // - // ```ruby - // File.size("loop.gb") # => 321123 - // ``` - // - // @param fileName [String] - // @return [Integer] - Name: "size", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Returns size of file in bytes. + // + // ```ruby + // File.size("loop.gb") # => 321123 + // ``` + // + // @param fileName [String] + // @return [Integer] + Name: "size", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - fn, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + fn, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - if !filepath.IsAbs(fn.value) { - fn.value = filepath.Join(t.vm.fileDir, fn.value) - } + if !filepath.IsAbs(fn.value) { + fn.value = filepath.Join(t.vm.fileDir, fn.value) + } - fs, err := os.Stat(fn.value) - if err != nil { - return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) - } + fs, err := os.Stat(fn.value) + if err != nil { + return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) + } - return t.vm.InitIntegerObject(int(fs.Size())) + return t.vm.InitIntegerObject(int(fs.Size())) - }, }, - { - // Returns array of path and file. - // - // ```ruby - // File.split("/home/goby/.settings") # => ["/home/goby/", ".settings"] - // ``` - // - // @param filePath [String] - // @return [Array] - Name: "split", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Returns array of path and file. + // + // ```ruby + // File.split("/home/goby/.settings") # => ["/home/goby/", ".settings"] + // ``` + // + // @param filePath [String] + // @return [Array] + Name: "split", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - fn, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + fn, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - d, f := filepath.Split(fn.value) + d, f := filepath.Split(fn.value) - return t.vm.InitArrayObject([]Object{t.vm.InitStringObject(d), t.vm.InitStringObject(f)}) + return t.vm.InitArrayObject([]Object{t.vm.InitStringObject(d), t.vm.InitStringObject(f)}) - }, }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinFileInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Closes the instance of File class. Possible to close twice. - // - // ```ruby - // File.open("/tmp/goby/out.txt", "w", 0755) do |f| - // f.close # redundant: instance f will automatically close - // end - // - // f = File.new("/tmp/goby/out.txt", "w", 0755) - // f.close - // f.close - // ``` - // - // @return [Null] - Name: "close", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - file := receiver.(*FileObject).File - file.Close() - - return NULL - - }, - }, - // Returns the path and the file name. +var builtinFileInstanceMethods = []*BuiltinMethodObject{ + { + // Closes the instance of File class. Possible to close twice. // // ```ruby // File.open("/tmp/goby/out.txt", "w", 0755) do |f| - // puts f.name #=> "/tmp/goby/out.txt" + // f.close # redundant: instance f will automatically close // end + // + // f = File.new("/tmp/goby/out.txt", "w", 0755) + // f.close + // f.close // ``` // - // @return [String] - { - Name: "name", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - name := receiver.(*FileObject).File.Name() - return t.vm.InitStringObject(name) + // @return [Null] + Name: "close", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + file := receiver.(*FileObject).File + file.Close() + + return NULL - }, }, - // Returns the contents of the specified file. + }, + // Returns the path and the file name. + // + // ```ruby + // File.open("/tmp/goby/out.txt", "w", 0755) do |f| + // puts f.name #=> "/tmp/goby/out.txt" + // end + // ``` + // + // @return [String] + { + Name: "name", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + name := receiver.(*FileObject).File.Name() + return t.vm.InitStringObject(name) + + }, + }, + // Returns the contents of the specified file. + // + // ```ruby + // File.open("/tmp/goby/out.txt", "w", 0755) do |f| + // f.write("Hello, Goby!") + // puts f.read #=> "Hello, Goby!" + // end + // ``` + // + // @return [String] + { + Name: "read", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + var result string + var f []byte + var err error + + file := receiver.(*FileObject).File + + if file.Name() == "/dev/stdin" { + reader := bufio.NewReader(os.Stdin) + result, err = reader.ReadString('\n') + } else { + f, err = ioutil.ReadFile(file.Name()) + result = string(f) + } + + if err != nil { + return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) + } + + return t.vm.InitStringObject(result) + + }, + }, + { + // Returns size of file in bytes. // // ```ruby - // File.open("/tmp/goby/out.txt", "w", 0755) do |f| - // f.write("Hello, Goby!") - // puts f.read #=> "Hello, Goby!" - // end + // File.new("loop.gb").size # => 321123 // ``` // - // @return [String] - { - Name: "read", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - var result string - var f []byte - var err error - - file := receiver.(*FileObject).File - - if file.Name() == "/dev/stdin" { - reader := bufio.NewReader(os.Stdin) - result, err = reader.ReadString('\n') - } else { - f, err = ioutil.ReadFile(file.Name()) - result = string(f) - } + // @return [Integer] + Name: "size", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + file := receiver.(*FileObject).File - if err != nil { - return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) - } + fileStats, err := os.Stat(file.Name()) + if err != nil { + return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) + } - return t.vm.InitStringObject(result) + return t.vm.InitIntegerObject(int(fileStats.Size())) - }, }, - { - // Returns size of file in bytes. - // - // ```ruby - // File.new("loop.gb").size # => 321123 - // ``` - // - // @return [Integer] - Name: "size", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - file := receiver.(*FileObject).File - - fileStats, err := os.Stat(file.Name()) - if err != nil { - return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) - } + }, + { + Name: "write", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + file := receiver.(*FileObject).File + data := args[0].(*StringObject).value + length, err := file.Write([]byte(data)) - return t.vm.InitIntegerObject(int(fileStats.Size())) + if err != nil { + return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) + } - }, - }, - { - Name: "write", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - file := receiver.(*FileObject).File - data := args[0].(*StringObject).value - length, err := file.Write([]byte(data)) + return t.vm.InitIntegerObject(length) - if err != nil { - return t.vm.InitErrorObject(errors.IOError, sourceLine, err.Error()) - } - - return t.vm.InitIntegerObject(length) - - }, }, - } + }, } // Internal functions =================================================== @@ -479,8 +475,8 @@ func (vm *VM) initFileObject(f *os.File) *FileObject { func (vm *VM) initFileClass() *RClass { fc := vm.initializeClass(classes.FileClass) - fc.setBuiltinMethods(builtinFileClassMethods(), true) - fc.setBuiltinMethods(builtinFileInstanceMethods(), false) + fc.setBuiltinMethods(builtinFileClassMethods, true) + fc.setBuiltinMethods(builtinFileInstanceMethods, false) vm.libFiles = append(vm.libFiles, "file.gb") diff --git a/vm/float.go b/vm/float.go index ba6b1a012..58f5d16e8 100644 --- a/vm/float.go +++ b/vm/float.go @@ -25,345 +25,341 @@ type FloatObject struct { } // Class methods -------------------------------------------------------- -func builtinFloatClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitNoMethodError(sourceLine, "#new", receiver) - - }, +var builtinFloatClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitNoMethodError(sourceLine, "#new", receiver) + }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinFloatInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Returns the sum of self and a Numeric. - // - // ```Ruby - // 1.1 + 2 # => 3.1 - // ``` - // - // @return [Float] - Name: "+", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - operation := func(leftValue float64, rightValue float64) float64 { - return leftValue + rightValue - } - - return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - - }, +var builtinFloatInstanceMethods = []*BuiltinMethodObject{ + { + // Returns the sum of self and a Numeric. + // + // ```Ruby + // 1.1 + 2 # => 3.1 + // ``` + // + // @return [Float] + Name: "+", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + operation := func(leftValue float64, rightValue float64) float64 { + return leftValue + rightValue + } + + return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, false) + }, - { - // Returns the modulo between self and a Numeric. - // - // ```Ruby - // 5.5 % 2 # => 1.5 - // ``` - // - // @return [Float] - Name: "%", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - operation := math.Mod - return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, true) - - }, + }, + { + // Returns the modulo between self and a Numeric. + // + // ```Ruby + // 5.5 % 2 # => 1.5 + // ``` + // + // @return [Float] + Name: "%", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + operation := math.Mod + return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, true) + }, - { - // Returns the subtraction of a Numeric from self. - // - // ```Ruby - // 1.5 - 1 # => 0.5 - // ``` - // - // @return [Float] - Name: "-", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - operation := func(leftValue float64, rightValue float64) float64 { - return leftValue - rightValue - } - - return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - - }, + }, + { + // Returns the subtraction of a Numeric from self. + // + // ```Ruby + // 1.5 - 1 # => 0.5 + // ``` + // + // @return [Float] + Name: "-", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + operation := func(leftValue float64, rightValue float64) float64 { + return leftValue - rightValue + } + + return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, false) + }, - { - // Returns self multiplying a Numeric. - // - // ```Ruby - // 2.5 * 10 # => 25.0 - // ``` - // - // @return [Float] - Name: "*", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - operation := func(leftValue float64, rightValue float64) float64 { - return leftValue * rightValue - } - - return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - - }, + }, + { + // Returns self multiplying a Numeric. + // + // ```Ruby + // 2.5 * 10 # => 25.0 + // ``` + // + // @return [Float] + Name: "*", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + operation := func(leftValue float64, rightValue float64) float64 { + return leftValue * rightValue + } + + return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, false) + }, - { - // Returns self squaring a Numeric. - // - // ```Ruby - // 4.0 ** 2.5 # => 32.0 - // ``` - // - // @return [Float] - Name: "**", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - operation := math.Pow - return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, false) - - }, + }, + { + // Returns self squaring a Numeric. + // + // ```Ruby + // 4.0 ** 2.5 # => 32.0 + // ``` + // + // @return [Float] + Name: "**", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + operation := math.Pow + return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, false) + }, - { - // Returns self divided by a Numeric. - // - // ```Ruby - // 7.5 / 3 # => 2.5 - // ``` - // - // @return [Float] - Name: "/", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - operation := func(leftValue float64, rightValue float64) float64 { - return leftValue / rightValue - } - - return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, true) - - }, + }, + { + // Returns self divided by a Numeric. + // + // ```Ruby + // 7.5 / 3 # => 2.5 + // ``` + // + // @return [Float] + Name: "/", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + operation := func(leftValue float64, rightValue float64) float64 { + return leftValue / rightValue + } + + return receiver.(*FloatObject).arithmeticOperation(t, args[0], operation, sourceLine, true) + }, - { - // Returns if self is larger than a Numeric. - // - // ```Ruby - // 10 > -1 # => true - // 3 > 3 # => false - // ``` - // - // @return [Boolean] - Name: ">", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - rightObj, ok := args[0].(*FloatObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) - } - operation := func(leftValue float64, rightValue float64) bool { - return leftValue > rightValue - } - - return toBooleanObject(receiver.(*FloatObject).numericComparison(rightObj, operation)) - - }, + }, + { + // Returns if self is larger than a Numeric. + // + // ```Ruby + // 10 > -1 # => true + // 3 > 3 # => false + // ``` + // + // @return [Boolean] + Name: ">", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + rightObj, ok := args[0].(*FloatObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) + } + operation := func(leftValue float64, rightValue float64) bool { + return leftValue > rightValue + } + + return toBooleanObject(receiver.(*FloatObject).numericComparison(rightObj, operation)) + }, - { - // Returns if self is larger than or equals to a Numeric. - // - // ```Ruby - // 2 >= 1 # => true - // 1 >= 1 # => true - // ``` - // - // @return [Boolean] - Name: ">=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - rightObj, ok := args[0].(*FloatObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) - } - operation := func(leftValue float64, rightValue float64) bool { - return leftValue >= rightValue - } - - return toBooleanObject(receiver.(*FloatObject).numericComparison(rightObj, operation)) - }, + }, + { + // Returns if self is larger than or equals to a Numeric. + // + // ```Ruby + // 2 >= 1 # => true + // 1 >= 1 # => true + // ``` + // + // @return [Boolean] + Name: ">=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + rightObj, ok := args[0].(*FloatObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) + } + operation := func(leftValue float64, rightValue float64) bool { + return leftValue >= rightValue + } + + return toBooleanObject(receiver.(*FloatObject).numericComparison(rightObj, operation)) }, - { - // Returns if self is smaller than a Numeric. - // - // ```Ruby - // 1 < 3 # => true - // 1 < 1 # => false - // ``` - // - // @return [Boolean] - Name: "<", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - rightObj, ok := args[0].(*FloatObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) - } - operation := func(leftValue float64, rightValue float64) bool { - return leftValue < rightValue - } - - return toBooleanObject(receiver.(*FloatObject).numericComparison(rightObj, operation)) - - }, + }, + { + // Returns if self is smaller than a Numeric. + // + // ```Ruby + // 1 < 3 # => true + // 1 < 1 # => false + // ``` + // + // @return [Boolean] + Name: "<", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + rightObj, ok := args[0].(*FloatObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) + } + operation := func(leftValue float64, rightValue float64) bool { + return leftValue < rightValue + } + + return toBooleanObject(receiver.(*FloatObject).numericComparison(rightObj, operation)) + }, - { - // Returns if self is smaller than or equals to a Numeric. - // - // ```Ruby - // 1 <= 3 # => true - // 1 <= 1 # => true - // ``` - // - // @return [Boolean] - Name: "<=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - rightObj, ok := args[0].(*FloatObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) - } - - operation := func(leftValue float64, rightValue float64) bool { - return leftValue <= rightValue - } - - return toBooleanObject(receiver.(*FloatObject).numericComparison(rightObj, operation)) - - }, + }, + { + // Returns if self is smaller than or equals to a Numeric. + // + // ```Ruby + // 1 <= 3 # => true + // 1 <= 1 # => true + // ``` + // + // @return [Boolean] + Name: "<=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + rightObj, ok := args[0].(*FloatObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) + } + + operation := func(leftValue float64, rightValue float64) bool { + return leftValue <= rightValue + } + + return toBooleanObject(receiver.(*FloatObject).numericComparison(rightObj, operation)) + }, - { - // Returns 1 if self is larger than a Numeric, -1 if smaller. Otherwise 0. - // - // ```Ruby - // 1.5 <=> 3 # => -1 - // 1.0 <=> 1 # => 0 - // 3.5 <=> 1 # => 1 - // ``` - // - // @return [Float] - Name: "<=>", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - rightNumeric, ok := args[0].(Numeric) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) - } - - leftValue := receiver.(*FloatObject).value - rightValue := rightNumeric.floatValue() - - if leftValue < rightValue { - return t.vm.InitIntegerObject(-1) - } - if leftValue > rightValue { - return t.vm.InitIntegerObject(1) - } - - return t.vm.InitIntegerObject(0) - - }, + }, + { + // Returns 1 if self is larger than a Numeric, -1 if smaller. Otherwise 0. + // + // ```Ruby + // 1.5 <=> 3 # => -1 + // 1.0 <=> 1 # => 0 + // 3.5 <=> 1 # => 1 + // ``` + // + // @return [Float] + Name: "<=>", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + rightNumeric, ok := args[0].(Numeric) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", args[0].Class().Name) + } + + leftValue := receiver.(*FloatObject).value + rightValue := rightNumeric.floatValue() + + if leftValue < rightValue { + return t.vm.InitIntegerObject(-1) + } + if leftValue > rightValue { + return t.vm.InitIntegerObject(1) + } + + return t.vm.InitIntegerObject(0) + }, - { - // Returns if self is equal to an Object. - // If the Object is a Numeric, a comparison is performed, otherwise, the - // result is always false. - // - // ```Ruby - // 1.0 == 3 # => false - // 1.0 == 1 # => true - // 1.0 == '1.0' # => false - // ``` - // - // @return [Boolean] - Name: "==", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - result := receiver.(*FloatObject).equalityTest(args[0]) - - return toBooleanObject(result) - - }, + }, + { + // Returns if self is equal to an Object. + // If the Object is a Numeric, a comparison is performed, otherwise, the + // result is always false. + // + // ```Ruby + // 1.0 == 3 # => false + // 1.0 == 1 # => true + // 1.0 == '1.0' # => false + // ``` + // + // @return [Boolean] + Name: "==", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + result := receiver.(*FloatObject).equalityTest(args[0]) + + return toBooleanObject(result) + }, - { - // Returns if self is not equal to an Object. - // If the Object is a Numeric, a comparison is performed, otherwise, the - // result is always true. - // - // ```Ruby - // 1.0 != 3 # => true - // 1.0 != 1 # => false - // 1.0 != '1.0' # => true - // ``` - // - // @return [Boolean] - Name: "!=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - result := !receiver.(*FloatObject).equalityTest(args[0]) - - return toBooleanObject(result) - - }, + }, + { + // Returns if self is not equal to an Object. + // If the Object is a Numeric, a comparison is performed, otherwise, the + // result is always true. + // + // ```Ruby + // 1.0 != 3 # => true + // 1.0 != 1 # => false + // 1.0 != '1.0' # => true + // ``` + // + // @return [Boolean] + Name: "!=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + result := !receiver.(*FloatObject).equalityTest(args[0]) + + return toBooleanObject(result) + }, - { - // Converts the Integer object into Decimal object and returns it. - // Each digit of the float is literally transferred to the corresponding digit - // of the Decimal, via a string representation of the float. - // - // ```Ruby - // "100.1".to_f.to_d # => 100.1 - // - // a = "3.14159265358979".to_f - // b = a.to_d #=> 3.14159265358979 - // ``` - // - // @return [Decimal] - Name: "to_d", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - fl := receiver.(*FloatObject).value - - fs := strconv.FormatFloat(fl, 'f', -1, 64) - de, err := new(Decimal).SetString(fs) - if err == false { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.InvalidNumericString, fs) - } - - return t.vm.initDecimalObject(de) - - }, + }, + { + // Converts the Integer object into Decimal object and returns it. + // Each digit of the float is literally transferred to the corresponding digit + // of the Decimal, via a string representation of the float. + // + // ```Ruby + // "100.1".to_f.to_d # => 100.1 + // + // a = "3.14159265358979".to_f + // b = a.to_d #=> 3.14159265358979 + // ``` + // + // @return [Decimal] + Name: "to_d", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + + fl := receiver.(*FloatObject).value + + fs := strconv.FormatFloat(fl, 'f', -1, 64) + de, err := new(Decimal).SetString(fs) + if err == false { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.InvalidNumericString, fs) + } + + return t.vm.initDecimalObject(de) + }, - { - // Returns the `Integer` representation of self. - // - // ```Ruby - // 100.1.to_i # => 100 - // ``` - // - // @return [Integer] - Name: "to_i", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - r := receiver.(*FloatObject).value - newInt := t.vm.InitIntegerObject(int(r)) - newInt.flag = i - return newInt - - }, + }, + { + // Returns the `Integer` representation of self. + // + // ```Ruby + // 100.1.to_i # => 100 + // ``` + // + // @return [Integer] + Name: "to_i", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + r := receiver.(*FloatObject).value + newInt := t.vm.InitIntegerObject(int(r)) + newInt.flag = i + return newInt + }, - { - Name: "ptr", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - r := receiver.(*FloatObject) - return t.vm.initGoObject(&r.value) + }, + { + Name: "ptr", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + r := receiver.(*FloatObject) + return t.vm.initGoObject(&r.value) - }, }, - } + }, } // Internal functions =================================================== @@ -379,8 +375,8 @@ func (vm *VM) initFloatObject(value float64) *FloatObject { func (vm *VM) initFloatClass() *RClass { ic := vm.initializeClass(classes.FloatClass) - ic.setBuiltinMethods(builtinFloatInstanceMethods(), false) - ic.setBuiltinMethods(builtinFloatClassMethods(), true) + ic.setBuiltinMethods(builtinFloatInstanceMethods, false) + ic.setBuiltinMethods(builtinFloatClassMethods, true) return ic } diff --git a/vm/go_map.go b/vm/go_map.go index 735186eeb..ff2d0d0e8 100644 --- a/vm/go_map.go +++ b/vm/go_map.go @@ -14,115 +14,111 @@ type GoMap struct { } // Class methods -------------------------------------------------------- -func builtinGoMapClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Initialize a new GoMap instance. - // It can be called without any arguments, which will create an empty map. - // Or you can pass a hash as argument, so the map will have same pairs. - // - // @return [GoMap] - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - m := make(map[string]interface{}) - - if len(args) == 0 { - return t.vm.initGoMap(m) - } - - hash, ok := args[0].(*HashObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.HashClass, args[0].Class().Name) - } - - for k, v := range hash.Pairs { - m[k] = v.Value() - } - +var builtinGoMapClassMethods = []*BuiltinMethodObject{ + { + // Initialize a new GoMap instance. + // It can be called without any arguments, which will create an empty map. + // Or you can pass a hash as argument, so the map will have same pairs. + // + // @return [GoMap] + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + m := make(map[string]interface{}) + + if len(args) == 0 { return t.vm.initGoMap(m) + } + + hash, ok := args[0].(*HashObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.HashClass, args[0].Class().Name) + } + + for k, v := range hash.Pairs { + m[k] = v.Value() + } + + return t.vm.initGoMap(m) - }, }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinGoMapInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "get", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } +var builtinGoMapInstanceMethods = []*BuiltinMethodObject{ + { + Name: "get", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - key, ok := args[0].(*StringObject) + key, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - m := receiver.(*GoMap).data + m := receiver.(*GoMap).data - result, ok := m[key.value] + result, ok := m[key.value] - if !ok { - return NULL - } + if !ok { + return NULL + } - obj, ok := result.(Object) + obj, ok := result.(Object) - if !ok { - obj = t.vm.InitObjectFromGoType(result) - } + if !ok { + obj = t.vm.InitObjectFromGoType(result) + } - return obj + return obj - }, }, - { - Name: "set", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) - } + }, + { + Name: "set", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) + } - key, ok := args[0].(*StringObject) + key, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.StringClass, args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.StringClass, args[0].Class().Name) + } - m := receiver.(*GoMap).data + m := receiver.(*GoMap).data - m[key.value] = args[1] + m[key.value] = args[1] - return args[1] + return args[1] - }, }, - { - Name: "to_hash", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + Name: "to_hash", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - m := receiver.(*GoMap) + m := receiver.(*GoMap) - pairs := map[string]Object{} + pairs := map[string]Object{} - for k, obj := range m.data { - pairs[k] = t.vm.InitObjectFromGoType(obj) + for k, obj := range m.data { + pairs[k] = t.vm.InitObjectFromGoType(obj) - } + } - return t.vm.InitHashObject(pairs) + return t.vm.InitHashObject(pairs) - }, }, - } + }, } // Internal functions =================================================== @@ -135,8 +131,8 @@ func (vm *VM) initGoMap(d map[string]interface{}) *GoMap { func (vm *VM) initGoMapClass() *RClass { sc := vm.initializeClass(classes.GoMapClass) - sc.setBuiltinMethods(builtinGoMapClassMethods(), true) - sc.setBuiltinMethods(builtinGoMapInstanceMethods(), false) + sc.setBuiltinMethods(builtinGoMapClassMethods, true) + sc.setBuiltinMethods(builtinGoMapInstanceMethods, false) vm.objectClass.setClassConstant(sc) return sc } diff --git a/vm/go_object.go b/vm/go_object.go index 61917f3c4..a9871f336 100644 --- a/vm/go_object.go +++ b/vm/go_object.go @@ -15,50 +15,46 @@ type GoObject struct { } // Class methods -------------------------------------------------------- -func builtinGoObjectClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{} -} +var builtinGoObjectClassMethods = []*BuiltinMethodObject{} // Instance methods ----------------------------------------------------- -func builtinGoObjectInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // An experimental method for loading plugins (written in Golang) dynamically. - // Needs improvements. - // - /// ```ruby - /// require "plugin" - // - // p = Plugin.use "../test_fixtures/import_test/plugin/plugin.go" - // p.go_func("Foo", "!") - // p.go_func("Baz") - // ``` - // - // @param name [String] - // @return [Object] - Name: "go_func", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - s, ok := args[0].(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } - - funcName := s.value - r := receiver.(*GoObject) - - funcArgs, err := ConvertToGoFuncArgs(args[1:]) - - if err != nil { - t.vm.InitErrorObject(errors.TypeError, sourceLine, err.Error()) - } - - result := metago.CallFunc(r.data, funcName, funcArgs...) - return t.vm.InitObjectFromGoType(result) - - }, +var builtinGoObjectInstanceMethods = []*BuiltinMethodObject{ + { + // An experimental method for loading plugins (written in Golang) dynamically. + // Needs improvements. + // + /// ```ruby + /// require "plugin" + // + // p = Plugin.use "../test_fixtures/import_test/plugin/plugin.go" + // p.go_func("Foo", "!") + // p.go_func("Baz") + // ``` + // + // @param name [String] + // @return [Object] + Name: "go_func", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + s, ok := args[0].(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } + + funcName := s.value + r := receiver.(*GoObject) + + funcArgs, err := ConvertToGoFuncArgs(args[1:]) + + if err != nil { + t.vm.InitErrorObject(errors.TypeError, sourceLine, err.Error()) + } + + result := metago.CallFunc(r.data, funcName, funcArgs...) + return t.vm.InitObjectFromGoType(result) + }, - } + }, } // Internal functions =================================================== @@ -71,8 +67,8 @@ func (vm *VM) initGoObject(d interface{}) *GoObject { func (vm *VM) initGoClass() *RClass { sc := vm.initializeClass(classes.GoObjectClass) - sc.setBuiltinMethods(builtinGoObjectClassMethods(), true) - sc.setBuiltinMethods(builtinGoObjectInstanceMethods(), false) + sc.setBuiltinMethods(builtinGoObjectClassMethods, true) + sc.setBuiltinMethods(builtinGoObjectInstanceMethods, false) vm.objectClass.setClassConstant(sc) return sc } diff --git a/vm/hash.go b/vm/hash.go index fd8503550..bce0848f3 100644 --- a/vm/hash.go +++ b/vm/hash.go @@ -52,1162 +52,1158 @@ type HashObject struct { } // Class methods -------------------------------------------------------- -func builtinHashClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitNoMethodError(sourceLine, "new", receiver) - - }, +var builtinHashClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitNoMethodError(sourceLine, "new", receiver) + }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinHashInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Retrieves the value (object) that corresponds to the key specified. - // When a key doesn't exist, `nil` is returned, or the default, if set. - // - // ```Ruby - // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: 'v' } } - // h['a'] #=> 1 - // h['b'] #=> "2" - // h['c'] #=> [1, 2, 3] - // h['d'] #=> { k: 'v' } - // - // h = { a: 1 } - // h['c'] #=> nil - // h.default = 0 - // h['c'] #=> 0 - // h #=> { a: 1 } - // h['d'] += 2 - // h #=> { a: 1, d: 2 } - // ``` - // - // @param key [String] - // @return [Object] - Name: "[]", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - i := args[0] - key, ok := i.(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, i.Class().Name) - } - - h := receiver.(*HashObject) - - value, ok := h.Pairs[key.value] - - if !ok { - if h.Default != nil { - return h.Default - } - - return NULL - } +var builtinHashInstanceMethods = []*BuiltinMethodObject{ + { + // Retrieves the value (object) that corresponds to the key specified. + // When a key doesn't exist, `nil` is returned, or the default, if set. + // + // ```Ruby + // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: 'v' } } + // h['a'] #=> 1 + // h['b'] #=> "2" + // h['c'] #=> [1, 2, 3] + // h['d'] #=> { k: 'v' } + // + // h = { a: 1 } + // h['c'] #=> nil + // h.default = 0 + // h['c'] #=> 0 + // h #=> { a: 1 } + // h['d'] += 2 + // h #=> { a: 1, d: 2 } + // ``` + // + // @param key [String] + // @return [Object] + Name: "[]", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + i := args[0] + key, ok := i.(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, i.Class().Name) + } + + h := receiver.(*HashObject) + + value, ok := h.Pairs[key.value] + + if !ok { + if h.Default != nil { + return h.Default + } + + return NULL + } + + return value - return value - - }, }, - { - // Associates the value given by `value` with the key given by `key`. - // Returns the `value`. - // - // ```Ruby - // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: 'v' } } - // h['a'] = 1 #=> 1 - // h['b'] = "2" #=> "2" - // h['c'] = [1, 2, 3] #=> [1, 2, 3] - // h['d'] = { k: 'v' } #=> { k: 'v' } - // ``` - // - // @param key [String] - // @return [Object] The value - Name: "[]=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - // First arg is index - // Second arg is assigned value - if len(args) != 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) - } - k := args[0] - key, ok := k.(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, k.Class().Name) - } - - h := receiver.(*HashObject) - h.Pairs[key.value] = args[1] + }, + { + // Associates the value given by `value` with the key given by `key`. + // Returns the `value`. + // + // ```Ruby + // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: 'v' } } + // h['a'] = 1 #=> 1 + // h['b'] = "2" #=> "2" + // h['c'] = [1, 2, 3] #=> [1, 2, 3] + // h['d'] = { k: 'v' } #=> { k: 'v' } + // ``` + // + // @param key [String] + // @return [Object] The value + Name: "[]=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + // First arg is index + // Second arg is assigned value + if len(args) != 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) + } + k := args[0] + key, ok := k.(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, k.Class().Name) + } + + h := receiver.(*HashObject) + h.Pairs[key.value] = args[1] + + return args[1] - return args[1] - - }, }, - { - // Passes each (key, value) pair of the collection to the given block. - // The method returns true if any of the results by the block is true. - // - // ```ruby - // a = { a: 1, b: 2 } - // - // a.any? do |k, v| - // v == 2 - // end # => true - // a.any? do |k, v| - // v - // end # => true - // a.any? do |k, v| - // v == 5 - // end # => false - // a.any? do |k, v| - // nil - // end # => false - // - // a = {} - // - // a.any? do |k, v| - // true - // end # => false - // ``` - // - // @return [Boolean] - Name: "any?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - hash := receiver.(*HashObject) - if blockIsEmpty(blockFrame) { - return FALSE - } - - if len(hash.Pairs) == 0 { - t.callFrameStack.pop() + }, + { + // Passes each (key, value) pair of the collection to the given block. + // The method returns true if any of the results by the block is true. + // + // ```ruby + // a = { a: 1, b: 2 } + // + // a.any? do |k, v| + // v == 2 + // end # => true + // a.any? do |k, v| + // v + // end # => true + // a.any? do |k, v| + // v == 5 + // end # => false + // a.any? do |k, v| + // nil + // end # => false + // + // a = {} + // + // a.any? do |k, v| + // true + // end # => false + // ``` + // + // @return [Boolean] + Name: "any?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + hash := receiver.(*HashObject) + if blockIsEmpty(blockFrame) { + return FALSE + } + + if len(hash.Pairs) == 0 { + t.callFrameStack.pop() + } + + for stringKey, value := range hash.Pairs { + objectKey := t.vm.InitStringObject(stringKey) + result := t.builtinMethodYield(blockFrame, objectKey, value) + + /* + TODO: Discuss this behavior + + ```ruby + { key: "foo", bar: "baz" }.any? do |k, v| + true + break + end + ``` + + The block returns nil because of the break. + But in Ruby the final result is nil, which means the block's result is completely ignored + */ + if blockFrame.IsRemoved() { + return NULL } - for stringKey, value := range hash.Pairs { - objectKey := t.vm.InitStringObject(stringKey) - result := t.builtinMethodYield(blockFrame, objectKey, value) - - /* - TODO: Discuss this behavior - - ```ruby - { key: "foo", bar: "baz" }.any? do |k, v| - true - break - end - ``` - - The block returns nil because of the break. - But in Ruby the final result is nil, which means the block's result is completely ignored - */ - if blockFrame.IsRemoved() { - return NULL - } - - if result.Target.isTruthy() { - return TRUE - } + if result.Target.isTruthy() { + return TRUE } + } - return FALSE + return FALSE - }, }, - { - // Returns empty hash (no key-value pairs) - // - // ```Ruby - // { a: "Hello", b: "World" }.clear # => {} - // {}.clear # => {} - // ``` - // - // @return [Hash] - Name: "clear", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - h := receiver.(*HashObject) - - h.Pairs = make(map[string]Object) - - return h + }, + { + // Returns empty hash (no key-value pairs) + // + // ```Ruby + // { a: "Hello", b: "World" }.clear # => {} + // {}.clear # => {} + // ``` + // + // @return [Hash] + Name: "clear", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + h := receiver.(*HashObject) + + h.Pairs = make(map[string]Object) + + return h - }, }, - { - // Returns the configured default value of the Hash. - // If no default value has been specified, nil is returned. - // - // ```Ruby - // h = { a: 1 } - // h.default #=> nil - // h.default = 2 - // h.default #=> 2 - // ``` - // - // @return [Object] - Name: "default", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - hash := receiver.(*HashObject) + }, + { + // Returns the configured default value of the Hash. + // If no default value has been specified, nil is returned. + // + // ```Ruby + // h = { a: 1 } + // h.default #=> nil + // h.default = 2 + // h.default #=> 2 + // ``` + // + // @return [Object] + Name: "default", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + hash := receiver.(*HashObject) + + if hash.Default == nil { + return NULL + } + + return hash.Default - if hash.Default == nil { - return NULL - } - - return hash.Default - - }, }, - { - // Sets the default value of this Hash for the missing keys, and returns the default value. - // Note that Arrays/Hashes are not accepted because they're unsafe. - // - // ```Ruby - // h = { a: 1 } - // h['c'] #=> nil - // h.default = 2 - // h['c'] #=> 2 - // h.default = [] #=> ArgumentError - // ``` - // - // @param default value [Object] - // @return [Object] - Name: "default=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Sets the default value of this Hash for the missing keys, and returns the default value. + // Note that Arrays/Hashes are not accepted because they're unsafe. + // + // ```Ruby + // h = { a: 1 } + // h['c'] #=> nil + // h.default = 2 + // h['c'] #=> 2 + // h.default = [] #=> ArgumentError + // ``` + // + // @param default value [Object] + // @return [Object] + Name: "default=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + // Arrays and Hashes are generally a mistake, since a single instance would be used for all the accesses + // via default. + switch args[0].(type) { + case *HashObject, *ArrayObject: + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Arrays and Hashes are not accepted as default values") + } + + hash := receiver.(*HashObject) + hashDefault := args[0] + + hash.Default = hashDefault + + return hashDefault - // Arrays and Hashes are generally a mistake, since a single instance would be used for all the accesses - // via default. - switch args[0].(type) { - case *HashObject, *ArrayObject: - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Arrays and Hashes are not accepted as default values") - } - - hash := receiver.(*HashObject) - hashDefault := args[0] - - hash.Default = hashDefault - - return hashDefault - - }, }, - { - // Remove the key from the hash if key exist - // - // ```Ruby - // h = { a: 1, b: 2, c: 3 } - // h.delete("b") # => { a: 1, c: 3 } - // ``` - // - // @param key [String] - // @return [Hash] - Name: "delete", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - h := receiver.(*HashObject) - d := args[0] - deleteKey, ok := d.(*StringObject) + }, + { + // Remove the key from the hash if key exist + // + // ```Ruby + // h = { a: 1, b: 2, c: 3 } + // h.delete("b") # => { a: 1, c: 3 } + // ``` + // + // @param key [String] + // @return [Hash] + Name: "delete", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + h := receiver.(*HashObject) + d := args[0] + deleteKey, ok := d.(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, d.Class().Name) + } + + deleteKeyValue := deleteKey.value + if _, ok := h.Pairs[deleteKeyValue]; ok { + delete(h.Pairs, deleteKeyValue) + } + return h - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, d.Class().Name) - } - - deleteKeyValue := deleteKey.value - if _, ok := h.Pairs[deleteKeyValue]; ok { - delete(h.Pairs, deleteKeyValue) - } - return h - - }, }, - { - // Deletes every key-value pair from the hash for which block evaluates to anything except false and nil. - // - // Returns the modified hash. - // - // ```Ruby - // { a: 1, b: 2}.delete_if do |k, v| v == 1 end # => { b: 2 } - // { a: 1, b: 2}.delete_if do |k, v| 5 end # => { } - // { a: 1, b: 2}.delete_if do |k, v| false end # => { a: 1, b: 2} - // { a: 1, b: 2}.delete_if do |k, v| nil end # => { a: 1, b: 2} - // ``` - // - // @param block - // @return [Hash] - Name: "delete_if", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - hash := receiver.(*HashObject) - if blockIsEmpty(blockFrame) { - return hash - } + }, + { + // Deletes every key-value pair from the hash for which block evaluates to anything except false and nil. + // + // Returns the modified hash. + // + // ```Ruby + // { a: 1, b: 2}.delete_if do |k, v| v == 1 end # => { b: 2 } + // { a: 1, b: 2}.delete_if do |k, v| 5 end # => { } + // { a: 1, b: 2}.delete_if do |k, v| false end # => { a: 1, b: 2} + // { a: 1, b: 2}.delete_if do |k, v| nil end # => { a: 1, b: 2} + // ``` + // + // @param block + // @return [Hash] + Name: "delete_if", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + hash := receiver.(*HashObject) + if blockIsEmpty(blockFrame) { + return hash + } - if len(hash.Pairs) == 0 { - t.callFrameStack.pop() - } + if len(hash.Pairs) == 0 { + t.callFrameStack.pop() + } - // Note that from the Go specification, https://golang.org/ref/spec#For_statements, - // it's safe to delete elements from a Map, while iterating it. - for stringKey, value := range hash.Pairs { - objectKey := t.vm.InitStringObject(stringKey) - result := t.builtinMethodYield(blockFrame, objectKey, value) + // Note that from the Go specification, https://golang.org/ref/spec#For_statements, + // it's safe to delete elements from a Map, while iterating it. + for stringKey, value := range hash.Pairs { + objectKey := t.vm.InitStringObject(stringKey) + result := t.builtinMethodYield(blockFrame, objectKey, value) - booleanResult, isResultBoolean := result.Target.(*BooleanObject) + booleanResult, isResultBoolean := result.Target.(*BooleanObject) - if isResultBoolean { - if booleanResult.value { - delete(hash.Pairs, stringKey) - } - } else if result.Target != NULL { + if isResultBoolean { + if booleanResult.value { delete(hash.Pairs, stringKey) } + } else if result.Target != NULL { + delete(hash.Pairs, stringKey) } + } - return hash + return hash - }, }, - { - // Extracts the nested value specified by the sequence of idx objects by calling `dig` at each step, - // Returns nil if any intermediate step is nil. - // - // ```Ruby - // { a: 1 , b: 2 }.dig(:a) # => 1 - // { a: {}, b: 2 }.dig(:a, :b) # => nil - // { a: {}, b: 2 }.dig(:a, :b, :c) # => nil - // { a: 1, b: 2 }.dig(:a, :b) # => TypeError: Expect target to be Diggable - // ``` - // - // @param key [String] - // @return [Object] - Name: "dig", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) < 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, len(args)) - } + }, + { + // Extracts the nested value specified by the sequence of idx objects by calling `dig` at each step, + // Returns nil if any intermediate step is nil. + // + // ```Ruby + // { a: 1 , b: 2 }.dig(:a) # => 1 + // { a: {}, b: 2 }.dig(:a, :b) # => nil + // { a: {}, b: 2 }.dig(:a, :b, :c) # => nil + // { a: 1, b: 2 }.dig(:a, :b) # => TypeError: Expect target to be Diggable + // ``` + // + // @param key [String] + // @return [Object] + Name: "dig", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) < 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, len(args)) + } + + hash := receiver.(*HashObject) + value := hash.dig(t, args, sourceLine) + + return value - hash := receiver.(*HashObject) - value := hash.dig(t, args, sourceLine) - - return value - - }, }, - { - // Calls block once for each key in the hash (in sorted key order), passing the - // key-value pair as parameters. - // Returns `self`. - // - // ```Ruby - // h = { b: "2", a: 1 } - // h.each do |k, v| - // puts k.to_s + "->" + v.to_s - // end - // # => a->1 - // # => b->2 - // ``` - // - // @param block - // @return [Hash] - Name: "each", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - h := receiver.(*HashObject) - - if len(h.Pairs) == 0 { - t.callFrameStack.pop() - } else { - keys := h.sortedKeys() + }, + { + // Calls block once for each key in the hash (in sorted key order), passing the + // key-value pair as parameters. + // Returns `self`. + // + // ```Ruby + // h = { b: "2", a: 1 } + // h.each do |k, v| + // puts k.to_s + "->" + v.to_s + // end + // # => a->1 + // # => b->2 + // ``` + // + // @param block + // @return [Hash] + Name: "each", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + h := receiver.(*HashObject) + + if len(h.Pairs) == 0 { + t.callFrameStack.pop() + } else { + keys := h.sortedKeys() - for _, k := range keys { - v := h.Pairs[k] - strK := t.vm.InitStringObject(k) + for _, k := range keys { + v := h.Pairs[k] + strK := t.vm.InitStringObject(k) - t.builtinMethodYield(blockFrame, strK, v) - } + t.builtinMethodYield(blockFrame, strK, v) } + } - return h + return h - }, }, - { - // Loops through keys of the hash with given block frame. - // Then returns an array of keys in alphabetical order. - // - // ```Ruby - // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: 'v' } } - // h.each_key do |k| - // puts k - // end - // # => a - // # => b - // # => c - // # => d - // ``` - // - // @param block - // @return [Array] - Name: "each_key", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - h := receiver.(*HashObject) - - if len(h.Pairs) == 0 { - t.callFrameStack.pop() - } - - keys := h.sortedKeys() - var arrOfKeys []Object + }, + { + // Loops through keys of the hash with given block frame. + // Then returns an array of keys in alphabetical order. + // + // ```Ruby + // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: 'v' } } + // h.each_key do |k| + // puts k + // end + // # => a + // # => b + // # => c + // # => d + // ``` + // + // @param block + // @return [Array] + Name: "each_key", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + h := receiver.(*HashObject) + + if len(h.Pairs) == 0 { + t.callFrameStack.pop() + } + + keys := h.sortedKeys() + var arrOfKeys []Object + + for _, k := range keys { + obj := t.vm.InitStringObject(k) + arrOfKeys = append(arrOfKeys, obj) + t.builtinMethodYield(blockFrame, obj) + } + + return t.vm.InitArrayObject(arrOfKeys) - for _, k := range keys { - obj := t.vm.InitStringObject(k) - arrOfKeys = append(arrOfKeys, obj) - t.builtinMethodYield(blockFrame, obj) - } + }, + }, + { + // Loops through values of the hash with given block frame. + // Then returns an array of values of the hash in the alphabetical order of the keys. + // + // ```Ruby + // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: "v" } } + // h.each_value do |v| + // puts v + // end + // # => 1 + // # => "2" + // # => [1, 2, 3] + // # => { k: "v" } + // ``` + // + // @param block + // @return [Array] + Name: "each_value", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + h := receiver.(*HashObject) + + if len(h.Pairs) == 0 { + t.callFrameStack.pop() + } + + keys := h.sortedKeys() + var arrOfValues []Object + + for _, k := range keys { + value := h.Pairs[k] + arrOfValues = append(arrOfValues, value) + t.builtinMethodYield(blockFrame, value) + } + + return t.vm.InitArrayObject(arrOfValues) - return t.vm.InitArrayObject(arrOfKeys) + }, + }, + { + // Returns true if hash has no key-value pairs + // + // ```Ruby + // {}.empty? # => true + // { a: 1 }.empty? # => false + // ``` + // + // @return [Boolean] + Name: "empty?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + h := receiver.(*HashObject) + if h.length() == 0 { + return TRUE + } + return FALSE - }, }, - { - // Loops through values of the hash with given block frame. - // Then returns an array of values of the hash in the alphabetical order of the keys. - // - // ```Ruby - // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: "v" } } - // h.each_value do |v| - // puts v - // end - // # => 1 - // # => "2" - // # => [1, 2, 3] - // # => { k: "v" } - // ``` - // - // @param block - // @return [Array] - Name: "each_value", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns true if hash is exactly equal to another hash + // + // ```Ruby + // { a: "Hello", b: "World" }.eql?(1) # => false + // ``` + // + // @param object [Object] + // @return [Boolean] + Name: "eql?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + h := receiver.(*HashObject) + c := args[0] + compare, ok := c.(*HashObject) + + if ok && reflect.DeepEqual(h, compare) { + return TRUE + } + return FALSE - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + }, + }, + { + // Returns a value from the hash for the given key. + // If the key can’t be found, there are several options: + // + // - With no other arguments, it will raise an ArgumentError. + // - If a default value is given as a second argument, then that will be returned. + // - If an optional code block is specified, then runs the block and returns the result. + // - If a block and a second argument is given together, it raises an ArgumentError. + // + // ```Ruby + // h = { spaghetti: "eat" } + // h.fetch("spaghetti") #=> "eat" + // h.fetch("pizza") #=> ArgumentError + // h.fetch("pizza", "not eat") #=> "not eat" + // h.fetch("pizza") do |el| "eat " + el end #=> "eat pizza" + // ``` + // + // @param key [String], default value [Object] + // @return [Object] + Name: "fetch", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen < 1 || aLen > 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 1, 2, aLen) + } + + key, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, key.Class().Name) + } + + if aLen == 2 { + if blockFrame != nil { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "The default argument can't be passed along with a block") } + return args[1] + } - h := receiver.(*HashObject) + hash := receiver.(*HashObject) + value, ok := hash.Pairs[key.value] - if len(h.Pairs) == 0 { + if ok { + if blockFrame != nil { t.callFrameStack.pop() } + return value + } - keys := h.sortedKeys() - var arrOfValues []Object - - for _, k := range keys { - value := h.Pairs[k] - arrOfValues = append(arrOfValues, value) - t.builtinMethodYield(blockFrame, value) - } - - return t.vm.InitArrayObject(arrOfValues) - - }, - }, - { - // Returns true if hash has no key-value pairs - // - // ```Ruby - // {}.empty? # => true - // { a: 1 }.empty? # => false - // ``` - // - // @return [Boolean] - Name: "empty?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - h := receiver.(*HashObject) - if h.length() == 0 { - return TRUE - } - return FALSE + if blockFrame != nil { + return t.builtinMethodYield(blockFrame, key).Target + } + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "The value was not found, and no block has been provided") - }, }, - { - // Returns true if hash is exactly equal to another hash - // - // ```Ruby - // { a: "Hello", b: "World" }.eql?(1) # => false - // ``` - // - // @param object [Object] - // @return [Boolean] - Name: "eql?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - h := receiver.(*HashObject) - c := args[0] - compare, ok := c.(*HashObject) + }, + { + // Returns an array containing the values associated with the given keys. + // When even one of keys can’t be found, it raises an ArgumentError. + // + // ```Ruby + // h = { cat: "feline", dog: "canine", cow: "bovine" } + // + // h.fetch_values("cow", "cat") #=> ["bovine", "feline"] + // h.fetch_values("cow", "bird") # raises ArgumentError + // h.fetch_values("cow", "bird") do |k| k.upcase end #=> ["bovine", "BIRD"] + // ``` + // + // @param key [String]... + // @return [ArrayObject] + Name: "fetch_values", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen < 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, aLen) + } + + values := make([]Object, aLen) + + hash := receiver.(*HashObject) + blockFramePopped := false + + for index, objectKey := range args { + stringKey, ok := objectKey.(*StringObject) - if ok && reflect.DeepEqual(h, compare) { - return TRUE + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, objectKey.Class().Name) } - return FALSE - }, - }, - { - // Returns a value from the hash for the given key. - // If the key can’t be found, there are several options: - // - // - With no other arguments, it will raise an ArgumentError. - // - If a default value is given as a second argument, then that will be returned. - // - If an optional code block is specified, then runs the block and returns the result. - // - If a block and a second argument is given together, it raises an ArgumentError. - // - // ```Ruby - // h = { spaghetti: "eat" } - // h.fetch("spaghetti") #=> "eat" - // h.fetch("pizza") #=> ArgumentError - // h.fetch("pizza", "not eat") #=> "not eat" - // h.fetch("pizza") do |el| "eat " + el end #=> "eat pizza" - // ``` - // - // @param key [String], default value [Object] - // @return [Object] - Name: "fetch", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen < 1 || aLen > 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 1, 2, aLen) - } + value, ok := hash.Pairs[stringKey.value] - key, ok := args[0].(*StringObject) if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, key.Class().Name) - } - - if aLen == 2 { if blockFrame != nil { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "The default argument can't be passed along with a block") + value = t.builtinMethodYield(blockFrame, objectKey).Target + blockFramePopped = true + } else { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "There is no value for the key `%s`, and no block has been provided", stringKey.value) } - return args[1] } - hash := receiver.(*HashObject) - value, ok := hash.Pairs[key.value] + values[index] = value + } - if ok { - if blockFrame != nil { - t.callFrameStack.pop() - } - return value - } + if blockFrame != nil && !blockFramePopped { + t.callFrameStack.pop() + } - if blockFrame != nil { - return t.builtinMethodYield(blockFrame, key).Target - } - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "The value was not found, and no block has been provided") + return t.vm.InitArrayObject(values) - }, }, - { - // Returns an array containing the values associated with the given keys. - // When even one of keys can’t be found, it raises an ArgumentError. - // - // ```Ruby - // h = { cat: "feline", dog: "canine", cow: "bovine" } - // - // h.fetch_values("cow", "cat") #=> ["bovine", "feline"] - // h.fetch_values("cow", "bird") # raises ArgumentError - // h.fetch_values("cow", "bird") do |k| k.upcase end #=> ["bovine", "BIRD"] - // ``` - // - // @param key [String]... - // @return [ArrayObject] - Name: "fetch_values", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen < 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, aLen) - } - - values := make([]Object, aLen) - - hash := receiver.(*HashObject) - blockFramePopped := false - - for index, objectKey := range args { - stringKey, ok := objectKey.(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, objectKey.Class().Name) - } - - value, ok := hash.Pairs[stringKey.value] - - if !ok { - if blockFrame != nil { - value = t.builtinMethodYield(blockFrame, objectKey).Target - blockFramePopped = true - } else { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "There is no value for the key `%s`, and no block has been provided", stringKey.value) - } - } - - values[index] = value - } + }, + { + // Returns true if the specified key exists in the hash + // Currently, only string can be taken. + // type object. + // + // ```Ruby + // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: "v" } } + // h.has_key?("a") # => true + // h.has_key?("e") # => false + // h.has_key?(:b) # => true + // h.has_key?(:f) # => false + // ``` + // + // @param key [String] + // @return [Boolean] + Name: "has_key?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + h := receiver.(*HashObject) + i := args[0] + input, ok := i.(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, i.Class().Name) + } + + if _, ok := h.Pairs[input.value]; ok { + return TRUE + } + return FALSE - if blockFrame != nil && !blockFramePopped { - t.callFrameStack.pop() - } - - return t.vm.InitArrayObject(values) - - }, }, - { - // Returns true if the specified key exists in the hash - // Currently, only string can be taken. - // type object. - // - // ```Ruby - // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: "v" } } - // h.has_key?("a") # => true - // h.has_key?("e") # => false - // h.has_key?(:b) # => true - // h.has_key?(:f) # => false - // ``` - // - // @param key [String] - // @return [Boolean] - Name: "has_key?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - h := receiver.(*HashObject) - i := args[0] - input, ok := i.(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, i.Class().Name) - } - - if _, ok := h.Pairs[input.value]; ok { + }, + { + // Returns true if the value exist in the hash. + // + // ```Ruby + // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: "v" } } + // h.has_value?(1) # => true + // h.has_value?(2) # => false + // h.has_value?("2") # => true + // h.has_value?([1, 2, 3]) # => true + // h.has_value?({ k: "v" }) # => true + // ``` + // + // @param value [Object] + // @return [Boolean] + Name: "has_value?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + h := receiver.(*HashObject) + + for _, v := range h.Pairs { + if reflect.DeepEqual(v, args[0]) { return TRUE } - return FALSE - - }, - }, - { - // Returns true if the value exist in the hash. - // - // ```Ruby - // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: "v" } } - // h.has_value?(1) # => true - // h.has_value?(2) # => false - // h.has_value?("2") # => true - // h.has_value?([1, 2, 3]) # => true - // h.has_value?({ k: "v" }) # => true - // ``` - // - // @param value [Object] - // @return [Boolean] - Name: "has_value?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + } + return FALSE - h := receiver.(*HashObject) - - for _, v := range h.Pairs { - if reflect.DeepEqual(v, args[0]) { - return TRUE - } - } - return FALSE - - }, }, - { - // Returns an array of keys (in arbitrary order) - // - // ```Ruby - // { a: 1, b: "2", c: [3, true, "Hello"] }.keys - // # => ["c", "b", "a"] or ["b", "a", "c"] ... etc - // ``` - // - // @return [Boolean] - Name: "keys", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - h := receiver.(*HashObject) - var keys []Object - for k := range h.Pairs { - keys = append(keys, t.vm.InitStringObject(k)) - } - return t.vm.InitArrayObject(keys) + }, + { + // Returns an array of keys (in arbitrary order) + // + // ```Ruby + // { a: 1, b: "2", c: [3, true, "Hello"] }.keys + // # => ["c", "b", "a"] or ["b", "a", "c"] ... etc + // ``` + // + // @return [Boolean] + Name: "keys", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + h := receiver.(*HashObject) + var keys []Object + for k := range h.Pairs { + keys = append(keys, t.vm.InitStringObject(k)) + } + return t.vm.InitArrayObject(keys) - }, }, - { - // Returns the number of key-value pairs of the hash. - // - // ```Ruby - // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: 'v' } } - // h.length #=> 4 - // ``` - // - // @return [Integer] - Name: "length", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns the number of key-value pairs of the hash. + // + // ```Ruby + // h = { a: 1, b: "2", c: [1, 2, 3], d: { k: 'v' } } + // h.length #=> 4 + // ``` + // + // @return [Integer] + Name: "length", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + h := receiver.(*HashObject) + return t.vm.InitIntegerObject(h.length()) - h := receiver.(*HashObject) - return t.vm.InitIntegerObject(h.length()) - - }, }, - { - // Returns a new hash with the results of running the block once for every value. - // This method does not change the keys and the receiver hash values. - // - // ```Ruby - // h = { a: 1, b: 2, c: 3 } - // result = h.map_values do |v| - // v * 3 - // end - // h # => { a: 1, b: 2, c: 3 } - // result # => { a: 3, b: 6, c: 9 } - // ``` - // - // @param block - // @return [Boolean] - Name: "map_values", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - h := receiver.(*HashObject) - if blockIsEmpty(blockFrame) { - return h - } + }, + { + // Returns a new hash with the results of running the block once for every value. + // This method does not change the keys and the receiver hash values. + // + // ```Ruby + // h = { a: 1, b: 2, c: 3 } + // result = h.map_values do |v| + // v * 3 + // end + // h # => { a: 1, b: 2, c: 3 } + // result # => { a: 3, b: 6, c: 9 } + // ``` + // + // @param block + // @return [Boolean] + Name: "map_values", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + h := receiver.(*HashObject) + if blockIsEmpty(blockFrame) { + return h + } - result := make(map[string]Object) + result := make(map[string]Object) - if len(h.Pairs) == 0 { - t.callFrameStack.pop() - } + if len(h.Pairs) == 0 { + t.callFrameStack.pop() + } - for k, v := range h.Pairs { - result[k] = t.builtinMethodYield(blockFrame, v).Target - } - return t.vm.InitHashObject(result) + for k, v := range h.Pairs { + result[k] = t.builtinMethodYield(blockFrame, v).Target + } + return t.vm.InitHashObject(result) - }, }, - { - // Returns a newly merged hash. One or more hashes can be taken. - // If keys are duplicate between the receiver and the argument, the last ones in the argument are prioritized. - // - // ```Ruby - // h = { a: 1, b: "2", c: [1, 2, 3] } - // h.merge({ b: "Hello", d: "World" }) - // # => { a: 1, b: "Hello", c: [1, 2, 3], d: "World" } - // - // { a: "Hello"}.merge({a: 0}, {a: 99}) - // # => { a: 99 } - // ``` - // - // @param hash [Hash] - // @return [Hash] - Name: "merge", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) < 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, len(args)) + }, + { + // Returns a newly merged hash. One or more hashes can be taken. + // If keys are duplicate between the receiver and the argument, the last ones in the argument are prioritized. + // + // ```Ruby + // h = { a: 1, b: "2", c: [1, 2, 3] } + // h.merge({ b: "Hello", d: "World" }) + // # => { a: 1, b: "Hello", c: [1, 2, 3], d: "World" } + // + // { a: "Hello"}.merge({a: 0}, {a: 99}) + // # => { a: 99 } + // ``` + // + // @param hash [Hash] + // @return [Hash] + Name: "merge", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) < 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, len(args)) + } + + h := receiver.(*HashObject) + result := make(map[string]Object) + for k, v := range h.Pairs { + result[k] = v + } + + for _, obj := range args { + hashObj, ok := obj.(*HashObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.HashClass, obj.Class().Name) } - - h := receiver.(*HashObject) - result := make(map[string]Object) - for k, v := range h.Pairs { + for k, v := range hashObj.Pairs { result[k] = v } + } - for _, obj := range args { - hashObj, ok := obj.(*HashObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.HashClass, obj.Class().Name) - } - for k, v := range hashObj.Pairs { - result[k] = v - } - } - - return t.vm.InitHashObject(result) + return t.vm.InitHashObject(result) - }, }, - { - // Returns a new hash consisting of entries for which the block does not return false - // or nil. - // - // ```ruby - // a = { a: 1, b: 2 } - // - // a.select do |k, v| - // v == 2 - // end # => { a: 1 } - // a.select do |k, v| - // 5 - // end # => { a: 1, b: 2 } - // a.select do |k, v| - // nil - // end # => { } - // a.select do |k, v| - // false - // end # => { } - // ``` - // - // @param block - // @return [Hash] - Name: "select", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - destinationPairs := map[string]Object{} - if blockIsEmpty(blockFrame) { - return t.vm.InitHashObject(destinationPairs) - } + }, + { + // Returns a new hash consisting of entries for which the block does not return false + // or nil. + // + // ```ruby + // a = { a: 1, b: 2 } + // + // a.select do |k, v| + // v == 2 + // end # => { a: 1 } + // a.select do |k, v| + // 5 + // end # => { a: 1, b: 2 } + // a.select do |k, v| + // nil + // end # => { } + // a.select do |k, v| + // false + // end # => { } + // ``` + // + // @param block + // @return [Hash] + Name: "select", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + destinationPairs := map[string]Object{} + if blockIsEmpty(blockFrame) { + return t.vm.InitHashObject(destinationPairs) + } - sourceHash := receiver.(*HashObject) + sourceHash := receiver.(*HashObject) - if len(sourceHash.Pairs) == 0 { - t.callFrameStack.pop() - } + if len(sourceHash.Pairs) == 0 { + t.callFrameStack.pop() + } - for stringKey, value := range sourceHash.Pairs { - objectKey := t.vm.InitStringObject(stringKey) - result := t.builtinMethodYield(blockFrame, objectKey, value) + for stringKey, value := range sourceHash.Pairs { + objectKey := t.vm.InitStringObject(stringKey) + result := t.builtinMethodYield(blockFrame, objectKey, value) - if result.Target.isTruthy() { - destinationPairs[stringKey] = value - } + if result.Target.isTruthy() { + destinationPairs[stringKey] = value } + } - return t.vm.InitHashObject(destinationPairs) + return t.vm.InitHashObject(destinationPairs) - }, }, - { - // Returns an array of keys (in arbitrary order) - // - // ```Ruby - // { a: 1, b: "2", c: [3, true, "Hello"] }.sorted_keys - // # => ["a", "b", "c"] - // { c: 1, b: "2", a: [3, true, "Hello"] }.sorted_keys - // # => ["a", "b", "c"] - // { b: 1, c: "2", a: [3, true, "Hello"] }.sorted_keys - // # => ["a", "b", "c"] - // { b: 1, c: "2", b: [3, true, "Hello"] }.sorted_keys - // # => ["b", "c"] - // ``` - // - // @return [Array] - Name: "sorted_keys", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns an array of keys (in arbitrary order) + // + // ```Ruby + // { a: 1, b: "2", c: [3, true, "Hello"] }.sorted_keys + // # => ["a", "b", "c"] + // { c: 1, b: "2", a: [3, true, "Hello"] }.sorted_keys + // # => ["a", "b", "c"] + // { b: 1, c: "2", a: [3, true, "Hello"] }.sorted_keys + // # => ["a", "b", "c"] + // { b: 1, c: "2", b: [3, true, "Hello"] }.sorted_keys + // # => ["b", "c"] + // ``` + // + // @return [Array] + Name: "sorted_keys", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + h := receiver.(*HashObject) + sortedKeys := h.sortedKeys() + var keys []Object + for _, k := range sortedKeys { + keys = append(keys, t.vm.InitStringObject(k)) + } + return t.vm.InitArrayObject(keys) - h := receiver.(*HashObject) - sortedKeys := h.sortedKeys() - var keys []Object - for _, k := range sortedKeys { - keys = append(keys, t.vm.InitStringObject(k)) - } - return t.vm.InitArrayObject(keys) - - }, }, - { - // Returns two-dimensional array with the key-value pairs of hash. If specified true - // then it will return sorted key value pairs array - // - // ```Ruby - // { a: 1, b: 2, c: 3 }.to_a - // # => [["a", 1], ["c", 3], ["b", 2]] or [["b", 2], ["c", 3], ["a", 1]] ... etc - // { a: 1, b: 2, c: 3 }.to_a(true) - // # => [["a", 1], ["b", 2], ["c", 3]] - // { b: 1, a: 2, c: 3 }.to_a(true) - // # => [["a", 2], ["b", 1], ["c", 3]] - // { b: 1, a: 2, a: 3 }.to_a(true) - // # => [["a", 3], ["b", 1]] - // ``` - // - // @param sorting [Boolean] - // @return [Array] - Name: "to_a", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen > 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + }, + { + // Returns two-dimensional array with the key-value pairs of hash. If specified true + // then it will return sorted key value pairs array + // + // ```Ruby + // { a: 1, b: 2, c: 3 }.to_a + // # => [["a", 1], ["c", 3], ["b", 2]] or [["b", 2], ["c", 3], ["a", 1]] ... etc + // { a: 1, b: 2, c: 3 }.to_a(true) + // # => [["a", 1], ["b", 2], ["c", 3]] + // { b: 1, a: 2, c: 3 }.to_a(true) + // # => [["a", 2], ["b", 1], ["c", 3]] + // { b: 1, a: 2, a: 3 }.to_a(true) + // # => [["a", 3], ["b", 1]] + // ``` + // + // @param sorting [Boolean] + // @return [Array] + Name: "to_a", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen > 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 1, aLen) + } + + var sorted bool + if aLen == 0 { + sorted = false + } else { + s := args[0] + st, ok := s.(*BooleanObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.BooleanClass, s.Class().Name) } + sorted = st.value + } - var sorted bool - if aLen == 0 { - sorted = false - } else { - s := args[0] - st, ok := s.(*BooleanObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.BooleanClass, s.Class().Name) - } - sorted = st.value + h := receiver.(*HashObject) + var resultArr []Object + if sorted { + for _, k := range h.sortedKeys() { + var pairArr []Object + pairArr = append(pairArr, t.vm.InitStringObject(k)) + pairArr = append(pairArr, h.Pairs[k]) + resultArr = append(resultArr, t.vm.InitArrayObject(pairArr)) } - - h := receiver.(*HashObject) - var resultArr []Object - if sorted { - for _, k := range h.sortedKeys() { - var pairArr []Object - pairArr = append(pairArr, t.vm.InitStringObject(k)) - pairArr = append(pairArr, h.Pairs[k]) - resultArr = append(resultArr, t.vm.InitArrayObject(pairArr)) - } - } else { - for k, v := range h.Pairs { - var pairArr []Object - pairArr = append(pairArr, t.vm.InitStringObject(k)) - pairArr = append(pairArr, v) - resultArr = append(resultArr, t.vm.InitArrayObject(pairArr)) - } + } else { + for k, v := range h.Pairs { + var pairArr []Object + pairArr = append(pairArr, t.vm.InitStringObject(k)) + pairArr = append(pairArr, v) + resultArr = append(resultArr, t.vm.InitArrayObject(pairArr)) } - return t.vm.InitArrayObject(resultArr) + } + return t.vm.InitArrayObject(resultArr) - }, }, - { - // Returns json that is corresponding to the hash. - // Basically just like Hash#to_json in Rails but currently doesn't support options. - // - // ```Ruby - // h = { a: 1, b: [1, "2", [4, 5, nil], { foo: "bar" }]}.to_json - // puts(h) #=> {"a":1,"b":[1, "2", [4, 5, null], {"foo":"bar"}]} - // ``` - // - // @return [String] - Name: "to_json", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - r := receiver.(*HashObject) - return t.vm.InitStringObject(r.ToJSON(t)) + }, + { + // Returns json that is corresponding to the hash. + // Basically just like Hash#to_json in Rails but currently doesn't support options. + // + // ```Ruby + // h = { a: 1, b: [1, "2", [4, 5, nil], { foo: "bar" }]}.to_json + // puts(h) #=> {"a":1,"b":[1, "2", [4, 5, null], {"foo":"bar"}]} + // ``` + // + // @return [String] + Name: "to_json", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*HashObject) + return t.vm.InitStringObject(r.ToJSON(t)) - }, }, - { - // Returns json that is corresponding to the hash. - // Basically just like Hash#to_json in Rails but currently doesn't support options. - // - // ```Ruby - // h = { a: 1, b: [1, "2", [4, 5, nil], { foo: "bar" }]}.to_s - // puts(h) #=> "{ a: 1, b: [1, \"2\", [4, 5, null], { foo: \"bar \" }] }" - // ``` - // - // @return [String] - Name: "to_s", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns json that is corresponding to the hash. + // Basically just like Hash#to_json in Rails but currently doesn't support options. + // + // ```Ruby + // h = { a: 1, b: [1, "2", [4, 5, nil], { foo: "bar" }]}.to_s + // puts(h) #=> "{ a: 1, b: [1, \"2\", [4, 5, null], { foo: \"bar \" }] }" + // ``` + // + // @return [String] + Name: "to_s", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + h := receiver.(*HashObject) + return t.vm.InitStringObject(h.ToString()) - h := receiver.(*HashObject) - return t.vm.InitStringObject(h.ToString()) - - }, }, - { - // Returns a new hash with the results of running the block once for every value. - // This method does not change the keys. Unlike Hash#map_values, it does not - // change the receiver's hash values. - // - // ```Ruby - // h = { a: 1, b: 2, c: 3 } - // result = h.transform_values do |v| - // v * 3 - // end - // h # => { a: 1, b: 2, c: 3 } - // result # => { a: 3, b: 6, c: 9 } - // ``` - // - // @param block - // @return [Boolean] - Name: "transform_values", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - h := receiver.(*HashObject) - - if len(h.Pairs) == 0 { - t.callFrameStack.pop() - } - - resultHash := make(map[string]Object) - for k, v := range h.Pairs { - result := t.builtinMethodYield(blockFrame, v) - resultHash[k] = result.Target - } - return t.vm.InitHashObject(resultHash) + }, + { + // Returns a new hash with the results of running the block once for every value. + // This method does not change the keys. Unlike Hash#map_values, it does not + // change the receiver's hash values. + // + // ```Ruby + // h = { a: 1, b: 2, c: 3 } + // result = h.transform_values do |v| + // v * 3 + // end + // h # => { a: 1, b: 2, c: 3 } + // result # => { a: 3, b: 6, c: 9 } + // ``` + // + // @param block + // @return [Boolean] + Name: "transform_values", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + h := receiver.(*HashObject) + + if len(h.Pairs) == 0 { + t.callFrameStack.pop() + } + + resultHash := make(map[string]Object) + for k, v := range h.Pairs { + result := t.builtinMethodYield(blockFrame, v) + resultHash[k] = result.Target + } + return t.vm.InitHashObject(resultHash) - }, }, - { - // Returns an array of values. - // The order of the returned values are indeterminable. - // - // ```Ruby - // { a: 1, b: "2", c: [3, true, "Hello"] }.keys - // # => [1, "2", [3, true, "Hello"]] or ["2", [3, true, "Hello"], 1] ... etc - // ``` - // - // @return [Array] - Name: "values", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - h := receiver.(*HashObject) - var keys []Object - for _, v := range h.Pairs { - keys = append(keys, v) - } - return t.vm.InitArrayObject(keys) + }, + { + // Returns an array of values. + // The order of the returned values are indeterminable. + // + // ```Ruby + // { a: 1, b: "2", c: [3, true, "Hello"] }.keys + // # => [1, "2", [3, true, "Hello"]] or ["2", [3, true, "Hello"], 1] ... etc + // ``` + // + // @return [Array] + Name: "values", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + h := receiver.(*HashObject) + var keys []Object + for _, v := range h.Pairs { + keys = append(keys, v) + } + return t.vm.InitArrayObject(keys) - }, }, - { - // Return an array containing the values associated with the given keys. - // - // ```Ruby - // { a: 1, b: "2" }.values_at("a", "c") # => [1, nil] - // ``` - // - // @param key [String]... - // @return [Array] - Name: "values_at", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - hash := receiver.(*HashObject) - var result []Object - - for _, objectKey := range args { - stringObjectKey, ok := objectKey.(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, objectKey.Class().Name) - } + }, + { + // Return an array containing the values associated with the given keys. + // + // ```Ruby + // { a: 1, b: "2" }.values_at("a", "c") # => [1, nil] + // ``` + // + // @param key [String]... + // @return [Array] + Name: "values_at", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + hash := receiver.(*HashObject) + var result []Object + + for _, objectKey := range args { + stringObjectKey, ok := objectKey.(*StringObject) - value, ok := hash.Pairs[stringObjectKey.value] + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, objectKey.Class().Name) + } - if !ok { - value = NULL - } + value, ok := hash.Pairs[stringObjectKey.value] - result = append(result, value) + if !ok { + value = NULL } - return t.vm.InitArrayObject(result) + result = append(result, value) + } + + return t.vm.InitArrayObject(result) - }, }, - } + }, } // Internal functions =================================================== @@ -1223,8 +1219,8 @@ func (vm *VM) InitHashObject(pairs map[string]Object) *HashObject { func (vm *VM) initHashClass() *RClass { hc := vm.initializeClass(classes.HashClass) - hc.setBuiltinMethods(builtinHashInstanceMethods(), false) - hc.setBuiltinMethods(builtinHashClassMethods(), true) + hc.setBuiltinMethods(builtinHashInstanceMethods, false) + hc.setBuiltinMethods(builtinHashClassMethods, true) return hc } diff --git a/vm/http.go b/vm/http.go index 452f35952..7e5d86ea3 100644 --- a/vm/http.go +++ b/vm/http.go @@ -23,158 +23,156 @@ var ( ) // Class methods -------------------------------------------------------- -func builtinHTTPClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Sends a GET request to the target and returns the HTTP response as a string. Will error on non-200 responses, for more control over http requests look at the `start` method. - Name: "get", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - arg0, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 0, "String", args[0].Class().Name) - } - - uri, err := url.Parse(arg0.value) - - if len(args) > 1 { - var arr []string - - for i, v := range args[1:] { - argn, ok := v.(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, invalidSplatArgument, v.Class().Name, i) - } - arr = append(arr, argn.value) +var builtinHTTPClassMethods = []*BuiltinMethodObject{ + { + // Sends a GET request to the target and returns the HTTP response as a string. Will error on non-200 responses, for more control over http requests look at the `start` method. + Name: "get", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + arg0, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 0, "String", args[0].Class().Name) + } + + uri, err := url.Parse(arg0.value) + + if len(args) > 1 { + var arr []string + + for i, v := range args[1:] { + argn, ok := v.(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, invalidSplatArgument, v.Class().Name, i) } - - uri.Path = path.Join(arr...) - } - - resp, err := http.Get(uri.String()) - if err != nil { - return t.vm.InitErrorObject(errors.HTTPError, sourceLine, couldNotCompleteRequest, err) - } - if resp.StatusCode != http.StatusOK { - return t.vm.InitErrorObject(errors.HTTPError, sourceLine, non200Response, resp.Status, resp.StatusCode) - } - - content, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - - if err != nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, err.Error()) + arr = append(arr, argn.value) } - return t.vm.InitStringObject(string(content)) + uri.Path = path.Join(arr...) + } - }, - }, { - // Sends a POST request to the target with type header and body. Returns the HTTP response as a string. Will error on non-200 responses, for more control over http requests look at the `start` method. - Name: "post", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 3 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 3, len(args)) - } + resp, err := http.Get(uri.String()) + if err != nil { + return t.vm.InitErrorObject(errors.HTTPError, sourceLine, couldNotCompleteRequest, err) + } + if resp.StatusCode != http.StatusOK { + return t.vm.InitErrorObject(errors.HTTPError, sourceLine, non200Response, resp.Status, resp.StatusCode) + } - arg0, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 0, "String", args[0].Class().Name) - } - host := arg0.value + content, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() - arg1, ok := args[1].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, "String", args[0].Class().Name) - } - contentType := arg1.value + if err != nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, err.Error()) + } - arg2, ok := args[2].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, "String", args[0].Class().Name) - } - body := arg2.value - - resp, err := http.Post(host, contentType, strings.NewReader(body)) - if err != nil { - return t.vm.InitErrorObject(errors.HTTPError, sourceLine, couldNotCompleteRequest, err) - } - if resp.StatusCode != http.StatusOK { - return t.vm.InitErrorObject(errors.HTTPError, sourceLine, non200Response, resp.Status, resp.StatusCode) - } + return t.vm.InitStringObject(string(content)) - content, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - - if err != nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, err.Error()) - } - - return t.vm.InitStringObject(string(content)) - - }, - }, { - // Sends a HEAD request to the target with type header and body. Returns the HTTP headers as a map[string]string. Will error on non-200 responses, for more control over http requests look at the `start` method. - Name: "head", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - arg0, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 0, "String", args[0].Class().Name) - } - - uri, err := url.Parse(arg0.value) - - if len(args) > 1 { - var arr []string + }, + }, { + // Sends a POST request to the target with type header and body. Returns the HTTP response as a string. Will error on non-200 responses, for more control over http requests look at the `start` method. + Name: "post", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 3 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 3, len(args)) + } + + arg0, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 0, "String", args[0].Class().Name) + } + host := arg0.value + + arg1, ok := args[1].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, "String", args[0].Class().Name) + } + contentType := arg1.value + + arg2, ok := args[2].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, "String", args[0].Class().Name) + } + body := arg2.value + + resp, err := http.Post(host, contentType, strings.NewReader(body)) + if err != nil { + return t.vm.InitErrorObject(errors.HTTPError, sourceLine, couldNotCompleteRequest, err) + } + if resp.StatusCode != http.StatusOK { + return t.vm.InitErrorObject(errors.HTTPError, sourceLine, non200Response, resp.Status, resp.StatusCode) + } + + content, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + + if err != nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, err.Error()) + } + + return t.vm.InitStringObject(string(content)) - for i, v := range args[1:] { - argn, ok := v.(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, invalidSplatArgument, v.Class().Name, i) - } - arr = append(arr, argn.value) + }, + }, { + // Sends a HEAD request to the target with type header and body. Returns the HTTP headers as a map[string]string. Will error on non-200 responses, for more control over http requests look at the `start` method. + Name: "head", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + arg0, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 0, "String", args[0].Class().Name) + } + + uri, err := url.Parse(arg0.value) + + if len(args) > 1 { + var arr []string + + for i, v := range args[1:] { + argn, ok := v.(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, invalidSplatArgument, v.Class().Name, i) } - - uri.Path = path.Join(arr...) + arr = append(arr, argn.value) } - resp, err := http.Head(uri.String()) - if err != nil { - return t.vm.InitErrorObject(errors.HTTPError, sourceLine, couldNotCompleteRequest, err) - } - if resp.StatusCode != http.StatusOK { - return t.vm.InitErrorObject(errors.HTTPError, sourceLine, non200Response, resp.Status, resp.StatusCode) - } + uri.Path = path.Join(arr...) + } - ret := t.vm.InitHashObject(map[string]Object{}) + resp, err := http.Head(uri.String()) + if err != nil { + return t.vm.InitErrorObject(errors.HTTPError, sourceLine, couldNotCompleteRequest, err) + } + if resp.StatusCode != http.StatusOK { + return t.vm.InitErrorObject(errors.HTTPError, sourceLine, non200Response, resp.Status, resp.StatusCode) + } - for k, v := range resp.Header { - ret.Pairs[k] = t.vm.InitStringObject(strings.Join(v, " ")) - } + ret := t.vm.InitHashObject(map[string]Object{}) - return ret + for k, v := range resp.Header { + ret.Pairs[k] = t.vm.InitStringObject(strings.Join(v, " ")) + } - }, - }, { - // Starts an HTTP client. This method requires a block which takes a Net::HTTP::Client object. The return value of this method is the last evaluated value of the provided block. - Name: "start", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + return ret + + }, + }, { + // Starts an HTTP client. This method requires a block which takes a Net::HTTP::Client object. The return value of this method is the last evaluated value of the provided block. + Name: "start", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - gobyClient := httpClientClass.initializeInstance() + gobyClient := httpClientClass.initializeInstance() - result := t.builtinMethodYield(blockFrame, gobyClient) + result := t.builtinMethodYield(blockFrame, gobyClient) - if err, ok := result.Target.(*Error); ok { - return err //an Error object - } + if err, ok := result.Target.(*Error); ok { + return err //an Error object + } - return result.Target + return result.Target - }, }, - } + }, } // Internal functions =================================================== @@ -184,7 +182,7 @@ func builtinHTTPClassMethods() []*BuiltinMethodObject { func initHTTPClass(vm *VM) { net := vm.loadConstant("Net", true) http := vm.initializeClass("HTTP") - http.setBuiltinMethods(builtinHTTPClassMethods(), true) + http.setBuiltinMethods(builtinHTTPClassMethods, true) initRequestClass(vm, http) initResponseClass(vm, http) initClientClass(vm, http) diff --git a/vm/integer.go b/vm/integer.go index d7acef41c..332fc293b 100644 --- a/vm/integer.go +++ b/vm/integer.go @@ -44,696 +44,692 @@ const ( ) // Class methods -------------------------------------------------------- -func builtinIntegerClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitNoMethodError(sourceLine, "new", receiver) +var builtinIntegerClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitNoMethodError(sourceLine, "new", receiver) - }, }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinIntegerInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Returns the sum of self and another Numeric. - // - // ```Ruby - // 1 + 2 # => 3 - // ``` - // @return [Numeric] - Name: "+", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - intOperation := func(leftValue int, rightValue int) int { - return leftValue + rightValue - } - floatOperation := func(leftValue float64, rightValue float64) float64 { - return leftValue + rightValue - } - - return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, false) - - }, - }, - { - // Divides left hand operand by right hand operand and returns remainder. - // - // ```Ruby - // 5 % 2 # => 1 - // ``` - // @return [Numeric] - Name: "%", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - intOperation := func(leftValue int, rightValue int) int { - return leftValue % rightValue - } - floatOperation := math.Mod - - return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, true) - - }, - }, - { - // Returns the subtraction of another Numeric from self. - // - // ```Ruby - // 1 - 1 # => 0 - // ``` - // @return [Numeric] - Name: "-", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - intOperation := func(leftValue int, rightValue int) int { - return leftValue - rightValue - } - floatOperation := func(leftValue float64, rightValue float64) float64 { - return leftValue - rightValue - } - - return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, false) - - }, - }, - { - // Returns self multiplying another Numeric. - // - // ```Ruby - // 2 * 10 # => 20 - // ``` - // @return [Numeric] - Name: "*", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - intOperation := func(leftValue int, rightValue int) int { - return leftValue * rightValue - } - floatOperation := func(leftValue float64, rightValue float64) float64 { - return leftValue * rightValue - } - - return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, false) - - }, - }, - { - // Returns self squaring another Numeric. - // - // ```Ruby - // 2 ** 8 # => 256 - // ``` - // @return [Numeric] - Name: "**", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - intOperation := func(leftValue int, rightValue int) int { - return int(math.Pow(float64(leftValue), float64(rightValue))) - } - floatOperation := math.Pow - - return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, false) - - }, - }, - { - // Returns self divided by another Numeric. - // - // ```Ruby - // 6 / 3 # => 2 - // ``` - // @return [Numeric] - Name: "/", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - intOperation := func(leftValue int, rightValue int) int { - return leftValue / rightValue - } - floatOperation := func(leftValue float64, rightValue float64) float64 { - return leftValue / rightValue - } - - return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, true) - - }, - }, - { - // Returns if self is larger than another Numeric. - // - // ```Ruby - // 10 > -1 # => true - // 3 > 3 # => false - // ``` - // @return [Boolean] - Name: ">", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - intComparison := func(leftValue int, rightValue int) bool { - return leftValue > rightValue - } - floatComparison := func(leftValue float64, rightValue float64) bool { - return leftValue > rightValue - } - - switch arg := args[0].(type) { - case *IntegerObject, *FloatObject: - return toBooleanObject(receiver.(*IntegerObject).numericComparison(args[0], intComparison, floatComparison)) - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", arg.Class().Name) - } - }, - }, - { - // Returns if self is larger than or equals to another Numeric. - // - // ```Ruby - // 2 >= 1 # => true - // 1 >= 1 # => true - // ``` - // @return [Boolean] - Name: ">=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - intComparison := func(leftValue int, rightValue int) bool { - return leftValue >= rightValue - } - floatComparison := func(leftValue float64, rightValue float64) bool { - return leftValue >= rightValue - } - - switch arg := args[0].(type) { - case *IntegerObject, *FloatObject: - return toBooleanObject(receiver.(*IntegerObject).numericComparison(args[0], intComparison, floatComparison)) - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", arg.Class().Name) - } - - }, - }, - { - // Returns if self is smaller than another Numeric. - // - // ```Ruby - // 1 < 3 # => true - // 1 < 1 # => false - // ``` - // @return [Boolean] - Name: "<", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - intComparison := func(leftValue int, rightValue int) bool { - return leftValue < rightValue - } - floatComparison := func(leftValue float64, rightValue float64) bool { - return leftValue < rightValue - } - - switch arg := args[0].(type) { - case *IntegerObject, *FloatObject: - return toBooleanObject(receiver.(*IntegerObject).numericComparison(args[0], intComparison, floatComparison)) - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", arg.Class().Name) - } - - }, - }, - { - // Returns if self is smaller than or equals to another Numeric. - // - // ```Ruby - // 1 <= 3 # => true - // 1 <= 1 # => true - // ``` - // @return [Boolean] - Name: "<=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - intComparison := func(leftValue int, rightValue int) bool { - return leftValue <= rightValue - } - floatComparison := func(leftValue float64, rightValue float64) bool { - return leftValue <= rightValue - } - - switch arg := args[0].(type) { - case *IntegerObject, *FloatObject: - return toBooleanObject(receiver.(*IntegerObject).numericComparison(args[0], intComparison, floatComparison)) - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", arg.Class().Name) - } - - }, - }, - { - // Returns 1 if self is larger than the incoming Numeric, -1 if smaller. Otherwise 0. - // - // ```Ruby - // 1 <=> 3 # => -1 - // 1 <=> 1 # => 0 - // 3 <=> 1 # => 1 - // ``` - // @return [Integer] - Name: "<=>", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - rightObject := args[0] - - switch rightObject := rightObject.(type) { - case *IntegerObject: - leftValue := receiver.(*IntegerObject).value - rightValue := rightObject.value - - if leftValue < rightValue { - return t.vm.InitIntegerObject(-1) - } - if leftValue > rightValue { - return t.vm.InitIntegerObject(1) - } - - return t.vm.InitIntegerObject(0) - case *FloatObject: - leftValue := float64(receiver.(*IntegerObject).value) - rightValue := rightObject.value - - if leftValue < rightValue { - return t.vm.InitIntegerObject(-1) - } - if leftValue > rightValue { - return t.vm.InitIntegerObject(1) - } - - return t.vm.InitIntegerObject(0) - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", rightObject.Class().Name) - } - - }, - }, - { - // Returns if self is equal to an Object. - // If the Object is a Numeric, a comparison is performed, otherwise, the - // result is always false. - // - // ```Ruby - // 1 == 3 # => false - // 1 == 1 # => true - // 1 == '1' # => false - // ``` - // @return [Boolean] - Name: "==", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - result := receiver.(*IntegerObject).equalityTest(args[0]) - - return toBooleanObject(result) - - }, - }, - { - // Returns if self is not equal to an Object. - // If the Object is a Numeric, a comparison is performed, otherwise, the - // result is always true. - // - // ```Ruby - // 1 != 3 # => true - // 1 != 1 # => false - // 1 != '1' # => true - // ``` - // @return [Boolean] - Name: "!=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - result := !receiver.(*IntegerObject).equalityTest(args[0]) - - return toBooleanObject(result) - - }, - }, - { - // Returns if self is even. - // - // ```Ruby - // 1.even? # => false - // 2.even? # => true - // ``` - // @return [Boolean] - Name: "even?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - i := receiver.(*IntegerObject) - even := i.value%2 == 0 - - if even { - return TRUE - } +var builtinIntegerInstanceMethods = []*BuiltinMethodObject{ + { + // Returns the sum of self and another Numeric. + // + // ```Ruby + // 1 + 2 # => 3 + // ``` + // @return [Numeric] + Name: "+", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + intOperation := func(leftValue int, rightValue int) int { + return leftValue + rightValue + } + floatOperation := func(leftValue float64, rightValue float64) float64 { + return leftValue + rightValue + } + + return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, false) + + }, + }, + { + // Divides left hand operand by right hand operand and returns remainder. + // + // ```Ruby + // 5 % 2 # => 1 + // ``` + // @return [Numeric] + Name: "%", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + intOperation := func(leftValue int, rightValue int) int { + return leftValue % rightValue + } + floatOperation := math.Mod - return FALSE + return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, true) - }, }, - // Returns the `Decimal` conversion of self. + }, + { + // Returns the subtraction of another Numeric from self. // // ```Ruby - // 100.to_d # => '100'.to_d + // 1 - 1 # => 0 // ``` - // @return [Decimal] - { - Name: "to_d", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + // @return [Numeric] + Name: "-", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + intOperation := func(leftValue int, rightValue int) int { + return leftValue - rightValue + } + floatOperation := func(leftValue float64, rightValue float64) float64 { + return leftValue - rightValue + } + + return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, false) + + }, + }, + { + // Returns self multiplying another Numeric. + // + // ```Ruby + // 2 * 10 # => 20 + // ``` + // @return [Numeric] + Name: "*", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + intOperation := func(leftValue int, rightValue int) int { + return leftValue * rightValue + } + floatOperation := func(leftValue float64, rightValue float64) float64 { + return leftValue * rightValue + } + + return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, false) + + }, + }, + { + // Returns self squaring another Numeric. + // + // ```Ruby + // 2 ** 8 # => 256 + // ``` + // @return [Numeric] + Name: "**", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + intOperation := func(leftValue int, rightValue int) int { + return int(math.Pow(float64(leftValue), float64(rightValue))) + } + floatOperation := math.Pow - r := receiver.(*IntegerObject) - return t.vm.initDecimalObject(intToDecimal(r)) + return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, false) - }, }, - // Returns the `Float` conversion of self. + }, + { + // Returns self divided by another Numeric. // // ```Ruby - // 100.to_f # => '100.0'.to_f + // 6 / 3 # => 2 // ``` - // @return [Float] - { - Name: "to_f", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - r := receiver.(*IntegerObject) - newFloat := t.vm.initFloatObject(float64(r.value)) - return newFloat - - }, - }, - { - // Returns self. - // - // ```Ruby - // 100.to_i # => 100 - // ``` - // @return [Integer] - Name: "to_i", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - return receiver - - }, - }, - { - // Returns a `String` representation of self. - // - // ```Ruby - // 100.to_s # => "100" - // ``` - // @return [String] - Name: "to_s", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + // @return [Numeric] + Name: "/", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - int := receiver.(*IntegerObject) + intOperation := func(leftValue int, rightValue int) int { + return leftValue / rightValue + } + floatOperation := func(leftValue float64, rightValue float64) float64 { + return leftValue / rightValue + } - return t.vm.InitStringObject(strconv.Itoa(int.value)) + return receiver.(*IntegerObject).arithmeticOperation(t, args[0], intOperation, floatOperation, sourceLine, true) - }, }, - { - // Returns self + 1. - // - // ```ruby - // 100.next # => 101 - // ``` - // @return [Integer] - Name: "next", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - i := receiver.(*IntegerObject) - return t.vm.InitIntegerObject(i.value + 1) - - }, - }, - { - // Returns if self is odd. - // - // ```ruby - // 3.odd? # => true - // 4.odd? # => false - // ``` - // @return [Boolean] - Name: "odd?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - i := receiver.(*IntegerObject) - odd := i.value%2 != 0 - if odd { - return TRUE - } - - return FALSE - - }, - }, - { - // Returns self - 1. - // - // ```ruby - // 40.pred # => 39 - // ``` - // @return [Integer] - Name: "pred", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - i := receiver.(*IntegerObject) - return t.vm.InitIntegerObject(i.value - 1) - - }, - }, - { - // Yields a block a number of times equals to self. - // - // ```Ruby - // a = 0 - // 3.times do - // a += 1 - // end - // a # => 3 - // ``` - Name: "times", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - n := receiver.(*IntegerObject) - - if n.value < 0 { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, "Expect the receiver to be positive integer. got: %d", n.value) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - for i := 0; i < n.value; i++ { - t.builtinMethodYield(blockFrame, t.vm.InitIntegerObject(i)) - } + }, + { + // Returns if self is larger than another Numeric. + // + // ```Ruby + // 10 > -1 # => true + // 3 > 3 # => false + // ``` + // @return [Boolean] + Name: ">", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + intComparison := func(leftValue int, rightValue int) bool { + return leftValue > rightValue + } + floatComparison := func(leftValue float64, rightValue float64) bool { + return leftValue > rightValue + } + + switch arg := args[0].(type) { + case *IntegerObject, *FloatObject: + return toBooleanObject(receiver.(*IntegerObject).numericComparison(args[0], intComparison, floatComparison)) + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", arg.Class().Name) + } + }, + }, + { + // Returns if self is larger than or equals to another Numeric. + // + // ```Ruby + // 2 >= 1 # => true + // 1 >= 1 # => true + // ``` + // @return [Boolean] + Name: ">=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + intComparison := func(leftValue int, rightValue int) bool { + return leftValue >= rightValue + } + floatComparison := func(leftValue float64, rightValue float64) bool { + return leftValue >= rightValue + } + + switch arg := args[0].(type) { + case *IntegerObject, *FloatObject: + return toBooleanObject(receiver.(*IntegerObject).numericComparison(args[0], intComparison, floatComparison)) + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", arg.Class().Name) + } + + }, + }, + { + // Returns if self is smaller than another Numeric. + // + // ```Ruby + // 1 < 3 # => true + // 1 < 1 # => false + // ``` + // @return [Boolean] + Name: "<", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + intComparison := func(leftValue int, rightValue int) bool { + return leftValue < rightValue + } + floatComparison := func(leftValue float64, rightValue float64) bool { + return leftValue < rightValue + } + + switch arg := args[0].(type) { + case *IntegerObject, *FloatObject: + return toBooleanObject(receiver.(*IntegerObject).numericComparison(args[0], intComparison, floatComparison)) + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", arg.Class().Name) + } + + }, + }, + { + // Returns if self is smaller than or equals to another Numeric. + // + // ```Ruby + // 1 <= 3 # => true + // 1 <= 1 # => true + // ``` + // @return [Boolean] + Name: "<=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + intComparison := func(leftValue int, rightValue int) bool { + return leftValue <= rightValue + } + floatComparison := func(leftValue float64, rightValue float64) bool { + return leftValue <= rightValue + } + + switch arg := args[0].(type) { + case *IntegerObject, *FloatObject: + return toBooleanObject(receiver.(*IntegerObject).numericComparison(args[0], intComparison, floatComparison)) + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", arg.Class().Name) + } + + }, + }, + { + // Returns 1 if self is larger than the incoming Numeric, -1 if smaller. Otherwise 0. + // + // ```Ruby + // 1 <=> 3 # => -1 + // 1 <=> 1 # => 0 + // 3 <=> 1 # => 1 + // ``` + // @return [Integer] + Name: "<=>", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + rightObject := args[0] - return n + switch rightObject := rightObject.(type) { + case *IntegerObject: + leftValue := receiver.(*IntegerObject).value + rightValue := rightObject.value - }, - }, - { - Name: "to_int", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + if leftValue < rightValue { + return t.vm.InitIntegerObject(-1) } - - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = i - return newInt - - }, - }, - { - Name: "to_int8", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + if leftValue > rightValue { + return t.vm.InitIntegerObject(1) } - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = i8 - return newInt + return t.vm.InitIntegerObject(0) + case *FloatObject: + leftValue := float64(receiver.(*IntegerObject).value) + rightValue := rightObject.value - }, - }, - { - Name: "to_int16", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + if leftValue < rightValue { + return t.vm.InitIntegerObject(-1) } - - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = i16 - return newInt - - }, - }, - { - Name: "to_int32", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + if leftValue > rightValue { + return t.vm.InitIntegerObject(1) } - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = i32 - return newInt + return t.vm.InitIntegerObject(0) + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Numeric", rightObject.Class().Name) + } - }, }, - { - Name: "to_int64", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns if self is equal to an Object. + // If the Object is a Numeric, a comparison is performed, otherwise, the + // result is always false. + // + // ```Ruby + // 1 == 3 # => false + // 1 == 1 # => true + // 1 == '1' # => false + // ``` + // @return [Boolean] + Name: "==", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + result := receiver.(*IntegerObject).equalityTest(args[0]) - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = i64 - return newInt + return toBooleanObject(result) - }, }, - { - Name: "to_uint", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns if self is not equal to an Object. + // If the Object is a Numeric, a comparison is performed, otherwise, the + // result is always true. + // + // ```Ruby + // 1 != 3 # => true + // 1 != 1 # => false + // 1 != '1' # => true + // ``` + // @return [Boolean] + Name: "!=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + result := !receiver.(*IntegerObject).equalityTest(args[0]) - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = ui - return newInt + return toBooleanObject(result) - }, }, - { - Name: "to_uint8", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns if self is even. + // + // ```Ruby + // 1.even? # => false + // 2.even? # => true + // ``` + // @return [Boolean] + Name: "even?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + i := receiver.(*IntegerObject) + even := i.value%2 == 0 + + if even { + return TRUE + } + + return FALSE + + }, + }, + // Returns the `Decimal` conversion of self. + // + // ```Ruby + // 100.to_d # => '100'.to_d + // ``` + // @return [Decimal] + { + Name: "to_d", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + return t.vm.initDecimalObject(intToDecimal(r)) + + }, + }, + // Returns the `Float` conversion of self. + // + // ```Ruby + // 100.to_f # => '100.0'.to_f + // ``` + // @return [Float] + { + Name: "to_f", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newFloat := t.vm.initFloatObject(float64(r.value)) + return newFloat + + }, + }, + { + // Returns self. + // + // ```Ruby + // 100.to_i # => 100 + // ``` + // @return [Integer] + Name: "to_i", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = ui8 - return newInt + return receiver - }, }, - { - Name: "to_uint16", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = ui16 - return newInt + }, + { + // Returns a `String` representation of self. + // + // ```Ruby + // 100.to_s # => "100" + // ``` + // @return [String] + Name: "to_s", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - }, - }, - { - Name: "to_uint32", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + int := receiver.(*IntegerObject) - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = ui32 - return newInt + return t.vm.InitStringObject(strconv.Itoa(int.value)) - }, }, - { - Name: "to_uint64", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns self + 1. + // + // ```ruby + // 100.next # => 101 + // ``` + // @return [Integer] + Name: "next", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = ui64 - return newInt + i := receiver.(*IntegerObject) + return t.vm.InitIntegerObject(i.value + 1) - }, }, - { - Name: "to_float32", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns if self is odd. + // + // ```ruby + // 3.odd? # => true + // 4.odd? # => false + // ``` + // @return [Boolean] + Name: "odd?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = f32 - return newInt + i := receiver.(*IntegerObject) + odd := i.value%2 != 0 + if odd { + return TRUE + } - }, - }, - { - Name: "to_float64", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + return FALSE - r := receiver.(*IntegerObject) - newInt := t.vm.InitIntegerObject(r.value) - newInt.flag = f64 - return newInt - - }, }, - { - Name: "ptr", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns self - 1. + // + // ```ruby + // 40.pred # => 39 + // ``` + // @return [Integer] + Name: "pred", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - r := receiver.(*IntegerObject) - return t.vm.initGoObject(&r.value) + i := receiver.(*IntegerObject) + return t.vm.InitIntegerObject(i.value - 1) - }, }, - } + }, + { + // Yields a block a number of times equals to self. + // + // ```Ruby + // a = 0 + // 3.times do + // a += 1 + // end + // a # => 3 + // ``` + Name: "times", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + n := receiver.(*IntegerObject) + + if n.value < 0 { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, "Expect the receiver to be positive integer. got: %d", n.value) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + for i := 0; i < n.value; i++ { + t.builtinMethodYield(blockFrame, t.vm.InitIntegerObject(i)) + } + + return n + + }, + }, + { + Name: "to_int", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = i + return newInt + + }, + }, + { + Name: "to_int8", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = i8 + return newInt + + }, + }, + { + Name: "to_int16", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = i16 + return newInt + + }, + }, + { + Name: "to_int32", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = i32 + return newInt + + }, + }, + { + Name: "to_int64", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = i64 + return newInt + + }, + }, + { + Name: "to_uint", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = ui + return newInt + + }, + }, + { + Name: "to_uint8", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = ui8 + return newInt + + }, + }, + { + Name: "to_uint16", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = ui16 + return newInt + + }, + }, + { + Name: "to_uint32", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = ui32 + return newInt + + }, + }, + { + Name: "to_uint64", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = ui64 + return newInt + + }, + }, + { + Name: "to_float32", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = f32 + return newInt + + }, + }, + { + Name: "to_float64", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + newInt := t.vm.InitIntegerObject(r.value) + newInt.flag = f64 + return newInt + + }, + }, + { + Name: "ptr", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + r := receiver.(*IntegerObject) + return t.vm.initGoObject(&r.value) + + }, + }, } // Internal functions =================================================== @@ -750,8 +746,8 @@ func (vm *VM) InitIntegerObject(value int) *IntegerObject { func (vm *VM) initIntegerClass() *RClass { ic := vm.initializeClass(classes.IntegerClass) - ic.setBuiltinMethods(builtinIntegerInstanceMethods(), false) - ic.setBuiltinMethods(builtinIntegerClassMethods(), true) + ic.setBuiltinMethods(builtinIntegerInstanceMethods, false) + ic.setBuiltinMethods(builtinIntegerClassMethods, true) return ic } diff --git a/vm/json.go b/vm/json.go index b94e114df..d589f5943 100644 --- a/vm/json.go +++ b/vm/json.go @@ -10,89 +10,85 @@ import ( type jsonObj map[string]interface{} // Class methods -------------------------------------------------------- -func builtinJSONClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "parse", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - j, ok := args[0].(*StringObject) +var builtinJSONClassMethods = []*BuiltinMethodObject{ + { + Name: "parse", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + j, ok := args[0].(*StringObject) - var obj jsonObj - var objs []jsonObj + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - jsonString := j.value + var obj jsonObj + var objs []jsonObj - err := json.Unmarshal([]byte(jsonString), &obj) + jsonString := j.value - if err != nil { - err = json.Unmarshal([]byte(jsonString), &objs) + err := json.Unmarshal([]byte(jsonString), &obj) - if err != nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, "Can't parse string `%s` as json: %s", jsonString, err.Error()) - } + if err != nil { + err = json.Unmarshal([]byte(jsonString), &objs) - var objects []Object + if err != nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, "Can't parse string `%s` as json: %s", jsonString, err.Error()) + } - for _, obj := range objs { - objects = append(objects, t.vm.convertJSONToHashObj(obj)) - } + var objects []Object - return t.vm.InitArrayObject(objects) + for _, obj := range objs { + objects = append(objects, t.vm.convertJSONToHashObj(obj)) } - return t.vm.convertJSONToHashObj(obj) + return t.vm.InitArrayObject(objects) + } - }, - }, - { - Name: "validate", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + return t.vm.convertJSONToHashObj(obj) - j, ok := args[0].(*StringObject) + }, + }, + { + Name: "validate", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + j, ok := args[0].(*StringObject) - var obj jsonObj - var objs []jsonObj + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - jsonString := j.value + var obj jsonObj + var objs []jsonObj - err := json.Unmarshal([]byte(jsonString), &obj) + jsonString := j.value - if err != nil { - err = json.Unmarshal([]byte(jsonString), &objs) + err := json.Unmarshal([]byte(jsonString), &obj) - if err != nil { - return FALSE - } + if err != nil { + err = json.Unmarshal([]byte(jsonString), &objs) - return TRUE + if err != nil { + return FALSE } return TRUE + } + + return TRUE - }, }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinJSONInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{} -} +var builtinJSONInstanceMethods = []*BuiltinMethodObject{} // Internal functions =================================================== @@ -100,8 +96,8 @@ func builtinJSONInstanceMethods() []*BuiltinMethodObject { func initJSONClass(vm *VM) { class := vm.initializeClass("JSON") - class.setBuiltinMethods(builtinJSONClassMethods(), true) - class.setBuiltinMethods(builtinJSONInstanceMethods(), false) + class.setBuiltinMethods(builtinJSONClassMethods, true) + class.setBuiltinMethods(builtinJSONInstanceMethods, false) vm.objectClass.setClassConstant(class) } diff --git a/vm/match_data.go b/vm/match_data.go index 2568142a0..c64427ac0 100644 --- a/vm/match_data.go +++ b/vm/match_data.go @@ -27,130 +27,126 @@ type MatchDataObject struct { } // Class methods -------------------------------------------------------- -func builtInMatchDataClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitNoMethodError(sourceLine, "#new", receiver) - - }, +var builtInMatchDataClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitNoMethodError(sourceLine, "#new", receiver) + }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinMatchDataInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Returns the array of captures; equivalent to `match.to_a[1..-1]`. - // - // ```ruby - // c1, c2 = 'abcd'.match(Regexp.new('a(b)(c)')).captures - // c1 #=> "b" - // c2 #=> "c" - // ``` - // - // @return [Array] - Name: "captures", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - offset := 1 - - g := receiver.(*MatchDataObject).match - n := len(g.Groups()) - offset - destCaptures := make([]Object, n, n) - - for i := 0; i < n; i++ { - destCaptures[i] = t.vm.InitStringObject(g.GroupByNumber(i + offset).String()) - } - - return t.vm.InitArrayObject(destCaptures) - - }, +var builtinMatchDataInstanceMethods = []*BuiltinMethodObject{ + { + // Returns the array of captures; equivalent to `match.to_a[1..-1]`. + // + // ```ruby + // c1, c2 = 'abcd'.match(Regexp.new('a(b)(c)')).captures + // c1 #=> "b" + // c2 #=> "c" + // ``` + // + // @return [Array] + Name: "captures", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + offset := 1 + + g := receiver.(*MatchDataObject).match + n := len(g.Groups()) - offset + destCaptures := make([]Object, n, n) + + for i := 0; i < n; i++ { + destCaptures[i] = t.vm.InitStringObject(g.GroupByNumber(i + offset).String()) + } + + return t.vm.InitArrayObject(destCaptures) + }, - { - // Returns the array of captures. - // - // ```ruby - // c0, c1, c2 = 'abcd'.match(Regexp.new('a(b)(c)')).to_a - // c0 #=> "abc" - // c1 #=> "b" - // c2 #=> "c" - // ``` - // - // @return [Array] - Name: "to_a", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - g := receiver.(*MatchDataObject).match - n := len(g.Groups()) - destCaptures := make([]Object, n, n) - - for i := 0; i < n; i++ { - destCaptures[i] = t.vm.InitStringObject(g.GroupByNumber(i).String()) - } - - return t.vm.InitArrayObject(destCaptures) - - }, + }, + { + // Returns the array of captures. + // + // ```ruby + // c0, c1, c2 = 'abcd'.match(Regexp.new('a(b)(c)')).to_a + // c0 #=> "abc" + // c1 #=> "b" + // c2 #=> "c" + // ``` + // + // @return [Array] + Name: "to_a", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + g := receiver.(*MatchDataObject).match + n := len(g.Groups()) + destCaptures := make([]Object, n, n) + + for i := 0; i < n; i++ { + destCaptures[i] = t.vm.InitStringObject(g.GroupByNumber(i).String()) + } + + return t.vm.InitArrayObject(destCaptures) + }, - { - // Returns the hash of captures, including the whole matched text(`0:`). - // You can use named-capture as well. - // - // ```ruby - // h = 'abcd'.match(Regexp.new('a(b)(c)')).to_h - // puts h #=> { "0": "abc", "1": "b", "2": "c" } - // - // h = 'abcd'.match(Regexp.new('a(?b)(?c)')).to_h - // puts h #=> { "0": "abc", "first": "b", "second": "c" } - // ``` - // - // @return [Hash] - Name: "to_h", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - groups := receiver.(*MatchDataObject).match - result := make(map[string]Object) - - for _, g := range groups.Groups() { - result[g.Name] = t.vm.InitStringObject(g.String()) - } - - return t.vm.InitHashObject(result) - - }, + }, + { + // Returns the hash of captures, including the whole matched text(`0:`). + // You can use named-capture as well. + // + // ```ruby + // h = 'abcd'.match(Regexp.new('a(b)(c)')).to_h + // puts h #=> { "0": "abc", "1": "b", "2": "c" } + // + // h = 'abcd'.match(Regexp.new('a(?b)(?c)')).to_h + // puts h #=> { "0": "abc", "first": "b", "second": "c" } + // ``` + // + // @return [Hash] + Name: "to_h", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + groups := receiver.(*MatchDataObject).match + result := make(map[string]Object) + + for _, g := range groups.Groups() { + result[g.Name] = t.vm.InitStringObject(g.String()) + } + + return t.vm.InitHashObject(result) + }, - { - // Returns the length of the array; equivalent to `match.to_a.length`. - // - // ```ruby - // 'abcd'.match(Regexp.new('a(b)(c)')).length # => 3 - // ``` - // @return [Integer] - Name: "length", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - m := receiver.(*MatchDataObject).match - - return t.vm.InitIntegerObject(m.GroupCount()) - - }, + }, + { + // Returns the length of the array; equivalent to `match.to_a.length`. + // + // ```ruby + // 'abcd'.match(Regexp.new('a(b)(c)')).length # => 3 + // ``` + // @return [Integer] + Name: "length", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + m := receiver.(*MatchDataObject).match + + return t.vm.InitIntegerObject(m.GroupCount()) + }, - } + }, } // Internal functions =================================================== @@ -169,8 +165,8 @@ func (vm *VM) initMatchDataObject(match *Match, pattern, text string) *MatchData func (vm *VM) initMatchDataClass() *RClass { klass := vm.initializeClass(classes.MatchDataClass) - klass.setBuiltinMethods(builtinMatchDataInstanceMethods(), false) - klass.setBuiltinMethods(builtInMatchDataClassMethods(), true) + klass.setBuiltinMethods(builtinMatchDataInstanceMethods, false) + klass.setBuiltinMethods(builtInMatchDataClassMethods, true) return klass } diff --git a/vm/null.go b/vm/null.go index 4fd049880..161e17ff9 100644 --- a/vm/null.go +++ b/vm/null.go @@ -18,120 +18,116 @@ var ( ) // Class methods -------------------------------------------------------- -func builtinNullClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitNoMethodError(sourceLine, "new", receiver) - - }, +var builtinNullClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitNoMethodError(sourceLine, "new", receiver) + }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinNullInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Returns true: the flipped boolean value of nil object. - // - // ```ruby - // a = nil - // !a - // # => true - // ``` - Name: "!", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - return TRUE +var builtinNullInstanceMethods = []*BuiltinMethodObject{ + { + // Returns true: the flipped boolean value of nil object. + // + // ```ruby + // a = nil + // !a + // # => true + // ``` + Name: "!", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + + return TRUE - }, }, - { - Name: "to_i", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + Name: "to_i", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - return t.vm.InitIntegerObject(0) + return t.vm.InitIntegerObject(0) - }, }, - { - Name: "to_s", + }, + { + Name: "to_s", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - n := receiver.(*NullObject) - return t.vm.InitStringObject(n.ToString()) + n := receiver.(*NullObject) + return t.vm.InitStringObject(n.ToString()) - }, - }, - { - // Returns true because it is nil. (See the main implementation of nil? method in vm/class.go) - // - // ```ruby - // a = nil - // a == nil - // # => true - // ``` - Name: "==", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 1 argument. got: %d", len(args)) - } - - if _, ok := args[0].(*NullObject); ok { - return TRUE - } - return FALSE - - }, }, - { - // Returns true: the flipped boolean value of nil object. - // - // ```ruby - // a = nil - // a != nil - // # => false - // ``` - Name: "!=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 1 argument. got: %d", len(args)) - } - - if _, ok := args[0].(*NullObject); !ok { - return TRUE - } - return FALSE - - }, + }, + { + // Returns true because it is nil. (See the main implementation of nil? method in vm/class.go) + // + // ```ruby + // a = nil + // a == nil + // # => true + // ``` + Name: "==", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 1 argument. got: %d", len(args)) + } + + if _, ok := args[0].(*NullObject); ok { + return TRUE + } + return FALSE + }, - { - // Returns true because it is nil. - // - // ```ruby - // a = nil - // a.nil? - // # => true - // ``` - Name: "nil?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 argument. got: %d", len(args)) - } + }, + { + // Returns true: the flipped boolean value of nil object. + // + // ```ruby + // a = nil + // a != nil + // # => false + // ``` + Name: "!=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 1 argument. got: %d", len(args)) + } + + if _, ok := args[0].(*NullObject); !ok { return TRUE + } + return FALSE + + }, + }, + { + // Returns true because it is nil. + // + // ```ruby + // a = nil + // a.nil? + // # => true + // ``` + Name: "nil?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 argument. got: %d", len(args)) + } + return TRUE - }, }, - } + }, } // Internal functions =================================================== @@ -140,8 +136,8 @@ func builtinNullInstanceMethods() []*BuiltinMethodObject { func (vm *VM) initNullClass() *RClass { nc := vm.initializeClass(classes.NullClass) - nc.setBuiltinMethods(builtinNullInstanceMethods(), false) - nc.setBuiltinMethods(builtinNullClassMethods(), true) + nc.setBuiltinMethods(builtinNullInstanceMethods, false) + nc.setBuiltinMethods(builtinNullClassMethods, true) NULL = &NullObject{BaseObj: &BaseObj{class: nc}} return nc } diff --git a/vm/range.go b/vm/range.go index f70dc2247..ea8edac2c 100644 --- a/vm/range.go +++ b/vm/range.go @@ -34,484 +34,480 @@ type RangeObject struct { } // Class methods -------------------------------------------------------- -func builtinRangeClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitNoMethodError(sourceLine, "#new", receiver) - - }, +var builtinRangeClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitNoMethodError(sourceLine, "#new", receiver) + }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinRangeInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Returns a Boolean of compared two ranges - // - // ```ruby - // (1..5) == (1..5) # => true - // (1..5) == (1..6) # => false - // ``` - // - // @return [Boolean] - Name: "==", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - left := receiver.(*RangeObject) - r := args[0] - right, ok := r.(*RangeObject) - - if !ok { - return FALSE - } +var builtinRangeInstanceMethods = []*BuiltinMethodObject{ + { + // Returns a Boolean of compared two ranges + // + // ```ruby + // (1..5) == (1..5) # => true + // (1..5) == (1..6) # => false + // ``` + // + // @return [Boolean] + Name: "==", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + + left := receiver.(*RangeObject) + r := args[0] + right, ok := r.(*RangeObject) + + if !ok { + return FALSE + } - if left.Start == right.Start && left.End == right.End { - return TRUE - } + if left.Start == right.Start && left.End == right.End { + return TRUE + } - return FALSE + return FALSE - }, }, - { - // Returns a Boolean of compared two ranges - // - // ```ruby - // (1..5) != (1..5) # => false - // (1..5) != (1..6) # => true - // ``` - // - // @return [Boolean] - Name: "!=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - right, ok := args[0].(*RangeObject) - if !ok { - return TRUE - } + }, + { + // Returns a Boolean of compared two ranges + // + // ```ruby + // (1..5) != (1..5) # => false + // (1..5) != (1..6) # => true + // ``` + // + // @return [Boolean] + Name: "!=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + + right, ok := args[0].(*RangeObject) + if !ok { + return TRUE + } - left := receiver.(*RangeObject) - if left.Start == right.Start && left.End == right.End { - return FALSE - } + left := receiver.(*RangeObject) + if left.Start == right.Start && left.End == right.End { + return FALSE + } - return TRUE + return TRUE - }, }, - { - // By using binary search, finds a value in range which meets the given condition in O(log n) - // where n is the size of the range. - // - // You can use this method in two use cases: a find-minimum mode and a find-any mode. In either - // case, the elements of the range must be monotone (or sorted) with respect to the block. - // - // In find-minimum mode (this is a good choice for typical use case), the block must return true - // or false, and there must be a value x so that: - // - // - the block returns false for any value which is less than x - // - the block returns true for any value which is greater than or equal to x. - // - // If x is within the range, this method returns the value x. Otherwise, it returns nil. - // - // ```ruby - // ary = [0, 4, 7, 10, 12] - // (0..4).bsearch {|i| ary[i] >= 4 } #=> 1 - // (0..4).bsearch {|i| ary[i] >= 6 } #=> 2 - // (0..4).bsearch {|i| ary[i] >= 8 } #=> 3 - // (0..4).bsearch {|i| ary[i] >= 100 } #=> nil - // ``` - // - // In find-any mode , the block must return a number, and there must be two values x and y - // (x <= y) so that: - // - // - the block returns a positive number for v if v < x - // - the block returns zero for v if x <= v < y - // - the block returns a negative number for v if y <= v - // - // This method returns any value which is within the intersection of the given range and x…y - // (if any). If there is no value that satisfies the condition, it returns nil. - // - // ```ruby - // ary = [0, 100, 100, 100, 200] - // (0..4).bsearch {|i| 100 - ary[i] } #=> 1, 2 or 3 - // (0..4).bsearch {|i| 300 - ary[i] } #=> nil - // (0..4).bsearch {|i| 50 - ary[i] } #=> nil - // ``` - // - // @return [Integer] - Name: "bsearch", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - ro := receiver.(*RangeObject) - - if ro.Start < 0 || ro.End < 0 { - // if block is not used, it should be popped - t.callFrameStack.pop() - return NULL - } - - var start, end int - if ro.Start < ro.End { - start, end = ro.Start, ro.End - } else { - start, end = ro.End, ro.Start + }, + { + // By using binary search, finds a value in range which meets the given condition in O(log n) + // where n is the size of the range. + // + // You can use this method in two use cases: a find-minimum mode and a find-any mode. In either + // case, the elements of the range must be monotone (or sorted) with respect to the block. + // + // In find-minimum mode (this is a good choice for typical use case), the block must return true + // or false, and there must be a value x so that: + // + // - the block returns false for any value which is less than x + // - the block returns true for any value which is greater than or equal to x. + // + // If x is within the range, this method returns the value x. Otherwise, it returns nil. + // + // ```ruby + // ary = [0, 4, 7, 10, 12] + // (0..4).bsearch {|i| ary[i] >= 4 } #=> 1 + // (0..4).bsearch {|i| ary[i] >= 6 } #=> 2 + // (0..4).bsearch {|i| ary[i] >= 8 } #=> 3 + // (0..4).bsearch {|i| ary[i] >= 100 } #=> nil + // ``` + // + // In find-any mode , the block must return a number, and there must be two values x and y + // (x <= y) so that: + // + // - the block returns a positive number for v if v < x + // - the block returns zero for v if x <= v < y + // - the block returns a negative number for v if y <= v + // + // This method returns any value which is within the intersection of the given range and x…y + // (if any). If there is no value that satisfies the condition, it returns nil. + // + // ```ruby + // ary = [0, 100, 100, 100, 200] + // (0..4).bsearch {|i| 100 - ary[i] } #=> 1, 2 or 3 + // (0..4).bsearch {|i| 300 - ary[i] } #=> nil + // (0..4).bsearch {|i| 50 - ary[i] } #=> nil + // ``` + // + // @return [Integer] + Name: "bsearch", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + ro := receiver.(*RangeObject) + + if ro.Start < 0 || ro.End < 0 { + // if block is not used, it should be popped + t.callFrameStack.pop() + return NULL + } + + var start, end int + if ro.Start < ro.End { + start, end = ro.Start, ro.End + } else { + start, end = ro.End, ro.Start + } + + // the element of the range + // object with the highest value + rEnd := end + + var mid int + pivot := -1 + + for { + mid = (start + end) / 2 + if (start+end)%2 != 0 { + mid++ } - // the element of the range - // object with the highest value - rEnd := end - - var mid int - pivot := -1 + result := t.builtinMethodYield(blockFrame, t.vm.InitIntegerObject(mid)) - for { - mid = (start + end) / 2 - if (start+end)%2 != 0 { - mid++ + switch r := result.Target.(type) { + case *BooleanObject: + if r.value { + pivot = mid } - result := t.builtinMethodYield(blockFrame, t.vm.InitIntegerObject(mid)) - - switch r := result.Target.(type) { - case *BooleanObject: - if r.value { - pivot = mid - } - - if start >= end { - if pivot == -1 { - return NULL - } - return t.vm.InitIntegerObject(pivot) - } - - if r.value { - end = mid - 1 - } else if mid+1 > rEnd { + if start >= end { + if pivot == -1 { return NULL - } else { - start = mid + 1 - } - case *IntegerObject: - if r.value == 0 { - return t.vm.InitIntegerObject(mid) } + return t.vm.InitIntegerObject(pivot) + } - if start == end { - return NULL - } + if r.value { + end = mid - 1 + } else if mid+1 > rEnd { + return NULL + } else { + start = mid + 1 + } + case *IntegerObject: + if r.value == 0 { + return t.vm.InitIntegerObject(mid) + } - if r.value > 0 { - start = mid + 1 - } else { - end = mid - 1 - } - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, "Expect argument to be Integer or Boolean. got: %s", r.Class().Name) + if start == end { + return NULL } - } - }, - }, - { - // Iterates over the elements of range, passing each in turn to the block. - // Returns `nil`. - // - // ```ruby - // sum = 0 - // (1..5).each do |i| - // sum = sum + i - // end - // sum # => 15 - // - // sum = 0 - // (-1..-5).each do |i| - // sum = sum + i - // end - // sum # => -15 - // ``` - // - // **Note:** - // - Only `do`-`end` block is supported: `{ }` block is unavailable. - // - Three-dot range `...` is not supported yet. - // - // @return [Range] - Name: "each", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - ro := receiver.(*RangeObject) - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + if r.value > 0 { + start = mid + 1 + } else { + end = mid - 1 + } + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, "Expect argument to be Integer or Boolean. got: %s", r.Class().Name) } + } - ro.each(func(i int) error { - obj := t.vm.InitIntegerObject(i) - t.builtinMethodYield(blockFrame, obj) - - return nil - }) - - return ro - - }, }, - { - // Returns the first value of the range. - // - // ```ruby - // (1..5).first # => 1 - // (5..1).first # => 5 - // (-2..3).first # => -2 - // (-5..-7).first # => -5 - // ``` - // - // @return [Integer] - Name: "first", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitIntegerObject(receiver.(*RangeObject).Start) - - }, - }, - { - // The include method will check whether the integer object is in the range - // - // ```ruby - // (5..10).include?(10) # => true - // (5..10).include?(11) # => false - // (5..10).include?(7) # => true - // (5..10).include?(5) # => true - // (5..10).include?(4) # => false - // (-5..1).include?(-2) # => true - // (-5..-2).include?(-2) # => true - // (-5..-3).include?(-2) # => false - // (1..-5).include?(-2) # => true - // (-2..-5).include?(-2) # => true - // (-3..-5).include?(-2) # => false - // ``` - // - // @param number [Integer] - // @return [Boolean] - Name: "include?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - ro := receiver.(*RangeObject) + }, + { + // Iterates over the elements of range, passing each in turn to the block. + // Returns `nil`. + // + // ```ruby + // sum = 0 + // (1..5).each do |i| + // sum = sum + i + // end + // sum # => 15 + // + // sum = 0 + // (-1..-5).each do |i| + // sum = sum + i + // end + // sum # => -15 + // ``` + // + // **Note:** + // - Only `do`-`end` block is supported: `{ }` block is unavailable. + // - Three-dot range `...` is not supported yet. + // + // @return [Range] + Name: "each", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + ro := receiver.(*RangeObject) + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + ro.each(func(i int) error { + obj := t.vm.InitIntegerObject(i) + t.builtinMethodYield(blockFrame, obj) + + return nil + }) + + return ro - value := args[0].(*IntegerObject).value - ascendRangeBool := ro.Start <= ro.End && value >= ro.Start && value <= ro.End - descendRangeBool := ro.End <= ro.Start && value <= ro.Start && value >= ro.End - - if ascendRangeBool || descendRangeBool { - return TRUE - } - return FALSE + }, + }, + { + // Returns the first value of the range. + // + // ```ruby + // (1..5).first # => 1 + // (5..1).first # => 5 + // (-2..3).first # => -2 + // (-5..-7).first # => -5 + // ``` + // + // @return [Integer] + Name: "first", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitIntegerObject(receiver.(*RangeObject).Start) - }, }, - { - // Returns the last value of the range. - // - // ```ruby - // (1..5).last # => 5 - // (5..1).last # => 1 - // (-2..3).last # => 3 - // (-5..-7).last # => -7 - // ``` - // - // @return [Integer] - Name: "last", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitIntegerObject(receiver.(*RangeObject).End) - - }, + }, + { + // The include method will check whether the integer object is in the range + // + // ```ruby + // (5..10).include?(10) # => true + // (5..10).include?(11) # => false + // (5..10).include?(7) # => true + // (5..10).include?(5) # => true + // (5..10).include?(4) # => false + // (-5..1).include?(-2) # => true + // (-5..-2).include?(-2) # => true + // (-5..-3).include?(-2) # => false + // (1..-5).include?(-2) # => true + // (-2..-5).include?(-2) # => true + // (-3..-5).include?(-2) # => false + // ``` + // + // @param number [Integer] + // @return [Boolean] + Name: "include?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + ro := receiver.(*RangeObject) + + value := args[0].(*IntegerObject).value + ascendRangeBool := ro.Start <= ro.End && value >= ro.Start && value <= ro.End + descendRangeBool := ro.End <= ro.Start && value <= ro.Start && value >= ro.End + + if ascendRangeBool || descendRangeBool { + return TRUE + } + return FALSE + }, - { - // Loop through each element with the given range. Return a new array with each yield element. Only a block is required, and no other arguments are acceptable. - // - // ```ruby - // (1..10).map do |i| - // i * i - // end - // - // # => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] - // ``` - // @return [Array] - Name: "map", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns the last value of the range. + // + // ```ruby + // (1..5).last # => 5 + // (5..1).last # => 1 + // (-2..3).last # => 3 + // (-5..-7).last # => -7 + // ``` + // + // @return [Integer] + Name: "last", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitIntegerObject(receiver.(*RangeObject).End) - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + }, + }, + { + // Loop through each element with the given range. Return a new array with each yield element. Only a block is required, and no other arguments are acceptable. + // + // ```ruby + // (1..10).map do |i| + // i * i + // end + // + // # => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] + // ``` + // @return [Array] + Name: "map", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + ro := receiver.(*RangeObject) + var el []Object + + ro.each(func(i int) error { + if blockIsEmpty(blockFrame) { + el = append(el, NULL) + } else { + obj := t.vm.InitIntegerObject(i) + el = append(el, t.builtinMethodYield(blockFrame, obj).Target) } - ro := receiver.(*RangeObject) - var el []Object + return nil + }) - ro.each(func(i int) error { - if blockIsEmpty(blockFrame) { - el = append(el, NULL) - } else { - obj := t.vm.InitIntegerObject(i) - el = append(el, t.builtinMethodYield(blockFrame, obj).Target) - } + return t.vm.InitArrayObject(el) - return nil - }) - - return t.vm.InitArrayObject(el) - - }, }, - { - // Returns the size of the range - // - // ```ruby - // (1..5).size # => 5 - // (3..9).size # => 7 - // (-1..-5).size # => 5 - // (-1..7).size # => 9 - // ``` - // - // @return [Integer] - Name: "size", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - ro := receiver.(*RangeObject) - - if ro.Start <= ro.End { - return t.vm.InitIntegerObject(ro.End - ro.Start + 1) - } - return t.vm.InitIntegerObject(ro.Start - ro.End + 1) + }, + { + // Returns the size of the range + // + // ```ruby + // (1..5).size # => 5 + // (3..9).size # => 7 + // (-1..-5).size # => 5 + // (-1..7).size # => 9 + // ``` + // + // @return [Integer] + Name: "size", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + ro := receiver.(*RangeObject) + + if ro.Start <= ro.End { + return t.vm.InitIntegerObject(ro.End - ro.Start + 1) + } + return t.vm.InitIntegerObject(ro.Start - ro.End + 1) - }, }, - { - // The step method can loop through the first to the last of the object with given steps. - // An error will occur if not yielded to the block. - // - // ```ruby - // sum = 0 - // (2..9).step(3) do |i| - // sum = sum + i - // end - // sum # => 15 - // - // sum = 0 - // (2..-9).step(3) do |i| - // sum = sum + i - // end - // sum # => 0 - // - // sum = 0 - // (-1..5).step(2) do |i| - // sum = sum + 1 - // end - // sum # => 8 - // - // sum = 0 - // (-1..-5).step(2) do |i| - // sum = sum + 1 - // end - // sum # => 0 - // ``` - // - // @param positive number [Integer] - // @return [Range] - Name: "step", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } - - ro := receiver.(*RangeObject) - step := args[0].(*IntegerObject).value - if step <= 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.NegativeValue, step) + }, + { + // The step method can loop through the first to the last of the object with given steps. + // An error will occur if not yielded to the block. + // + // ```ruby + // sum = 0 + // (2..9).step(3) do |i| + // sum = sum + i + // end + // sum # => 15 + // + // sum = 0 + // (2..-9).step(3) do |i| + // sum = sum + i + // end + // sum # => 0 + // + // sum = 0 + // (-1..5).step(2) do |i| + // sum = sum + 1 + // end + // sum # => 8 + // + // sum = 0 + // (-1..-5).step(2) do |i| + // sum = sum + 1 + // end + // sum # => 0 + // ``` + // + // @param positive number [Integer] + // @return [Range] + Name: "step", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + ro := receiver.(*RangeObject) + step := args[0].(*IntegerObject).value + if step <= 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.NegativeValue, step) + } + + blockFrameUsed := false + + ro.each(func(i int) error { + if (i-ro.Start)%step != 0 { + return nil } - blockFrameUsed := false + obj := t.vm.InitIntegerObject(i) + t.builtinMethodYield(blockFrame, obj) + blockFrameUsed = true - ro.each(func(i int) error { - if (i-ro.Start)%step != 0 { - return nil - } + return nil + }) - obj := t.vm.InitIntegerObject(i) - t.builtinMethodYield(blockFrame, obj) - blockFrameUsed = true + // if block is not used, it should be popped + if !blockFrameUsed { + t.callFrameStack.pop() + } - return nil - }) + return ro - // if block is not used, it should be popped - if !blockFrameUsed { - t.callFrameStack.pop() - } - - return ro - - }, }, - { - // Returns an Array object that contains the values of the range. - // - // ```ruby - // (1..5).to_a # => [1, 2, 3, 4, 5] - // (1..5).to_a[2] # => 3 - // (-1..-5).to_a # => [-1, -2, -3, -4, -5] - // (-1..3).to_a # => [-1, 0, 1, 2, 3] - // ``` - // - // @return [Array] - Name: "to_a", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - var offset int - - ro := receiver.(*RangeObject) - if ro.Start <= ro.End { - offset = 1 - } else { - offset = -1 - } + }, + { + // Returns an Array object that contains the values of the range. + // + // ```ruby + // (1..5).to_a # => [1, 2, 3, 4, 5] + // (1..5).to_a[2] # => 3 + // (-1..-5).to_a # => [-1, -2, -3, -4, -5] + // (-1..3).to_a # => [-1, 0, 1, 2, 3] + // ``` + // + // @return [Array] + Name: "to_a", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + var offset int + + ro := receiver.(*RangeObject) + if ro.Start <= ro.End { + offset = 1 + } else { + offset = -1 + } + + el := []Object{} + for i := ro.Start; i != (ro.End + offset); i += offset { + el = append(el, t.vm.InitIntegerObject(i)) + } + + return t.vm.InitArrayObject(el) - el := []Object{} - for i := ro.Start; i != (ro.End + offset); i += offset { - el = append(el, t.vm.InitIntegerObject(i)) - } - - return t.vm.InitArrayObject(el) - - }, }, - { - // The to_s method can convert range to string format - // - // ```ruby - // (1..5).to_s # "(1..5)" - // (-1..-3).to_s # "(-1..-3)" - // ``` - // - // @return [String] - Name: "to_s", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - ro := receiver.(*RangeObject) - - return t.vm.InitStringObject(ro.ToString()) - - }, + }, + { + // The to_s method can convert range to string format + // + // ```ruby + // (1..5).to_s # "(1..5)" + // (-1..-3).to_s # "(-1..-3)" + // ``` + // + // @return [String] + Name: "to_s", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + ro := receiver.(*RangeObject) + + return t.vm.InitStringObject(ro.ToString()) + }, - } + }, } // Internal functions =================================================== @@ -528,8 +524,8 @@ func (vm *VM) initRangeObject(start, end int) *RangeObject { func (vm *VM) initRangeClass() *RClass { rc := vm.initializeClass(classes.RangeClass) - rc.setBuiltinMethods(builtinRangeInstanceMethods(), false) - rc.setBuiltinMethods(builtinRangeClassMethods(), true) + rc.setBuiltinMethods(builtinRangeInstanceMethods, false) + rc.setBuiltinMethods(builtinRangeClassMethods, true) vm.libFiles = append(vm.libFiles, "range.gb") vm.libFiles = append(vm.libFiles, "range_enumerator.gb") return rc diff --git a/vm/regexp.go b/vm/regexp.go index 83cc8fa2b..150208afa 100644 --- a/vm/regexp.go +++ b/vm/regexp.go @@ -41,101 +41,96 @@ type RegexpObject struct { } // Class methods -------------------------------------------------------- -func builtInRegexpClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - arg, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, arg.Class().Name) - } - - r := t.vm.initRegexpObject(args[0].ToString()) - if r == nil { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Invalid regexp: %v", args[0].ToString()) - } - return r - - }, +var builtInRegexpClassMethods = []*BuiltinMethodObject{ + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + arg, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, arg.Class().Name) + } + + r := t.vm.initRegexpObject(args[0].ToString()) + if r == nil { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Invalid regexp: %v", args[0].ToString()) + } + return r + }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinRegexpInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - - { - // Returns true if the two regexp patterns are exactly the same, or returns false if not. - // If comparing with non Regexp class, just returns false. - // - // ```ruby - // r1 = Regexp.new("goby[0-9]+") - // r2 = Regexp.new("goby[0-9]+") - // r3 = Regexp.new("Goby[0-9]+") - // - // r1 == r2 # => true - // r1 == r2 # => false - // ``` - // - // @param regexp [Regexp] - // @return [Boolean] - Name: "==", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - right, ok := args[0].(*RegexpObject) - if !ok { - return FALSE - } - - left := receiver.(*RegexpObject) - - if left.Value() == right.Value() { - return TRUE - } +var builtinRegexpInstanceMethods = []*BuiltinMethodObject{ + { + // Returns true if the two regexp patterns are exactly the same, or returns false if not. + // If comparing with non Regexp class, just returns false. + // + // ```ruby + // r1 = Regexp.new("goby[0-9]+") + // r2 = Regexp.new("goby[0-9]+") + // r3 = Regexp.new("Goby[0-9]+") + // + // r1 == r2 # => true + // r1 == r2 # => false + // ``` + // + // @param regexp [Regexp] + // @return [Boolean] + Name: "==", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + right, ok := args[0].(*RegexpObject) + if !ok { return FALSE + } + + left := receiver.(*RegexpObject) + + if left.Value() == right.Value() { + return TRUE + } + return FALSE - }, }, - { - // Returns boolean value to indicate the result of regexp match with the string given. The methods evaluates a String object. - // - // ```ruby - // r = Regexp.new("o") - // r.match?("pow") # => true - // r.match?("gee") # => false - // ``` - // - // @param string [String] - // @return [Boolean] - Name: "match?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - arg := args[0] - input, ok := arg.(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, arg.Class().Name) - } - - re := receiver.(*RegexpObject).regexp - m, _ := re.MatchString(input.value) - - return toBooleanObject(m) - - }, + }, + { + // Returns boolean value to indicate the result of regexp match with the string given. The methods evaluates a String object. + // + // ```ruby + // r = Regexp.new("o") + // r.match?("pow") # => true + // r.match?("gee") # => false + // ``` + // + // @param string [String] + // @return [Boolean] + Name: "match?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + arg := args[0] + input, ok := arg.(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, arg.Class().Name) + } + + re := receiver.(*RegexpObject).regexp + m, _ := re.MatchString(input.value) + + return toBooleanObject(m) + }, - } + }, } // Internal functions =================================================== @@ -155,8 +150,8 @@ func (vm *VM) initRegexpObject(regexp string) *RegexpObject { func (vm *VM) initRegexpClass() *RClass { rc := vm.initializeClass(classes.RegexpClass) - rc.setBuiltinMethods(builtinRegexpInstanceMethods(), false) - rc.setBuiltinMethods(builtInRegexpClassMethods(), true) + rc.setBuiltinMethods(builtinRegexpInstanceMethods, false) + rc.setBuiltinMethods(builtInRegexpClassMethods, true) return rc } diff --git a/vm/string.go b/vm/string.go index 70dffd09f..ddf1b839c 100644 --- a/vm/string.go +++ b/vm/string.go @@ -36,1436 +36,1405 @@ type StringObject struct { } // Class methods -------------------------------------------------------- -func builtinStringClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // The String.fmt implements formatted I/O with functions analogous to C's printf and scanf. - // Currently only support plain "%s" formatting. - // TODO: Support other kind of formatting such as %f, %v ... etc - // - // ```ruby - // String.fmt("Hello! %s Lang!", "Goby") # => "Hello! Goby Lang!" - // String.fmt("I love to eat %s and %s!", "Sushi", "Ramen") # => "I love to eat Sushi and Ramen" - // ``` - // - // @param string [String], insertions [String] - // @return [String] - Name: "fmt", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) < 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, len(args)) - } +var builtinStringClassMethods = []*BuiltinMethodObject{ + { + // The String.fmt implements formatted I/O with functions analogous to C's printf and scanf. + // Currently only support plain "%s" formatting. + // TODO: Support other kind of formatting such as %f, %v ... etc + // + // ```ruby + // String.fmt("Hello! %s Lang!", "Goby") # => "Hello! Goby Lang!" + // String.fmt("I love to eat %s and %s!", "Sushi", "Ramen") # => "I love to eat Sushi and Ramen" + // ``` + // + // @param string [String], insertions [String] + // @return [String] + Name: "fmt", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) < 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentMore, 1, len(args)) + } - formatObj, ok := args[0].(*StringObject) + formatObj, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - format := formatObj.value - arguments := []interface{}{} + format := formatObj.value + arguments := []interface{}{} - for _, arg := range args[1:] { - arguments = append(arguments, arg.ToString()) - } + for _, arg := range args[1:] { + arguments = append(arguments, arg.ToString()) + } - count := strings.Count(format, "%s") + count := strings.Count(format, "%s") - if len(args[1:]) != count { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect %d additional string(s) to insert. got: %d", count, len(args[1:])) - } + if len(args[1:]) != count { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect %d additional string(s) to insert. got: %d", count, len(args[1:])) + } - return t.vm.InitStringObject(fmt.Sprintf(format, arguments...)) + return t.vm.InitStringObject(fmt.Sprintf(format, arguments...)) - }, }, - { - Name: "new", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitNoMethodError(sourceLine, "new", receiver) + }, + { + Name: "new", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + return t.vm.InitNoMethodError(sourceLine, "new", receiver) - }, }, - } + }, } // Instance methods ----------------------------------------------------- -func builtinStringInstanceMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Returns the concatenation of self and another String. - // - // ```ruby - // "first" + "-second" # => "first-second" - // ``` - // - // @param string [String] - // @return [String] - Name: "+", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - right, ok := args[0].(*StringObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } +var builtinStringInstanceMethods = []*BuiltinMethodObject{ + { + // Returns the concatenation of self and another String. + // + // ```ruby + // "first" + "-second" # => "first-second" + // ``` + // + // @param string [String] + // @return [String] + Name: "+", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - left := receiver.(*StringObject) - return t.vm.InitStringObject(left.value + right.value) + right, ok := args[0].(*StringObject) - }, - }, - { - // Returns self multiplying another Integer. - // - // ```ruby - // "string " * 2 # => "string string string " - // ``` - // - // #param positive integer [Integer] - // @return [String] - Name: "*", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - right, ok := args[0].(*IntegerObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - if right.value < 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.NegativeSecondValue, right.value) - } + left := receiver.(*StringObject) + return t.vm.InitStringObject(left.value + right.value) - var result string + }, + }, + { + // Returns self multiplying another Integer. + // + // ```ruby + // "string " * 2 # => "string string string " + // ``` + // + // #param positive integer [Integer] + // @return [String] + Name: "*", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - left := receiver.(*StringObject) - for i := 0; i < right.value; i++ { - result += left.value - } + right, ok := args[0].(*IntegerObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) + } - return t.vm.InitStringObject(result) + if right.value < 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.NegativeSecondValue, right.value) + } - }, - }, - { - // Returns a Boolean if first string greater than second string. - // - // ```ruby - // "a" < "b" # => true - // ``` - // - // @param string [String] - // @return [Boolean] - Name: ">", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - right, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + var result string - left := receiver.(*StringObject) - if left.value > right.value { - return TRUE - } + left := receiver.(*StringObject) + for i := 0; i < right.value; i++ { + result += left.value + } - return FALSE + return t.vm.InitStringObject(result) - }, }, - { - // Returns a Boolean if first string less than second string. - // - // ```ruby - // "a" < "b" # => true - // ``` - // - // @param string [String] - // @return [Boolean] - Name: "<", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - right, ok := args[0].(*StringObject) + }, + { + // Returns a Boolean if first string greater than second string. + // + // ```ruby + // "a" < "b" # => true + // ``` + // + // @param string [String] + // @return [Boolean] + Name: ">", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + right, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - left := receiver.(*StringObject) - if left.value < right.value { - return TRUE - } + left := receiver.(*StringObject) + if left.value > right.value { + return TRUE + } - return FALSE + return FALSE - }, }, - { - // Returns a Boolean of compared two strings. - // - // ```ruby - // "first" == "second" # => false - // "two" == "two" # => true - // ``` - // - // @param string [String] - // @return [Boolean] - Name: "==", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - right, ok := args[0].(*StringObject) - if !ok { - return FALSE - } + }, + { + // Returns a Boolean if first string less than second string. + // + // ```ruby + // "a" < "b" # => true + // ``` + // + // @param string [String] + // @return [Boolean] + Name: "<", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - left := receiver.(*StringObject) - if left.value == right.value { - return TRUE - } + right, ok := args[0].(*StringObject) - return FALSE + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - }, - }, - { - // Matches the receiver with a Regexp, and returns the number of matched strings. - // - // ```ruby - // "pizza" =~ Regex.new("zz") # => 2 - // "pizza" =~ Regex.new("OH!") # => nil - // ``` - // - // @param regexp [Regexp] - // @return [Integer] - Name: "=~", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + left := receiver.(*StringObject) + if left.value < right.value { + return TRUE + } - re, ok := args[0].(*RegexpObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.RegexpClass, args[0].Class().Name) - } + return FALSE - text := receiver.(*StringObject).value + }, + }, + { + // Returns a Boolean of compared two strings. + // + // ```ruby + // "first" == "second" # => false + // "two" == "two" # => true + // ``` + // + // @param string [String] + // @return [Boolean] + Name: "==", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - match, _ := re.regexp.FindStringMatch(text) - if match == nil { - return NULL - } + right, ok := args[0].(*StringObject) + if !ok { + return FALSE + } - position := match.Groups()[0].Captures[0].Index + left := receiver.(*StringObject) + if left.value == right.value { + return TRUE + } - return t.vm.InitIntegerObject(position) + return FALSE - }, }, - { - // Returns a Integer. - // Returns -1 if the first string is less than the second string returns -1, returns 0 if equal to, or returns 1 if greater than. - // - // - // ```ruby - // "abc" <=> "abcd" # => -1 - // "abc" <=> "abc" # => 0 - // "abcd" <=> "abc" # => 1 - // ``` - // - // @param string [String] - // @return [Integer] - Name: "<=>", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - right, ok := args[0].(*StringObject) + }, + { + // Matches the receiver with a Regexp, and returns the number of matched strings. + // + // ```ruby + // "pizza" =~ Regex.new("zz") # => 2 + // "pizza" =~ Regex.new("OH!") # => nil + // ``` + // + // @param regexp [Regexp] + // @return [Integer] + Name: "=~", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + re, ok := args[0].(*RegexpObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.RegexpClass, args[0].Class().Name) + } - left := receiver.(*StringObject) - switch { - case left.value < right.value: - return t.vm.InitIntegerObject(-1) - case left.value > right.value: - return t.vm.InitIntegerObject(1) - default: - return t.vm.InitIntegerObject(0) - } + text := receiver.(*StringObject).value - }, - }, - { - // Returns a Boolean of compared two strings. - // - // ```ruby - // "first" != "second" # => true - // "two" != "two" # => false - // ``` - // - // @param object [Object] - // @return [Boolean] - Name: "!=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - right, ok := args[0].(*StringObject) - if !ok { - return TRUE - } + match, _ := re.regexp.FindStringMatch(text) + if match == nil { + return NULL + } - left := receiver.(*StringObject) - if left.value != right.value { - return TRUE - } + position := match.Groups()[0].Captures[0].Index - return FALSE + return t.vm.InitIntegerObject(position) - }, }, - { - // Returns the character of the string with specified index. - // Raises an error if the input is not an Integer type. - // - // ```ruby - // "Hello"[1] # => "e" - // "Hello"[5] # => nil - // "Hello\nWorld"[5] # => "\n" - // "Hello"[-1] # => "o" - // "Hello"[-6] # => nil - // "Hello😊"[5] # => "😊" - // "Hello😊"[-1] # => "😊" - // ``` - // - // @param index [Integer] - // @return [String] - Name: "[]", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Returns a Integer. + // Returns -1 if the first string is less than the second string returns -1, returns 0 if equal to, or returns 1 if greater than. + // + // + // ```ruby + // "abc" <=> "abcd" # => -1 + // "abc" <=> "abc" # => 0 + // "abcd" <=> "abc" # => 1 + // ``` + // + // @param string [String] + // @return [Integer] + Name: "<=>", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + + right, ok := args[0].(*StringObject) + + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } + + left := receiver.(*StringObject) + switch { + case left.value < right.value: + return t.vm.InitIntegerObject(-1) + case left.value > right.value: + return t.vm.InitIntegerObject(1) + default: + return t.vm.InitIntegerObject(0) + } - str := receiver.(*StringObject).value - i := args[0] + }, + }, + { + // Returns a Boolean of compared two strings. + // + // ```ruby + // "first" != "second" # => true + // "two" != "two" # => false + // ``` + // + // @param object [Object] + // @return [Boolean] + Name: "!=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - switch index := i.(type) { - case *IntegerObject: - indexValue := index.value + right, ok := args[0].(*StringObject) + if !ok { + return TRUE + } - if indexValue < 0 { - strLength := utf8.RuneCountInString(str) - if -indexValue > strLength { - return NULL - } - return t.vm.InitStringObject(string([]rune(str)[strLength+indexValue])) - } + left := receiver.(*StringObject) + if left.value != right.value { + return TRUE + } - if len(str) > indexValue { - return t.vm.InitStringObject(string([]rune(str)[indexValue])) - } - - return NULL - case *RangeObject: - strLength := utf8.RuneCountInString(str) - start := index.Start - end := index.End + return FALSE - if start < 0 { - start = strLength + start + }, + }, + { + // Returns the character of the string with specified index. + // Raises an error if the input is not an Integer type. + // + // ```ruby + // "Hello"[1] # => "e" + // "Hello"[5] # => nil + // "Hello\nWorld"[5] # => "\n" + // "Hello"[-1] # => "o" + // "Hello"[-6] # => nil + // "Hello😊"[5] # => "😊" + // "Hello😊"[-1] # => "😊" + // ``` + // + // @param index [Integer] + // @return [String] + Name: "[]", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - if start < 0 { - return NULL - } - } + str := receiver.(*StringObject).value + i := args[0] - if end < 0 { - end = strLength + end - } + switch index := i.(type) { + case *IntegerObject: + indexValue := index.value - if start > strLength { + if indexValue < 0 { + strLength := utf8.RuneCountInString(str) + if -indexValue > strLength { return NULL } - - if end >= strLength { - end = strLength - 1 - } - - return t.vm.InitStringObject(string([]rune(str)[start : end+1])) - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, i.Class().Name) - } - - }, - }, - { - // Replaces the receiver's string with input string. A destructive method. - // Raises an error if the index is not Integer type or the index value is out of - // range of the string length - // - // Currently only support assign string type value. - // TODO: Support to assign type which have to_s method - // - // ```ruby - // "Ruby"[1] = "oo" # => "Rooby" - // "Go"[2] = "by" # => "Goby" - // "Hello\nWorld"[5] = " " # => "Hello World" - // "Ruby"[-3] = "oo" # => "Rooby" - // "Hello😊"[5] = "🐟" # => "Hello🐟" - // ``` - // - // @param index [Integer] - // @return [String] - Name: "[]=", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) + return t.vm.InitStringObject(string([]rune(str)[strLength+indexValue])) } - index, ok := args[0].(*IntegerObject) - - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) + if len(str) > indexValue { + return t.vm.InitStringObject(string([]rune(str)[indexValue])) } - indexValue := index.value - str := receiver.(*StringObject).value + return NULL + case *RangeObject: strLength := utf8.RuneCountInString(str) + start := index.Start + end := index.End - if strLength < indexValue { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.IndexOutOfRange, strconv.Itoa(indexValue)) - } + if start < 0 { + start = strLength + start - replaceStr, ok := args[1].(*StringObject) + if start < 0 { + return NULL + } + } - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[1].Class().Name) + if end < 0 { + end = strLength + end } - replaceStrValue := replaceStr.value - // Negative Index Case - if indexValue < 0 { - if -indexValue > strLength { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.IndexOutOfRange, strconv.Itoa(indexValue)) - } - // Change to positive index to replace the string - indexValue += strLength + if start > strLength { + return NULL } - if strLength == indexValue { - return t.vm.InitStringObject(str + replaceStrValue) + if end >= strLength { + end = strLength - 1 } - // Using rune type to support UTF-8 encoding to replace character - result := string([]rune(str)[:indexValue]) + replaceStrValue + string([]rune(str)[indexValue+1:]) - return t.vm.InitStringObject(result) - }, - }, - { - // Returns a new String with the first character converted to uppercase. - // Non case-sensitive characters will be remained untouched. - // - // ```ruby - // "test".capitalize # => "Test" - // "tEST".capitalize # => "Test" - // "heLlo\nWoRLd".capitalize # => "Hello\nworld" - // "😊HeLlO🐟".capitalize # => "😊hello🐟" - // ``` - // - // @return [String] - Name: "capitalize", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - str := receiver.(*StringObject).value - start := string([]rune(str)[0]) - rest := string([]rune(str)[1:]) - result := strings.ToUpper(start) + strings.ToLower(rest) - - return t.vm.InitStringObject(result) - - }, + return t.vm.InitStringObject(string([]rune(str)[start : end+1])) + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, i.Class().Name) + } + }, - { - // Returns a string with the last character chopped. - // - // ```ruby - // "Hello".chop # => "Hell" - // "Hello World\n".chop # => "Hello World" - // "Hello😊".chop # => "Hello" - // ``` - // - // @return [String] - Name: "chop", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - str := receiver.(*StringObject).value - strLength := utf8.RuneCountInString(str) + }, + { + // Replaces the receiver's string with input string. A destructive method. + // Raises an error if the index is not Integer type or the index value is out of + // range of the string length + // + // Currently only support assign string type value. + // TODO: Support to assign type which have to_s method + // + // ```ruby + // "Ruby"[1] = "oo" # => "Rooby" + // "Go"[2] = "by" # => "Goby" + // "Hello\nWorld"[5] = " " # => "Hello World" + // "Ruby"[-3] = "oo" # => "Rooby" + // "Hello😊"[5] = "🐟" # => "Hello🐟" + // ``` + // + // @param index [Integer] + // @return [String] + Name: "[]=", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) + } - // Support UTF-8 Encoding - return t.vm.InitStringObject(string([]rune(str)[:strLength-1])) + index, ok := args[0].(*IntegerObject) - }, - }, - { - // Returns a string which is concatenate with the input string or character. - // - // ```ruby - // "Hello ".concat("World") # => "Hello World" - // "Hello World".concat("😊") # => "Hello World😊" - // ``` - // - // @param string [String] - // @return [String] - Name: "concat", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name) + } - concatStr, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + indexValue := index.value + str := receiver.(*StringObject).value + strLength := utf8.RuneCountInString(str) - str := receiver.(*StringObject).value - return t.vm.InitStringObject(str + concatStr.value) + if strLength < indexValue { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.IndexOutOfRange, strconv.Itoa(indexValue)) + } - }, - }, - { - // Returns the integer that count the string chars as UTF-8. - // - // ```ruby - // "abcde".count # => 5 - // "ε“ˆε›‰οΌδΈ–η•ŒοΌ".count # => 6 - // "Hello\nWorld".count # => 11 - // "Hello\nWorld😊".count # => 12 - // ``` - // - // @return [Integer] - Name: "count", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - str := receiver.(*StringObject).value - - // Support UTF-8 Encoding - return t.vm.InitIntegerObject(utf8.RuneCountInString(str)) - - }, - }, - { - // Returns a string which is being partially deleted with specified values. - // - // ```ruby - // "Hello hello HeLlo".delete("el") # => "Hlo hlo HeLlo" - // "Hello 😊 Hello 😊 Hello".delete("😊") # => "Hello Hello Hello" - // # TODO: Handle delete intersection of multiple strings' input case - // "Hello hello HeLlo".delete("el", "e") # => "Hllo hllo HLlo" - // ``` - // - // @param string [String] - // @return [String] - Name: "delete", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + replaceStr, ok := args[1].(*StringObject) - deleteStr, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[1].Class().Name) + } + replaceStrValue := replaceStr.value - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + // Negative Index Case + if indexValue < 0 { + if -indexValue > strLength { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.IndexOutOfRange, strconv.Itoa(indexValue)) } + // Change to positive index to replace the string + indexValue += strLength + } - str := receiver.(*StringObject).value - return t.vm.InitStringObject(strings.Replace(str, deleteStr.value, "", -1)) + if strLength == indexValue { + return t.vm.InitStringObject(str + replaceStrValue) + } + // Using rune type to support UTF-8 encoding to replace character + result := string([]rune(str)[:indexValue]) + replaceStrValue + string([]rune(str)[indexValue+1:]) + return t.vm.InitStringObject(result) - }, }, - { - // Returns a new String with all characters is lowercase. - // - // ```ruby - // "erROR".downcase # => "error" - // "HeLlO\tWorLD".downcase # => "hello\tworld" - // ``` - // - // @return [String] - Name: "downcase", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - str := receiver.(*StringObject).value - - return t.vm.InitStringObject(strings.ToLower(str)) - - }, - }, - { - // Split and loop through the string byte. - // - // ```ruby - // "Sushi 🍣".each_byte do |byte| - // puts byte - // end - // # => 83 # "S" - // # => 117 # "u" - // # => 115 # "s" - // # => 104 # "h" - // # => 105 # "i" - // # => 32 # " " - // # => 240 # "\xF0" - // # => 159 # "\x9F" - // # => 141 # "\x8D" - // # => 163 # "\xA3" - // ``` - // - // @return [String] - Name: "each_byte", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns a new String with the first character converted to uppercase. + // Non case-sensitive characters will be remained untouched. + // + // ```ruby + // "test".capitalize # => "Test" + // "tEST".capitalize # => "Test" + // "heLlo\nWoRLd".capitalize # => "Hello\nworld" + // "😊HeLlO🐟".capitalize # => "😊hello🐟" + // ``` + // + // @return [String] + Name: "capitalize", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } + str := receiver.(*StringObject).value + start := string([]rune(str)[0]) + rest := string([]rune(str)[1:]) + result := strings.ToUpper(start) + strings.ToLower(rest) - str := receiver.(*StringObject).value - if blockIsEmpty(blockFrame) { - return t.vm.InitStringObject(str) - } + return t.vm.InitStringObject(result) - for _, byte := range []byte(str) { - t.builtinMethodYield(blockFrame, t.vm.InitIntegerObject(int(byte))) - } + }, + }, + { + // Returns a string with the last character chopped. + // + // ```ruby + // "Hello".chop # => "Hell" + // "Hello World\n".chop # => "Hello World" + // "Hello😊".chop # => "Hello" + // ``` + // + // @return [String] + Name: "chop", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitStringObject(str) + str := receiver.(*StringObject).value + strLength := utf8.RuneCountInString(str) + + // Support UTF-8 Encoding + return t.vm.InitStringObject(string([]rune(str)[:strLength-1])) - }, }, - { - // Split and loop through the string characters. - // - // ```ruby - // "Sushi 🍣".each_char do |char| - // puts char - // end - // # => "S" - // # => "u" - // # => "s" - // # => "h" - // # => "i" - // # => " " - // # => "🍣" - // ``` - // - // @return [String] - Name: "each_char", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns a string which is concatenate with the input string or character. + // + // ```ruby + // "Hello ".concat("World") # => "Hello World" + // "Hello World".concat("😊") # => "Hello World😊" + // ``` + // + // @param string [String] + // @return [String] + Name: "concat", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } + concatStr, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - str := receiver.(*StringObject).value - if blockIsEmpty(blockFrame) { - return t.vm.InitStringObject(str) - } + str := receiver.(*StringObject).value + return t.vm.InitStringObject(str + concatStr.value) - for _, char := range []rune(str) { - t.builtinMethodYield(blockFrame, t.vm.InitStringObject(string(char))) - } + }, + }, + { + // Returns the integer that count the string chars as UTF-8. + // + // ```ruby + // "abcde".count # => 5 + // "ε“ˆε›‰οΌδΈ–η•ŒοΌ".count # => 6 + // "Hello\nWorld".count # => 11 + // "Hello\nWorld😊".count # => 12 + // ``` + // + // @return [Integer] + Name: "count", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - return t.vm.InitStringObject(str) + str := receiver.(*StringObject).value - }, - }, - { - // Split and loop through the string segment split by the newline escaped character. - // - // ```ruby - // "Hello\nWorld\nGoby".each_line do |line| - // puts line - // end - // # => "Hello" - // # => "World" - // # => "Goby" - // ``` - // - // @return [String] - Name: "each_line", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + // Support UTF-8 Encoding + return t.vm.InitIntegerObject(utf8.RuneCountInString(str)) - if blockFrame == nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) - } + }, + }, + { + // Returns a string which is being partially deleted with specified values. + // + // ```ruby + // "Hello hello HeLlo".delete("el") # => "Hlo hlo HeLlo" + // "Hello 😊 Hello 😊 Hello".delete("😊") # => "Hello Hello Hello" + // # TODO: Handle delete intersection of multiple strings' input case + // "Hello hello HeLlo".delete("el", "e") # => "Hllo hllo HLlo" + // ``` + // + // @param string [String] + // @return [String] + Name: "delete", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - str := receiver.(*StringObject).value - if blockIsEmpty(blockFrame) { - return t.vm.InitStringObject(str) - } - lineArray := strings.Split(str, "\n") + deleteStr, ok := args[0].(*StringObject) - for _, line := range lineArray { - t.builtinMethodYield(blockFrame, t.vm.InitStringObject(line)) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - return t.vm.InitStringObject(str) + str := receiver.(*StringObject).value + return t.vm.InitStringObject(strings.Replace(str, deleteStr.value, "", -1)) - }, }, - { - // Returns true if string is empty value. - // - // ```ruby - // "".empty? # => true - // "Hello".empty? # => false - // ``` - // - // @return [Boolean] - Name: "empty?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - str := receiver.(*StringObject).value - - if str == "" { - return TRUE - } - return FALSE + }, + { + // Returns a new String with all characters is lowercase. + // + // ```ruby + // "erROR".downcase # => "error" + // "HeLlO\tWorLD".downcase # => "hello\tworld" + // ``` + // + // @return [String] + Name: "downcase", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - }, - }, - { - // Returns true if receiver string end with the argument string - // - // ```ruby - // "Hello".end_with?("llo") # => true - // "Hello".end_with?("ell") # => false - // "😊Hello🐟".end_with?("🐟") # => true - // "😊Hello🐟".end_with?("😊") # => false - // ``` - // - // @return [Boolean] - Name: "end_with?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + str := receiver.(*StringObject).value - compareStr, ok := args[0].(*StringObject) + return t.vm.InitStringObject(strings.ToLower(str)) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + }, + }, + { + // Split and loop through the string byte. + // + // ```ruby + // "Sushi 🍣".each_byte do |byte| + // puts byte + // end + // # => 83 # "S" + // # => 117 # "u" + // # => 115 # "s" + // # => 104 # "h" + // # => 105 # "i" + // # => 32 # " " + // # => 240 # "\xF0" + // # => 159 # "\x9F" + // # => 141 # "\x8D" + // # => 163 # "\xA3" + // ``` + // + // @return [String] + Name: "each_byte", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + str := receiver.(*StringObject).value + if blockIsEmpty(blockFrame) { + return t.vm.InitStringObject(str) + } - compareStrValue := compareStr.value - compareStrLength := utf8.RuneCountInString(compareStrValue) + for _, byte := range []byte(str) { + t.builtinMethodYield(blockFrame, t.vm.InitIntegerObject(int(byte))) + } - str := receiver.(*StringObject).value - strLength := utf8.RuneCountInString(str) + return t.vm.InitStringObject(str) - if compareStrLength > strLength { - return FALSE - } + }, + }, + { + // Split and loop through the string characters. + // + // ```ruby + // "Sushi 🍣".each_char do |char| + // puts char + // end + // # => "S" + // # => "u" + // # => "s" + // # => "h" + // # => "i" + // # => " " + // # => "🍣" + // ``` + // + // @return [String] + Name: "each_char", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + str := receiver.(*StringObject).value + if blockIsEmpty(blockFrame) { + return t.vm.InitStringObject(str) + } - if compareStrValue == string([]rune(str)[strLength-compareStrLength:]) { - return TRUE - } - return FALSE + for _, char := range []rune(str) { + t.builtinMethodYield(blockFrame, t.vm.InitStringObject(string(char))) + } + + return t.vm.InitStringObject(str) - }, }, - { - // Returns true if receiver string is equal to argument string. - // - // ```ruby - // "Hello".eql?("Hello") # => true - // "Hello".eql?("World") # => false - // "Hello😊".eql?("Hello😊") # => true - // "Hello😊".eql?(1) # => false - // ``` - // - // @param object [Object] - // @return [Boolean] - Name: "eql?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Split and loop through the string segment split by the newline escaped character. + // + // ```ruby + // "Hello\nWorld\nGoby".each_line do |line| + // puts line + // end + // # => "Hello" + // # => "World" + // # => "Goby" + // ``` + // + // @return [String] + Name: "each_line", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + if blockFrame == nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat) + } + + str := receiver.(*StringObject).value + if blockIsEmpty(blockFrame) { + return t.vm.InitStringObject(str) + } + lineArray := strings.Split(str, "\n") - str := receiver.(*StringObject).value - compareStr, ok := args[0].(*StringObject) + for _, line := range lineArray { + t.builtinMethodYield(blockFrame, t.vm.InitStringObject(line)) + } - if !ok { - return FALSE - } else if compareStr.value == str { - return TRUE - } - return FALSE + return t.vm.InitStringObject(str) - }, }, - { - // Checks if the specified string is included in the receiver. - // - // ```ruby - // "Hello\nWorld".include?("\n") # => true - // "Hello 😊 Hello".include?("😊") # => true - // ``` - // - // @param string [String] - // @return [Bool] - Name: "include?", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - includeStr, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + }, + { + // Returns true if string is empty value. + // + // ```ruby + // "".empty? # => true + // "Hello".empty? # => false + // ``` + // + // @return [Boolean] + Name: "empty?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - str := receiver.(*StringObject).value - if strings.Contains(str, includeStr.value) { - return TRUE - } + str := receiver.(*StringObject).value - return FALSE + if str == "" { + return TRUE + } + return FALSE - }, }, - { - // Insert a string input in specified index value of the receiver string. - // - // It will raise error if index value is not an integer or index value is out - // of receiver string's range. - // - // It will also raise error if the input string value is not type string. - // - // ```ruby - // "Hello".insert(0, "X") # => "XHello" - // "Hello".insert(2, "X") # => "HeXllo" - // "Hello".insert(5, "X") # => "HelloX" - // "Hello".insert(-1, "X") # => "HelloX" - // "Hello".insert(-3, "X") # => "HelXlo" - // ``` - // - // @param index [Integer], string [String] - // @return [String] - Name: "insert", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) - } + }, + { + // Returns true if receiver string end with the argument string + // + // ```ruby + // "Hello".end_with?("llo") # => true + // "Hello".end_with?("ell") # => false + // "😊Hello🐟".end_with?("🐟") # => true + // "😊Hello🐟".end_with?("😊") # => false + // ``` + // + // @return [Boolean] + Name: "end_with?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - index, ok := args[0].(*IntegerObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.IntegerClass, args[0].Class().Name) - } + compareStr, ok := args[0].(*StringObject) - insertStr, ok := args[1].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, args[1].Class().Name) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - indexValue := index.value - str := receiver.(*StringObject).value - strLength := utf8.RuneCountInString(str) + compareStrValue := compareStr.value + compareStrLength := utf8.RuneCountInString(compareStrValue) - if indexValue < 0 { - if -indexValue > strLength+1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.IndexOutOfRange, indexValue) - } else if -indexValue == strLength+1 { - return t.vm.InitStringObject(insertStr.value + str) - } - // Change it to positive index value to replace the string via index - indexValue += strLength - } + str := receiver.(*StringObject).value + strLength := utf8.RuneCountInString(str) - if strLength < indexValue { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.IndexOutOfRange, indexValue) - } + if compareStrLength > strLength { + return FALSE + } - // Support UTF-8 Encoding - return t.vm.InitStringObject(string([]rune(str)[:indexValue]) + insertStr.value + string([]rune(str)[indexValue:])) + if compareStrValue == string([]rune(str)[strLength-compareStrLength:]) { + return TRUE + } + return FALSE - }, }, - { - // Returns the character length of self. - // - // ```ruby - // "zero".length # => 4 - // "".length # => 0 - // "😊".length # => 1 - // ``` - // - // @return [Integer] - Name: "length", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - str := receiver.(*StringObject).value - - // Support UTF-8 Encoding - return t.vm.InitIntegerObject(utf8.RuneCountInString(str)) - - }, + }, + { + // Returns true if receiver string is equal to argument string. + // + // ```ruby + // "Hello".eql?("Hello") # => true + // "Hello".eql?("World") # => false + // "Hello😊".eql?("Hello😊") # => true + // "Hello😊".eql?(1) # => false + // ``` + // + // @param object [Object] + // @return [Boolean] + Name: "eql?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + str := receiver.(*StringObject).value + compareStr, ok := args[0].(*StringObject) + + if !ok { + return FALSE + } else if compareStr.value == str { + return TRUE + } + return FALSE + }, - { - // Add padding strings to the right side of the string to be "left-justification" with the specified length. - // If the padding is omitted, one space character " " will be the default padding. - // - // If the specified length is equal to or shorter than the current length, no padding will be performed, and the receiver will be returned. - // If the padding is performed, a new padded string will be returned. - // - // Raises an error if the input string length is not integer type. - // - // ```ruby - // "Hello".ljust(2) # => "Hello" - // "Hello".ljust(7) # => "Hello " - // "Hello".ljust(10, "xo") # => "Helloxoxox" - // "Hello".ljust(10, "😊🐟") # => "Hello😊🐟😊🐟😊" - // ``` - // @param length [Integer], padding [String] - // @return [String] - Name: "ljust", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen < 1 || aLen > 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 1, 2, aLen) + }, + { + // Checks if the specified string is included in the receiver. + // + // ```ruby + // "Hello\nWorld".include?("\n") # => true + // "Hello 😊 Hello".include?("😊") # => true + // ``` + // + // @param string [String] + // @return [Bool] + Name: "include?", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + includeStr, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } + + str := receiver.(*StringObject).value + if strings.Contains(str, includeStr.value) { + return TRUE + } + + return FALSE + + }, + }, + { + // Insert a string input in specified index value of the receiver string. + // + // It will raise error if index value is not an integer or index value is out + // of receiver string's range. + // + // It will also raise error if the input string value is not type string. + // + // ```ruby + // "Hello".insert(0, "X") # => "XHello" + // "Hello".insert(2, "X") # => "HeXllo" + // "Hello".insert(5, "X") # => "HelloX" + // "Hello".insert(-1, "X") # => "HelloX" + // "Hello".insert(-3, "X") # => "HelXlo" + // ``` + // + // @param index [Integer], string [String] + // @return [String] + Name: "insert", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) + } + + index, ok := args[0].(*IntegerObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.IntegerClass, args[0].Class().Name) + } + + insertStr, ok := args[1].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, args[1].Class().Name) + } + + indexValue := index.value + str := receiver.(*StringObject).value + strLength := utf8.RuneCountInString(str) + + if indexValue < 0 { + if -indexValue > strLength+1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.IndexOutOfRange, indexValue) + } else if -indexValue == strLength+1 { + return t.vm.InitStringObject(insertStr.value + str) } + // Change it to positive index value to replace the string via index + indexValue += strLength + } - strLength, ok := args[0].(*IntegerObject) + if strLength < indexValue { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.IndexOutOfRange, indexValue) + } - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.IntegerClass, args[0].Class().Name) - } + // Support UTF-8 Encoding + return t.vm.InitStringObject(string([]rune(str)[:indexValue]) + insertStr.value + string([]rune(str)[indexValue:])) - strLengthValue := strLength.value + }, + }, + { + // Returns the character length of self. + // + // ```ruby + // "zero".length # => 4 + // "".length # => 0 + // "😊".length # => 1 + // ``` + // + // @return [Integer] + Name: "length", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - var padStrValue string - if aLen == 1 { - padStrValue = " " - } else { - p := args[1] - padStr, ok := p.(*StringObject) + str := receiver.(*StringObject).value - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, p.Class().Name) - } + // Support UTF-8 Encoding + return t.vm.InitIntegerObject(utf8.RuneCountInString(str)) - padStrValue = padStr.value - } + }, + }, + { + // Add padding strings to the right side of the string to be "left-justification" with the specified length. + // If the padding is omitted, one space character " " will be the default padding. + // + // If the specified length is equal to or shorter than the current length, no padding will be performed, and the receiver will be returned. + // If the padding is performed, a new padded string will be returned. + // + // Raises an error if the input string length is not integer type. + // + // ```ruby + // "Hello".ljust(2) # => "Hello" + // "Hello".ljust(7) # => "Hello " + // "Hello".ljust(10, "xo") # => "Helloxoxox" + // "Hello".ljust(10, "😊🐟") # => "Hello😊🐟😊🐟😊" + // ``` + // @param length [Integer], padding [String] + // @return [String] + Name: "ljust", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen < 1 || aLen > 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 1, 2, aLen) + } - str := receiver.(*StringObject).value - currentStrLength := utf8.RuneCountInString(str) - padStrLength := utf8.RuneCountInString(padStrValue) + strLength, ok := args[0].(*IntegerObject) - if strLengthValue > currentStrLength { - for i := currentStrLength; i < strLengthValue; i += padStrLength { - str += padStrValue - } - str = string([]rune(str)[:strLengthValue]) - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.IntegerClass, args[0].Class().Name) + } - // Support UTF-8 Encoding - return t.vm.InitStringObject(str) + strLengthValue := strLength.value - }, - }, - { - // Returns the matched data of the regex with the receiver's string. - // - // ```ruby - // 'pow'.match(Regexp.new("o")) # => # - // 'pow'.match(Regexp.new("x")) # => nil - // ``` - // - // @param regexp [Regexp] - // @return [MatchData] - Name: "match", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } - - arg := args[0] - regexpObj, ok := arg.(*RegexpObject) + var padStrValue string + if aLen == 1 { + padStrValue = " " + } else { + p := args[1] + padStr, ok := p.(*StringObject) if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.RegexpClass, args[0].Class().Name) + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, p.Class().Name) } - re := regexpObj.regexp - text := receiver.(*StringObject).value + padStrValue = padStr.value + } - match, _ := re.FindStringMatch(text) + str := receiver.(*StringObject).value + currentStrLength := utf8.RuneCountInString(str) + padStrLength := utf8.RuneCountInString(padStrValue) - if match == nil { - return NULL + if strLengthValue > currentStrLength { + for i := currentStrLength; i < strLengthValue; i += padStrLength { + str += padStrValue } + str = string([]rune(str)[:strLengthValue]) + } - return t.vm.initMatchDataObject(match, re.String(), text) + // Support UTF-8 Encoding + return t.vm.InitStringObject(str) - }, }, - { - // Returns a copy of str with the all occurrences of pattern substituted for the second argument. - // The pattern is typically a String or Regexp; if given as a String, any - // regular expression metacharacters it contains will be interpreted literally, e.g. '\\d' will - // match a backslash followed by β€˜d’, instead of a digit. - // - // `#replace` is equivalent to Ruby's `gsub`. - // ```ruby - // "Ruby Lang".replace("Ru", "Go") # => "Goby Lang" - // "Hello 😊 Hello 😊 Hello".replace("😊", "🐟") # => "Hello 🐟 Hello 🐟 Hello" - // - // re = Regexp.new("(Ru|ru)") - // "Ruby Lang".replace(re, "Go") # => "Goby Lang" - // ``` - // - // @param pattern [Regexp/String], [String] the new string - // @return [String] - Name: "replace", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) - } - - r := args[1] - replacement, ok := r.(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, args[1].Class().Name) - } + }, + { + // Returns the matched data of the regex with the receiver's string. + // + // ```ruby + // 'pow'.match(Regexp.new("o")) # => # + // 'pow'.match(Regexp.new("x")) # => nil + // ``` + // + // @param regexp [Regexp] + // @return [MatchData] + Name: "match", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - var result string - var err error - target := receiver.(*StringObject).value - switch pattern := args[0].(type) { - case *StringObject: - result = strings.Replace(target, pattern.value, replacement.value, -1) - case *RegexpObject: - result, err = pattern.regexp.Replace(target, replacement.value, 0, -1) - if err != nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.RegexpFailure, args[0].Class().Name) - } - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.StringClass+" or "+classes.RegexpClass, args[0].Class().Name) - } + arg := args[0] + regexpObj, ok := arg.(*RegexpObject) - return t.vm.InitStringObject(result) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.RegexpClass, args[0].Class().Name) + } - }, - }, - { - // Returns a copy of string that substituted once with the pattern for the second argument. - // The pattern is typically a String or Regexp; if given as a String, any - // regular expression metacharacters it contains will be interpreted literally, e.g. '\\d' will - // match a backslash followed by β€˜d’, instead of a digit. - // - // ```ruby - // "Ruby Lang Ruby lang".replace_once("Ru", "Go") # => "Goby Lang Ruby Lang" - // - // re = Regexp.new("(Ru|ru)") - // "Ruby Lang ruby lang".replace_once(re, "Go") # => "Goby Lang ruby lang" - // ``` - // - // @param pattern [Regexp/String], [String] the new string - // @return [String] - Name: "replace_once", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) - } - - r := args[1] - replacement, ok := r.(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, args[1].Class().Name) - } + re := regexpObj.regexp + text := receiver.(*StringObject).value - var result string - var err error - target := receiver.(*StringObject).value - switch pattern := args[0].(type) { - case *StringObject: - result = strings.Replace(target, pattern.value, replacement.value, 1) - case *RegexpObject: - result, err = pattern.regexp.Replace(target, replacement.value, 0, 1) - if err != nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.RegexpFailure, args[0].Class().Name) - } - default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.StringClass+" or "+classes.RegexpClass, args[0].Class().Name) - } + match, _ := re.FindStringMatch(text) + + if match == nil { + return NULL + } - return t.vm.InitStringObject(result) + return t.vm.initMatchDataObject(match, re.String(), text) - }, }, - { - // Returns a new String with reverse order of self. - // - // ```ruby - // "reverse".reverse # => "esrever" - // "Hello\nWorld".reverse # => "dlroW\nolleH" - // "Hello 😊🐟 World".reverse # => "dlroW 🐟😊 olleH" - // ``` - // - // @return [String] - Name: "reverse", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - str := receiver.(*StringObject).value - - var revert string - for i := utf8.RuneCountInString(str) - 1; i >= 0; i-- { - revert += string([]rune(str)[i]) - } + }, + { + // Returns a copy of str with the all occurrences of pattern substituted for the second argument. + // The pattern is typically a String or Regexp; if given as a String, any + // regular expression metacharacters it contains will be interpreted literally, e.g. '\\d' will + // match a backslash followed by β€˜d’, instead of a digit. + // + // `#replace` is equivalent to Ruby's `gsub`. + // ```ruby + // "Ruby Lang".replace("Ru", "Go") # => "Goby Lang" + // "Hello 😊 Hello 😊 Hello".replace("😊", "🐟") # => "Hello 🐟 Hello 🐟 Hello" + // + // re = Regexp.new("(Ru|ru)") + // "Ruby Lang".replace(re, "Go") # => "Goby Lang" + // ``` + // + // @param pattern [Regexp/String], [String] the new string + // @return [String] + Name: "replace", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) + } + + r := args[1] + replacement, ok := r.(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, args[1].Class().Name) + } + + var result string + var err error + target := receiver.(*StringObject).value + switch pattern := args[0].(type) { + case *StringObject: + result = strings.Replace(target, pattern.value, replacement.value, -1) + case *RegexpObject: + result, err = pattern.regexp.Replace(target, replacement.value, 0, -1) + if err != nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.RegexpFailure, args[0].Class().Name) + } + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.StringClass+" or "+classes.RegexpClass, args[0].Class().Name) + } + + return t.vm.InitStringObject(result) - // Support UTF-8 Encoding - return t.vm.InitStringObject(revert) + }, + }, + { + // Returns a copy of string that substituted once with the pattern for the second argument. + // The pattern is typically a String or Regexp; if given as a String, any + // regular expression metacharacters it contains will be interpreted literally, e.g. '\\d' will + // match a backslash followed by β€˜d’, instead of a digit. + // + // ```ruby + // "Ruby Lang Ruby lang".replace_once("Ru", "Go") # => "Goby Lang Ruby Lang" + // + // re = Regexp.new("(Ru|ru)") + // "Ruby Lang ruby lang".replace_once(re, "Go") # => "Goby Lang ruby lang" + // ``` + // + // @param pattern [Regexp/String], [String] the new string + // @return [String] + Name: "replace_once", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 2, len(args)) + } + + r := args[1] + replacement, ok := r.(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, args[1].Class().Name) + } + + var result string + var err error + target := receiver.(*StringObject).value + switch pattern := args[0].(type) { + case *StringObject: + result = strings.Replace(target, pattern.value, replacement.value, 1) + case *RegexpObject: + result, err = pattern.regexp.Replace(target, replacement.value, 0, 1) + if err != nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, errors.RegexpFailure, args[0].Class().Name) + } + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.StringClass+" or "+classes.RegexpClass, args[0].Class().Name) + } + + return t.vm.InitStringObject(result) - }, }, - { - // Add padding strings to the left side of the string to be "right-justification" with the specified length. - // If the padding is omitted, one space character " " will be the default padding. - // - // If the specified length is equal to or shorter than the current length, no padding will be performed, and the receiver will be returned. - // If the padding is performed, a new padded string will be returned. - // - // Raises an error if the input string length is not integer type. - // - // ```ruby - // "Hello".rjust(2) # => "Hello" - // "Hello".rjust(7) # => " Hello" - // "Hello".rjust(10, "xo") # => "xoxoxHello" - // "Hello".rjust(10, "😊🐟") # => "😊🐟😊🐟😊Hello" - // ``` - // - // @param length [Integer], padding [String] - // @return [String] - Name: "rjust", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - aLen := len(args) - if aLen < 1 || aLen > 2 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 1, 2, aLen) - } + }, + { + // Returns a new String with reverse order of self. + // + // ```ruby + // "reverse".reverse # => "esrever" + // "Hello\nWorld".reverse # => "dlroW\nolleH" + // "Hello 😊🐟 World".reverse # => "dlroW 🐟😊 olleH" + // ``` + // + // @return [String] + Name: "reverse", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - strLength, ok := args[0].(*IntegerObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.IntegerClass, args[0].Class().Name) - } + str := receiver.(*StringObject).value - strLengthValue := strLength.value + var revert string + for i := utf8.RuneCountInString(str) - 1; i >= 0; i-- { + revert += string([]rune(str)[i]) + } - var padStrValue string - if aLen == 1 { - padStrValue = " " - } else { - p := args[1] - padStr, ok := p.(*StringObject) + // Support UTF-8 Encoding + return t.vm.InitStringObject(revert) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, args[1].Class().Name) - } + }, + }, + { + // Add padding strings to the left side of the string to be "right-justification" with the specified length. + // If the padding is omitted, one space character " " will be the default padding. + // + // If the specified length is equal to or shorter than the current length, no padding will be performed, and the receiver will be returned. + // If the padding is performed, a new padded string will be returned. + // + // Raises an error if the input string length is not integer type. + // + // ```ruby + // "Hello".rjust(2) # => "Hello" + // "Hello".rjust(7) # => " Hello" + // "Hello".rjust(10, "xo") # => "xoxoxHello" + // "Hello".rjust(10, "😊🐟") # => "😊🐟😊🐟😊Hello" + // ``` + // + // @param length [Integer], padding [String] + // @return [String] + Name: "rjust", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + aLen := len(args) + if aLen < 1 || aLen > 2 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentRange, 1, 2, aLen) + } + + strLength, ok := args[0].(*IntegerObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 1, classes.IntegerClass, args[0].Class().Name) + } + + strLengthValue := strLength.value + + var padStrValue string + if aLen == 1 { + padStrValue = " " + } else { + p := args[1] + padStr, ok := p.(*StringObject) - padStrValue = padStr.value + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, classes.StringClass, args[1].Class().Name) } - padStrLength := utf8.RuneCountInString(padStrValue) + padStrValue = padStr.value + } - str := receiver.(*StringObject).value - if strLengthValue > len(str) { - origin := str - originStrLength := utf8.RuneCountInString(origin) - for i := originStrLength; i < strLengthValue; i += padStrLength { - str = padStrValue + str - } - currentStrLength := utf8.RuneCountInString(str) - if currentStrLength > strLengthValue { - chopLength := currentStrLength - strLengthValue - str = string([]rune(str)[:currentStrLength-originStrLength-chopLength]) + origin - } + padStrLength := utf8.RuneCountInString(padStrValue) + + str := receiver.(*StringObject).value + if strLengthValue > len(str) { + origin := str + originStrLength := utf8.RuneCountInString(origin) + for i := originStrLength; i < strLengthValue; i += padStrLength { + str = padStrValue + str + } + currentStrLength := utf8.RuneCountInString(str) + if currentStrLength > strLengthValue { + chopLength := currentStrLength - strLengthValue + str = string([]rune(str)[:currentStrLength-originStrLength-chopLength]) + origin } + } - // Support UTF-8 Encoding - return t.vm.InitStringObject(str) + // Support UTF-8 Encoding + return t.vm.InitStringObject(str) - }, }, - { - // Returns the character length of self. - // - // ```ruby - // "zero".size # => 4 - // "".size # => 0 - // "😊".size # => 1 - // ``` - // - // @return [Integer] - Name: "size", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - str := receiver.(*StringObject).value - - // Support UTF-8 Encoding - return t.vm.InitIntegerObject(utf8.RuneCountInString(str)) - - }, - }, - { - // Returns a string sliced according to the input range. - // - // ```ruby - // "Hello World".slice(1..6) # => "ello W" - // "1234567890".slice(6..1) # => "" - // "1234567890".slice(11..1) # => nil - // "1234567890".slice(11..-1) # => nil - // "1234567890".slice(-10..1) # => "12" - // "1234567890".slice(-5..1) # => "" - // "1234567890".slice(-10..-1) # => "1234567890" - // "1234567890".slice(-10..-11) # => "" - // "1234567890".slice(1..-1) # => "234567890" - // "1234567890".slice(1..-1234) # => "" - // "1234567890".slice(-11..5) # => nil - // "1234567890".slice(-10..-5) # => "123456" - // "1234567890".slice(-5..-10) # => "" - // "1234567890".slice(-11..-12) # => nil - // "1234567890".slice(-10..-12) # => "" - // "Hello 😊🐟 World".slice(1..6) # => "ello 😊" - // "Hello 😊🐟 World".slice(-10..7) # => "o 😊🐟" - // "Hello 😊🐟 World".slice(1..-1) # => "ello 😊🐟 World" - // "Hello 😊🐟 World".slice(-12..-5) # => "llo 😊🐟 W" - // "Hello World".slice(4) # => "o" - // "Hello\nWorld".slice(6) # => "\n" - // "Hello World".slice(-3) # => "r" - // "Hello World".slice(-11) # => "H" - // "Hello World".slice(-12) # => nil - // "Hello World".slice(11) # => nil - // "Hello World".slice(4) # => "o" - // "Hello 😊🐟 World".slice(6) # => "😊" - // "Hello 😊🐟 World".slice(-7) # => "🐟" - // "Hello 😊🐟 World".slice(-10) # => "o" - // "Hello 😊🐟 World".slice(-15) # => nil - // "Hello 😊🐟 World".slice(14) # => nil - // ``` - // - // @param slicing point or range [Integer/Range] - // @return [String] - Name: "slice", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + }, + { + // Returns the character length of self. + // + // ```ruby + // "zero".size # => 4 + // "".size # => 0 + // "😊".size # => 1 + // ``` + // + // @return [Integer] + Name: "size", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - str := receiver.(*StringObject).value - strLength := utf8.RuneCountInString(str) + str := receiver.(*StringObject).value - // All Case Support UTF-8 Encoding - slice := args[0] - switch slice.(type) { - case *RangeObject: - ro := slice.(*RangeObject) - switch { - case ro.Start >= 0 && ro.End >= 0: - if ro.Start > strLength { - return NULL - } else if ro.Start > ro.End { - return t.vm.InitStringObject("") - } - return t.vm.InitStringObject(string([]rune(str)[ro.Start : ro.End+1])) - case ro.Start < 0 && ro.End >= 0: - positiveStart := strLength + ro.Start - if -ro.Start > strLength { - return NULL - } else if positiveStart > ro.End { - return t.vm.InitStringObject("") - } - return t.vm.InitStringObject(string([]rune(str)[positiveStart : ro.End+1])) - case ro.Start >= 0 && ro.End < 0: - positiveEnd := strLength + ro.End - if ro.Start > strLength { - return NULL - } else if positiveEnd < 0 || ro.Start > positiveEnd { - return t.vm.InitStringObject("") - } - return t.vm.InitStringObject(string([]rune(str)[ro.Start : positiveEnd+1])) - default: - positiveStart := strLength + ro.Start - positiveEnd := strLength + ro.End - if positiveStart < 0 { - return NULL - } else if positiveStart > positiveEnd { - return t.vm.InitStringObject("") - } - return t.vm.InitStringObject(string([]rune(str)[positiveStart : positiveEnd+1])) - } + // Support UTF-8 Encoding + return t.vm.InitIntegerObject(utf8.RuneCountInString(str)) - case *IntegerObject: - iv := slice.(*IntegerObject).value - if iv < 0 { - if -iv > strLength { - return NULL - } - return t.vm.InitStringObject(string([]rune(str)[strLength+iv])) + }, + }, + { + // Returns a string sliced according to the input range. + // + // ```ruby + // "Hello World".slice(1..6) # => "ello W" + // "1234567890".slice(6..1) # => "" + // "1234567890".slice(11..1) # => nil + // "1234567890".slice(11..-1) # => nil + // "1234567890".slice(-10..1) # => "12" + // "1234567890".slice(-5..1) # => "" + // "1234567890".slice(-10..-1) # => "1234567890" + // "1234567890".slice(-10..-11) # => "" + // "1234567890".slice(1..-1) # => "234567890" + // "1234567890".slice(1..-1234) # => "" + // "1234567890".slice(-11..5) # => nil + // "1234567890".slice(-10..-5) # => "123456" + // "1234567890".slice(-5..-10) # => "" + // "1234567890".slice(-11..-12) # => nil + // "1234567890".slice(-10..-12) # => "" + // "Hello 😊🐟 World".slice(1..6) # => "ello 😊" + // "Hello 😊🐟 World".slice(-10..7) # => "o 😊🐟" + // "Hello 😊🐟 World".slice(1..-1) # => "ello 😊🐟 World" + // "Hello 😊🐟 World".slice(-12..-5) # => "llo 😊🐟 W" + // "Hello World".slice(4) # => "o" + // "Hello\nWorld".slice(6) # => "\n" + // "Hello World".slice(-3) # => "r" + // "Hello World".slice(-11) # => "H" + // "Hello World".slice(-12) # => nil + // "Hello World".slice(11) # => nil + // "Hello World".slice(4) # => "o" + // "Hello 😊🐟 World".slice(6) # => "😊" + // "Hello 😊🐟 World".slice(-7) # => "🐟" + // "Hello 😊🐟 World".slice(-10) # => "o" + // "Hello 😊🐟 World".slice(-15) # => nil + // "Hello 😊🐟 World".slice(14) # => nil + // ``` + // + // @param slicing point or range [Integer/Range] + // @return [String] + Name: "slice", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } + + str := receiver.(*StringObject).value + strLength := utf8.RuneCountInString(str) + + // All Case Support UTF-8 Encoding + slice := args[0] + switch slice.(type) { + case *RangeObject: + ro := slice.(*RangeObject) + switch { + case ro.Start >= 0 && ro.End >= 0: + if ro.Start > strLength { + return NULL + } else if ro.Start > ro.End { + return t.vm.InitStringObject("") } - if iv > strLength-1 { + return t.vm.InitStringObject(string([]rune(str)[ro.Start : ro.End+1])) + case ro.Start < 0 && ro.End >= 0: + positiveStart := strLength + ro.Start + if -ro.Start > strLength { return NULL + } else if positiveStart > ro.End { + return t.vm.InitStringObject("") } - return t.vm.InitStringObject(string([]rune(str)[iv])) - + return t.vm.InitStringObject(string([]rune(str)[positiveStart : ro.End+1])) + case ro.Start >= 0 && ro.End < 0: + positiveEnd := strLength + ro.End + if ro.Start > strLength { + return NULL + } else if positiveEnd < 0 || ro.Start > positiveEnd { + return t.vm.InitStringObject("") + } + return t.vm.InitStringObject(string([]rune(str)[ro.Start : positiveEnd+1])) default: - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Range or Integer", slice.Class().Name) + positiveStart := strLength + ro.Start + positiveEnd := strLength + ro.End + if positiveStart < 0 { + return NULL + } else if positiveStart > positiveEnd { + return t.vm.InitStringObject("") + } + return t.vm.InitStringObject(string([]rune(str)[positiveStart : positiveEnd+1])) } - }, - }, - { - // Returns an array of strings separated by the given delimiter. - // - // ```ruby - // "Hello World".split("o") # => ["Hell", " W", "rld"] - // "Goby".split("") # => ["G", "o", "b", "y"] - // "Hello\nWorld\nGoby".split("o") # => ["Hello", "World", "Goby"] - // "Hello🐟World🐟Goby".split("🐟") # => ["Hello", "World", "Goby"] - // ``` - // - // @param delimiter [String] - // @return [Array] - Name: "split", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + case *IntegerObject: + iv := slice.(*IntegerObject).value + if iv < 0 { + if -iv > strLength { + return NULL + } + return t.vm.InitStringObject(string([]rune(str)[strLength+iv])) } - - separator, ok := args[0].(*StringObject) - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + if iv > strLength-1 { + return NULL } + return t.vm.InitStringObject(string([]rune(str)[iv])) - str := receiver.(*StringObject).value - arr := strings.Split(str, separator.value) + default: + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, "Range or Integer", slice.Class().Name) + } - var elements []Object - for i := 0; i < len(arr); i++ { - elements = append(elements, t.vm.InitStringObject(arr[i])) - } + }, + }, + { + // Returns an array of strings separated by the given delimiter. + // + // ```ruby + // "Hello World".split("o") # => ["Hell", " W", "rld"] + // "Goby".split("") # => ["G", "o", "b", "y"] + // "Hello\nWorld\nGoby".split("o") # => ["Hello", "World", "Goby"] + // "Hello🐟World🐟Goby".split("🐟") # => ["Hello", "World", "Goby"] + // ``` + // + // @param delimiter [String] + // @return [Array] + Name: "split", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - return t.vm.InitArrayObject(elements) + separator, ok := args[0].(*StringObject) + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - }, - }, - { - // Returns true if receiver string start with the argument string. - // - // ```ruby - // "Hello".start_with("Hel") # => true - // "Hello".start_with("hel") # => false - // "😊Hello🐟".start_with("😊") # => true - // "😊Hello🐟".start_with("🐟") # => false - // ``` - // - // @param string [String] - // @return [Boolean] - Name: "start_with", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 1 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) - } + str := receiver.(*StringObject).value + arr := strings.Split(str, separator.value) - compareStr, ok := args[0].(*StringObject) + var elements []Object + for i := 0; i < len(arr); i++ { + elements = append(elements, t.vm.InitStringObject(arr[i])) + } - if !ok { - return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) - } + return t.vm.InitArrayObject(elements) - compareStrValue := compareStr.value - compareStrLength := utf8.RuneCountInString(compareStrValue) + }, + }, + { + // Returns true if receiver string start with the argument string. + // + // ```ruby + // "Hello".start_with("Hel") # => true + // "Hello".start_with("hel") # => false + // "😊Hello🐟".start_with("😊") # => true + // "😊Hello🐟".start_with("🐟") # => false + // ``` + // + // @param string [String] + // @return [Boolean] + Name: "start_with", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 1 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args)) + } - str := receiver.(*StringObject).value - strLength := utf8.RuneCountInString(str) + compareStr, ok := args[0].(*StringObject) - if compareStrLength > strLength { - return FALSE - } + if !ok { + return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name) + } - if compareStrValue == string([]rune(str)[:compareStrLength]) { - return TRUE - } + compareStrValue := compareStr.value + compareStrLength := utf8.RuneCountInString(compareStrValue) + + str := receiver.(*StringObject).value + strLength := utf8.RuneCountInString(str) + + if compareStrLength > strLength { return FALSE + } - }, - }, - { - // Returns a copy of str with leading and trailing whitespace removed. - // Whitespace is defined as any of the following characters: null, horizontal tab, - // line feed, vertical tab, form feed, carriage return, space. - // - // ```ruby - // " Goby Lang ".strip # => "Goby Lang" - // "\nGoby Lang\r\t".strip # => "Goby Lang" - // ``` - // - // @return [String] - Name: "strip", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - str := receiver.(*StringObject).value - - for { - str = strings.Trim(str, " ") - - if strings.HasPrefix(str, "\n") || strings.HasPrefix(str, "\t") || strings.HasPrefix(str, "\r") || strings.HasPrefix(str, "\v") { - str = string([]rune(str)[1:]) - continue - } - if strings.HasSuffix(str, "\n") || strings.HasSuffix(str, "\t") || strings.HasSuffix(str, "\r") || strings.HasSuffix(str, "\v") { - str = string([]rune(str)[:utf8.RuneCountInString(str)-2]) - continue - } - break - } - return t.vm.InitStringObject(str) + if compareStrValue == string([]rune(str)[:compareStrLength]) { + return TRUE + } + return FALSE - }, }, - { - // Returns an array of characters converted from a string. - // Passing an empty string returns an empty array. - // - // ```ruby - // "Goby".to_a # => ["G", "o", "b", "y"] - // "😊Hello🐟".to_a # => ["😊", "H", "e", "l", "l", "o", "🐟"] - // "".to_a # => [ ] - // ``` - // - // @return [String] - Name: "to_a", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns a copy of str with leading and trailing whitespace removed. + // Whitespace is defined as any of the following characters: null, horizontal tab, + // line feed, vertical tab, form feed, carriage return, space. + // + // ```ruby + // " Goby Lang ".strip # => "Goby Lang" + // "\nGoby Lang\r\t".strip # => "Goby Lang" + // ``` + // + // @return [String] + Name: "strip", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - str := receiver.(*StringObject) - strLength := utf8.RuneCountInString(str.value) - e := []Object{} + str := receiver.(*StringObject).value - for i := 0; i < strLength; i++ { - e = append(e, t.vm.InitStringObject(string([]rune(str.value)[i]))) - } + for { + str = strings.Trim(str, " ") - return t.vm.InitArrayObject(e) + if strings.HasPrefix(str, "\n") || strings.HasPrefix(str, "\t") || strings.HasPrefix(str, "\r") || strings.HasPrefix(str, "\v") { + str = string([]rune(str)[1:]) + continue + } + if strings.HasSuffix(str, "\n") || strings.HasSuffix(str, "\t") || strings.HasSuffix(str, "\r") || strings.HasSuffix(str, "\v") { + str = string([]rune(str)[:utf8.RuneCountInString(str)-2]) + continue + } + break + } + return t.vm.InitStringObject(str) - }, }, - // Returns an array of byte strings, which is fo GoObject. + }, + { + // Returns an array of characters converted from a string. // Passing an empty string returns an empty array. // // ```ruby @@ -1475,162 +1444,189 @@ func builtinStringInstanceMethods() []*BuiltinMethodObject { // ``` // // @return [String] - { - Name: "to_bytes", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - r := receiver.(*StringObject) - return t.vm.initGoObject([]byte(r.value)) - }, - }, - { - // Converts a string of decimal number to Decimal object. - // Returns an error when failed. - // - // ```ruby - // "3.14".to_d # => 3.14 - // "-0.7238943".to_d # => -0.7238943 - // "355/113".to_d # => 3.14159292 - // ``` - // - // @return [String] - Name: "to_d", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + Name: "to_a", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - str := receiver.(*StringObject).value + str := receiver.(*StringObject) + strLength := utf8.RuneCountInString(str.value) + e := []Object{} - de, err := new(Decimal).SetString(str) - if err == false { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.InvalidNumericString, str) - } + for i := 0; i < strLength; i++ { + e = append(e, t.vm.InitStringObject(string([]rune(str.value)[i]))) + } - return t.vm.initDecimalObject(de) + return t.vm.InitArrayObject(e) - }, }, - { - // Returns the result of converting self to Float. - // Passing a non-numerical string returns a 0.0 value, except trailing whitespace, - // which is ignored. - // - // ```ruby - // "123.5".to_f # => 123.5 - // ".5".to_f # => 0.5 - // " 3.5".to_f # => 3.5 - // "3.5e2".to_f # => 350 - // "3.5ef".to_f # => 0 - // ``` - // - // @return [Float] - Name: "to_f", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } - - str := receiver.(*StringObject).value - - for i, char := range str { - if !unicode.IsSpace(char) { - str = str[i:] - break - } - } + }, + // Returns an array of byte strings, which is fo GoObject. + // Passing an empty string returns an empty array. + // + // ```ruby + // "Goby".to_a # => ["G", "o", "b", "y"] + // "😊Hello🐟".to_a # => ["😊", "H", "e", "l", "l", "o", "🐟"] + // "".to_a # => [ ] + // ``` + // + // @return [String] + { + Name: "to_bytes", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + r := receiver.(*StringObject) + return t.vm.initGoObject([]byte(r.value)) + }, + }, + { + // Converts a string of decimal number to Decimal object. + // Returns an error when failed. + // + // ```ruby + // "3.14".to_d # => 3.14 + // "-0.7238943".to_d # => -0.7238943 + // "355/113".to_d # => 3.14159292 + // ``` + // + // @return [String] + Name: "to_d", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - parsedStr, ok := strconv.ParseFloat(str, 64) + str := receiver.(*StringObject).value - if ok != nil { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.InvalidNumericString, str) - } + de, err := new(Decimal).SetString(str) + if err == false { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.InvalidNumericString, str) + } - return t.vm.initFloatObject(parsedStr) + return t.vm.initDecimalObject(de) - }, }, - { - // Returns the result of converting self to Integer. - // Passing a non-numerical string returns a 0 value. - // - // ```ruby - // "123".to_i # => 123 - // "3d print".to_i # => 3 - // " 321".to_i # => 321 - // "some text".to_i # => 0 - // ``` - // - // @return [Integer] - Name: "to_i", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + }, + { + // Returns the result of converting self to Float. + // Passing a non-numerical string returns a 0.0 value, except trailing whitespace, + // which is ignored. + // + // ```ruby + // "123.5".to_f # => 123.5 + // ".5".to_f # => 0.5 + // " 3.5".to_f # => 3.5 + // "3.5e2".to_f # => 350 + // "3.5ef".to_f # => 0 + // ``` + // + // @return [Float] + Name: "to_f", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + str := receiver.(*StringObject).value + + for i, char := range str { + if !unicode.IsSpace(char) { + str = str[i:] + break } + } - str := receiver.(*StringObject).value - parsedStr, err := strconv.ParseInt(str, 10, 0) + parsedStr, ok := strconv.ParseFloat(str, 64) - if err == nil { - return t.vm.InitIntegerObject(int(parsedStr)) - } + if ok != nil { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.InvalidNumericString, str) + } - var digits string - for _, char := range str { - if unicode.IsDigit(char) { - digits += string(char) - } else if unicode.IsSpace(char) && len(digits) == 0 { - // do nothing; allow trailing spaces - } else { - break - } - } + return t.vm.initFloatObject(parsedStr) - if len(digits) > 0 { - parsedStr, _ = strconv.ParseInt(digits, 10, 0) - return t.vm.InitIntegerObject(int(parsedStr)) + }, + }, + { + // Returns the result of converting self to Integer. + // Passing a non-numerical string returns a 0 value. + // + // ```ruby + // "123".to_i # => 123 + // "3d print".to_i # => 3 + // " 321".to_i # => 321 + // "some text".to_i # => 0 + // ``` + // + // @return [Integer] + Name: "to_i", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } + + str := receiver.(*StringObject).value + parsedStr, err := strconv.ParseInt(str, 10, 0) + + if err == nil { + return t.vm.InitIntegerObject(int(parsedStr)) + } + + var digits string + for _, char := range str { + if unicode.IsDigit(char) { + digits += string(char) + } else if unicode.IsSpace(char) && len(digits) == 0 { + // do nothing; allow trailing spaces + } else { + break } + } - return t.vm.InitIntegerObject(0) + if len(digits) > 0 { + parsedStr, _ = strconv.ParseInt(digits, 10, 0) + return t.vm.InitIntegerObject(int(parsedStr)) + } + + return t.vm.InitIntegerObject(0) - }, }, - { - // Returns a new String with self value. - // - // ```ruby - // "string".to_s # => "string" - // ``` - // - // @return [String] - Name: "to_s", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - if len(args) != 0 { - return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) - } + }, + { + // Returns a new String with self value. + // + // ```ruby + // "string".to_s # => "string" + // ``` + // + // @return [String] + Name: "to_s", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + if len(args) != 0 { + return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 0, len(args)) + } - str := receiver.(*StringObject).value + str := receiver.(*StringObject).value - return t.vm.InitStringObject(str) - }, + return t.vm.InitStringObject(str) }, - { - // Returns a new String with all characters is upcase. - // - // ```ruby - // "very big".upcase # => "VERY BIG" - // ``` - // - // @return [String] - Name: "upcase", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - - str := receiver.(*StringObject).value - - return t.vm.InitStringObject(strings.ToUpper(str)) - - }, + }, + { + // Returns a new String with all characters is upcase. + // + // ```ruby + // "very big".upcase # => "VERY BIG" + // ``` + // + // @return [String] + Name: "upcase", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + + str := receiver.(*StringObject).value + + return t.vm.InitStringObject(strings.ToUpper(str)) + }, - } + }, } // Internal functions =================================================== @@ -1646,8 +1642,8 @@ func (vm *VM) InitStringObject(value string) *StringObject { func (vm *VM) initStringClass() *RClass { sc := vm.initializeClass(classes.StringClass) - sc.setBuiltinMethods(builtinStringInstanceMethods(), false) - sc.setBuiltinMethods(builtinStringClassMethods(), true) + sc.setBuiltinMethods(builtinStringInstanceMethods, false) + sc.setBuiltinMethods(builtinStringClassMethods, true) return sc } diff --git a/vm/uri.go b/vm/uri.go index 737e9aacb..d27496ef5 100644 --- a/vm/uri.go +++ b/vm/uri.go @@ -8,99 +8,97 @@ import ( ) // Class methods -------------------------------------------------------- -func builtinURIClassMethods() []*BuiltinMethodObject { - return []*BuiltinMethodObject{ - { - // Returns a Net::HTTP or Net::HTTPS's instance (depends on the url scheme). - // - // ```ruby - // u = URI.parse("https://example.com") - // u.scheme # => "https" - // u.host # => "example.com" - // u.port # => 80 - // u.path # => "/" - // ``` - Name: "parse", - Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { - uri := args[0].(*StringObject).value - uriModule := t.vm.TopLevelClass("URI") - u, err := url.Parse(uri) +var builtinURIClassMethods = []*BuiltinMethodObject{ + { + // Returns a Net::HTTP or Net::HTTPS's instance (depends on the url scheme). + // + // ```ruby + // u = URI.parse("https://example.com") + // u.scheme # => "https" + // u.host # => "example.com" + // u.port # => 80 + // u.path # => "/" + // ``` + Name: "parse", + Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object { + uri := args[0].(*StringObject).value + uriModule := t.vm.TopLevelClass("URI") + u, err := url.Parse(uri) + + if err != nil { + return t.vm.InitErrorObject(errors.InternalError, sourceLine, err.Error()) + } + + uriAttrs := map[string]Object{ + "@user": NULL, + "@password": NULL, + "@query": NULL, + "@path": t.vm.InitStringObject("/"), + } + + // Scheme + uriAttrs["@scheme"] = t.vm.InitStringObject(u.Scheme) + + // Host + uriAttrs["@host"] = t.vm.InitStringObject(u.Host) + + // Port + if len(u.Port()) == 0 { + switch u.Scheme { + case "http": + uriAttrs["@port"] = t.vm.InitIntegerObject(80) + case "https": + uriAttrs["@port"] = t.vm.InitIntegerObject(443) + } + } else { + p, err := strconv.ParseInt(u.Port(), 0, 64) if err != nil { return t.vm.InitErrorObject(errors.InternalError, sourceLine, err.Error()) } - uriAttrs := map[string]Object{ - "@user": NULL, - "@password": NULL, - "@query": NULL, - "@path": t.vm.InitStringObject("/"), - } - - // Scheme - uriAttrs["@scheme"] = t.vm.InitStringObject(u.Scheme) - - // Host - uriAttrs["@host"] = t.vm.InitStringObject(u.Host) - - // Port - if len(u.Port()) == 0 { - switch u.Scheme { - case "http": - uriAttrs["@port"] = t.vm.InitIntegerObject(80) - case "https": - uriAttrs["@port"] = t.vm.InitIntegerObject(443) - } - } else { - p, err := strconv.ParseInt(u.Port(), 0, 64) - - if err != nil { - return t.vm.InitErrorObject(errors.InternalError, sourceLine, err.Error()) - } + uriAttrs["@port"] = t.vm.InitIntegerObject(int(p)) + } - uriAttrs["@port"] = t.vm.InitIntegerObject(int(p)) - } + // Path + if len(u.Path) != 0 { + uriAttrs["@path"] = t.vm.InitStringObject(u.Path) + } - // Path - if len(u.Path) != 0 { - uriAttrs["@path"] = t.vm.InitStringObject(u.Path) - } + // Query + if len(u.RawQuery) != 0 { + uriAttrs["@query"] = t.vm.InitStringObject(u.RawQuery) + } - // Query - if len(u.RawQuery) != 0 { - uriAttrs["@query"] = t.vm.InitStringObject(u.RawQuery) + // User + if u.User != nil { + if len(u.User.Username()) != 0 { + uriAttrs["@user"] = t.vm.InitStringObject(u.User.Username()) } - // User - if u.User != nil { - if len(u.User.Username()) != 0 { - uriAttrs["@user"] = t.vm.InitStringObject(u.User.Username()) - } - - if p, ok := u.User.Password(); ok { - uriAttrs["@password"] = t.vm.InitStringObject(p) - } + if p, ok := u.User.Password(); ok { + uriAttrs["@password"] = t.vm.InitStringObject(p) } + } - var c *RClass + var c *RClass - if u.Scheme == "https" { - c = uriModule.getClassConstant("HTTPS") - } else { - c = uriModule.getClassConstant("HTTP") - } + if u.Scheme == "https" { + c = uriModule.getClassConstant("HTTPS") + } else { + c = uriModule.getClassConstant("HTTP") + } - i := c.initializeInstance() + i := c.initializeInstance() - for varName, value := range uriAttrs { - i.InstanceVariables.set(varName, value) - } + for varName, value := range uriAttrs { + i.InstanceVariables.set(varName, value) + } - return i + return i - }, }, - } + }, } // Internal functions =================================================== @@ -115,7 +113,7 @@ func initURIClass(vm *VM) { https.pseudoSuperClass = http uri.setClassConstant(http) uri.setClassConstant(https) - uri.setBuiltinMethods(builtinURIClassMethods(), true) + uri.setBuiltinMethods(builtinURIClassMethods, true) attrs := []Object{ vm.InitStringObject("host"),