chickadee » jiffi » define-enum-group

(define-enum-group ...)syntax

Defines procedures to convert integer constants to symbols, and vice versa, and optionally defines a variable for each integer constant. Also generates type declarations for the converter procedures.

If you only want to define variables, and don't need symbol conversion procedures, see define-foreign-values.

Usage:

(define-enum-group
  type: FOREIGN-TYPE
  vars: VARS-MODE             ; optional, default: define

  symbol->int: SYMBOL->INT    ; optional
  allow-ints: ALLOW-INTS      ; optional

  int->symbol: INT->SYMBOL    ; optional

  ;; Optional `requires:' clause for enum requirements
  ((requires: SCHEME-FEATURE-CLAUSE "C_PREPROCESSOR_EXPR")
   ;; If VAR and CONSTANT are the same, you can write
   ;; VAR instead of (VAR CONSTANT)
   (SYMBOL (VAR CONSTANT) FLAG ...)
   ...)

  ...)

FOREIGN-TYPE is a foreign integer type specifier that will be used for all the constants. The type specifier integer is a safe choice, but if you know all the constants are in the range -1,073,741,824 to 1,073,741,823 inclusive, you can safely use int which may be more efficient.

VARS-MODE specifies whether variables for the enums should be defined and/or exported from the current module, using define-foreign-values. If VARS-MODE is define or the clause is omitted, each VAR will be defined but not automatically exported, equivalent to (define-foreign-values export: #f ...). If VARS-MODE is export, every VAR will be defined and also automatically exported, equivalent to (define-foreign-values export: #t ...). If VARS-MODE is #f, variables will not be defined or exported. This allows you to define the enum converter procedures without defining any variables.

SYMBOL->INT and INT->SYMBOL are procedure names to define as enum converters. If any is #f or its keyword clause is omitted, that procedure will not be defined.

If ALLOW-INTS is #t, SYMBOL->INT will also accept integers, not only symbols. Integers are returned immediately, without any checks. If ALLOW-INTS is #f or the keyword clause is omitted, then if SYMBOL->INT is called with an integer, it will invoke its not-found-callback (if there is one) or signal an exception.

The optional (requires: SCHEME-FEATURE-CLAUSE "C_PREPROCESSOR_EXPR") clause is used to specify enum requirements. If the (requires: ...) clause is omitted, the enums in the list will always be included in the group.

Each (SYMBOL (VAR CONSTANT) FLAG ...) or (SYMBOL VAR FLAG ...) defines the relationship between a symbol and an integer constant.

Each SYMBOL is a non-quoted symbol. It will be a valid argument to SYMBOL->INT. And it will be a possible return value from INT->SYMBOL, unless it is marked with the alias flag.

Each VAR is a variable name to define in Scheme, with the value of CONSTANT.

Each CONSTANT is a non-quoted symbol or string containing the name of a C integer constant, such as a global const, #define, or enum value. (It is not necessary for all the CONSTANTs to be from the same enum statement.) Or, it can be a string containing an integer constant expression, which must be a valid case label in a C switch statement, such as "1 + 1" or "FOO - 1". The value of each constant or expression must be unique within this enum group, unless it is marked with the alias flag.

If VAR and CONSTANT are the same, you can write VAR instead of (VAR CONSTANT). This must be a variable name (non-quoted symbol), not a string.

FLAGs are optional non-quoted symbols marking this symbol/constant relationship as special in some way. The following flag is currently recognized (other flags are ignored):

alias
This constant has the same integer value as another constant in this group. This constant will be omitted from INT->SYMBOL to avoid a C compiler error about duplicate case values.

Basic example:

;; C enum definitions
(foreign-declare "
typedef enum {
  FOO_BLEND_NONE = 0,
  FOO_BLEND_ADD = 1,
  FOO_BLEND_SUB = 2,
  FOO_BLEND_MUL = 4
} FOO_BlendMode;
")

(define-enum-group
  type: int
  vars: export
  symbol->int: blend-mode->int
  int->symbol: int->blend-mode
  ((none BLEND_NONE)
   (add  BLEND_ADD)
   (sub  BLEND_SUB)
   (mul  BLEND_MUL)))

(blend-mode->int 'sub)      ; ⇒ 2
(int->blend-mode BLEND_SUB) ; ⇒ 'sub

(blend-mode->int 'zzz)   ; Error: unrecognized enum symbol: zzz
(int->blend-mode 42)     ; Error: unrecognized enum value: 42

;; Using not-found-callbacks:
(blend-mode->int 'zzz (lambda (sym) -1))     ; ⇒ -1
(int->blend-mode 42   identity)              ; ⇒ 42

Advanced example (renaming, aliases, requirements):

;; C enum definitions
(foreign-declare "
#define FOO_VERSION 110

typedef enum {
  POWER_EMPTY = 0,
  POWER_NONE = 0, /* alias */
  POWER_LOW = 1,
  POWER_HIGH,
} PowerLevel;
")

;; Or compile with flag: -D foo-1.1+
(begin-for-syntax
 (register-feature! 'foo-1.1+))

(define-enum-group
  type: int
  vars: export
  symbol->int: power-level->int
  allow-ints: #t
  int->symbol: int->power-level

  ;; These enums are always included.
  ((empty (power/empty FOO_POWER_EMPTY))
   (none  (power/none  FOO_POWER_NONE)  alias)
   (low   (power/low   FOO_POWER_LOW))
   (high  (power/high  FOO_POWER_HIGH)))

  ;; Feature ID foo-1.1+ is registered above, so these requirements
  ;; are satisfied, so this enum will be included.
  ((requires: foo-1.1+ "FOO_VERSION >= 110")
   (wired (power/wired FOO_POWER_WIRED)))

  ;; These requirements are not satisfied, so this will be omitted.
  ((requires: foo-1.2+ "FOO_VERSION >= 120")
   (charge (power/charge FOO_POWER_CHARGE))))

;; `FOO_POWER_LOW' was renamed to `power/low'
power/low                     ; => 1
FOO_POWER_LOW                 ; Error: unbound variable: FOO_POWER_LOW

;; power/none is an alias, so it is omitted from int->power-level.
;; power/empty has the same integer value, so 'empty is returned.
(int->power-level power/none) ; ⇒ 'empty

;; `(requires: foo-1.2+ "FOO_VERSION >= 120")` was not satisfied.
power/charge                  ; Error: unbound variable: power/charge
(power-level->int 'charge)    ; Error: unrecognized enum symbol: charge

;; `allow-ints: #t` so power-level->int accepts integer args.
(power-level->int 42)         ; ⇒ 42

define-enum-group is based on foreign-value and foreign-primitive. Converting from a symbol to an integer uses a Scheme case form. Converting from an integer to a symbol uses a C switch statement.