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 and !(args.length == 1 and Array === args[0] and args[0].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) fails = [] syntax = Sentence.expand_syntax(syntax) Sentence.each(syntax, *args) {|t| begin yield t rescue MiniTest::Assertion => e fails << e.message end } assert(fails.empty?, proc {fails.join("\n--------\n")}) 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 end