Oldes ENUM Implementation

I noticed while looking at Oldes's implementation of modern cipher suites that he has a Mezzanine function for creating enums.

Examples

*FX-DX8: enum [
    CHORUS
    COMPRESSOR
    DISTORTION
    ECHO
    FLANGER
    GARGLE
    I3DL2REVERB
    PARAMEQ
    REVERB
] "DX8 effect ID"

assert [object? *FX-DX8]
assert [0 = *FX-DX8/CHORUS]
assert [8 = *FX-DX8/REVERB]
assert [find [ECHO FLANGER] *FX-DX8/name 4]

*family: enum [
    Alice: 1
    Boban
    Bolek
    Lolek:  #{FF}
    Brian  ; does Oldes have a sibling named Brian?
] 'Just-Some-Names

assert [object? *family]
assert ['Boban = *family/name 2]
assert ['Lolek = *family/name 255]
assert [not *family/name 13]
assert [256 = *family/Brian]

Observations

  • It follows the C convention that if you don't do a direct assignment (in this case via SET-WORD!) then plain WORD! will be incremented with each value.

  • The values start by default at 0, and not 1... matching C conventions and not Rebol's.

  • If you provide a value, it can be [integer! | issue! | binary! | char!] ... however the enum will be converted to integer! for its value. (So there is a presumed endianness for BINARY!)

  • You have to pass in a name as a second parameter which is used for debug messages.

Implementation

All enums are derived from a standard object. This object has a field title*, and methods assert (to check if a mapped-to value is in an enumeration) and name (which lets you do a reverse-lookup of an enum's name by its value).

system/standard/enum: object [
    title*: none
    assert: func[
        "Checks if value exists as an enumeration. Throws error if not."
        value [integer!]
    ][
        unless find values-of self value [
            cause-error 'Script 'invalid-value-for reduce [value title*]
        ]
        true
    ]
    name: func[
        "Returns name of the emumeration by its value if value exists, else none."
        value [integer!]
        /local pos
    ][
        all [
            pos: find values-of self value
            pick words-of self index? pos
        ]
    ]
    ;@@ add some other accessor functions?
]

enum itself is a function that uses PARSE on the specification to make the object:

enum: function [
    "Creates enumeration object from given specification"
    spec [block!] "Specification with names and values."
    title [string! word!] "Enumeration name"
][
    enum-value: 0
    spec: copy spec
    parse spec [any [
        pos: word! insert enum-value (
            change pos to set-word! pos/1
            enum-value: enum-value + 1
        )
        | some set-word! pos: [
            integer! | issue! | binary! | char!
        ] (
            if error? try [
                enum-value: to integer! pos/1
                pos: change pos enum-value
                enum-value: enum-value + 1
            ][
                cause-error 'Script 'invalid-data reduce [pos]
            ]
        ) :pos
        | pos: 1 skip (
            cause-error 'Script 'invalid-data reduce [pos]
        )
        ]
    ]
    enum: make system/standard/enum spec
    enum/title*: title
    enum
]

So the big thing one notices here, is that the ENUM function makes a lookup and conversion utility object, not something more like a DATATYPE!.

It's thus not surprising that the names are things like *FX-DX8 and *family as opposed to FX-DX8! and family!

If you can't use an enum as a type constraint on an argument to a function, it seems to me you're missing a large part of the value. So I'm definitely looking for something different...and I'd like to see something that can do enum parameterization

But it is worth asking if the thing you make with ENUM--even if it's a DATATYPE!--could have utility functions that could be called even if you don't have an instance of a type to work on. e.g. "static methods" on datatypes...

This strikes me as undesirable. In the past, Ren-C has used its ability to take the left-hand side of an assignment literally to capture the assigned-to thing's name.

enum: infix func ['left [set-xxx?] ...] [...]

Previously, picking up the left-hand set-xxx? could be optional... because there was <skip>-ability... and so if there was no SET-WORD! on the left you could just say it was anonymous. Skippability has been removed, however.

It's a dodgy mechanism, in any case. It would be nice if the system could retain this kind of information for you implicitly.

There's a facility by which ACTION!s retain the names under which they were last assigned. It costs a pointer in the Cell, and not all types have room for this. In fact FRAME!s do not, but they use the special case of antiform frames to say that the spot which would be used for a "Lens" is used for the name... if it looks in the Lens slot and detects that the pointed-to thing is a Symbol*, then it's a "Lensless" frame.

OBJECT! Cells do have room free--at this moment--for a cache of the last name under which it was assigned. So do DATATYPE! Cells--if they were what ENUM made.

We'd presume this would be tunneled through in the "this" pointer for methods, which is currently passed as . so you could say something like label of . to get it.

Since this is all getting glommed into one big OBJECT!, you couldn't name an enum value title* or assert or name, and any future extensions would compete with some enum values. I'm never crazy about these techniques which wind up imposing rules about what values you can't use in-band.

Ren-C can give different meanings to my-enum.foo and my-enum/foo (and also my-enum:foo ... I haven't really thought about what kind of axis of extensibility CHAIN! would have). But at least you could distinguish between enum states and functions offered by the enum itself.

(That raises a question of if this should be a feature of OBJECT! in general... could it have separate namespaces for non-methods and methods?)

1 Like