iyi

Prelude, 0.18.0

Enum

What an enum can do — SPEC.md, "0.1.0", item 3.

In the prelude: every iyi program has it, with no import.

The compiler gives an enum exactly two methods: value, the integer the member was numbered with, and new, which wraps one (EnumType#initialize). Everything a program does with an enum is the library's, and until samples/iyi/enums.iyi asked there was none of it — so the answers were Object's: Level::Warn == Level::Warn was **false**, to_s printed the type's name, and case level when Level::Warn matched nothing, because a when naming a member lowers to Level::Warn === level and === is ==. The compiler's own per-member question methods (level.warn?) call that same ==, and a @[Flags] enum's call includes?, so those were broken by one gap apiece.

Enum is predefined — it is what every enum's ancestor is, created before a prelude line is read — so this reopens it the way Range and String are reopened rather than declaring it.

Read off the type at each instantiation: a method here is stencilled onto the enum that calls it, so @type is that enum and @type.constants are its members. Nothing walks a table at run time and nothing is allocated to answer a name: to_s is a case over integers the compiler folds.

Enumstruct

Enumiyi doc Enum
struct Enum < Value

  def &(other : self) : self

  # An order, because a level is a threshold: `level >= Level::Info` is the
  # first thing a program writes after a `case`. Four operators rather than a
  # `Comparable` include — the prelude has no such module, and a trait would be
  # `std/traits`' to carry (SPEC.md II.6).
  def <(other : self) : Bool

  def <=(other : self) : Bool

  # Equality is the value's, which is what makes every other name work: `!=`
  # and `===` are `Object`'s and both read this, `case` lowers to `===`, and
  # the question methods the compiler writes are `self == Member`.
  def ==(other : self) : Bool

  def >(other : self) : Bool

  def >=(other : self) : Bool

  # A key in a `Hash` is hashed and then compared, so a type used as one owes
  # both (`src/iyi/hash.iyi`). The value's own hash: `to_i32` panicked past it.
  def hash : Int32

  # Whether every bit of *other* is in this one, which is what a `@[Flags]`
  # enum's question methods ask: the compiler writes `mode.read?` as
  # `self.includes?(Mode::Read)`.
  def includes?(other : self) : Bool

  # `#{Level::Warn.inspect}` is the member written down rather than printed,
  # and for an enum the two are the same text. It is here because `Object`'s
  # sends it back to `to_s` — which was the type's name — and a sample that
  # inspects a `Level?` was reading that.
  def inspect : String

  # The member's name, and the value's digits where no member has it —
  # `Level.new(9).to_s` is `"9"`, which is what a value off a wire prints as.
  #
  # A flags enum answers with a member's name when one has its value (`None`
  # for zero, `All` for every bit, which was skipped), else the names of the
  # bits it holds, each named once, and the rest as a number: `Read | 8`.
  def to_s : String

  # The two set operators a flags enum is built to be used with. `Mode::Read |
  # Mode::Write` is how one is written down, and `mode & Mode::Write` is how a
  # bit is taken out of it.
  def |(other : self) : self

  # A member from the integer it was numbered with, or `nil` where no member
  # has it. `new` takes any value of the base type by design — that is how an
  # enum comes back from C or off a wire — and this is the question that asks
  # whether the one in hand is a member.
  def self.from_value?(value : Int) : self | ::Nil

  # The same list said in text, which is what a message naming the choices
  # needs: `"one of #{Level.names.join(", ")}"`.
  def self.names : Array(String)

  # A member read back from text, or `nil` where no member has that name. What
  # a program does with an argument or a configuration line, so the comparison
  # is the lenient one a person types: `"warn"`, `"Warn"` and `"WARN"` all
  # answer `Level::Warn`, and nothing else does.
  #
  # `nil` rather than a panic, because the text is somebody else's input and
  # the caller is the only thing that knows what to say about it (III.1).
  def self.parse?(name : String) : self | ::Nil

  # Every member, in the order they were written. A menu, a validation table,
  # a loop that has to reach all of them — and for a flags enum the members
  # rather than `None` and `All`, which are the compiler's own.
  def self.values : Array(self)
end