diff options
38 files changed, 1160 insertions, 200 deletions
@@ -3,11 +3,12 @@ ## What's mruby mruby is the lightweight implementation of the Ruby language complying to (part of) -the [ISO standard](http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=59579). -mruby can be linked and embedded within your application. We provide the interpreter program "mruby" and +the [ISO standard](http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=59579). +mruby can be linked and embedded within your application. We provide the interpreter program "mruby" and the interactive mruby shell "mirb" as examples. You can also compile Ruby programs into compiled byte code -using the mruby compiler "mrbc". All those tools reside in "bin" directory. The "mrbc" is also able to -generate compiled byte code in a C source file. You can check the "mrbtest" program under the "test" directory. +using the mruby compiler "mrbc". All those tools reside in the "bin" directory. "mrbc" is also able to +generate compiled byte code in a C source file, see the "mrbtest" program under the "test" directory +for an example. This achievement was sponsored by the Regional Innovation Creation R&D Programs of the Ministry of Economy, Trade and Industry of Japan. @@ -19,7 +20,7 @@ The stable version 1.0.0 of mruby can be downloaded via the following URL: https://github.com/mruby/mruby/archive/1.0.0.zip -The latest mruby development version of can be downloaded via the following URL: +The latest development version of mruby can be downloaded via the following URL: https://github.com/mruby/mruby/zipball/master diff --git a/doc/compile/README.md b/doc/compile/README.md index d0dfaf9c2..a06a6b952 100644 --- a/doc/compile/README.md +++ b/doc/compile/README.md @@ -14,8 +14,8 @@ To compile mruby out of the source code you need the following tools: Optional: * GIT (to update mruby source and integrate mrbgems easier) -* C++ compiler (to use GEMs which include *.cpp) -* Assembler (to use GEMs which include *.asm) +* C++ compiler (to use GEMs which include \*.cpp) +* Assembler (to use GEMs which include \*.asm) ## Usage @@ -175,6 +175,7 @@ Integrate GEMs in the build process. See doc/mrbgems/README.md for more option about mrbgems. + ### Mrbtest Configuration Mrbtest build process. @@ -186,7 +187,7 @@ If you want mrbtest.a only, You should set ```conf.build_mrbtest_lib_only``` ### Bintest Tests for mrbgem tools using CRuby. -To have bintests place *.rb scripts to ```bintest/``` directory of mrbgems. +To have bintests place \*.rb scripts to ```bintest/``` directory of mrbgems. See ```mruby-bin-*/bintest/*.rb``` if you need examples. If you want a temporary files use `tempfile` module of CRuby instead of ```/tmp/```. @@ -285,7 +286,7 @@ result will be stored in *build/host/src/y.tab.c*) * create *build/host/lib/libmruby_core.a* out of all object files (C only) * create ```build/host/bin/mrbc``` by compiling *tools/mrbc/mrbc.c* and linking with *build/host/lib/libmruby_core.a* -* create *build/host/mrblib/mrblib.c* by compiling all *.rb files +* create *build/host/mrblib/mrblib.c* by compiling all \*.rb files under *mrblib* with ```build/host/bin/mrbc``` * compile *build/host/mrblib/mrblib.c* to *build/host/mrblib/mrblib.o* * create *build/host/lib/libmruby.a* out of all object files (C and Ruby) @@ -359,7 +360,7 @@ in *build/i386/src*) * generate parser grammar out of *src/parse.y* (generated result will be stored in *build/i386/src/y.tab.c*) * cross-compile *build/i386/src/y.tab.c* to *build/i386/src/y.tab.o* -* create *build/i386/mrblib/mrblib.c* by compiling all *.rb files +* create *build/i386/mrblib/mrblib.c* by compiling all \*.rb files under *mrblib* with the native ```build/host/bin/mrbc``` * cross-compile *build/host/mrblib/mrblib.c* to *build/host/mrblib/mrblib.o* * create *build/i386/lib/libmruby.a* out of all object files (C and Ruby) diff --git a/include/mruby.h b/include/mruby.h index ca75d2984..9c63689a0 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -72,6 +72,7 @@ enum mrb_fiber_state { MRB_FIBER_RUNNING, MRB_FIBER_RESUMING, MRB_FIBER_SUSPENDED, + MRB_FIBER_TRANSFERRED, MRB_FIBER_TERMINATED, }; @@ -217,7 +218,7 @@ struct RClass * mrb_define_module_under(mrb_state *mrb, struct RClass *outer, co #define MRB_ARGS_BLOCK() ((mrb_aspec)1) /* accept any number of arguments */ -#define MRB_ARGS_ANY() ARGS_REST() +#define MRB_ARGS_ANY() MRB_ARGS_REST() /* accept no arguments */ #define MRB_ARGS_NONE() ((mrb_aspec)0) @@ -275,6 +276,7 @@ void mrb_close(mrb_state*); mrb_value mrb_top_self(mrb_state *); mrb_value mrb_run(mrb_state*, struct RProc*, mrb_value); +mrb_value mrb_toplevel_run(mrb_state*, struct RProc*); mrb_value mrb_context_run(mrb_state*, struct RProc*, mrb_value, unsigned int); void mrb_p(mrb_state*, mrb_value); @@ -389,6 +391,7 @@ mrb_bool mrb_obj_is_instance_of(mrb_state *mrb, mrb_value obj, struct RClass* c) /* fiber functions (you need to link mruby-fiber mrbgem to use) */ mrb_value mrb_fiber_yield(mrb_state *mrb, int argc, mrb_value *argv); +#define E_FIBER_ERROR (mrb_class_get(mrb, "FiberError")) /* memory pool implementation */ typedef struct mrb_pool mrb_pool; diff --git a/include/mruby/class.h b/include/mruby/class.h index 28ba6b1b7..3c4915dc4 100644 --- a/include/mruby/class.h +++ b/include/mruby/class.h @@ -41,6 +41,8 @@ mrb_class(mrb_state *mrb, mrb_value v) return mrb->float_class; case MRB_TT_CPTR: return mrb->object_class; + case MRB_TT_ENV: + return NULL; default: return mrb_obj_ptr(v)->c; } diff --git a/mrbgems/mruby-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb index 337cef632..feec10ead 100644 --- a/mrbgems/mruby-array-ext/mrblib/array.rb +++ b/mrbgems/mruby-array-ext/mrblib/array.rb @@ -201,4 +201,14 @@ class Array self.replace(result) end end + + # for efficiency + def reverse_each(&block) + i = self.size - 1 + while i>=0 + block.call(self[i]) + i -= 1 + end + self + end end diff --git a/mrbgems/mruby-enum-ext/mrblib/enum.rb b/mrbgems/mruby-enum-ext/mrblib/enum.rb index e54e0de2e..ead9a794a 100644 --- a/mrbgems/mruby-enum-ext/mrblib/enum.rb +++ b/mrbgems/mruby-enum-ext/mrblib/enum.rb @@ -13,11 +13,12 @@ module Enumerable # a.drop(3) #=> [4, 5, 0] def drop(n) - raise TypeError, "expected Integer for 1st argument" unless n.kind_of? Integer + raise TypeError, "no implicit conversion of #{n.class} into Integer" unless n.respond_to?(:to_int) raise ArgumentError, "attempt to drop negative size" if n < 0 + n = n.to_int ary = [] - self.each {|e| n == 0 ? ary << e : n -= 1 } + self.each {|*val| n == 0 ? ary << val.__svalue : n -= 1 } ary end @@ -34,9 +35,9 @@ module Enumerable def drop_while(&block) ary, state = [], false - self.each do |e| - state = true if !state and !block.call(e) - ary << e if state + self.each do |*val| + state = true if !state and !block.call(*val) + ary << val.__svalue if state end ary end @@ -51,13 +52,14 @@ module Enumerable # a.take(3) #=> [1, 2, 3] def take(n) - raise TypeError, "expected Integer for 1st argument" unless n.kind_of? Integer + raise TypeError, "no implicit conversion of #{n.class} into Integer" unless n.respond_to?(:to_int) raise ArgumentError, "attempt to take negative size" if n < 0 + n = n.to_int ary = [] - self.each do |e| + self.each do |*val| break if ary.size >= n - ary << e + ary << val.__svalue end ary end @@ -75,9 +77,9 @@ module Enumerable def take_while(&block) ary = [] - self.each do |e| - return ary unless block.call(e) - ary << e + self.each do |*val| + return ary unless block.call(*val) + ary << val.__svalue end ary end @@ -102,13 +104,14 @@ module Enumerable # [8, 9, 10] def each_cons(n, &block) - raise TypeError, "expected Integer for 1st argument" unless n.kind_of? Integer + raise TypeError, "no implicit conversion of #{n.class} into Integer" unless n.respond_to?(:to_int) raise ArgumentError, "invalid size" if n <= 0 ary = [] - self.each do |e| + n = n.to_int + self.each do |*val| ary.shift if ary.size == n - ary << e + ary << val.__svalue block.call(ary.dup) if ary.size == n end end @@ -128,12 +131,13 @@ module Enumerable # [10] def each_slice(n, &block) - raise TypeError, "expected Integer for 1st argument" unless n.kind_of? Integer + raise TypeError, "no implicit conversion of #{n.class} into Integer" unless n.respond_to?(:to_int) raise ArgumentError, "invalid slice size" if n <= 0 ary = [] - self.each do |e| - ary << e + n = n.to_int + self.each do |*val| + ary << val.__svalue if ary.size == n block.call(ary) ary = [] @@ -154,9 +158,10 @@ module Enumerable def group_by(&block) h = {} - self.each do |e| - key = block.call(e) - h.key?(key) ? (h[key] << e) : (h[key] = [e]) + self.each do |*val| + key = block.call(*val) + sv = val.__svalue + h.key?(key) ? (h[key] << sv) : (h[key] = [sv]) end h end @@ -193,37 +198,421 @@ module Enumerable # second form returns an empty array. def first(n=NONE) if n == NONE - self.each do |e| - return e + self.each do |*val| + return val.__svalue end return nil else a = [] i = 0 - self.each do |e| + self.each do |*val| break if n<=i - a.push e + a.push val.__svalue i += 1 end a end end + ## + # call-seq: + # enum.count -> int + # enum.count(item) -> int + # enum.count { |obj| block } -> int + # + # Returns the number of items in +enum+ through enumeration. + # If an argument is given, the number of items in +enum+ that + # are equal to +item+ are counted. If a block is given, it + # counts the number of elements yielding a true value. def count(v=NONE, &block) count = 0 if block - self.each do |e| - count += 1 if block.call(e) + self.each do |*val| + count += 1 if block.call(*val) end else if v == NONE self.each { count += 1 } else - self.each do |e| - count += 1 if e == v + self.each do |*val| + count += 1 if val.__svalue == v end end end count end + + ## + # call-seq: + # enum.flat_map { |obj| block } -> array + # enum.collect_concat { |obj| block } -> array + # enum.flat_map -> an_enumerator + # enum.collect_concat -> an_enumerator + # + # Returns a new array with the concatenated results of running + # <em>block</em> once for every element in <i>enum</i>. + # + # If no block is given, an enumerator is returned instead. + # + # [1, 2, 3, 4].flat_map { |e| [e, -e] } #=> [1, -1, 2, -2, 3, -3, 4, -4] + # [[1, 2], [3, 4]].flat_map { |e| e + [100] } #=> [1, 2, 100, 3, 4, 100] + def flat_map(&block) + return to_enum :flat_map unless block_given? + + ary = [] + self.each do |*e| + e2 = block.call(*e) + if e2.respond_to? :each + e2.each {|e3| ary.push(e3) } + else + ary.push(e2) + end + end + ary + end + alias collect_concat flat_map + + ## + # call-seq: + # enum.max_by {|obj| block } -> obj + # enum.max_by -> an_enumerator + # + # Returns the object in <i>enum</i> that gives the maximum + # value from the given block. + # + # If no block is given, an enumerator is returned instead. + # + # %w[albatross dog horse].max_by {|x| x.length } #=> "albatross" + + def max_by(&block) + return to_enum :max_by unless block_given? + + first = true + max = nil + max_cmp = nil + + self.each do |*val| + if first + max = val.__svalue + max_cmp = block.call(*val) + first = false + else + if (cmp = block.call(*val)) > max_cmp + max = val.__svalue + max_cmp = cmp + end + end + end + max + end + + ## + # call-seq: + # enum.min_by {|obj| block } -> obj + # enum.min_by -> an_enumerator + # + # Returns the object in <i>enum</i> that gives the minimum + # value from the given block. + # + # If no block is given, an enumerator is returned instead. + # + # %w[albatross dog horse].min_by {|x| x.length } #=> "dog" + + def min_by(&block) + return to_enum :min_by unless block_given? + + first = true + min = nil + min_cmp = nil + + self.each do |*val| + if first + min = val.__svalue + min_cmp = block.call(*val) + first = false + else + if (cmp = block.call(*val)) < min_cmp + min = val.__svalue + min_cmp = cmp + end + end + end + min + end + + ## + # call-seq: + # enum.minmax -> [min, max] + # enum.minmax { |a, b| block } -> [min, max] + # + # Returns two elements array which contains the minimum and the + # maximum value in the enumerable. The first form assumes all + # objects implement <code>Comparable</code>; the second uses the + # block to return <em>a <=> b</em>. + # + # a = %w(albatross dog horse) + # a.minmax #=> ["albatross", "horse"] + # a.minmax { |a, b| a.length <=> b.length } #=> ["dog", "albatross"] + + def minmax(&block) + max = nil + min = nil + first = true + + self.each do |*val| + if first + val = val.__svalue + max = val + min = val + first = false + else + if block + max = val.__svalue if block.call(*val, max) > 0 + min = val.__svalue if block.call(*val, min) < 0 + else + val = val.__svalue + max = val if (val <=> max) > 0 + min = val if (val <=> min) < 0 + end + end + end + [min, max] + end + + ## + # call-seq: + # enum.minmax_by { |obj| block } -> [min, max] + # enum.minmax_by -> an_enumerator + # + # Returns a two element array containing the objects in + # <i>enum</i> that correspond to the minimum and maximum values respectively + # from the given block. + # + # If no block is given, an enumerator is returned instead. + # + # %w(albatross dog horse).minmax_by { |x| x.length } #=> ["dog", "albatross"] + + def minmax_by(&block) + max = nil + max_cmp = nil + min = nil + min_cmp = nil + first = true + + self.each do |*val| + if first + max = min = val.__svalue + max_cmp = min_cmp = block.call(*val) + first = false + else + if (cmp = block.call(*val)) > max_cmp + max = val.__svalue + max_cmp = cmp + end + if (cmp = block.call(*val)) < min_cmp + min = val.__svalue + min_cmp = cmp + end + end + end + [min, max] + end + + ## + # call-seq: + # enum.none? [{ |obj| block }] -> true or false + # + # Passes each element of the collection to the given block. The method + # returns <code>true</code> if the block never returns <code>true</code> + # for all elements. If the block is not given, <code>none?</code> will return + # <code>true</code> only if none of the collection members is true. + # + # %w(ant bear cat).none? { |word| word.length == 5 } #=> true + # %w(ant bear cat).none? { |word| word.length >= 4 } #=> false + # [].none? #=> true + # [nil, false].none? #=> true + # [nil, true].none? #=> false + + def none?(&block) + if block + self.each do |*val| + return false if block.call(*val) + end + else + self.each do |*val| + return false if val.__svalue + end + end + true + end + + ## + # call-seq: + # enum.one? [{ |obj| block }] -> true or false + # + # Passes each element of the collection to the given block. The method + # returns <code>true</code> if the block returns <code>true</code> + # exactly once. If the block is not given, <code>one?</code> will return + # <code>true</code> only if exactly one of the collection members is + # true. + # + # %w(ant bear cat).one? { |word| word.length == 4 } #=> true + # %w(ant bear cat).one? { |word| word.length > 4 } #=> false + # %w(ant bear cat).one? { |word| word.length < 4 } #=> false + # [nil, true, 99].one? #=> false + # [nil, true, false].one? #=> true + # + + def one?(&block) + count = 0 + if block + self.each do |*val| + count += 1 if block.call(*val) + return false if count > 1 + end + else + self.each do |*val| + count += 1 if val.__svalue + return false if count > 1 + end + end + + count == 1 ? true : false + end + + ## + # call-seq: + # enum.each_with_object(obj) { |(*args), memo_obj| ... } -> obj + # enum.each_with_object(obj) -> an_enumerator + # + # Iterates the given block for each element with an arbitrary + # object given, and returns the initially given object. + # + # If no block is given, returns an enumerator. + # + # (1..10).each_with_object([]) { |i, a| a << i*2 } + # #=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] + # + + def each_with_object(obj=nil, &block) + raise ArgumentError, "wrong number of arguments (0 for 1)" if obj == nil + + return to_enum :each_with_object unless block_given? + + self.each {|*val| block.call(val.__svalue, obj) } + obj + end + + ## + # call-seq: + # enum.reverse_each { |item| block } -> enum + # enum.reverse_each -> an_enumerator + # + # Builds a temporary array and traverses that array in reverse order. + # + # If no block is given, an enumerator is returned instead. + # + # (1..3).reverse_each { |v| p v } + # + # produces: + # + # 3 + # 2 + # 1 + # + + def reverse_each(&block) + ary = self.to_a + i = ary.size - 1 + while i>=0 + block.call(ary[i]) + i -= 1 + end + self + end + + ## + # call-seq: + # enum.cycle(n=nil) { |obj| block } -> nil + # enum.cycle(n=nil) -> an_enumerator + # + # Calls <i>block</i> for each element of <i>enum</i> repeatedly _n_ + # times or forever if none or +nil+ is given. If a non-positive + # number is given or the collection is empty, does nothing. Returns + # +nil+ if the loop has finished without getting interrupted. + # + # Enumerable#cycle saves elements in an internal array so changes + # to <i>enum</i> after the first pass have no effect. + # + # If no block is given, an enumerator is returned instead. + # + # a = ["a", "b", "c"] + # a.cycle { |x| puts x } # print, a, b, c, a, b, c,.. forever. + # a.cycle(2) { |x| puts x } # print, a, b, c, a, b, c. + # + + def cycle(n=nil, &block) + ary = [] + if n == nil + self.each do|*val| + ary.push val + block.call(*val) + end + loop do + ary.each do|e| + block.call(*e) + end + end + else + raise TypeError, "no implicit conversion of #{n.class} into Integer" unless n.respond_to?(:to_int) + + n = n.to_int + self.each do|*val| + ary.push val + end + count = 0 + while count < n + ary.each do|e| + block.call(*e) + end + count += 1 + end + end + end + + ## + # call-seq: + # enum.find_index(value) -> int or nil + # enum.find_index { |obj| block } -> int or nil + # enum.find_index -> an_enumerator + # + # Compares each entry in <i>enum</i> with <em>value</em> or passes + # to <em>block</em>. Returns the index for the first for which the + # evaluated value is non-false. If no object matches, returns + # <code>nil</code> + # + # If neither block nor argument is given, an enumerator is returned instead. + # + # (1..10).find_index { |i| i % 5 == 0 and i % 7 == 0 } #=> nil + # (1..100).find_index { |i| i % 5 == 0 and i % 7 == 0 } #=> 34 + # (1..100).find_index(50) #=> 49 + # + + def find_index(val=NONE, &block) + return to_enum :find_index if !block_given? && val == NONE + + idx = 0 + if block + self.each do |*e| + return idx if block.call(*e) + idx += 1 + end + else + self.each do |*e| + return idx if e.__svalue == val + idx += 1 + end + end + nil + end end diff --git a/mrbgems/mruby-enum-ext/test/enum.rb b/mrbgems/mruby-enum-ext/test/enum.rb index 065ef7f5f..daf737d37 100644 --- a/mrbgems/mruby-enum-ext/test/enum.rb +++ b/mrbgems/mruby-enum-ext/test/enum.rb @@ -47,11 +47,21 @@ assert("Enumerable#sort_by") do end assert("Enumerable#first") do - a = [1, 2, 3] + a = Object.new + a.extend Enumerable + def a.each + yield 1 + yield 2 + yield 3 + end assert_equal 1, a.first assert_equal [1, 2], a.first(2) assert_equal [1, 2, 3], a.first(10) - assert_nil [].first + a = Object.new + a.extend Enumerable + def a.each + end + assert_nil a.first end assert("Enumerable#count") do @@ -60,3 +70,68 @@ assert("Enumerable#count") do assert_equal 2, a.count(2) assert_equal 3, a.count{|x| x % 2 == 0} end + +assert("Enumerable#flat_map") do + assert_equal [1, 2, 3, 4], [1, 2, 3, 4].flat_map { |e| e } + assert_equal [1, -1, 2, -2, 3, -3, 4, -4], [1, 2, 3, 4].flat_map { |e| [e, -e] } + assert_equal [1, 2, 100, 3, 4, 100], [[1, 2], [3, 4]].flat_map { |e| e + [100] } +end + +assert("Enumerable#max_by") do + assert_equal "albatross", %w[albatross dog horse].max_by { |x| x.length } +end + +assert("Enumerable#min_by") do + assert_equal "dog", %w[albatross dog horse].min_by { |x| x.length } +end + +assert("Enumerable#minmax") do + a = %w(albatross dog horse) + assert_equal ["albatross", "horse"], a.minmax + assert_equal ["dog", "albatross"], a.minmax { |a, b| a.length <=> b.length } +end + +assert("Enumerable#minmax_by") do + assert_equal ["dog", "albatross"], %w(albatross dog horse).minmax_by { |x| x.length } +end + +assert("Enumerable#none?") do + assert_true %w(ant bear cat).none? { |word| word.length == 5 } + assert_false %w(ant bear cat).none? { |word| word.length >= 4 } + assert_true [].none? + assert_true [nil, false].none? + assert_false [nil, true].none? +end + +assert("Enumerable#one?") do + assert_true %w(ant bear cat).one? { |word| word.length == 4 } + assert_false %w(ant bear cat).one? { |word| word.length > 4 } + assert_false %w(ant bear cat).one? { |word| word.length < 4 } + assert_false [nil, true, 99].one? + assert_true [nil, true, false].one? +end + +assert("Enumerable#each_with_object") do + assert_true [2, 4, 6, 8, 10, 12, 14, 16, 18, 20], (1..10).each_with_object([]) { |i, a| a << i*2 } + assert_raise(ArgumentError) { (1..10).each_with_object() { |i, a| a << i*2 } } +end + +assert("Enumerable#reverse_each") do + r = (1..3) + a = [] + assert_equal (1..3), r.reverse_each { |v| a << v } + assert_equal [3, 2, 1], a +end + +assert("Enumerable#cycle") do + a = [] + ["a", "b", "c"].cycle(2) { |v| a << v } + assert_equal ["a", "b", "c", "a", "b", "c"], a + assert_raise(TypeError) { ["a", "b", "c"].cycle("a") { |v| a << v } } +end + +assert("Enumerable#find_index") do + assert_nil (1..10).find_index { |i| i % 5 == 0 and i % 7 == 0 } + assert_equal 34, (1..100).find_index { |i| i % 5 == 0 and i % 7 == 0 } + assert_equal 49 ,(1..100).find_index(50) +end diff --git a/mrbgems/mruby-enumerator/test/enumerator.rb b/mrbgems/mruby-enumerator/test/enumerator.rb index c2b4c09ab..5954af4e0 100644 --- a/mrbgems/mruby-enumerator/test/enumerator.rb +++ b/mrbgems/mruby-enumerator/test/enumerator.rb @@ -486,6 +486,42 @@ assert 'Hash#each' do assert_equal [[:a,1], [:b,2]], c.sort end +assert 'Hash#each_key' do + assert_equal [:a,:b], {a:1,b:2}.each_key.to_a.sort +end + +assert 'Hash#each_value' do + assert_equal [1,2], {a:1,b:2}.each_value.to_a.sort +end + +assert 'Hash#select' do + h = {1=>2,3=>4,5=>6} + hret = h.select.with_index {|a,b| a[1] == 4} + assert_equal({3=>4}, hret) + assert_equal({1=>2,3=>4,5=>6}, h) +end + +assert 'Hash#select!' do + h = {1=>2,3=>4,5=>6} + hret = h.select!.with_index {|a,b| a[1] == 4} + assert_equal h, hret + assert_equal({3=>4}, h) +end + +assert 'Hash#reject' do + h = {1=>2,3=>4,5=>6} + hret = h.reject.with_index {|a,b| a[1] == 4} + assert_equal({1=>2,5=>6}, hret) + assert_equal({1=>2,3=>4,5=>6}, h) +end + +assert 'Hash#reject!' do + h = {1=>2,3=>4,5=>6} + hret = h.reject!.with_index {|a,b| a[1] == 4} + assert_equal h, hret + assert_equal({1=>2,5=>6}, h) +end + assert 'Range#each' do a = (1..5) b = a.each diff --git a/mrbgems/mruby-fiber/src/fiber.c b/mrbgems/mruby-fiber/src/fiber.c index 0a6e0cdd5..86f6e81cc 100644 --- a/mrbgems/mruby-fiber/src/fiber.c +++ b/mrbgems/mruby-fiber/src/fiber.c @@ -36,7 +36,7 @@ * * 1 * 2 - * resuming dead fiber (RuntimeError) + * resuming dead fiber (FiberError) * * The <code>Fiber#resume</code> method accepts an arbitrary number of * parameters, if it is the first call to <code>resume</code> then they @@ -57,7 +57,7 @@ * * 12 * 14 - * resuming dead fiber (RuntimeError) + * resuming dead fiber (FiberError) * */ static mrb_value @@ -77,7 +77,7 @@ fiber_init(mrb_state *mrb, mrb_value self) } p = mrb_proc_ptr(blk); if (MRB_PROC_CFUNC_P(p)) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "tried to create Fiber from C defined method"); + mrb_raise(mrb, E_FIBER_ERROR, "tried to create Fiber from C defined method"); } f->cxt = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context)); @@ -120,13 +120,13 @@ fiber_check(mrb_state *mrb, mrb_value fib) mrb_assert(f->tt == MRB_TT_FIBER); if (!f->cxt) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized Fiber"); + mrb_raise(mrb, E_FIBER_ERROR, "uninitialized Fiber"); } return f->cxt; } static mrb_value -fiber_result(mrb_state *mrb, mrb_value *a, int len) +fiber_result(mrb_state *mrb, const mrb_value *a, int len) { if (len == 0) return mrb_nil_value(); if (len == 1) return a[0]; @@ -136,42 +136,28 @@ fiber_result(mrb_state *mrb, mrb_value *a, int len) /* mark return from context modifying method */ #define MARK_CONTEXT_MODIFY(c) (c)->ci->target_class = NULL -/* - * call-seq: - * fiber.resume(args, ...) -> obj - * - * Resumes the fiber from the point at which the last <code>Fiber.yield</code> - * was called, or starts running it if it is the first call to - * <code>resume</code>. Arguments passed to resume will be the value of - * the <code>Fiber.yield</code> expression or will be passed as block - * parameters to the fiber's block if this is the first <code>resume</code>. - * - * Alternatively, when resume is called it evaluates to the arguments passed - * to the next <code>Fiber.yield</code> statement inside the fiber's block - * or to the block value if it runs to completion without any - * <code>Fiber.yield</code> - */ static mrb_value -fiber_resume(mrb_state *mrb, mrb_value self) +fiber_switch(mrb_state *mrb, mrb_value self, int len, const mrb_value *a, mrb_bool resume) { struct mrb_context *c = fiber_check(mrb, self); - mrb_value *a; - int len; mrb_callinfo *ci; for (ci = c->ci; ci >= c->cibase; ci--) { if (ci->acc < 0) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "can't cross C function boundary"); + mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary"); } } + if (resume && c->status == MRB_FIBER_TRANSFERRED) { + mrb_raise(mrb, E_FIBER_ERROR, "resuming transfered fiber"); + } if (c->status == MRB_FIBER_RUNNING || c->status == MRB_FIBER_RESUMING) { - mrb_raise(mrb, E_RUNTIME_ERROR, "double resume"); + mrb_raise(mrb, E_FIBER_ERROR, "double resume"); } if (c->status == MRB_FIBER_TERMINATED) { - mrb_raise(mrb, E_RUNTIME_ERROR, "resuming dead fiber"); + mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber"); } - mrb_get_args(mrb, "*", &a, &len); - mrb->c->status = MRB_FIBER_RESUMING; + mrb->c->status = resume ? MRB_FIBER_RESUMING : MRB_FIBER_TRANSFERRED; + c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c); if (c->status == MRB_FIBER_CREATED) { mrb_value *b = c->stack+1; mrb_value *e = b + len; @@ -180,7 +166,6 @@ fiber_resume(mrb_state *mrb, mrb_value self) *b++ = *a++; } c->cibase->argc = len; - c->prev = mrb->c; if (c->prev->fib) mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)c->prev->fib); mrb_write_barrier(mrb, (struct RBasic*)c->fib); @@ -191,7 +176,6 @@ fiber_resume(mrb_state *mrb, mrb_value self) return c->ci->proc->env->stack[0]; } MARK_CONTEXT_MODIFY(c); - c->prev = mrb->c; if (c->prev->fib) mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)c->prev->fib); mrb_write_barrier(mrb, (struct RBasic*)c->fib); @@ -202,6 +186,30 @@ fiber_resume(mrb_state *mrb, mrb_value self) /* * call-seq: + * fiber.resume(args, ...) -> obj + * + * Resumes the fiber from the point at which the last <code>Fiber.yield</code> + * was called, or starts running it if it is the first call to + * <code>resume</code>. Arguments passed to resume will be the value of + * the <code>Fiber.yield</code> expression or will be passed as block + * parameters to the fiber's block if this is the first <code>resume</code>. + * + * Alternatively, when resume is called it evaluates to the arguments passed + * to the next <code>Fiber.yield</code> statement inside the fiber's block + * or to the block value if it runs to completion without any + * <code>Fiber.yield</code> + */ +static mrb_value +fiber_resume(mrb_state *mrb, mrb_value self) +{ + mrb_value *a; + int len; + mrb_get_args(mrb, "*", &a, &len); + return fiber_switch(mrb, self, len, a, TRUE); +} + +/* + * call-seq: * fiber.alive? -> true or false * * Returns true if the fiber can still be resumed. After finishing @@ -226,6 +234,29 @@ fiber_eq(mrb_state *mrb, mrb_value self) return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other)); } +static mrb_value +fiber_transfer(mrb_state *mrb, mrb_value self) +{ + struct mrb_context *c = fiber_check(mrb, self); + mrb_value* a; + int len; + + mrb_get_args(mrb, "*", &a, &len); + + if (c == mrb->root_c) { + mrb->c->status = MRB_FIBER_TRANSFERRED; + mrb->c = c; + c->status = MRB_FIBER_RUNNING; + MARK_CONTEXT_MODIFY(c); + return fiber_result(mrb, a, len); + } + + if (c == mrb->c) { + return fiber_result(mrb, a, len); + } + + return fiber_switch(mrb, self, len, a, FALSE); +} mrb_value mrb_fiber_yield(mrb_state *mrb, int len, mrb_value *a) @@ -235,11 +266,11 @@ mrb_fiber_yield(mrb_state *mrb, int len, mrb_value *a) for (ci = c->ci; ci >= c->cibase; ci--) { if (ci->acc < 0) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "can't cross C function boundary"); + mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary"); } } if (!c->prev) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "can't yield from root fiber"); + mrb_raise(mrb, E_FIBER_ERROR, "can't yield from root fiber"); } c->prev->status = MRB_FIBER_RUNNING; @@ -300,11 +331,14 @@ mrb_mruby_fiber_gem_init(mrb_state* mrb) mrb_define_method(mrb, c, "initialize", fiber_init, MRB_ARGS_NONE()); mrb_define_method(mrb, c, "resume", fiber_resume, MRB_ARGS_ANY()); + mrb_define_method(mrb, c, "transfer", fiber_transfer, MRB_ARGS_ANY()); mrb_define_method(mrb, c, "alive?", fiber_alive_p, MRB_ARGS_NONE()); mrb_define_method(mrb, c, "==", fiber_eq, MRB_ARGS_REQ(1)); mrb_define_class_method(mrb, c, "yield", fiber_yield, MRB_ARGS_ANY()); mrb_define_class_method(mrb, c, "current", fiber_current, MRB_ARGS_NONE()); + + mrb_define_class(mrb, "FiberError", mrb->eStandardError_class); } void diff --git a/mrbgems/mruby-fiber/test/fiber.rb b/mrbgems/mruby-fiber/test/fiber.rb index c09b49925..c2bae2259 100644 --- a/mrbgems/mruby-fiber/test/fiber.rb +++ b/mrbgems/mruby-fiber/test/fiber.rb @@ -8,6 +8,27 @@ assert('Fiber#resume') { f.resume(2) } +assert('Fiber#transfer') do + f2 = nil + f1 = Fiber.new do |v| + Fiber.yield v + f2.transfer + end + f2 = Fiber.new do + f1.transfer(1) + f1.transfer(1) + Fiber.yield 2 + end + assert_equal 1, f2.resume + assert_raise(FiberError) { f2.resume } + assert_equal 2, f2.transfer + assert_raise(FiberError) { f1.resume } + f1.transfer + f2.resume + assert_false f1.alive? + assert_false f2.alive? +end + assert('Fiber#alive?') { f = Fiber.new{ Fiber.yield } f.resume @@ -35,6 +56,10 @@ assert('Fiber.yield') { f.resume(3) } +assert('FiberError') do + assert_equal StandardError, FiberError.superclass +end + assert('Fiber iteration') { f1 = Fiber.new{ [1,2,3].each{|x| Fiber.yield(x)} @@ -54,33 +79,24 @@ assert('Fiber with splat in the block argument list') { Fiber.new{|*x|x}.resume(1) == [1] } -assert('Fiber raises on resume when dead') { - r1 = true - begin +assert('Fiber raises on resume when dead') do + assert_raise(FiberError) do f = Fiber.new{} f.resume - r1 = f.alive? + assert_false f.alive? f.resume - false - rescue => e1 - true end -} +end -assert('Yield raises when called on root fiber') { - begin - Fiber.yield - false - rescue => e1 - true - end -} +assert('Yield raises when called on root fiber') do + assert_raise(FiberError) { Fiber.yield } +end assert('Double resume of Fiber') do f1 = Fiber.new {} f2 = Fiber.new { f1.resume - assert_raise(RuntimeError) { f2.resume } + assert_raise(FiberError) { f2.resume } Fiber.yield 0 } assert_equal 0, f2.resume @@ -91,7 +107,7 @@ end assert('Recursive resume of Fiber') do f1, f2 = nil, nil - f1 = Fiber.new { assert_raise(RuntimeError) { f2.resume } } + f1 = Fiber.new { assert_raise(FiberError) { f2.resume } } f2 = Fiber.new { f1.resume Fiber.yield 0 @@ -108,10 +124,85 @@ end assert('Root fiber resume') do root = Fiber.current - assert_raise(RuntimeError) { root.resume } + assert_raise(FiberError) { root.resume } f = Fiber.new { - assert_raise(RuntimeError) { root.resume } + assert_raise(FiberError) { root.resume } } f.resume assert_false f.alive? end + +assert('Fiber without block') do + assert_raise(ArgumentError) { Fiber.new } +end + + +assert('Transfer to self.') do + result = [] + f = Fiber.new { result << :start; f.transfer; result << :end } + f.transfer + assert_equal [:start, :end], result + + result = [] + f = Fiber.new { result << :start; f.transfer; result << :end } + f.resume + assert_equal [:start, :end], result +end + +assert('Resume transferred fiber') do + f = Fiber.new { + assert_raise(FiberError) { f.resume } + } + f.transfer +end + +assert('Root fiber transfer.') do + result = nil + root = Fiber.current + f = Fiber.new { + result = :ok + root.transfer + } + f.resume + assert_true f.alive? + assert_equal :ok, result +end + +assert('Break nested fiber with root fiber transfer') do + root = Fiber.current + + result = nil + f2 = nil + f1 = Fiber.new { + Fiber.yield f2.resume + result = :f1 + } + f2 = Fiber.new { + result = :to_root + root.transfer :from_f2 + result = :f2 + } + assert_equal :from_f2, f1.resume + assert_equal :to_root, result + assert_equal :f2, f2.transfer + assert_equal :f2, result + assert_false f2.alive? + assert_equal :f1, f1.resume + assert_equal :f1, result + assert_false f1.alive? +end + +assert('CRuby Fiber#transfer test.') do + ary = [] + f2 = nil + f1 = Fiber.new{ + ary << f2.transfer(:foo) + :ok + } + f2 = Fiber.new{ + ary << f1.transfer(:baz) + :ng + } + assert_equal :ok, f1.transfer + assert_equal [:baz], ary +end diff --git a/mrbgems/mruby-hash-ext/mrblib/hash.rb b/mrbgems/mruby-hash-ext/mrblib/hash.rb index 3fb83abba..723a0b907 100644 --- a/mrbgems/mruby-hash-ext/mrblib/hash.rb +++ b/mrbgems/mruby-hash-ext/mrblib/hash.rb @@ -10,4 +10,7 @@ class Hash end self end + + alias each_pair each + alias update merge! end diff --git a/mrbgems/mruby-objectspace/src/mruby_objectspace.c b/mrbgems/mruby-objectspace/src/mruby_objectspace.c index 538959f2a..31139c429 100644 --- a/mrbgems/mruby-objectspace/src/mruby_objectspace.c +++ b/mrbgems/mruby-objectspace/src/mruby_objectspace.c @@ -1,6 +1,7 @@ #include <mruby.h> #include <mruby/gc.h> #include <mruby/hash.h> +#include <mruby/class.h> struct os_count_struct { mrb_int total; @@ -100,11 +101,54 @@ os_count_objects(mrb_state *mrb, mrb_value self) return hash; } +struct os_each_object_data { + mrb_value block; + struct RClass *target_module; + mrb_int count; +}; + +static void +os_each_object_cb(mrb_state *mrb, struct RBasic *obj, void *ud) +{ + struct os_each_object_data *d = (struct os_each_object_data*)ud; + + /* filter dead objects */ + if (is_dead(mrb, obj)) { + return; + } + + /* filter class kind if target module defined */ + if (d->target_module && !mrb_obj_is_kind_of(mrb, mrb_obj_value(obj), d->target_module)) { + return; + } + + mrb_yield(mrb, d->block, mrb_obj_value(obj)); + ++d->count; +} + +static mrb_value +os_each_object(mrb_state *mrb, mrb_value self) +{ + mrb_value cls = mrb_nil_value(); + struct os_each_object_data d; + mrb_get_args(mrb, "&|C", &d.block, &cls); + + if (mrb_nil_p(d.block)) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "Expected block in ObjectSpace.each_object."); + } + + d.target_module = mrb_nil_p(cls) ? NULL : mrb_class_ptr(cls); + d.count = 0; + mrb_objspace_each_objects(mrb, os_each_object_cb, &d); + return mrb_fixnum_value(d.count); +} + void mrb_mruby_objectspace_gem_init(mrb_state *mrb) { struct RClass *os = mrb_define_module(mrb, "ObjectSpace"); mrb_define_class_method(mrb, os, "count_objects", os_count_objects, MRB_ARGS_OPT(1)); + mrb_define_class_method(mrb, os, "each_object", os_each_object, MRB_ARGS_OPT(1)); } void diff --git a/mrbgems/mruby-objectspace/test/objectspace.rb b/mrbgems/mruby-objectspace/test/objectspace.rb index 612137019..a619ffe37 100644 --- a/mrbgems/mruby-objectspace/test/objectspace.rb +++ b/mrbgems/mruby-objectspace/test/objectspace.rb @@ -35,3 +35,18 @@ assert('ObjectSpace.count_objects') do assert_equal(h[:MRB_TT_HASH], h_before[:MRB_TT_HASH] + 1000) assert_equal(h_after[:MRB_TT_HASH], h_before[:MRB_TT_HASH]) end + +assert('ObjectSpace.each_object') do + objs = [] + objs_count = ObjectSpace.each_object { |obj| + objs << obj + } + assert_equal objs.length, objs_count + + arys = [] + arys_count = ObjectSpace.each_object(Array) { |obj| + arys << obj + } + assert_equal arys.length, arys_count + assert_true arys.length < objs.length +end diff --git a/mrbgems/mruby-print/src/print.c b/mrbgems/mruby-print/src/print.c index e4e52624f..673ba2172 100644 --- a/mrbgems/mruby-print/src/print.c +++ b/mrbgems/mruby-print/src/print.c @@ -6,7 +6,7 @@ static void printstr(mrb_state *mrb, mrb_value obj) { char *s; - int len; + mrb_int len; if (mrb_string_p(obj)) { s = RSTRING_PTR(obj); diff --git a/mrbgems/mruby-range-ext/src/range.c b/mrbgems/mruby-range-ext/src/range.c index c980ecffd..9fbfd431f 100644 --- a/mrbgems/mruby-range-ext/src/range.c +++ b/mrbgems/mruby-range-ext/src/range.c @@ -78,16 +78,16 @@ mrb_range_cover(mrb_state *mrb, mrb_value range) static mrb_value mrb_range_first(mrb_state *mrb, mrb_value range) { - mrb_value num; + mrb_int num; mrb_value array; struct RRange *r = mrb_range_ptr(range); - if (mrb_get_args(mrb, "|o", &num) == 0) { + if (mrb_get_args(mrb, "|i", &num) == 0) { return r->edges->beg; } array = mrb_funcall(mrb, range, "to_a", 0); - return mrb_funcall(mrb, array, "first", 1, mrb_to_int(mrb, num)); + return mrb_funcall(mrb, array, "first", 1, mrb_fixnum_value(num)); } /* diff --git a/mrbgems/mruby-sprintf/src/sprintf.c b/mrbgems/mruby-sprintf/src/sprintf.c index 90ca913d5..bb5502b58 100644 --- a/mrbgems/mruby-sprintf/src/sprintf.c +++ b/mrbgems/mruby-sprintf/src/sprintf.c @@ -713,7 +713,7 @@ retry: str = mrb_obj_as_string(mrb, arg); len = RSTRING_LEN(str); if (RSTRING(result)->flags & MRB_STR_EMBED) { - int tmp_n = len; + mrb_int tmp_n = len; RSTRING(result)->flags &= ~MRB_STR_EMBED_LEN_MASK; RSTRING(result)->flags |= tmp_n << MRB_STR_EMBED_LEN_SHIFT; } else { @@ -843,7 +843,7 @@ retry: else { val = mrb_fixnum_to_str(mrb, mrb_fixnum_value(v), base); } - v = mrb_fixnum(mrb_str_to_inum(mrb, val, 10, 0/*Qfalse*/)); + v = mrb_fixnum(mrb_str_to_inum(mrb, val, 10, FALSE)); } if (sign) { char c = *p; diff --git a/mrbgems/mruby-string-utf8/src/string.c b/mrbgems/mruby-string-utf8/src/string.c index 91183f7b8..cd41afc66 100644 --- a/mrbgems/mruby-string-utf8/src/string.c +++ b/mrbgems/mruby-string-utf8/src/string.c @@ -23,7 +23,7 @@ static mrb_int utf8len(unsigned char* p) { mrb_int len; - int i; + mrb_int i; if (*p == 0) return 1; @@ -117,7 +117,7 @@ mrb_memsearch(const void *x0, mrb_int m, const void *y0, mrb_int n) static mrb_value str_subseq(mrb_state *mrb, mrb_value str, mrb_int beg, mrb_int len) { - int i; + mrb_int i; unsigned char *p = (unsigned char*) RSTRING_PTR(str), *t; unsigned char *e = p + RSTRING_LEN(str); diff --git a/mrbgems/mruby-struct/src/struct.c b/mrbgems/mruby-struct/src/struct.c index f8c1d12da..19807073c 100644 --- a/mrbgems/mruby-struct/src/struct.c +++ b/mrbgems/mruby-struct/src/struct.c @@ -124,7 +124,7 @@ mrb_struct_getmember(mrb_state *mrb, mrb_value obj, mrb_sym id) return ptr[i]; } } - mrb_raisef(mrb, E_INDEX_ERROR, "%S is not struct member", mrb_sym2str(mrb, id)); + mrb_raisef(mrb, E_INDEX_ERROR, "`%S' is not a struct member", mrb_sym2str(mrb, id)); return mrb_nil_value(); /* not reached */ } @@ -203,9 +203,8 @@ mrb_struct_set(mrb_state *mrb, mrb_value obj, mrb_value val) return ptr[i] = val; } } - mrb_raisef(mrb, E_INDEX_ERROR, "`%S' is not a struct member", - mrb_sym2str(mrb, mid)); - return mrb_nil_value(); /* not reached */ + mrb_raisef(mrb, E_INDEX_ERROR, "`%S' is not a struct member", mrb_sym2str(mrb, mid)); + return mrb_nil_value(); /* not reached */ } static mrb_value @@ -507,7 +506,7 @@ mrb_value mrb_struct_init_copy(mrb_state *mrb, mrb_value copy) { mrb_value s; - int i, len; + mrb_int i, len; mrb_get_args(mrb, "o", &s); diff --git a/mrblib/compar.rb b/mrblib/compar.rb index 40fb2e7f0..44595974a 100644 --- a/mrblib/compar.rb +++ b/mrblib/compar.rb @@ -13,7 +13,7 @@ module Comparable def < other cmp = self <=> other if cmp.nil? - false + raise ArgumentError, "comparison of #{self.class} with #{other.class} failed" elsif cmp < 0 true else @@ -30,7 +30,7 @@ module Comparable def <= other cmp = self <=> other if cmp.nil? - false + raise ArgumentError, "comparison of #{self.class} with #{other.class} failed" elsif cmp <= 0 true else @@ -62,7 +62,7 @@ module Comparable def > other cmp = self <=> other if cmp.nil? - false + raise ArgumentError, "comparison of #{self.class} with #{other.class} failed" elsif cmp > 0 true else @@ -79,7 +79,7 @@ module Comparable def >= other cmp = self <=> other if cmp.nil? - false + raise ArgumentError, "comparison of #{self.class} with #{other.class} failed" elsif cmp >= 0 true else diff --git a/mrblib/enum.rb b/mrblib/enum.rb index 38c51aa21..4f9682ac7 100644 --- a/mrblib/enum.rb +++ b/mrblib/enum.rb @@ -24,14 +24,14 @@ module Enumerable # ISO 15.3.2.2.1 def all?(&block) if block - self.each{|val| - unless block.call(val) + self.each{|*val| + unless block.call(*val) return false end } else - self.each{|val| - unless val + self.each{|*val| + unless val.__svalue return false end } @@ -49,14 +49,14 @@ module Enumerable # ISO 15.3.2.2.2 def any?(&block) if block - self.each{|val| - if block.call(val) + self.each{|*val| + if block.call(*val) return true end } else - self.each{|val| - if val + self.each{|*val| + if val.__svalue return true end } @@ -165,9 +165,10 @@ module Enumerable # ISO 15.3.2.2.9 def grep(pattern, &block) ary = [] - self.each{|val| - if pattern === val - ary.push((block)? block.call(val): val) + self.each{|*val| + sv = val.__svalue + if pattern === sv + ary.push((block)? block.call(*val): sv) end } ary @@ -181,8 +182,8 @@ module Enumerable # # ISO 15.3.2.2.10 def include?(obj) - self.each{|val| - if val == obj + self.each{|*val| + if val.__svalue == obj return true end } diff --git a/mrblib/hash.rb b/mrblib/hash.rb index c15f770f7..d24fce849 100644 --- a/mrblib/hash.rb +++ b/mrblib/hash.rb @@ -69,6 +69,8 @@ class Hash # # ISO 15.2.13.4.10 def each_key(&block) + return to_enum :each_key unless block_given? + self.keys.each{|k| block.call(k)} self end @@ -93,6 +95,8 @@ class Hash # # ISO 15.2.13.4.11 def each_value(&block) + return to_enum :each_value unless block_given? + self.keys.each{|k| block.call(self[k])} self end @@ -130,10 +134,12 @@ class Hash # 1.8/1.9 Hash#reject! returns Hash; ISO says nothing. def reject!(&b) + return to_enum :reject! unless block_given? + keys = [] self.each_key{|k| v = self[k] - if b.call(k, v) + if b.call([k, v]) keys.push(k) end } @@ -146,10 +152,12 @@ class Hash # 1.8/1.9 Hash#reject returns Hash; ISO says nothing. def reject(&b) + return to_enum :reject unless block_given? + h = {} self.each_key{|k| v = self[k] - unless b.call(k, v) + unless b.call([k, v]) h[k] = v end } @@ -158,10 +166,12 @@ class Hash # 1.9 Hash#select! returns Hash; ISO says nothing. def select!(&b) + return to_enum :select! unless block_given? + keys = [] self.each_key{|k| v = self[k] - unless b.call(k, v) + unless b.call([k, v]) keys.push(k) end } @@ -174,15 +184,22 @@ class Hash # 1.9 Hash#select returns Hash; ISO says nothing. def select(&b) + return to_enum :select unless block_given? + h = {} self.each_key{|k| v = self[k] - if b.call(k, v) + if b.call([k, v]) h[k] = v end } h end + + def __update(h) + h.each_key{|k| self[k] = h[k]} + self + end end ## diff --git a/src/class.c b/src/class.c index 30d376648..e73a28c56 100644 --- a/src/class.c +++ b/src/class.c @@ -612,6 +612,9 @@ mrb_get_args(mrb_state *mrb, const char *format, ...) *p = (mrb_int)f; } break; + case MRB_TT_STRING: + mrb_raise(mrb, E_TYPE_ERROR, "String can't be coerced into int"); + break; default: *p = mrb_fixnum(mrb_Integer(mrb, *sp)); break; diff --git a/src/codegen.c b/src/codegen.c index 317ce6232..2efad00bc 100644 --- a/src/codegen.c +++ b/src/codegen.c @@ -1501,16 +1501,32 @@ codegen(codegen_scope *s, node *tree, int val) case NODE_HASH: { int len = 0; + mrb_bool update = FALSE; while (tree) { codegen(s, tree->car->car, val); codegen(s, tree->car->cdr, val); len++; tree = tree->cdr; + if (val && len == 126) { + pop_n(len*2); + genop(s, MKOP_ABC(OP_HASH, cursp(), cursp(), len)); + if (update) { + pop(); + genop(s, MKOP_ABC(OP_SEND, cursp(), new_msym(s, mrb_intern_lit(s->mrb, "__update")), 1)); + } + push(); + update = TRUE; + len = 0; + } } if (val) { pop_n(len*2); genop(s, MKOP_ABC(OP_HASH, cursp(), cursp(), len)); + if (update) { + pop(); + genop(s, MKOP_ABC(OP_SEND, cursp(), new_msym(s, mrb_intern_lit(s->mrb, "__update")), 1)); + } push(); } } diff --git a/src/dump.c b/src/dump.c index f551b01c0..bdfa0787f 100644 --- a/src/dump.c +++ b/src/dump.c @@ -90,7 +90,7 @@ get_pool_block_size(mrb_state *mrb, mrb_irep *irep) { mrb_int len = RSTRING_LEN(str); mrb_assert(len >= 0); - mrb_assert((size_t)len <= SIZE_MAX); + mrb_assert(len <= SIZE_MAX); size += (size_t)len; } break; @@ -100,7 +100,7 @@ get_pool_block_size(mrb_state *mrb, mrb_irep *irep) int len; len = mrb_float_to_str(buf, mrb_float(irep->pool[pool_no])); mrb_assert(len >= 0); - mrb_assert((size_t)len <= SIZE_MAX); + mrb_assert(len <= SIZE_MAX); size += (size_t)len; } break; @@ -109,7 +109,7 @@ get_pool_block_size(mrb_state *mrb, mrb_irep *irep) { mrb_int len = RSTRING_LEN(irep->pool[pool_no]); mrb_assert(len >= 0); - mrb_assert((size_t)len <= SIZE_MAX); + mrb_assert(len <= SIZE_MAX); size += (size_t)len; } break; @@ -420,7 +420,7 @@ write_lineno_record_1(mrb_state *mrb, mrb_irep *irep, uint8_t* bin) uint32_to_bin((uint32_t)diff, bin); /* record size */ - mrb_assert((size_t)diff <= SIZE_MAX); + mrb_assert(diff <= SIZE_MAX); return (size_t)diff; } @@ -600,7 +600,7 @@ write_debug_record_1(mrb_state *mrb, mrb_irep *irep, uint8_t *bin, mrb_sym const mrb_assert(ret <= UINT32_MAX); uint32_to_bin(ret, bin); - mrb_assert((size_t)ret <= SIZE_MAX); + mrb_assert(ret <= SIZE_MAX); return (size_t)ret; } diff --git a/src/load.c b/src/load.c index b7382a3ba..d97776a16 100644 --- a/src/load.c +++ b/src/load.c @@ -157,7 +157,7 @@ read_irep_record_1(mrb_state *mrb, const uint8_t *bin, size_t *len, mrb_bool all diff = src - bin; mrb_assert(diff >= 0); - mrb_assert((size_t)diff <= SIZE_MAX); + mrb_assert(diff <= SIZE_MAX); *len = (size_t)diff; return irep; @@ -335,7 +335,7 @@ read_debug_record(mrb_state *mrb, const uint8_t *start, mrb_irep* irep, size_t * diff = bin - start; mrb_assert(diff >= 0); - mrb_assert((size_t)diff <= SIZE_MAX); + mrb_assert(diff <= SIZE_MAX); if (record_size != (size_t)diff) { return MRB_DUMP_GENERAL_FAILURE; @@ -352,7 +352,7 @@ read_debug_record(mrb_state *mrb, const uint8_t *start, mrb_irep* irep, size_t * diff = bin - start; mrb_assert(diff >= 0); - mrb_assert((size_t)diff <= SIZE_MAX); + mrb_assert(diff <= SIZE_MAX); *record_len = (size_t)diff; return MRB_DUMP_OK; @@ -497,7 +497,7 @@ mrb_load_irep_cxt(mrb_state *mrb, const uint8_t *bin, mrbc_context *c) proc = mrb_proc_new(mrb, irep); mrb_irep_decref(mrb, irep); if (c && c->no_exec) return mrb_obj_value(proc); - val = mrb_context_run(mrb, proc, mrb_top_self(mrb), 0); + val = mrb_toplevel_run(mrb, proc); return val; } @@ -711,7 +711,7 @@ mrb_load_irep_file_cxt(mrb_state *mrb, FILE* fp, mrbc_context *c) proc = mrb_proc_new(mrb, irep); mrb_irep_decref(mrb, irep); if (c && c->no_exec) return mrb_obj_value(proc); - val = mrb_context_run(mrb, proc, mrb_top_self(mrb), 0); + val = mrb_toplevel_run(mrb, proc); return val; } diff --git a/src/numeric.c b/src/numeric.c index 6adfff344..b2507fb0b 100644 --- a/src/numeric.c +++ b/src/numeric.c @@ -594,6 +594,16 @@ flo_round(mrb_state *mrb, mrb_value num) mrb_get_args(mrb, "|i", &ndigits); number = mrb_float(num); + + if (isinf(number)) { + if (0 < ndigits) return num; + else mrb_raise(mrb, E_FLOATDOMAIN_ERROR, number < 0 ? "-Infinity" : "Infinity"); + } + if (isnan(number)) { + if (0 < ndigits) return num; + else mrb_raise(mrb, E_FLOATDOMAIN_ERROR, "NaN"); + } + f = 1.0; i = abs(ndigits); while (--i >= 0) @@ -621,7 +631,11 @@ flo_round(mrb_state *mrb, mrb_value num) if (ndigits < 0) number *= f; else number /= f; } - if (ndigits > 0) return mrb_float_value(mrb, number); + + if (ndigits > 0) { + if (isinf(number) || isnan(number)) return num; + return mrb_float_value(mrb, number); + } return mrb_fixnum_value((mrb_int)number); } diff --git a/src/parse.y b/src/parse.y index 8e7056b75..e6ac036bc 100644 --- a/src/parse.y +++ b/src/parse.y @@ -40,7 +40,7 @@ static void yyerror(parser_state *p, const char *s); static void yywarn(parser_state *p, const char *s); static void yywarning(parser_state *p, const char *s); static void backref_error(parser_state *p, node *n); -static void tokadd(parser_state *p, int c); +static void tokadd(parser_state *p, int32_t c); #ifndef isascii #define isascii(c) (((c) & ~0x7f) == 0) @@ -3465,10 +3465,44 @@ newtok(parser_state *p) } static void -tokadd(parser_state *p, int c) +tokadd(parser_state *p, int32_t c) { - if (p->bidx < MRB_PARSER_BUF_SIZE) { - p->buf[p->bidx++] = c; + char utf8[4]; + unsigned len; + + /* mrb_assert(-0x10FFFF <= c && c <= 0xFF); */ + if (c >= 0) { + /* Single byte from source or non-Unicode escape */ + utf8[0] = (char)c; + len = 1; + } else { + /* Unicode character */ + c = -c; + if (c < 0x80) { + utf8[0] = (char)c; + len = 1; + } else if (c < 0x800) { + utf8[0] = (char)(0xC0 | (c >> 6)); + utf8[1] = (char)(0x80 | (c & 0x3F)); + len = 2; + } else if (c < 0x10000) { + utf8[0] = (char)(0xE0 | (c >> 12) ); + utf8[1] = (char)(0x80 | ((c >> 6) & 0x3F)); + utf8[2] = (char)(0x80 | ( c & 0x3F)); + len = 3; + } else { + utf8[0] = (char)(0xF0 | (c >> 18) ); + utf8[1] = (char)(0x80 | ((c >> 12) & 0x3F)); + utf8[2] = (char)(0x80 | ((c >> 6) & 0x3F)); + utf8[3] = (char)(0x80 | ( c & 0x3F)); + len = 4; + } + } + if (p->bidx+len <= MRB_PARSER_BUF_SIZE) { + unsigned i; + for (i = 0; i < len; i++) { + p->buf[p->bidx++] = utf8[i]; + } } } @@ -3522,15 +3556,15 @@ scan_oct(const int *start, int len, int *retlen) return retval; } -static int +static int32_t scan_hex(const int *start, int len, int *retlen) { static const char hexdigit[] = "0123456789abcdef0123456789ABCDEF"; const int *s = start; - int retval = 0; + int32_t retval = 0; char *tmp; - /* mrb_assert(len <= 2) */ + /* mrb_assert(len <= 8) */ while (len-- && *s && (tmp = (char*)strchr(hexdigit, *s))) { retval <<= 4; retval |= (tmp - hexdigit) & 15; @@ -3541,10 +3575,11 @@ scan_hex(const int *start, int len, int *retlen) return retval; } -static int +/* Return negative to indicate Unicode code point */ +static int32_t read_escape(parser_state *p) { - int c; + int32_t c; switch (c = nextc(p)) { case '\\':/* Backslash */ @@ -3611,6 +3646,53 @@ read_escape(parser_state *p) } return c; + case 'u': /* Unicode */ + { + int buf[9]; + int i; + + /* Look for opening brace */ + i = 0; + buf[0] = nextc(p); + if (buf[0] < 0) goto eof; + if (buf[0] == '{') { + /* \u{xxxxxxxx} form */ + for (i=0; i<9; i++) { + buf[i] = nextc(p); + if (buf[i] < 0) goto eof; + if (buf[i] == '}') { + break; + } else if (!ISXDIGIT(buf[i])) { + yyerror(p, "Invalid escape character syntax"); + pushback(p, buf[i]); + return 0; + } + } + } else if (ISXDIGIT(buf[0])) { + /* \uxxxx form */ + for (i=1; i<4; i++) { + buf[i] = nextc(p); + if (buf[i] < 0) goto eof; + if (!ISXDIGIT(buf[i])) { + pushback(p, buf[i]); + break; + } + } + } else { + pushback(p, buf[0]); + } + c = scan_hex(buf, i, &i); + if (i == 0) { + yyerror(p, "Invalid escape character syntax"); + return 0; + } + if (c < 0 || c > 0x10FFFF || (c & 0xFFFFF800) == 0xD800) { + yyerror(p, "Invalid Unicode code point"); + return 0; + } + } + return -c; + case 'b':/* backspace */ return '\010'; @@ -3726,9 +3808,14 @@ parse_string(parser_state *p) } else { if (type & STR_FUNC_REGEXP) { + if (c == 'u') { + pushback(p, c); + tokadd(p, read_escape(p)); + } else { tokadd(p, '\\'); if (c >= 0) tokadd(p, c); + } } else { pushback(p, c); tokadd(p, read_escape(p)); @@ -3932,7 +4019,7 @@ arg_ambiguous(parser_state *p) static int parser_yylex(parser_state *p) { - int c; + int32_t c; int space_seen = 0; int cmd_state; enum mrb_lex_state_enum last_state; @@ -5419,7 +5506,7 @@ load_exec(mrb_state *mrb, parser_state *p, mrbc_context *c) if (mrb->c->ci) { mrb->c->ci->target_class = target; } - v = mrb_context_run(mrb, proc, mrb_top_self(mrb), 0); + v = mrb_toplevel_run(mrb, proc); if (mrb->exc) return mrb_nil_value(); return v; } diff --git a/src/proc.c b/src/proc.c index c111e012f..4e6e2b95f 100644 --- a/src/proc.c +++ b/src/proc.c @@ -70,6 +70,7 @@ mrb_proc_new_cfunc(mrb_state *mrb, mrb_func_t func) p = (struct RProc*)mrb_obj_alloc(mrb, MRB_TT_PROC, mrb->proc_class); p->body.func = func; p->flags |= MRB_PROC_CFUNC; + p->env = 0; return p; } diff --git a/src/state.c b/src/state.c index eeb466e0c..3dfeed5dc 100644 --- a/src/state.c +++ b/src/state.c @@ -169,23 +169,41 @@ mrb_str_pool(mrb_state *mrb, mrb_value str) ns->tt = MRB_TT_STRING; ns->c = mrb->string_class; - if (s->flags & MRB_STR_EMBED) - len = (mrb_int)((s->flags & MRB_STR_EMBED_LEN_MASK) >> MRB_STR_EMBED_LEN_SHIFT); - else - len = s->as.heap.len; - ns->as.heap.len = len; if (s->flags & MRB_STR_NOFREE) { - ns->as.heap.ptr = s->as.heap.ptr; ns->flags = MRB_STR_NOFREE; + ns->as.heap.ptr = s->as.heap.ptr; + ns->as.heap.len = s->as.heap.len; + ns->as.heap.aux.capa = 0; } else { - ns->flags = 0; - ns->as.heap.ptr = (char *)mrb_malloc(mrb, (size_t)len+1); - ptr = (s->flags & MRB_STR_EMBED) ? s->as.ary : s->as.heap.ptr; - if (ptr) { - memcpy(ns->as.heap.ptr, ptr, len); + if (s->flags & MRB_STR_EMBED) { + ptr = s->as.ary; + len = (mrb_int)((s->flags & MRB_STR_EMBED_LEN_MASK) >> MRB_STR_EMBED_LEN_SHIFT); + } + else { + ptr = s->as.heap.ptr; + len = s->as.heap.len; + } + + if (len < RSTRING_EMBED_LEN_MAX) { + ns->flags |= MRB_STR_EMBED; + ns->flags &= ~MRB_STR_EMBED_LEN_MASK; + ns->flags |= (size_t)len << MRB_STR_EMBED_LEN_SHIFT; + if (ptr) { + memcpy(ns->as.ary, ptr, len); + } + ns->as.ary[len] = '\0'; + } + else { + ns->flags = 0; + ns->as.heap.ptr = (char *)mrb_malloc(mrb, (size_t)len+1); + ns->as.heap.len = len; + ns->as.heap.aux.capa = len; + if (ptr) { + memcpy(ns->as.heap.ptr, ptr, len); + } + ns->as.heap.ptr[len] = '\0'; } - ns->as.heap.ptr[len] = '\0'; } return mrb_obj_value(ns); } diff --git a/src/string.c b/src/string.c index 9d6e6a04b..266db4989 100644 --- a/src/string.c +++ b/src/string.c @@ -28,7 +28,6 @@ if (STR_EMBED_P(s)) {\ STR_SET_EMBED_LEN((s),(n));\ } else {\ - mrb_assert((n) <= MRB_INT_MAX);\ s->as.heap.len = (mrb_int)(n);\ }\ } while (0) @@ -73,12 +72,12 @@ mrb_str_strlen(mrb_state *mrb, struct RString *s) #define RESIZE_CAPA(s,capacity) do {\ if (STR_EMBED_P(s)) {\ if (RSTRING_EMBED_LEN_MAX < (capacity)) {\ - char *const tmp = (char *)mrb_malloc(mrb, (capacity)+1);\ - const mrb_int len = STR_EMBED_LEN(s);\ - memcpy(tmp, s->as.ary, len);\ + char *const __tmp__ = (char *)mrb_malloc(mrb, (capacity)+1);\ + const mrb_int __len__ = STR_EMBED_LEN(s);\ + memcpy(__tmp__, s->as.ary, __len__);\ STR_UNSET_EMBED_FLAG(s);\ - s->as.heap.ptr = tmp;\ - s->as.heap.len = len;\ + s->as.heap.ptr = __tmp__;\ + s->as.heap.len = __len__;\ s->as.heap.aux.capa = (capacity);\ }\ } else {\ @@ -130,7 +129,7 @@ mrb_str_modify(mrb_state *mrb, struct RString *s) return; } if (s->flags & MRB_STR_NOFREE) { - char *p = STR_PTR(s); + char *p = s->as.heap.ptr; s->as.heap.ptr = (char *)mrb_malloc(mrb, (size_t)s->as.heap.len+1); if (p) { @@ -146,7 +145,7 @@ mrb_str_modify(mrb_state *mrb, struct RString *s) mrb_value mrb_str_resize(mrb_state *mrb, mrb_value str, mrb_int len) { - int slen; + mrb_int slen; struct RString *s = mrb_str_ptr(str); mrb_str_modify(mrb, s); @@ -273,6 +272,7 @@ str_buf_cat(mrb_state *mrb, struct RString *s, const char *ptr, size_t len) ptr = STR_PTR(s) + off; } memcpy(STR_PTR(s) + STR_LEN(s), ptr, len); + mrb_assert(total <= MRB_INT_MAX); STR_SET_LEN(s, total); STR_PTR(s)[total] = '\0'; /* sentinel */ } @@ -382,16 +382,16 @@ str_make_shared(mrb_state *mrb, struct RString *s) } else if (s->flags & MRB_STR_NOFREE) { shared->nofree = TRUE; - shared->ptr = STR_PTR(s); + shared->ptr = s->as.heap.ptr; s->flags &= ~MRB_STR_NOFREE; } else { shared->nofree = FALSE; if (s->as.heap.aux.capa > s->as.heap.len) { - s->as.heap.ptr = shared->ptr = (char *)mrb_realloc(mrb, STR_PTR(s), s->as.heap.len+1); + s->as.heap.ptr = shared->ptr = (char *)mrb_realloc(mrb, s->as.heap.ptr, s->as.heap.len+1); } else { - shared->ptr = STR_PTR(s); + shared->ptr = s->as.heap.ptr; } } shared->len = s->as.heap.len; @@ -1085,7 +1085,7 @@ mrb_str_chop_bang(mrb_state *mrb, mrb_value str) mrb_str_modify(mrb, s); if (STR_LEN(s) > 0) { - int len; + mrb_int len; len = STR_LEN(s) - 1; if (STR_PTR(s)[len] == '\n') { if (len > 0 && @@ -1441,7 +1441,7 @@ str_replace(mrb_state *mrb, struct RString *s1, struct RString *s2) else { if (len <= RSTRING_EMBED_LEN_MAX) { STR_SET_EMBED_FLAG(s1); - memcpy(STR_PTR(s1), STR_PTR(s2), len); + memcpy(s1->as.ary, STR_PTR(s2), len); STR_SET_EMBED_LEN(s1, len); } else { @@ -1705,7 +1705,7 @@ mrb_str_rindex_m(mrb_state *mrb, mrb_value str) int argc; mrb_value sub; mrb_value vpos; - int pos, len = RSTRING_LEN(str); + mrb_int pos, len = RSTRING_LEN(str); mrb_get_args(mrb, "*", &argv, &argc); if (argc == 2) { @@ -2096,7 +2096,7 @@ mrb_value mrb_str_to_inum(mrb_state *mrb, mrb_value str, int base, mrb_bool badcheck) { char *s; - int len; + mrb_int len; str = mrb_str_to_str(mrb, str); if (badcheck) { @@ -2152,7 +2152,7 @@ mrb_str_to_i(mrb_state *mrb, mrb_value self) if (base < 0) { mrb_raisef(mrb, E_ARGUMENT_ERROR, "illegal radix %S", mrb_fixnum_value(base)); } - return mrb_str_to_inum(mrb, self, base, 0/*Qfalse*/); + return mrb_str_to_inum(mrb, self, base, FALSE); } double @@ -2228,7 +2228,7 @@ double mrb_str_to_dbl(mrb_state *mrb, mrb_value str, mrb_bool badcheck) { char *s; - int len; + mrb_int len; str = mrb_str_to_str(mrb, str); s = RSTRING_PTR(str); @@ -2262,7 +2262,7 @@ mrb_str_to_dbl(mrb_state *mrb, mrb_value str, mrb_bool badcheck) static mrb_value mrb_str_to_f(mrb_state *mrb, mrb_value self) { - return mrb_float_value(mrb, mrb_str_to_dbl(mrb, self, 0/*Qfalse*/)); + return mrb_float_value(mrb, mrb_str_to_dbl(mrb, self, FALSE)); } /* 15.2.10.5.40 */ @@ -1387,7 +1387,8 @@ RETRY_TRY_BLOCK: } } L_RESCUE: - irep = ci->proc->body.irep; + proc = ci->proc; + irep = proc->body.irep; pool = irep->pool; syms = irep->syms; regs = mrb->c->stack = ci[1].stackent; @@ -1423,7 +1424,7 @@ RETRY_TRY_BLOCK: goto L_RAISE; } if (mrb->c->prev->ci == mrb->c->prev->cibase) { - mrb_value exc = mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "double resume"); + mrb_value exc = mrb_exc_new_str_lit(mrb, E_FIBER_ERROR, "double resume"); mrb->exc = mrb_obj_ptr(exc); goto L_RAISE; } @@ -2269,3 +2270,20 @@ mrb_run(mrb_state *mrb, struct RProc *proc, mrb_value self) { return mrb_context_run(mrb, proc, self, mrb->c->ci->argc + 2); /* argc + 2 (receiver and block) */ } + +mrb_value +mrb_toplevel_run(mrb_state *mrb, struct RProc *proc) +{ + mrb_callinfo *ci; + mrb_value v; + + if (!mrb->c->cibase || mrb->c->ci == mrb->c->cibase) { + return mrb_context_run(mrb, proc, mrb_top_self(mrb), 0); + } + ci = cipush(mrb); + ci->acc = CI_ACC_SKIP; + v = mrb_context_run(mrb, proc, mrb_top_self(mrb), 0); + cipop(mrb); + + return v; +} diff --git a/tasks/mrbgem_spec.rake b/tasks/mrbgem_spec.rake index 60d6672f0..4d4189818 100644 --- a/tasks/mrbgem_spec.rake +++ b/tasks/mrbgem_spec.rake @@ -55,7 +55,7 @@ module MRuby MRuby::Gem.current = self @build.compilers.each do |compiler| compiler.include_paths << "#{dir}/include" - end if Dir.exist? "#{dir}/include" + end if File.directory? "#{dir}/include" MRuby::Build::COMMANDS.each do |command| instance_variable_set("@#{command}", @build.send(command).clone) end diff --git a/tasks/mruby_build.rake b/tasks/mruby_build.rake index 5fe0cbfce..5877c11cd 100644 --- a/tasks/mruby_build.rake +++ b/tasks/mruby_build.rake @@ -194,7 +194,7 @@ module MRuby end def run_bintest - targets = @gems.select { |v| Dir.exists? "#{v.dir}/bintest" }.map { |v| filename v.dir } + targets = @gems.select { |v| File.directory? "#{v.dir}/bintest" }.map { |v| filename v.dir } sh "ruby test/bintest.rb #{targets.join ' '}" end diff --git a/test/assert.rb b/test/assert.rb index 30d27d9ef..1daee01c7 100644 --- a/test/assert.rb +++ b/test/assert.rb @@ -218,7 +218,7 @@ def report() puts msg end - $total_test = $ok_test.+($ko_test) + $total_test = $ok_test+$ko_test+$kill_test t_print("Total: #{$total_test}\n") t_print(" OK: #{$ok_test}\n") diff --git a/test/t/comparable.rb b/test/t/comparable.rb index b5718d2d2..2ee28de7b 100644 --- a/test/t/comparable.rb +++ b/test/t/comparable.rb @@ -3,22 +3,26 @@ assert('Comparable#<', '15.3.3.2.1') do class Foo include Comparable def <=>(x) - 0 + x end end - - assert_false(Foo.new < Foo.new) + assert_false(Foo.new < 0) + assert_false(Foo.new < 1) + assert_true(Foo.new < -1) + assert_raise(ArgumentError){ Foo.new < nil } end assert('Comparable#<=', '15.3.3.2.2') do class Foo include Comparable def <=>(x) - 0 + x end end - - assert_true(Foo.new <= Foo.new) + assert_true(Foo.new <= 0) + assert_false(Foo.new <= 1) + assert_true(Foo.new <= -1) + assert_raise(ArgumentError){ Foo.new <= nil } end assert('Comparable#==', '15.3.3.2.3') do @@ -36,22 +40,26 @@ assert('Comparable#>', '15.3.3.2.4') do class Foo include Comparable def <=>(x) - 0 + x end end - - assert_false(Foo.new > Foo.new) + assert_false(Foo.new > 0) + assert_true(Foo.new > 1) + assert_false(Foo.new > -1) + assert_raise(ArgumentError){ Foo.new > nil } end assert('Comparable#>=', '15.3.3.2.5') do class Foo include Comparable def <=>(x) - 0 + x end end - - assert_true(Foo.new >= Foo.new) + assert_true(Foo.new >= 0) + assert_true(Foo.new >= 1) + assert_false(Foo.new >= -1) + assert_raise(ArgumentError){ Foo.new >= nil } end assert('Comparable#between?', '15.3.3.2.6') do diff --git a/test/t/float.rb b/test/t/float.rb index c817e01da..ded434320 100644 --- a/test/t/float.rb +++ b/test/t/float.rb @@ -130,6 +130,18 @@ assert('Float#round', '15.2.9.3.12') do assert_equal( 3, g) assert_float( 3.4, h) assert_float(3.423, i) + + assert_equal(42.0, 42.0.round(307)) + assert_equal(1.0e307, 1.0e307.round(2)) + + inf = 1.0/0.0 + assert_raise(FloatDomainError){ inf.round } + assert_raise(FloatDomainError){ inf.round(-1) } + assert_equal(inf, inf.round(1)) + nan = 0.0/0.0 + assert_raise(FloatDomainError){ nan.round } + assert_raise(FloatDomainError){ nan.round(-1) } + assert_true(nan.round(1).nan?) end assert('Float#to_f', '15.2.9.3.13') do diff --git a/test/t/true.rb b/test/t/true.rb index 3aebf43a1..e5da2112c 100644 --- a/test/t/true.rb +++ b/test/t/true.rb @@ -5,12 +5,14 @@ assert('TrueClass', '15.2.5') do assert_equal Class, TrueClass.class end -assert('TrueClass superclass', '15.2.5.2') do - assert_equal Object, TrueClass.superclass -end - assert('TrueClass true', '15.2.5.1') do assert_true true + assert_equal TrueClass, true.class + assert_false TrueClass.method_defined? :new +end + +assert('TrueClass superclass', '15.2.5.2') do + assert_equal Object, TrueClass.superclass end assert('TrueClass#&', '15.2.5.3.1') do diff --git a/test/t/unicode.rb b/test/t/unicode.rb new file mode 100644 index 000000000..a8e8c0e14 --- /dev/null +++ b/test/t/unicode.rb @@ -0,0 +1,60 @@ +# Test of the \u notation + +assert('bare \u notation test') do + # Mininum and maximum one byte characters + assert_equal("\u0000", "\x00") + assert_equal("\u007F", "\x7F") + + # Mininum and maximum two byte characters + assert_equal("\u0080", "\xC2\x80") + assert_equal("\u07FF", "\xDF\xBF") + + # Mininum and maximum three byte characters + assert_equal("\u0800", "\xE0\xA0\x80") + assert_equal("\uFFFF", "\xEF\xBF\xBF") + + # Four byte characters require the \U notation +end + +assert('braced \u notation test') do + # Mininum and maximum one byte characters + assert_equal("\u{0000}", "\x00") + assert_equal("\u{007F}", "\x7F") + + # Mininum and maximum two byte characters + assert_equal("\u{0080}", "\xC2\x80") + assert_equal("\u{07FF}", "\xDF\xBF") + + # Mininum and maximum three byte characters + assert_equal("\u{0800}", "\xE0\xA0\x80") + assert_equal("\u{FFFF}", "\xEF\xBF\xBF") + + # Mininum and maximum four byte characters + assert_equal("\u{10000}", "\xF0\x90\x80\x80") + assert_equal("\u{10FFFF}", "\xF4\x8F\xBF\xBF") +end + +# Test regular expressions only if implemented +begin + Regexp + have_regexp = true +rescue NameError + have_regexp = false +end +if have_regexp then + assert('Testing \u in regular expressions') do + # The regular expression uses the unbraced notation where the string uses + # the braced notation, and vice versa, so these tests will fail if the \u + # modification is not applied + + # Test of unbraced \u notation in a regular expression + assert_false(/\u0300/ =~ "\u{02FF}") + assert_true( /\u0300/ =~ "\u{0300}") + assert_false(/\u0300/ =~ "\u{0301}") + + # Test of braced \u notation in a regular expression + assert_false(/\u{0300}/ =~ "\u02FF") + assert_true( /\u{0300}/ =~ "\u0300") + assert_false(/\u{0300}/ =~ "\u0301") + end +end |
