/
enum.cr
391 lines (368 loc) · 9.91 KB
/
enum.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# Enum is the base type of all enums.
#
# An enum is a set of integer values, where each value has an associated name. For example:
#
# ```
# enum Color
# Red # 0
# Green # 1
# Blue # 2
# end
# ```
#
# Values start with the value 0 and are incremented by one, but can be overwritten.
#
# To get the underlying value you invoke value on it:
#
# ```
# Color::Green.value # => 1
# ```
#
# Each constant (member) in the enum has the type of the enum:
#
# ```
# typeof(Color::Red) # => Color
# ```
#
# ### Flags enum
#
# An enum can be marked with the @[Flags] attribute. This changes the default values:
#
# ```
# @[Flags]
# enum IOMode
# Read # 1
# Write # 2
# Async # 4
# end
# ```
#
# Additionally, some methods change their behaviour.
#
# ### Enums from integers
#
# An enum can be created from an integer:
#
# ```
# puts Color.new(1) # => prints "Green"
# ```
#
# Values that don't correspond to an enum's constants are allowed: the value will still be of type Color,
# but when printed you will get the underlying value:
#
# ```
# puts Color.new(10) # => prints "10"
# ```
#
# This method is mainly intended to convert integers from C to enums in Crystal.
#
# ### Question methods
#
# An enum automatically defines question methods for each member, using `String#underscore` for the
# method name. If the case of regular enums, this compares by equality (`==`). In the case of flags enums,
# this invokes `includes?`. For example:
#
# ```
# color = Color::Blue
# color.red? # => false
# color.blue? # => true
#
# mode = IOMode::Read | IOMode::Async
# mode.read? # => true
# mode.write? # => false
# mode.async? # => true
# ```
#
# This is very convenient in `case` expressions:
#
# ```
# case color
# when .red?
# puts "Got red"
# when .blue?
# puts "Got blue"
# end
# ```
struct Enum
include Comparable(self)
# Appends a String representation of this enum member to the given IO. See `to_s`.
macro def to_s(io : IO) : Nil
{% if @type.has_attribute?("Flags") %}
if value == 0
io << "None"
else
found = false
{% for member in @type.constants %}
{% if member.stringify != "All" %}
if {{@type}}::{{member}}.value != 0 && (value & {{@type}}::{{member}}.value) == {{@type}}::{{member}}.value
io << ", " if found
io << {{member.stringify}}
found = true
end
{% end %}
{% end %}
io << value unless found
end
{% else %}
io << to_s
{% end %}
nil
end
# Returns a String representation of this enum member.
# In the case of regular enums, this is just the name of the member.
# In the case of flag enums, it's the names joined by commas, or "None",
# if the value is zero.
#
# If an enum's value doesn't match a member's value, the raw value
# is returned as a string.
#
# ```
# Color::Red.to_s # => "Red"
# IOMode::None.to_s # => "None"
# (IOMode::Read | IOMode::Write).to_s # => "Read, Write"
#
# Color.new(10).to_s # => "10"
# ```
macro def to_s : String
{% if @type.has_attribute?("Flags") %}
String.build { |io| to_s(io) }
{% else %}
case value
{% for member in @type.constants %}
when {{@type}}::{{member}}.value
{{member.stringify}}
{% end %}
else
value.to_s
end
{% end %}
end
# Returns the value of this enum member as an `Int32`.
#
# ```
# Color::Blue.to_i # => 2
# (IOMode::Read | IOMode::Write).to_i # => 3
#
# Color.new(10).to_i # => 10
# ```
def to_i : Int32
value.to_i32
end
{% for name in %w(i8 i16 i32 i64 u8 u16 u32 u64 f32 f64) %}
{% prefix = name.starts_with?('i') ? "Int".id : (name.starts_with?('u') ? "UInt".id : "Float".id) %}
{% type = "#{prefix}#{name[1..-1].id}".id %}
# Returns the value of this enum member as a `{{type}}`
def to_{{name.id}} : {{type}}
value.to_{{name.id}}
end
{% end %}
# Returns the enum member that results from adding *other*
# to this enum member's value.
#
# ```
# Color::Red + 1 # => Color::Blue
# Color::Red + 2 # => Color::Green
# Color::Red + 3 # => 3
# ```
def +(other : Int)
self.class.new(value + other)
end
# Returns the enum member that results from subtracting *other*
# to this enum member's value.
#
# ```
# Color::Blue - 1 # => Color::Green
# Color::Blue - 2 # => Color::Red
# Color::Blue - 3 # => -1
# ```
def -(other : Int)
self.class.new(value - other)
end
# Returns the enum member that results from applying a logical
# "or" operation between this enum member's value and *other*.
# This is mostly useful with flag enums.
#
# ```
# (IOMode::Read | IOMode::Async) # => IOMode::Read | IOMode::Async
# ```
def |(other : self)
self.class.new(value | other.value)
end
# Returns the enum member that results from applying a logical
# "and" operation between this enum member's value and *other*.
# This is mostly useful with flag enums.
#
# ```
# (IOMode::Read | IOMode::Async) & IOMode::Read # => IOMode::Read
# ```
def &(other : self)
self.class.new(value & other.value)
end
# Returns the enum member that results from applying a logical
# "xor" operation between this enum member's value and *other*.
# This is mostly useful with flag enums.
def ^(other : self)
self.class.new(value ^ other.value)
end
# Returns the enum member that results from applying a logical
# "not" operation of this enum member's value.
def ~
self.class.new(~value)
end
# Compares this enum member against another, according to their underlying
# value.
#
# ```
# Color::Red <=> Color::Blue # => -1
# Color::Blue <=> Color::Red # => 1
# Color::Blue <=> Color::Blue # => 0
# ```
def <=>(other : self)
value <=> other.value
end
# Returns `true` if this enum member's value includes *other*. This
# performs a logical "and" between this enum member's value and *other*'s,
# so instead of writing:
#
# ```
# (member & value) != 0
# ```
#
# you can write:
#
# ```
# member.includes?(value)
# ```
#
# The above is mostly useful with flag enums.
#
# For example:
#
# ```
# mode = IOMode::Read | IOMode::Write
# mode.includes?(IOMode::Read) # => true
# mode.includes?(IOMode::Async) # => false
# ```
def includes?(other : self)
(value & other.value) != 0
end
# Returns `true` if this enum member and *other* have the same underlying value.
#
# ```
# Color::Red == Color::Red # => true
# Color::Red == Color::Blue # => false
# ```
def ==(other : self)
value == other.value
end
# Returns a hash value. This is the hash of the underlying value.
def hash
value.hash
end
# Returns all enum members as an `Array(String)`.
#
# ```
# Color.names # => ["Red", "Green", "Blue"]
# ```
macro def self.names : Array(String)
{% if @type.has_attribute?("Flags") %}
{{ @type.constants.select { |e| e.stringify != "None" && e.stringify != "All" }.map &.stringify }}
{% else %}
{{ @type.constants.map &.stringify }}
{% end %}
end
# Returns all enum members as an `Array(self)`.
#
# ```
# Color.values # => [Color::Red, Color::Green, Color::Blue]
# ```
macro def self.values : Array(self)
{% if @type.has_attribute?("Flags") %}
{{ @type.constants.select { |e| e.stringify != "None" && e.stringify != "All" }.map { |e| "#{@type}::#{e.id}".id } }}
{% else %}
{{ @type.constants.map { |e| "#{@type}::#{e.id}".id } }}
{% end %}
end
# Returns the enum member that has the given value, or `nil` if
# no such member exists.
#
# ```
# Color.from_value?(0) # => Color::Red
# Color.from_value?(1) # => Color::Green
# Color.from_value?(2) # => Color::Blue
# Color.from_value?(3) # => nil
# ```
macro def self.from_value?(value) : self | Nil
{% for member in @type.constants %}
return {{@type}}::{{member}} if {{@type}}::{{member}}.value == value
{% end %}
nil
end
# Returns the enum member that has the given value, or raises
# if no such member exists.
#
# ```
# Color.from_value(0) # => Color::Red
# Color.from_value(1) # => Color::Green
# Color.from_value(2) # => Color::Blue
# Color.from_value(3) # => Exception
# ```
def self.from_value(value) : self
from_value?(value) || raise "Unknown enum #{self} value: #{value}"
end
# macro def self.to_h : Hash(String, self)
# {
# {% for member in @type.constants %}
# {{member.stringify}} => {{member}},
# {% end %}
# }
# end
# Returns the enum member that has the given name, or
# raises if no such member exists. The comparison is made by using
# `String#camelcase` between *string* and the enum members names.
#
# ```
# Color.parse("Red") # => Color::Red
# Color.parse("BLUE") # => Color::Blue
# Color.parse("Yellow") # => Exception
# ```
def self.parse(string) : self
parse?(string) || raise "Unknown enum #{self} value: #{string}"
end
# Returns the enum member that has the given name, or
# `nil` if no such member exists. The comparison is made by using
# `String#camelcase` between *string* and the enum members names.
#
# ```
# Color.parse?("Red") # => Color::Red
# Color.parse?("BLUE") # => Color::Blue
# Color.parse?("Yellow") # => nil
# ```
macro def self.parse?(string) : self?
{% begin %}
case string.camelcase
{% for member in @type.constants %}
when {{member.stringify.camelcase}}
{{@type}}::{{member}}
{% end %}
else
nil
end
{% end %}
end
# Convenience macro to create an *or*ed enum from the given members.
#
# ```
# IOMode.flags(Read, Write) # => IOMode::Read | IOMode::Write
# ```
macro flags(*values)
{% for value, i in values %}\
{% if i != 0 %} | {% end %}\
{{ @type }}::{{ value }}{% end %}\
end
# def self.each
# to_h.each do |key, value|
# yield key, value
# end
# end
end