mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	 976becf7eb
			
		
	
	
		976becf7eb
		
	
	
	
	
		
			
			* vm_eval.c (rb_yield_lambda): new function which yields an array to a proc and splat to a lambda. mainly for Enumerable only. * vm_args.c (setup_parameters_complex): remove special lambda splatting for [Bug #9605]. [ruby-core:77065] [Bug #12705] * vm_insnhelper.c (vm_callee_setup_block_arg): ditto. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@58019 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
		
			
				
	
	
		
			425 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			425 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # frozen_string_literal: false
 | |
| require 'test/unit'
 | |
| require 'stringio'
 | |
| 
 | |
| class TestRubyYield < Test::Unit::TestCase
 | |
| 
 | |
|   def test_ary_each
 | |
|     ary = [1]
 | |
|     ary.each {|a, b, c, d| assert_equal [1,nil,nil,nil], [a,b,c,d] }
 | |
|     ary.each {|a, b, c| assert_equal [1,nil,nil], [a,b,c] }
 | |
|     ary.each {|a, b| assert_equal [1,nil], [a,b] }
 | |
|     ary.each {|a| assert_equal 1, a }
 | |
|   end
 | |
| 
 | |
|   def test_hash_each
 | |
|     h = {:a => 1}
 | |
|     h.each do |k, v|
 | |
|       assert_equal :a, k
 | |
|       assert_equal 1, v
 | |
|     end
 | |
|     h.each do |kv|
 | |
|       assert_equal [:a, 1], kv
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   def test_yield_0
 | |
|     assert_equal 1, iter0 { 1 }
 | |
|     assert_equal 2, iter0 { 2 }
 | |
|   end
 | |
| 
 | |
|   def iter0
 | |
|     yield
 | |
|   end
 | |
| 
 | |
|   def test_yield_1
 | |
|     iter1([]) {|a, b| assert_equal [nil,nil], [a, b] }
 | |
|     iter1([1]) {|a, b| assert_equal [1,nil], [a, b] }
 | |
|     iter1([1, 2]) {|a, b| assert_equal [1,2], [a,b] }
 | |
|     iter1([1, 2, 3]) {|a, b| assert_equal [1,2], [a,b] }
 | |
| 
 | |
|     iter1([]) {|a| assert_equal [], a }
 | |
|     iter1([1]) {|a| assert_equal [1], a }
 | |
|     iter1([1, 2]) {|a| assert_equal [1,2], a }
 | |
|     iter1([1, 2, 3]) {|a| assert_equal [1,2,3], a }
 | |
|   end
 | |
| 
 | |
|   def iter1(args)
 | |
|     yield args
 | |
|   end
 | |
| 
 | |
|   def test_yield2
 | |
|     def iter2_1() yield 1, *[2, 3] end
 | |
|     iter2_1 {|a, b, c| assert_equal [1,2,3], [a,b,c] }
 | |
|     def iter2_2() yield 1, *[] end
 | |
|     iter2_2 {|a, b, c| assert_equal [1,nil,nil], [a,b,c] }
 | |
|     def iter2_3() yield 1, *[2] end
 | |
|     iter2_3 {|a, b, c| assert_equal [1,2,nil], [a,b,c] }
 | |
|   end
 | |
| 
 | |
|   def test_yield_nested
 | |
|     [[1, [2, 3]]].each {|a, (b, c)|
 | |
|       assert_equal [1,2,3], [a,b,c]
 | |
|     }
 | |
|     [[1, [2, 3]]].map {|a, (b, c)|
 | |
|       assert_equal [1,2,3], [a,b,c]
 | |
|     }
 | |
|   end
 | |
| 
 | |
|   def test_with_enum
 | |
|     obj = Object.new
 | |
|     def obj.each
 | |
|       yield(*[])
 | |
|     end
 | |
|     obj.each{|*v| assert_equal([], [], '[ruby-dev:32392]')}
 | |
|     obj.to_enum.each{|*v| assert_equal([], [], '[ruby-dev:32392]')}
 | |
|   end
 | |
| 
 | |
|   def block_args_unleashed
 | |
|     yield(1,2,3,4,5)
 | |
|   end
 | |
| 
 | |
|   def test_block_args_unleashed
 | |
|     r = block_args_unleashed {|a,b=1,*c,d,e|
 | |
|       [a,b,c,d,e]
 | |
|     }
 | |
|     assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]")
 | |
|   end
 | |
| end
 | |
| 
 | |
| require_relative 'sentence'
 | |
| class TestRubyYieldGen < Test::Unit::TestCase
 | |
|   Syntax = {
 | |
|     :exp => [["0"],
 | |
|              ["nil"],
 | |
|              ["false"],
 | |
|              ["[]"],
 | |
|              ["[",:exps,"]"]],
 | |
|     :exps => [[:exp],
 | |
|               [:exp,",",:exps]],
 | |
|     :opt_block_param => [[],
 | |
|                          [:block_param_def]],
 | |
|     :block_param_def => [['|', '|'],
 | |
|                          ['|', :block_param, '|']],
 | |
|     :block_param => [[:f_arg, ",", :f_rest_arg, :opt_f_block_arg],
 | |
|                      [:f_arg, ","],
 | |
|                      [:f_arg, ',', :f_rest_arg, ",", :f_arg, :opt_f_block_arg],
 | |
|                      [:f_arg, :opt_f_block_arg],
 | |
|                      [:f_rest_arg, :opt_f_block_arg],
 | |
|                      [:f_rest_arg, ',', :f_arg, :opt_f_block_arg],
 | |
|                      [:f_block_arg]],
 | |
|     :f_arg => [[:f_arg_item],
 | |
|                [:f_arg, ',', :f_arg_item]],
 | |
|     :f_rest_arg => [['*', "var"],
 | |
|                     ['*']],
 | |
|     :opt_f_block_arg => [[',', :f_block_arg],
 | |
|                          []],
 | |
|     :f_block_arg => [['&', 'var']],
 | |
|     :f_arg_item => [[:f_norm_arg],
 | |
|                     ['(', :f_margs, ')']],
 | |
|     :f_margs => [[:f_marg_list],
 | |
|                  [:f_marg_list, ',', '*', :f_norm_arg],
 | |
|                  [:f_marg_list, ',', '*', :f_norm_arg, ',', :f_marg_list],
 | |
|                  [:f_marg_list, ',', '*'],
 | |
|                  [:f_marg_list, ',', '*',              ',', :f_marg_list],
 | |
|                  [                   '*', :f_norm_arg],
 | |
|                  [                   '*', :f_norm_arg, ',', :f_marg_list],
 | |
|                  [                   '*'],
 | |
|                  [                   '*',              ',', :f_marg_list]],
 | |
|     :f_marg_list => [[:f_marg],
 | |
|                      [:f_marg_list, ',', :f_marg]],
 | |
|     :f_marg => [[:f_norm_arg],
 | |
|                 ['(', :f_margs, ')']],
 | |
|     :f_norm_arg => [['var']],
 | |
| 
 | |
|     :command_args => [[:open_args]],
 | |
|     :open_args => [[' ',:call_args],
 | |
|                    ['(', ')'],
 | |
|                    ['(', :call_args2, ')']],
 | |
|     :call_args =>  [[:command],
 | |
|                     [           :args,               :opt_block_arg],
 | |
|                     [                       :assocs, :opt_block_arg],
 | |
|                     [           :args, ',', :assocs, :opt_block_arg],
 | |
|                     [                                    :block_arg]],
 | |
|     :call_args2 => [[:arg, ',', :args,               :opt_block_arg],
 | |
|                     [:arg, ',',                          :block_arg],
 | |
|                     [                       :assocs, :opt_block_arg],
 | |
|                     [:arg, ',',             :assocs, :opt_block_arg],
 | |
|                     [:arg, ',', :args, ',', :assocs, :opt_block_arg],
 | |
|                     [                                    :block_arg]],
 | |
| 
 | |
|     :command_args_noblock => [[:open_args_noblock]],
 | |
|     :open_args_noblock => [[' ',:call_args_noblock],
 | |
|                    ['(', ')'],
 | |
|                    ['(', :call_args2_noblock, ')']],
 | |
|     :call_args_noblock =>  [[:command],
 | |
|                     [           :args],
 | |
|                     [                       :assocs],
 | |
|                     [           :args, ',', :assocs]],
 | |
|     :call_args2_noblock => [[:arg, ',', :args],
 | |
|                             [                       :assocs],
 | |
|                             [:arg, ',',             :assocs],
 | |
|                             [:arg, ',', :args, ',', :assocs]],
 | |
| 
 | |
|     :command => [],
 | |
|     :args => [[:arg],
 | |
|               ["*",:arg],
 | |
|               [:args,",",:arg],
 | |
|               [:args,",","*",:arg]],
 | |
|     :arg => [[:exp]],
 | |
|     :assocs => [[:assoc],
 | |
|                 [:assocs, ',', :assoc]],
 | |
|     :assoc => [[:arg, '=>', :arg],
 | |
|                ['label', ':', :arg]],
 | |
|     :opt_block_arg => [[',', :block_arg],
 | |
|                        []],
 | |
|     :block_arg => [['&', :arg]],
 | |
|     #:test => [['def m() yield', :command_args_noblock, ' end; r = m {', :block_param_def, 'vars', '}; undef m; r']]
 | |
|     :test_proc => [['def m() yield', :command_args_noblock, ' end; r = m {', :block_param_def, 'vars', '}; undef m; r']],
 | |
|     :test_lambda => [['def m() yield', :command_args_noblock, ' end; r = m(&lambda {', :block_param_def, 'vars', '}); undef m; r']],
 | |
|     :test_enum => [['o = Object.new; def o.each() yield', :command_args_noblock, ' end; r1 = r2 = nil; o.each {|*x| r1 = x }; o.to_enum.each {|*x| r2 = x }; [r1, r2]']]
 | |
|   }
 | |
| 
 | |
|   def rename_var(obj)
 | |
|     vars = []
 | |
|     r = obj.subst('var') {
 | |
|       var = "v#{vars.length}"
 | |
|       vars << var
 | |
|       var
 | |
|     }
 | |
|     return r, vars
 | |
|   end
 | |
| 
 | |
|   def split_by_comma(ary)
 | |
|     return [] if ary.empty?
 | |
|     result = [[]]
 | |
|     ary.each {|e|
 | |
|       if e == ','
 | |
|         result << []
 | |
|       else
 | |
|         result.last << e
 | |
|       end
 | |
|     }
 | |
|     result
 | |
|   end
 | |
| 
 | |
|   def emu_return_args(*vs)
 | |
|     vs
 | |
|   end
 | |
| 
 | |
|   def emu_eval_args(args)
 | |
|     if args.last == []
 | |
|       args = args[0...-1]
 | |
|     end
 | |
|     code = "emu_return_args(#{args.map {|a| a.join('') }.join(",")})"
 | |
|     eval code, nil, 'generated_code_in_emu_eval_args'
 | |
|   end
 | |
| 
 | |
|   def emu_bind_single(arg, param, result_binding)
 | |
|     #p [:emu_bind_single, arg, param]
 | |
|     if param.length == 1 && String === param[0] && /\A[a-z0-9]+\z/ =~ param[0]
 | |
|       result_binding[param[0]] = arg
 | |
|     elsif param.length == 1 && Array === param[0] && param[0][0] == '(' && param[0][-1] == ')'
 | |
|       arg = [arg] unless Array === arg
 | |
|       emu_bind_params(arg, split_by_comma(param[0][1...-1]), false, result_binding)
 | |
|     else
 | |
|       raise "unexpected param: #{param.inspect}"
 | |
|     end
 | |
|     result_binding
 | |
|   end
 | |
| 
 | |
|   def emu_bind_params(args, params, islambda, result_binding={})
 | |
|     #p [:emu_bind_params, args, params]
 | |
|     if params.last == [] # extra comma
 | |
|       params.pop
 | |
|     end
 | |
| 
 | |
|     star_index = nil
 | |
|     params.each_with_index {|par, i|
 | |
|       star_index = i if par[0] == '*'
 | |
|     }
 | |
| 
 | |
|     if islambda
 | |
|       if star_index
 | |
|         if args.length < params.length - 1
 | |
|           throw :emuerror, ArgumentError
 | |
|         end
 | |
|       else
 | |
|         if args.length != params.length
 | |
|           throw :emuerror, ArgumentError
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # TRICK #2 : adjust mismatch on number of arguments
 | |
|     if star_index
 | |
|       pre_params = params[0...star_index]
 | |
|       rest_param = params[star_index]
 | |
|       post_params = params[(star_index+1)..-1]
 | |
|       pre_params.each {|par| emu_bind_single(args.shift, par, result_binding) }
 | |
|       if post_params.length <= args.length
 | |
|         post_params.reverse_each {|par| emu_bind_single(args.pop, par, result_binding) }
 | |
|       else
 | |
|         post_params.each {|par| emu_bind_single(args.shift, par, result_binding) }
 | |
|       end
 | |
|       if rest_param != ['*']
 | |
|         emu_bind_single(args, rest_param[1..-1], result_binding)
 | |
|       end
 | |
|     else
 | |
|       params.each_with_index {|par, i|
 | |
|         emu_bind_single(args[i], par, result_binding)
 | |
|       }
 | |
|     end
 | |
| 
 | |
|     #p [args, params, result_binding]
 | |
| 
 | |
|     result_binding
 | |
|   end
 | |
| 
 | |
|   def emu_bind(t, islambda)
 | |
|     #puts
 | |
|     #p t
 | |
|     command_args_noblock = t[1]
 | |
|     block_param_def = t[3]
 | |
|     command_args_noblock = command_args_noblock.expand {|a| !(a[0] == '[' && a[-1] == ']') }
 | |
|     block_param_def = block_param_def.expand {|a| !(a[0] == '(' && a[-1] == ')') }
 | |
| 
 | |
|     if command_args_noblock.to_a[0] == ' '
 | |
|       args = command_args_noblock.to_a[1..-1]
 | |
|     elsif command_args_noblock.to_a[0] == '(' && command_args_noblock.to_a[-1] == ')'
 | |
|       args = command_args_noblock.to_a[1...-1]
 | |
|     else
 | |
|       raise "unexpected command_args_noblock: #{command_args_noblock.inspect}"
 | |
|     end
 | |
|     args = emu_eval_args(split_by_comma(args))
 | |
| 
 | |
|     params = block_param_def.to_a[1...-1]
 | |
|     params = split_by_comma(params)
 | |
| 
 | |
|     #p [:emu0, args, params]
 | |
| 
 | |
|     result_binding = {}
 | |
| 
 | |
|     if params.last && params.last[0] == '&'
 | |
|       result_binding[params.last[1]] = nil
 | |
|       params.pop
 | |
|     end
 | |
| 
 | |
|     if !islambda
 | |
|       # TRICK #1 : single array argument is expanded if there are two or more params.
 | |
|       # * block parameter is not counted.
 | |
|       # * extra comma after single param forces the expansion.
 | |
|       if args.length == 1 && Array === args[0] && 1 < params.length
 | |
|         args = args[0]
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     emu_bind_params(args, params, islambda, result_binding)
 | |
|     #p result_binding
 | |
|     result_binding
 | |
|   end
 | |
| 
 | |
|   def emu(t, vars, islambda)
 | |
|     catch(:emuerror) {
 | |
|       emu_binding = emu_bind(t, islambda)
 | |
|       vars.map {|var| emu_binding.fetch(var, "NOVAL") }
 | |
|     }
 | |
|   end
 | |
| 
 | |
|   def disable_stderr
 | |
|     begin
 | |
|       save_stderr = $stderr
 | |
|       $stderr = StringIO.new
 | |
|       yield
 | |
|     ensure
 | |
|       $stderr = save_stderr
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   def check_nofork(t, islambda=false)
 | |
|     t, vars = rename_var(t)
 | |
|     t = t.subst('vars') { " [#{vars.join(",")}]" }
 | |
|     emu_values = emu(t, vars, islambda)
 | |
|     s = t.to_s
 | |
|     o = Object.new
 | |
|     #print "#{s}\t\t"
 | |
|     #STDOUT.flush
 | |
|     eval_values = disable_stderr {
 | |
|       begin
 | |
|         o.instance_eval(s, 'generated_code_in_check_nofork')
 | |
|       rescue ArgumentError
 | |
|         ArgumentError
 | |
|       end
 | |
|     }
 | |
|     #success = emu_values == eval_values ? 'succ' : 'fail'
 | |
|     #puts "eval:#{vs_ev.inspect[1...-1].delete(' ')}\temu:#{vs_emu.inspect[1...-1].delete(' ')}\t#{success}"
 | |
|     assert_equal(emu_values, eval_values, s)
 | |
|   end
 | |
| 
 | |
|   def assert_all_sentences(syntax, *args)
 | |
|     syntax = Sentence.expand_syntax(syntax)
 | |
|     all_assertions do |a|
 | |
|       Sentence.each(syntax, *args) {|t|
 | |
|         a.for(t) {yield t}
 | |
|       }
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   def test_yield
 | |
|     assert_all_sentences(Syntax, :test_proc, 4) {|t|
 | |
|       check_nofork(t)
 | |
|     }
 | |
|   end
 | |
| 
 | |
|   def test_yield_lambda
 | |
|     assert_all_sentences(Syntax, :test_lambda, 4) {|t|
 | |
|       check_nofork(t, true)
 | |
|     }
 | |
|   end
 | |
| 
 | |
|   def test_yield_enum
 | |
|     assert_all_sentences(Syntax, :test_enum, 4) {|t|
 | |
|       code = t.to_s
 | |
|       r1, r2 = disable_stderr {
 | |
|         eval(code, nil, 'generated_code_in_test_yield_enum')
 | |
|       }
 | |
|       assert_equal(r1, r2, "#{t}")
 | |
|     }
 | |
|   end
 | |
| 
 | |
|   def test_block_with_mock
 | |
|     y = Object.new
 | |
|     def y.s(a)
 | |
|       yield(a)
 | |
|     end
 | |
|     m = Object.new
 | |
|     def m.method_missing(*a)
 | |
|       super
 | |
|     end
 | |
|     assert_equal [m, nil], y.s(m){|a,b|[a,b]}
 | |
|   end
 | |
| 
 | |
|   def test_block_cached_argc
 | |
|     # [Bug #11451]
 | |
|     assert_separately([], <<-"end;")
 | |
|       class Yielder
 | |
|         def each
 | |
|           yield :x, :y, :z
 | |
|         end
 | |
|       end
 | |
|       class Getter1
 | |
|         include Enumerable
 | |
|         def each(&block)
 | |
|           Yielder.new.each(&block)
 | |
|         end
 | |
|       end
 | |
|       class Getter2
 | |
|         include Enumerable
 | |
|         def each
 | |
|           Yielder.new.each { |a, b, c, d| yield(a) }
 | |
|         end
 | |
|       end
 | |
|       Getter1.new.map{Getter2.new.each{|x|}}
 | |
|     end;
 | |
|   end
 | |
| end
 |