Skip to content

Instantly share code, notes, and snippets.

@SirTony
Created May 12, 2024 11:55
Show Gist options
  • Save SirTony/edf64e2de910e9bac688826372d393fa to your computer and use it in GitHub Desktop.
Save SirTony/edf64e2de910e9bac688826372d393fa to your computer and use it in GitHub Desktop.
A small DSL to easily create C-style enums and C#-style flags enums.
def enum( flags: false, &block )
raise ArgumentError, "block not given" unless block_given?
enum_class = Class.new do
def initialize( value )
raise ArgumentError, "value is not defined for this enum" unless defined?( value )
@value = value.is_a?( Symbol ) || value.is_a?( String ) ? self.class.const_get( value.upcase ) : value
end
def name
self.class.name( @value )
end
def flag?( flag )
raise ArgumentError, "cannot check flags on non-flags enums" unless self.class.flags?
raise ArgumentError, "cannot compare flags from different enums" unless flag.is_a?( self.class )
( @value & flag.to_i == flag.to_i ) && flag.to_i != 0
end
def self.defined?( value )
case value
when Integer
return true if @members.values.include?( value )
when Symbol, String
return true if @members.key?( value.upcase )
else
return false
end
end
def self.name( value )
case value
when Integer
return @members.key( value )
when Symbol, String
return defined?( value ) ? value.upcase : nil
else
return nil
end
end
def self.flags?
@flags
end
def |( other )
raise ArgumentError, "cannot perform bitwise operations on non-flags enums" unless self.class.flags?
raise ArgumentError, "cannot perform bitwise operations on different enums" unless other.is_a?( self.class )
self.class.new( @value | other.to_i )
end
def &( other )
raise ArgumentError, "cannot perform bitwise operations on non-flags enums" unless self.class.flags?
raise ArgumentError, "cannot perform bitwise operations on different enums" unless other.is_a?( self.class )
self.class.new( @value & other.to_i )
end
def to_i
@value
end
def to_s
if self.class.flags?
self.class
.instance_variable_get( "@members" )
.select { |_, v| ( v & @value == v ) && v != 0 }
.keys
.join( " | " )
else
@value.to_s
end
end
end
builder_class = Class.new do
attr_reader :members
define_method( :initialize ) do
@count = flags ? 1 : 0
@members = {}
end
define_method( :method_missing ) do |name, *args, &block |
raise ArgumentError, "enum members cannot have blocks" unless block.nil?
raise ArgumentError, "enum members cannot have multiple arguments" if args.count > 1
raise ArgumentError, "an enum member's value must be an Integer" if args.count == 1 && !args.first.is_a?( Integer )
op = flags ? :<< : :+
value = args.first || @count
@members[name.upcase] = value
@count = [ @members.values.max, flags ? 1 : 0 ].max.send( op, 1 )
end
end
builder = builder_class.new
builder.instance_eval( &block )
builder.members.each do |name, value|
instance = enum_class.new( value )
enum_class.const_set( name.upcase, instance )
end
if flags
enum_class.define_method( :empty? ) do
@value == 0
end
end
enum_class.instance_variable_set( "@flags", flags )
enum_class.instance_variable_set( "@members", builder.members )
enum_class
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment