#lang scribble/doc

@(require "base.ss")

@title[#:tag "enumeration"]{Enumerations (revised)}

@(define-eval enum-eval (planet untyped/unlib/enumeration))

@defmodule[(planet untyped/unlib/enumeration)]{

Utilities for defining simple enumerations of booleans, symbols and integers. These are useful wherever you would normally use a small collection of literals to represent possible values of a variable, and test for value equality with @scheme[eq?]. The @scheme[define-enum] form binds the literals to Scheme identifiers so the compiler catches typos that might otherwise take a long time to debug.

@defstruct[enum ([name symbol?]
                 [values        (listof (U boolean? symbol? integer?))]
                 [pretty-values (listof string?)])]{
An enumeration. For each symbol in @scheme[values] there is a human-readable string equivalent in @scheme[pretty-values].}
                                                   
@defform/subs[#:literals (_) (define-enum enum-id (value-clause ...) keyword-arg ...)
              ([value-clause value-id
                             [value-id pretty-expr]
                             [value-id value-expr pretty-expr]]
               [value-expr   any
                             _]
               [pretty-expr  string?]
               [keyword-arg (code:line #:plural id)])]{
Binds @scheme[enum-id] to an @italic{enumeration macro} that can be used:

@itemize{
  @item{in argument position to refer to an @scheme[enum] struct;}
  @item{in procedure call position to retrieve a value from the enumeration.}}

@examples[
  #:eval enum-eval
  (define-enum options
    ([a 'option1 "first option"]
     [b "second option"]
     c))
  (options a)
  (options b)
  (options c)
  (map (lambda (val)
         (enum-prettify options val))
       (list (options a) (options b) (options c)))]

The identifier @scheme[_] can be used as a @scheme[value-expr] as a shorthand for @scheme['value-id]. This is useful when writing value clauses where the @scheme[value-id] and the @scheme[pretty-expr] are the most important parts.

@examples[
  #:eval enum-eval
  (define-enum options
    ([a _ "first option"]
     [b _ "second option"]
     [c _ "third option"]))
  (options a)
  (options b)
  (options c)
  (map (lambda (val)
         (enum-prettify options val))
       (list (options a) (options b) (options c)))]}
  
@defproc[(enum->string [enum enum?] [separator string? ", "]) string?]{
Returns a string representation of @scheme[(enum-values enum)], useful for including in debugging output. @scheme[separator] is used to separate the enum values in the return value.
        
@examples[
  #:eval enum-eval
  (define-enum vehicle (car boat plane))
  (enum->string vehicle)]}

@defproc[(enum->pretty-string [enum enum?] [separator string? ", "]) string?]{
Returns a string representation of @scheme[(enum-pretty-values enum)], useful for describing the possible values to a user. @scheme[separator] is used to separate the enum values in the return value.

@examples[
  #:eval enum-eval
  (define-enum vehicle (car boat plane))
  (enum->pretty-string vehicle)]}

@defproc[(enum-value? [enum enum?] [value any]) boolean?]{
Returns @scheme[#t] if @scheme[value] is a member of @scheme[(enum-values enum)].

@defproc[(enum-value/c [enum enum?]) flat-contract?]{
Returns a contract that accepts values from @scheme[(enum-values enum)].}

@defform[(in-enum enum val ...)]{
Like @scheme[in-list] but iterates through @scheme[(enum-list enum val ...)].

@examples[
  #:eval enum-eval
  (define-enum vehicle (car boat plane))
  (for/list ([val (in-enum vehicle)])
    val)]
  (for/list ([val (in-enum vehicle car plane)])
    val)]}

@defform[(in-enum/pretty enum val ...)]{
Like @scheme[in-list] but iterates through the pretty versions of @scheme[(enum-list enum val ...)].

@examples[
  #:eval enum-eval
  (define-enum vehicle (car boat plane))
  (for/list ([val (in-enum/pretty vehicle)])
    val)]
  (for/list ([val (in-enum vehicle car plane)])
    val)]}

@examples[
  #:eval enum-eval
  (define-enum vehicle (car boat plane))
  (enum-value? vehicle 'car)
  (enum-value? vehicle 'apple)]}

@defproc[(enum-prettify [enum    enum?]
                        [value   symbol?]
                        [default (U string? (-> string?)) (cut raise-exn exn:fail:contract ...)])
         string?]{
Returns the pretty equivalent of @scheme[value]. If @scheme[value] is not found in @scheme[enum], @scheme[default] is used instead:

@itemize{
  @item{if @scheme[default] is a procedure, it is called to determine the return value;}
  @item{if @scheme[default] is not a procedure, it is returned.}}}

@defform[(enum-list enum value ...)]{
Expands to a @scheme[list] of @scheme[value]@schemeidfont{s} from @scheme[enum].

@examples[
  #:eval enum-eval
  (define-enum vehicles (car boat plane))
  (enum-list vehicles car boat)]}

@defform[(enum-complement enum value ...)]{
Expands to a @scheme[list] of all values in @scheme[enum] apart from the specified @scheme[value]@schemeidfont{s}.

@examples[
  #:eval enum-eval
  (define-enum vehicles (car boat plane))
  (enum-complement vehicles car boat)]}

@defform/subs[#:literals (else) (enum-case enum value clause ...)
              ([clause [(value ...) expr ...]
                       [else expr ...]])]{
Like @scheme[case] but each @scheme[value] must be a value from @scheme[enum]. If an @scheme[else] expression is not provided, the @scheme[value]@schemeidfont{s} must cover the complete enumeration.

@examples[
  #:eval enum-eval
  (define-enum vehicles (car boat plane))
  (define (flies? vehicle)
    (enum-case vehicles vehicle
      [(plane) #t]
      [(car boat) #f]))
  (flies? 'car)
  (flies? 'plane)]}
                                         
@defform/subs[#:literals (else) (enum-lambda enum clause ...)
              ([clause [(value ...) expr ...]
                       [else expr ...]])]{
Expands to a procedure that accepts a single argument and runs @scheme[enum-case] on it.

@examples[
  #:eval enum-eval
  (define-enum vehicles (car boat plane))
  (define flies?
    (enum-lambda vehicles
      [(plane) #t]
      [(car boat) #f]))
  (flies? 'car)
  (flies? 'plane)]}

} @;{end defmodule}