casegen 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/casegen +11 -0
- data/src/agents/sets.rb +336 -0
- data/src/agents/sets/enum/by.rb +244 -0
- data/src/agents/sets/enum/cluster.rb +164 -0
- data/src/agents/sets/enum/inject.rb +50 -0
- data/src/agents/sets/enum/install.rb +73 -0
- data/src/agents/sets/enum/nest.rb +117 -0
- data/src/agents/sets/enum/op.rb +283 -0
- data/src/agents/sets/enum/pipe.rb +160 -0
- data/src/agents/sets/enum/tree.rb +442 -0
- data/src/calc.sample.txt +13 -0
- data/src/cart.sample.txt +33 -0
- data/src/casegen.rb +191 -0
- data/src/ruby_array.sample.txt +20 -0
- metadata +66 -0
data/bin/casegen
ADDED
data/src/agents/sets.rb
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../casegen"
|
2
|
+
$LOAD_PATH << "#{File.expand_path(File.join(File.dirname(__FILE__), 'sets'))}"
|
3
|
+
require 'enum/op'
|
4
|
+
|
5
|
+
class String
|
6
|
+
def to_u
|
7
|
+
self.gsub(/ /, '_')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module CLabs::CaseGen
|
12
|
+
class Set
|
13
|
+
attr_reader :name, :data
|
14
|
+
|
15
|
+
def initialize(name, data_array)
|
16
|
+
@name = name
|
17
|
+
@data = data_array
|
18
|
+
strip_data
|
19
|
+
end
|
20
|
+
|
21
|
+
def strip_data
|
22
|
+
@data.collect! do |datum| datum.strip end
|
23
|
+
end
|
24
|
+
|
25
|
+
def values
|
26
|
+
@data
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Sets < Agent
|
31
|
+
attr_accessor :sets, :combinations, :set_titles
|
32
|
+
|
33
|
+
def Sets.agent_id
|
34
|
+
"casegen:sets"
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(data, reference_agents=nil)
|
38
|
+
@data = data
|
39
|
+
@sets = []
|
40
|
+
parse_sets
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_sets
|
44
|
+
set_names = @data.scan(/^\s*(\w.*):/)
|
45
|
+
set_data = @data.scan(/:(.*)/)
|
46
|
+
sets = set_names.flatten.zip(set_data.flatten)
|
47
|
+
sets.each do |set_array|
|
48
|
+
@sets << Set.new(set_array[0], set_array[1].split(/, /))
|
49
|
+
end
|
50
|
+
generate_combinations
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_combinations
|
54
|
+
arrays = []
|
55
|
+
@set_titles = []
|
56
|
+
@sets.each do |set| arrays << set.data; @set_titles << set.name end
|
57
|
+
@combinations = all(*arrays)
|
58
|
+
end
|
59
|
+
|
60
|
+
def titles
|
61
|
+
@set_titles
|
62
|
+
end
|
63
|
+
|
64
|
+
def all(*args)
|
65
|
+
result = []
|
66
|
+
EnumerableOperator::Product.new(*args).each { |tuple|
|
67
|
+
result << tuple
|
68
|
+
}
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_names
|
73
|
+
names = []
|
74
|
+
@sets.each do |set| names << set.name end
|
75
|
+
names
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_by_name(setname)
|
79
|
+
@sets.detect do |set| set.name =~ /#{Regexp.escape(setname)}/ end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
class Criteria
|
85
|
+
attr_reader :set_names, :set_values, :equalities
|
86
|
+
|
87
|
+
def initialize(data)
|
88
|
+
@data = data
|
89
|
+
@equalities = {}
|
90
|
+
parse
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse
|
94
|
+
@data.split(/AND/).each do |bit|
|
95
|
+
set_name, set_value = bit.split(/==|=/)
|
96
|
+
set_name.strip!; set_value.strip!
|
97
|
+
if @equalities.keys.include?(set_name)
|
98
|
+
raise ParserException.new("Rule cannot have the same set <#{set_name}> equal to different values <#{@equalities[set_name]}, #{set_value}>")
|
99
|
+
end
|
100
|
+
@equalities[set_name] = set_value
|
101
|
+
end
|
102
|
+
@set_names = @equalities.keys
|
103
|
+
@set_values = @equalities.values
|
104
|
+
end
|
105
|
+
|
106
|
+
# hash keys should be valid set names and hash values should be valid
|
107
|
+
# set values in the named set
|
108
|
+
def match(hash)
|
109
|
+
# must match all equalities
|
110
|
+
@equalities.each_pair do |eq_name, eq_value|
|
111
|
+
actual_value = hash[eq_name]
|
112
|
+
return false if actual_value.nil?
|
113
|
+
return false if actual_value != eq_value
|
114
|
+
end
|
115
|
+
return true
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_s
|
119
|
+
@data
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Rule
|
124
|
+
attr_reader :criteria, :description, :data
|
125
|
+
|
126
|
+
def initialize(rule_data)
|
127
|
+
@data = rule_data
|
128
|
+
parse_rule
|
129
|
+
end
|
130
|
+
|
131
|
+
def parse_rule
|
132
|
+
data = @data.sub(self.class.regexp, '')
|
133
|
+
criteria_data, *@description = data.split(/\n/)
|
134
|
+
criteria_data.strip!
|
135
|
+
@criteria = Criteria.new(criteria_data)
|
136
|
+
@description = (@description.join("\n") + "\n").outdent.strip
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class ExcludeRule < Rule
|
141
|
+
def type_description
|
142
|
+
"exclude"
|
143
|
+
end
|
144
|
+
|
145
|
+
def ExcludeRule.regexp
|
146
|
+
/^exclude/i
|
147
|
+
end
|
148
|
+
|
149
|
+
def ExcludeRule.create(rule_data)
|
150
|
+
return ExcludeRule.new(rule_data) if rule_data =~ regexp
|
151
|
+
return nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class Rules < Agent
|
156
|
+
def Rules.agent_id
|
157
|
+
"casegen:rules"
|
158
|
+
end
|
159
|
+
|
160
|
+
def initialize(data, reference_agents=[])
|
161
|
+
@data = data
|
162
|
+
@agents = reference_agents
|
163
|
+
@rules = []
|
164
|
+
@rule_classes = []
|
165
|
+
ObjectSpace.each_object(Class) do |obj|
|
166
|
+
@rule_classes << obj if obj.ancestors.include?(Rule) && obj != Rule
|
167
|
+
end
|
168
|
+
parse_data
|
169
|
+
end
|
170
|
+
|
171
|
+
def parse_data
|
172
|
+
raw_rules = @data.split(/(?=^\S)/)
|
173
|
+
|
174
|
+
raw_rules.each do |rule|
|
175
|
+
@rule_classes.each do |rule_class|
|
176
|
+
@rules << rule_class.create(rule.strip)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
@rules.compact!
|
180
|
+
@rules.flatten!
|
181
|
+
validate_rules
|
182
|
+
end
|
183
|
+
|
184
|
+
def validate_rules
|
185
|
+
@agents.each do |agent|
|
186
|
+
if agent.class == Sets
|
187
|
+
@rules.each do |rule|
|
188
|
+
rule.criteria.equalities.each_pair do |set_name, set_value|
|
189
|
+
set = agent.set_by_name(set_name)
|
190
|
+
if set.nil?
|
191
|
+
raise ParserException.new("Invalid set name <#{set_name}> " +
|
192
|
+
"in rule <#{rule.criteria}>. Valid set names are <#{agent.set_names.join(', ')}>.")
|
193
|
+
end
|
194
|
+
if !set.values.include?(set_value)
|
195
|
+
raise ParserException.new("Invalid set value <#{set_value}> " +
|
196
|
+
"in rule <#{rule.criteria}>. Valid set values for <#{set.name}> " +
|
197
|
+
"are <#{set.values.join(', ')}>.")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def length
|
206
|
+
@rules.length
|
207
|
+
end
|
208
|
+
|
209
|
+
def [](index)
|
210
|
+
return @rules[index]
|
211
|
+
end
|
212
|
+
|
213
|
+
def each(&block)
|
214
|
+
@rules.each(&block)
|
215
|
+
end
|
216
|
+
|
217
|
+
def combinations
|
218
|
+
return @combinations if !@combinations.nil?
|
219
|
+
if @agents[0].class == Sets
|
220
|
+
agent = @agents[0]
|
221
|
+
@combinations = []
|
222
|
+
agent.combinations.each do |combo|
|
223
|
+
delete = false
|
224
|
+
combo_hash = {}
|
225
|
+
i = 0
|
226
|
+
# combo is an array of values, in the same order of the set_titles.
|
227
|
+
# combo_hash will have set names matched with set values
|
228
|
+
agent.set_titles.each do |title|
|
229
|
+
combo_hash[title] = combo[i]
|
230
|
+
i += 1
|
231
|
+
end
|
232
|
+
@rules.each do |rule|
|
233
|
+
delete |= rule.criteria.match(combo_hash)
|
234
|
+
end
|
235
|
+
@combinations << combo if !delete
|
236
|
+
end
|
237
|
+
return @combinations
|
238
|
+
end
|
239
|
+
return []
|
240
|
+
end
|
241
|
+
|
242
|
+
def titles
|
243
|
+
@agents[0].titles
|
244
|
+
end
|
245
|
+
|
246
|
+
def to_s
|
247
|
+
puts @agents[0].combinations.inspect if !@agents[0].nil?
|
248
|
+
puts
|
249
|
+
puts @rules.inspect
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
class ConsoleOutput < Agent
|
254
|
+
def ConsoleOutput.agent_id
|
255
|
+
"casegen:console"
|
256
|
+
end
|
257
|
+
|
258
|
+
def initialize(data, reference_agents)
|
259
|
+
@data = data
|
260
|
+
@agents = reference_agents
|
261
|
+
table = formatted_table([@agents[0].titles] + @agents[0].combinations)
|
262
|
+
table.each do |ary|
|
263
|
+
puts ary.join(' | ')
|
264
|
+
end
|
265
|
+
puts
|
266
|
+
@agents[0].each do |rule|
|
267
|
+
puts rule.data
|
268
|
+
puts
|
269
|
+
end if @agents[0].is_a?(Rules)
|
270
|
+
end
|
271
|
+
|
272
|
+
def formatted_table(combinations)
|
273
|
+
col_widths = []
|
274
|
+
formatted_tuples = []
|
275
|
+
combinations.each do |tuple|
|
276
|
+
col = 0
|
277
|
+
tuple.each do |item|
|
278
|
+
col_widths[col] = item.to_s.length if col_widths[col].to_i < item.to_s.length
|
279
|
+
col += 1
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
combinations.each do |tuple|
|
284
|
+
col = 0
|
285
|
+
formatted_tuples << tuple.collect { |item|
|
286
|
+
formatted = item.to_s.ljust(col_widths[col]) if !col_widths[col].nil?
|
287
|
+
col += 1
|
288
|
+
formatted
|
289
|
+
}
|
290
|
+
end
|
291
|
+
formatted_tuples
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
class RubyArrayOutput < Agent
|
296
|
+
def self.agent_id
|
297
|
+
"casegen:ruby_array"
|
298
|
+
end
|
299
|
+
|
300
|
+
def initialize(data, reference_agents, io=STDOUT)
|
301
|
+
@io = io
|
302
|
+
@struct_name = "Case"
|
303
|
+
@struct_name = data if !data.empty?
|
304
|
+
@agents = reference_agents
|
305
|
+
@agents.each do |agent| execute(agent) end
|
306
|
+
end
|
307
|
+
|
308
|
+
def execute(agent)
|
309
|
+
struct_header = "#{@struct_name} = Struct.new("
|
310
|
+
struct = ''
|
311
|
+
agent.titles.each do |title|
|
312
|
+
struct << ', ' if !struct.empty?
|
313
|
+
struct << ":#{title.to_u.downcase}"
|
314
|
+
end
|
315
|
+
struct << ')'
|
316
|
+
|
317
|
+
guts_header = 'cases = ['
|
318
|
+
guts = ''
|
319
|
+
agent.combinations.each do |combo|
|
320
|
+
guts << ",\n#{' ' * guts_header.length}" if !guts.empty?
|
321
|
+
guts << "#{@struct_name}.new#{combo.inspect.gsub(/\[/, '(').gsub(/\]/, ')')}"
|
322
|
+
end
|
323
|
+
@io.print(struct_header)
|
324
|
+
@io.print(struct)
|
325
|
+
@io.print("\n\n")
|
326
|
+
@io.print(guts_header)
|
327
|
+
@io.print(guts)
|
328
|
+
@io.print("]\n")
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
if __FILE__ == $0
|
334
|
+
sets = CLabs::CaseGen::Sets.new("a: 1, 2\nb: 3, 4")
|
335
|
+
puts sets.combinations
|
336
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Enumerable
|
4
|
+
|
5
|
+
class LinkedListDelegator
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_reader :first, :next_name, :next_map, :args
|
9
|
+
|
10
|
+
def initialize first, next_spec = nil, *args, &next_proc
|
11
|
+
@first = first
|
12
|
+
@args = args
|
13
|
+
|
14
|
+
case next_spec
|
15
|
+
when Symbol
|
16
|
+
@next_name = next_spec
|
17
|
+
when String
|
18
|
+
@next_name = next_spec.intern
|
19
|
+
when nil
|
20
|
+
@next_map = next_proc
|
21
|
+
else
|
22
|
+
unless next_spec.respond_to? :[]
|
23
|
+
raise ArgumentError,
|
24
|
+
"next_spec must be a method name or respond to []."
|
25
|
+
end
|
26
|
+
@next_map = next_spec
|
27
|
+
end
|
28
|
+
|
29
|
+
unless @next_name or @next_map
|
30
|
+
raise ArgumentError,
|
31
|
+
"no next-getter specified."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def each
|
36
|
+
cur = @first
|
37
|
+
if @next_name
|
38
|
+
next_name = @next_name
|
39
|
+
message = next_name, *@args
|
40
|
+
while cur
|
41
|
+
yield cur
|
42
|
+
cur = cur.send *message
|
43
|
+
end
|
44
|
+
elsif @next_map
|
45
|
+
next_map = @next_map
|
46
|
+
args = @args
|
47
|
+
while cur
|
48
|
+
yield cur
|
49
|
+
cur = next_map[cur, *args]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
class Object
|
61
|
+
def by next_spec = nil, *args, &next_proc
|
62
|
+
Enumerable::LinkedListDelegator.new self, next_spec, *args, &next_proc
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
=begin
|
68
|
+
|
69
|
+
==class Object
|
70
|
+
===instance method
|
71
|
+
---Object#by next_spec = nil, *args, &next_proc
|
72
|
+
|
73
|
+
Allows use of (({Enumerable})) methods, such as (({each})), (({collect})),
|
74
|
+
(({select})), etc., to iterate over arbitrary objects. The caller supplies a
|
75
|
+
way of calculating the successor of each object, such as an accessor method for
|
76
|
+
the next element of a linked list.
|
77
|
+
|
78
|
+
Object#by returns an (({Enumerable})) object whose (({each})) method
|
79
|
+
iterates over the sequence beginning with (({self})) and continuing as
|
80
|
+
specified by the arguments. Only the current element of the sequence is
|
81
|
+
kept in memory. No attempt is made to avoid cycles.
|
82
|
+
|
83
|
+
If (({next_spec})) is a string or symbol, (({next_proc})) is ignored and
|
84
|
+
(({next_spec})) is treated as a method name. This method name is sent, along
|
85
|
+
with arguments (({args})), to each element of the sequence to generate the next
|
86
|
+
element. The sequence terminates at the first element for which the method
|
87
|
+
returns (({nil})) or (({false})).
|
88
|
+
|
89
|
+
If (({next_spec})) is anything else, except (({nil})), (({next_proc})) is
|
90
|
+
ignored and (({next_spec})) is required to be an object that responds to
|
91
|
+
(({[]})), such as a proc or a hash. The (({[]})) method of (({next_spec}))
|
92
|
+
is called with each element of the sequence in turn as an argument, along
|
93
|
+
with (({args})), to generate the next element. The sequence terminates at
|
94
|
+
the first element for which (({[]})) returns (({nil})) or (({false})).
|
95
|
+
|
96
|
+
If (({next_spec})) is not given, or is (({nil})), a block is required. In this
|
97
|
+
case, the block is converted to a proc and iteration proceeds as in the
|
98
|
+
preceding paragraph.
|
99
|
+
|
100
|
+
The return value is not an array, but an (({Enumerable})) object that refers
|
101
|
+
to the original objects. In this sense, (({Object#by})) is a ((*delegator*)).
|
102
|
+
Typically, (({by})) is used with the (({for .. in ..})) construct, or
|
103
|
+
(equivalently) with (({each})), or with (({collect})), (({select})), and so on.
|
104
|
+
In these cases, the dependence on the original sequence does not matter. To get
|
105
|
+
the array of entries produced by (({by})) as an independent data structure, use
|
106
|
+
(({Enumerable#entries})) or (({Enumerable#to_a})).
|
107
|
+
|
108
|
+
===examples
|
109
|
+
|
110
|
+
require 'enum/by'
|
111
|
+
|
112
|
+
class A; end
|
113
|
+
class B < A; end
|
114
|
+
class C < B; end
|
115
|
+
|
116
|
+
for cl in C.by :superclass
|
117
|
+
print cl, " "
|
118
|
+
end
|
119
|
+
|
120
|
+
# prints: C B A Object
|
121
|
+
|
122
|
+
steps = proc { |x, incr, limit| y = x + incr; y <= limit ? y : nil }
|
123
|
+
p 0.by(steps, 10, 50).to_a
|
124
|
+
|
125
|
+
# prints: [0, 10, 20, 30, 40, 50]
|
126
|
+
|
127
|
+
See the end of the source file for more examples.
|
128
|
+
|
129
|
+
==version
|
130
|
+
|
131
|
+
Enumerable tools 1.6
|
132
|
+
|
133
|
+
The current version of this software can be found at
|
134
|
+
((<"http://redshift.sourceforge.net/enum
|
135
|
+
"|URL:http://redshift.sourceforge.net/enum>)).
|
136
|
+
|
137
|
+
==license
|
138
|
+
This software is distributed under the Ruby license.
|
139
|
+
See ((<"http://www.ruby-lang.org"|URL:http://www.ruby-lang.org>)).
|
140
|
+
|
141
|
+
==author
|
142
|
+
Joel VanderWerf,
|
143
|
+
((<vjoel@users.sourceforge.net|URL:mailto:vjoel@users.sourceforge.net>))
|
144
|
+
|
145
|
+
==acknowledgement
|
146
|
+
Thanks to David Alan Black for his helpful comments on the Ruby mailing
|
147
|
+
list
|
148
|
+
((<"http://blade.nagaokaut.ac.jp/ruby/ruby-talk
|
149
|
+
"|URL:http://blade.nagaokaut.ac.jp/ruby/ruby-talk>)).
|
150
|
+
|
151
|
+
=end
|
152
|
+
|
153
|
+
|
154
|
+
if __FILE__ == $0
|
155
|
+
|
156
|
+
class Foo
|
157
|
+
attr_reader :value, :next_foo
|
158
|
+
|
159
|
+
def initialize value, next_foo = nil
|
160
|
+
@value = value
|
161
|
+
@next_foo = next_foo
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
list = Foo.new(0, Foo.new(1, Foo.new(2, Foo.new(3))))
|
166
|
+
|
167
|
+
puts "Iterating over a linked list by method next_foo:"
|
168
|
+
for foo in list.by :next_foo
|
169
|
+
print "#{foo.value} "
|
170
|
+
end
|
171
|
+
print "\n\n"
|
172
|
+
|
173
|
+
puts "By proc { |foo| foo.next_foo.next_foo }:"
|
174
|
+
for foo in list.by { |foo| foo.next_foo.next_foo }
|
175
|
+
print "#{foo.value} "
|
176
|
+
end
|
177
|
+
print "\n\n"
|
178
|
+
|
179
|
+
puts "Down a tree, taking a random branch at each node:"
|
180
|
+
puts "First, with a proc:"
|
181
|
+
tree = [[0, [1, 2]], 3, [4, 5, [6, 7, 8]], 9]
|
182
|
+
at_random = proc { |x| x.kind_of?(Array) && x.at(rand(x.size)) }
|
183
|
+
for node in tree.by at_random
|
184
|
+
p node
|
185
|
+
end
|
186
|
+
puts "Second, with methods:"
|
187
|
+
class Object
|
188
|
+
def at_random
|
189
|
+
nil
|
190
|
+
end
|
191
|
+
end
|
192
|
+
class Array
|
193
|
+
def at_random
|
194
|
+
at(rand(size))
|
195
|
+
end
|
196
|
+
end
|
197
|
+
for node in tree.by :at_random
|
198
|
+
p node
|
199
|
+
end
|
200
|
+
puts
|
201
|
+
|
202
|
+
puts "With numbers (but watch out for cycles!):"
|
203
|
+
p 0.by { |x| x<10 ? x+1 : nil }.to_a
|
204
|
+
puts
|
205
|
+
|
206
|
+
puts "With numbers using a proc and an argument:"
|
207
|
+
steps = proc { |x, incr, limit| y = x + incr; y <= limit ? y : nil }
|
208
|
+
p 0.by(steps, 10, 50).to_a
|
209
|
+
puts
|
210
|
+
|
211
|
+
puts "Up the superclass relation:"
|
212
|
+
class A; end
|
213
|
+
class B < A; end
|
214
|
+
class C < B; end
|
215
|
+
for cl in C.by :superclass
|
216
|
+
print cl, " "
|
217
|
+
end
|
218
|
+
puts "\n\n"
|
219
|
+
|
220
|
+
puts "Popping down a stack:"
|
221
|
+
p [0,1,2,3,4].by { |y| y.pop; y != [] && y.dup }.entries
|
222
|
+
puts
|
223
|
+
|
224
|
+
puts "#by behaves correctly with self==nil"
|
225
|
+
p nil.by(:something).entries
|
226
|
+
puts
|
227
|
+
|
228
|
+
puts "By hash, or other class responding to []:"
|
229
|
+
h = { :a => :b, :b => :c, :c => :d }
|
230
|
+
p :a.by {|x| h[x]}.entries
|
231
|
+
puts "The same effect, but simpler:"
|
232
|
+
p :a.by(h).entries
|
233
|
+
puts
|
234
|
+
|
235
|
+
puts "Around and around we go..."
|
236
|
+
n = 0;
|
237
|
+
for x in 0.by([1,2,3,4,0])
|
238
|
+
n += 1
|
239
|
+
print x, " "
|
240
|
+
break if n > 12
|
241
|
+
end
|
242
|
+
puts "..."
|
243
|
+
|
244
|
+
end
|