Enums are a data type composed of a fixed set of inner data types, known as variants. A common use is to define an "optional" or "maybe" type that will either have a "something" of X, or a nothing.

In addition to providing an Option as part of predefined data types, it is also possible to create new enums.

Declaration

By default, an enum is flat. The variants of a flat enum are placed into the module's namespace.

enum FlatRgb
{
    Red,
    Green,
    Blue,

    define is_blue: Boolean
    {
        match self: {
            case Blue: return true
            else:      return false
        }
    }
}

var v = Blue

print(v.is_blue()) # true
print(v) # Blue

The alternative is a scoped enum. Variants of a scoped enum are kept within the enum.

scoped enum ScopedRgb
{
    Red,
    Green,
    Blue,

    define is_blue: Boolean
    {
        match self: {
            case ScopedRgb.Blue:
                return true
            case ScopedRgb.Red,
                 ScopedRgb.Green:
                return false
        }
    }
}

var v = ScopedRgb.Blue

print(v.is_blue()) # true
print(v) # ScopedRgb.Blue

In both cases, variants are sealed inside of the enum they are declared in. It is not possible to, for example, declare a variable of type Some.

The above examples have a comma at the end of the last variant declared in the enum. However, the last variant is not required to have a comma at the end:

scoped enum CommaExample
{
    One,
    Two
}

Methods

Similar to classes, enum allow declaration of methods. Enum methods do not support any qualifiers. All enum methods act as they are public, and receive self as a hidden first parameter. Enum methods do not allow a scope because they cannot inherit or be inherited from.

enum Rgb
{
    Red,
    Green,
    Blue,

    define is_blue: Boolean
    {
        with self as Blue: {
            return true
        else:
            return false
        }
    }
}

var v = Blue

print(v.is_blue())    # true

# An alternative way of calling the above.
print(Rgb.is_blue(v)) # true
print(v)              # Blue

Arguments

It is possible for a variant to take arguments. Those arguments can include the enum itself. Variants that take values can be called like a Function, but they are not Function values.

Variants support all argument types, except optional arguments.

enum Tree
{
    Leaf(Integer),
    Branch(Tree, Tree),

    define walk: Integer
    {
        match self: {
            case Leaf(value):
                return value
            case Branch(left, right):
                return left.walk() + right.walk()
        }
    }
}

var tree =
    Branch(
        Branch(
            Leaf(10),
            Leaf(20)
        ),
        Leaf(30)
    )

print(tree.walk()) # 60

Variants can also have variable arguments.

enum Tree
{
    Leaf(Integer),
    Branch(Tree...),

    define walk: Integer
    {
        match self: {
            case Leaf(value):
                return value
            case Branch(targets):
                var total = 0

                for i in 0...targets.size() - 1: {
                    total += targets[i].walk()
                }

                return total
        }
    }
}

var tree =
    Branch(
        Branch(
            Leaf(10)
        ),
        Leaf(20),
        Leaf(30),
        Leaf(40)
    )

print(tree.walk()) # 100

Keyword arguments are also supported.

enum Color
{
    Blue,
    Green,
    Red,
    RGB(:red Integer, :green Integer, :blue Integer),

    define to_i: Integer
    {
        match self: {
            case Blue:
                return 0x0000ff
            case Green:
                return 0x00ff00
            case Red:
                return 0xff0000
            case RGB(r, g, b):
                return (r << 16) +
                       (g << 8) +
                       b
        }
    }
}

var v = RGB(:red 0xff,
            :blue 0,
            :green 0)

print(Green.to_i()) # 65280
print(v.to_i())     # 16711680

Variants support call piping.

{
    var v = Option.unwrap(Some(1))

    print(v) # 1
}
{
    var v = 1 |> Some |> Option.unwrap

    print(v) # 1
}

Value enums

In the above examples, variants do not themselves have any underlying value. The None of an Option is a None. If an enum is declared as inheriting from Integer, it is created as a value enum. The variants of a value enum are called value variants.

Here's that color example again:

enum Color < Integer {
    Red,        # No value given, defaults to 0
    Green = 12,
    Blue,       # 13 (the prior value + 1)

    define is_green: Boolean
    {
        match self: {
            case Green:
                return true
            else:
                return false
        }
    }
}

print(Red) # 0
print(Green) # 12
print(Blue) # 13

print(Blue.is_green()) # false

# Math is allowed, but the result decays to Integer.
var some_int: Integer = Green + Blue

print(some_int) # 25

If the first value isn't given, it defaults to 0. All others default to the prior value + 1. Value variants must be initialized with a literal value, and they must be unique to each other. Each variant must be empty as well.

Value enums are declared using < Integer (no other class is alloweed).

Internally, value enums are implemented as Integer values, instead of as a separate class.

import introspect

enum Color < Integer {
    Red,
    Green,
    Blue
}

print(Red |> introspect.class_name) # Integer

Because value enums inherit from Integer, they are allowed to use Integer methods.

enum Color < Integer {
    Red,
    Green,
    Blue
}

print(Green.to_hex()) # 0x1