* 2022-01-18 [ci skip]
[ruby-80x24.org.git] / test / ruby / test_yield.rb
blob9b2b2f37e06e041c3cd40c7e2ba8cf51f5fb8a9f
1 # frozen_string_literal: false
2 require 'test/unit'
3 require 'stringio'
5 class TestRubyYield < Test::Unit::TestCase
7   def test_ary_each
8     ary = [1]
9     ary.each {|a, b, c, d| assert_equal [1,nil,nil,nil], [a,b,c,d] }
10     ary.each {|a, b, c| assert_equal [1,nil,nil], [a,b,c] }
11     ary.each {|a, b| assert_equal [1,nil], [a,b] }
12     ary.each {|a| assert_equal 1, a }
13   end
15   def test_hash_each
16     h = {:a => 1}
17     h.each do |k, v|
18       assert_equal :a, k
19       assert_equal 1, v
20     end
21     h.each do |kv|
22       assert_equal [:a, 1], kv
23     end
24   end
26   def test_yield_0
27     assert_equal 1, iter0 { 1 }
28     assert_equal 2, iter0 { 2 }
29   end
31   def iter0
32     yield
33   end
35   def test_yield_1
36     iter1([]) {|a, b| assert_equal [nil,nil], [a, b] }
37     iter1([1]) {|a, b| assert_equal [1,nil], [a, b] }
38     iter1([1, 2]) {|a, b| assert_equal [1,2], [a,b] }
39     iter1([1, 2, 3]) {|a, b| assert_equal [1,2], [a,b] }
41     iter1([]) {|a| assert_equal [], a }
42     iter1([1]) {|a| assert_equal [1], a }
43     iter1([1, 2]) {|a| assert_equal [1,2], a }
44     iter1([1, 2, 3]) {|a| assert_equal [1,2,3], a }
45   end
47   def iter1(args)
48     yield args
49   end
51   def test_yield2
52     def iter2_1() yield 1, *[2, 3] end
53     iter2_1 {|a, b, c| assert_equal [1,2,3], [a,b,c] }
54     def iter2_2() yield 1, *[] end
55     iter2_2 {|a, b, c| assert_equal [1,nil,nil], [a,b,c] }
56     def iter2_3() yield 1, *[2] end
57     iter2_3 {|a, b, c| assert_equal [1,2,nil], [a,b,c] }
58   end
60   def test_yield_nested
61     [[1, [2, 3]]].each {|a, (b, c)|
62       assert_equal [1,2,3], [a,b,c]
63     }
64     [[1, [2, 3]]].map {|a, (b, c)|
65       assert_equal [1,2,3], [a,b,c]
66     }
67   end
69   def test_with_enum
70     obj = Object.new
71     def obj.each
72       yield(*[])
73     end
74     obj.each{|*v| assert_equal([], [], '[ruby-dev:32392]')}
75     obj.to_enum.each{|*v| assert_equal([], [], '[ruby-dev:32392]')}
76   end
78   def block_args_unleashed
79     yield(1,2,3,4,5)
80   end
82   def test_block_args_unleashed
83     r = block_args_unleashed {|a,b=1,*c,d,e|
84       [a,b,c,d,e]
85     }
86     assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]")
87   end
88 end
90 require_relative 'sentence'
91 class TestRubyYieldGen < Test::Unit::TestCase
92   Syntax = {
93     :exp => [["0"],
94              ["nil"],
95              ["false"],
96              ["[]"],
97              ["[",:exps,"]"]],
98     :exps => [[:exp],
99               [:exp,",",:exps]],
100     :opt_block_param => [[],
101                          [:block_param_def]],
102     :block_param_def => [['|', '|'],
103                          ['|', :block_param, '|']],
104     :block_param => [[:f_arg, ",", :f_rest_arg, :opt_f_block_arg],
105                      [:f_arg, ","],
106                      [:f_arg, ',', :f_rest_arg, ",", :f_arg, :opt_f_block_arg],
107                      [:f_arg, :opt_f_block_arg],
108                      [:f_rest_arg, :opt_f_block_arg],
109                      [:f_rest_arg, ',', :f_arg, :opt_f_block_arg],
110                      [:f_block_arg]],
111     :f_arg => [[:f_arg_item],
112                [:f_arg, ',', :f_arg_item]],
113     :f_rest_arg => [['*', "var"],
114                     ['*']],
115     :opt_f_block_arg => [[',', :f_block_arg],
116                          []],
117     :f_block_arg => [['&', 'var']],
118     :f_arg_item => [[:f_norm_arg],
119                     ['(', :f_margs, ')']],
120     :f_margs => [[:f_marg_list],
121                  [:f_marg_list, ',', '*', :f_norm_arg],
122                  [:f_marg_list, ',', '*', :f_norm_arg, ',', :f_marg_list],
123                  [:f_marg_list, ',', '*'],
124                  [:f_marg_list, ',', '*',              ',', :f_marg_list],
125                  [                   '*', :f_norm_arg],
126                  [                   '*', :f_norm_arg, ',', :f_marg_list],
127                  [                   '*'],
128                  [                   '*',              ',', :f_marg_list]],
129     :f_marg_list => [[:f_marg],
130                      [:f_marg_list, ',', :f_marg]],
131     :f_marg => [[:f_norm_arg],
132                 ['(', :f_margs, ')']],
133     :f_norm_arg => [['var']],
135     :command_args => [[:open_args]],
136     :open_args => [[' ',:call_args],
137                    ['(', ')'],
138                    ['(', :call_args2, ')']],
139     :call_args =>  [[:command],
140                     [           :args,               :opt_block_arg],
141                     [                       :assocs, :opt_block_arg],
142                     [           :args, ',', :assocs, :opt_block_arg],
143                     [                                    :block_arg]],
144     :call_args2 => [[:arg, ',', :args,               :opt_block_arg],
145                     [:arg, ',',                          :block_arg],
146                     [                       :assocs, :opt_block_arg],
147                     [:arg, ',',             :assocs, :opt_block_arg],
148                     [:arg, ',', :args, ',', :assocs, :opt_block_arg],
149                     [                                    :block_arg]],
151     :command_args_noblock => [[:open_args_noblock]],
152     :open_args_noblock => [[' ',:call_args_noblock],
153                    ['(', ')'],
154                    ['(', :call_args2_noblock, ')']],
155     :call_args_noblock =>  [[:command],
156                     [           :args],
157                     [                       :assocs],
158                     [           :args, ',', :assocs]],
159     :call_args2_noblock => [[:arg, ',', :args],
160                             [                       :assocs],
161                             [:arg, ',',             :assocs],
162                             [:arg, ',', :args, ',', :assocs]],
164     :command => [],
165     :args => [[:arg],
166               ["*",:arg],
167               [:args,",",:arg],
168               [:args,",","*",:arg]],
169     :arg => [[:exp]],
170     :assocs => [[:assoc],
171                 [:assocs, ',', :assoc]],
172     :assoc => [[:arg, '=>', :arg],
173                ['label', ':', :arg]],
174     :opt_block_arg => [[',', :block_arg],
175                        []],
176     :block_arg => [['&', :arg]],
177     #:test => [['def m() yield', :command_args_noblock, ' end; r = m {', :block_param_def, 'vars', '}; undef m; r']]
178     :test_proc => [['def m() yield', :command_args_noblock, ' end; r = m {', :block_param_def, 'vars', '}; undef m; r']],
179     :test_lambda => [['def m() yield', :command_args_noblock, ' end; r = m(&lambda {', :block_param_def, 'vars', '}); undef m; r']],
180     :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]']]
181   }
183   def rename_var(obj)
184     vars = []
185     r = obj.subst('var') {
186       var = "v#{vars.length}"
187       vars << var
188       var
189     }
190     return r, vars
191   end
193   def split_by_comma(ary)
194     return [] if ary.empty?
195     result = [[]]
196     ary.each {|e|
197       if e == ','
198         result << []
199       else
200         result.last << e
201       end
202     }
203     result
204   end
206   def emu_return_args(*vs)
207     vs
208   end
210   def emu_eval_args(args)
211     if args.last == []
212       args = args[0...-1]
213     end
214     code = "emu_return_args(#{args.map {|a| a.join('') }.join(",")})"
215     eval code, nil, 'generated_code_in_emu_eval_args'
216   end
218   def emu_bind_single(arg, param, result_binding)
219     #p [:emu_bind_single, arg, param]
220     if param.length == 1 && String === param[0] && /\A[a-z0-9]+\z/ =~ param[0]
221       result_binding[param[0]] = arg
222     elsif param.length == 1 && Array === param[0] && param[0][0] == '(' && param[0][-1] == ')'
223       arg = [arg] unless Array === arg
224       emu_bind_params(arg, split_by_comma(param[0][1...-1]), false, result_binding)
225     else
226       raise "unexpected param: #{param.inspect}"
227     end
228     result_binding
229   end
231   def emu_bind_params(args, params, islambda, result_binding={})
232     #p [:emu_bind_params, args, params]
233     if params.last == [] # extra comma
234       params.pop
235     end
237     star_index = nil
238     params.each_with_index {|par, i|
239       star_index = i if par[0] == '*'
240     }
242     if islambda
243       if star_index
244         if args.length < params.length - 1
245           throw :emuerror, ArgumentError
246         end
247       else
248         if args.length != params.length
249           throw :emuerror, ArgumentError
250         end
251       end
252     end
254     # TRICK #2 : adjust mismatch on number of arguments
255     if star_index
256       pre_params = params[0...star_index]
257       rest_param = params[star_index]
258       post_params = params[(star_index+1)..-1]
259       pre_params.each {|par| emu_bind_single(args.shift, par, result_binding) }
260       if post_params.length <= args.length
261         post_params.reverse_each {|par| emu_bind_single(args.pop, par, result_binding) }
262       else
263         post_params.each {|par| emu_bind_single(args.shift, par, result_binding) }
264       end
265       if rest_param != ['*']
266         emu_bind_single(args, rest_param[1..-1], result_binding)
267       end
268     else
269       params.each_with_index {|par, i|
270         emu_bind_single(args[i], par, result_binding)
271       }
272     end
274     #p [args, params, result_binding]
276     result_binding
277   end
279   def emu_bind(t, islambda)
280     #puts
281     #p t
282     command_args_noblock = t[1]
283     block_param_def = t[3]
284     command_args_noblock = command_args_noblock.expand {|a| !(a[0] == '[' && a[-1] == ']') }
285     block_param_def = block_param_def.expand {|a| !(a[0] == '(' && a[-1] == ')') }
287     if command_args_noblock.to_a[0] == ' '
288       args = command_args_noblock.to_a[1..-1]
289     elsif command_args_noblock.to_a[0] == '(' && command_args_noblock.to_a[-1] == ')'
290       args = command_args_noblock.to_a[1...-1]
291     else
292       raise "unexpected command_args_noblock: #{command_args_noblock.inspect}"
293     end
294     args = emu_eval_args(split_by_comma(args))
296     params = block_param_def.to_a[1...-1]
297     params = split_by_comma(params)
299     #p [:emu0, args, params]
301     result_binding = {}
303     if params.last && params.last[0] == '&'
304       result_binding[params.last[1]] = nil
305       params.pop
306     end
308     if !islambda
309       # TRICK #1 : single array argument is expanded if there are two or more params.
310       # * block parameter is not counted.
311       # * extra comma after single param forces the expansion.
312       if args.length == 1 && Array === args[0] && 1 < params.length
313         args = args[0]
314       end
315     end
317     emu_bind_params(args, params, islambda, result_binding)
318     #p result_binding
319     result_binding
320   end
322   def emu(t, vars, islambda)
323     catch(:emuerror) {
324       emu_binding = emu_bind(t, islambda)
325       vars.map {|var| emu_binding.fetch(var, "NOVAL") }
326     }
327   end
329   def disable_stderr
330     begin
331       save_stderr = $stderr
332       $stderr = StringIO.new
333       yield
334     ensure
335       $stderr = save_stderr
336     end
337   end
339   def check_nofork(t, islambda=false)
340     t, vars = rename_var(t)
341     t = t.subst('vars') { " [#{vars.join(",")}]" }
342     emu_values = emu(t, vars, islambda)
343     s = t.to_s
344     o = Object.new
345     #print "#{s}\t\t"
346     #STDOUT.flush
347     eval_values = disable_stderr {
348       begin
349         o.instance_eval(s, 'generated_code_in_check_nofork')
350       rescue ArgumentError
351         ArgumentError
352       end
353     }
354     #success = emu_values == eval_values ? 'succ' : 'fail'
355     #puts "eval:#{vs_ev.inspect[1...-1].delete(' ')}\temu:#{vs_emu.inspect[1...-1].delete(' ')}\t#{success}"
356     assert_equal(emu_values, eval_values, s)
357   end
359   def assert_all_sentences(syntax, *args)
360     syntax = Sentence.expand_syntax(syntax)
361     all_assertions do |a|
362       Sentence.each(syntax, *args) {|t|
363         a.for(t) {yield t}
364       }
365     end
366   end
368   def test_yield
369     assert_all_sentences(Syntax, :test_proc, 4) {|t|
370       check_nofork(t)
371     }
372   end
374   def test_yield_lambda
375     assert_all_sentences(Syntax, :test_lambda, 4) {|t|
376       check_nofork(t, true)
377     }
378   end
380   def test_yield_enum
381     assert_all_sentences(Syntax, :test_enum, 4) {|t|
382       code = t.to_s
383       r1, r2 = disable_stderr {
384         eval(code, nil, 'generated_code_in_test_yield_enum')
385       }
386       assert_equal(r1, r2, "#{t}")
387     }
388   end
390   def test_block_with_mock
391     y = Object.new
392     def y.s(a)
393       yield(a)
394     end
395     m = Object.new
396     def m.method_missing(*a)
397       super
398     end
399     assert_equal [m, nil], y.s(m){|a,b|[a,b]}
400   end
402   def test_block_cached_argc
403     # [Bug #11451]
404     assert_separately([], <<-"end;")
405       class Yielder
406         def each
407           yield :x, :y, :z
408         end
409       end
410       class Getter1
411         include Enumerable
412         def each(&block)
413           Yielder.new.each(&block)
414         end
415       end
416       class Getter2
417         include Enumerable
418         def each
419           Yielder.new.each { |a, b, c, d| yield(a) }
420         end
421       end
422       Getter1.new.map{Getter2.new.each{|x|}}
423     end;
424   end