<NME> Selectors.jl
<BEF> ADDFILE
<MSG> Merge pull request #76 from JuliaDocs/mh/selectors

Implement Selector interface
<DFF> @@ -0,0 +1,171 @@
+"""
+An extensible code selection interface.
+
+The `Selectors` module provides an extensible way to write code that has to dispatch on
+different predicates without hardcoding the control flow into a single chain of `if`
+statements.
+
+In the following example a selector for a simple condition is implemented and the generated
+selector code is described:
+
+```julia
+abstract MySelector <: Selectors.AbstractSelector
+
+# The different cases we want to test.
+abstract One    <: MySelector
+abstract NotOne <: MySelector
+
+# The order in which to test the cases.
+Selectors.order(::Type{One})    = 0.0
+Selectors.order(::Type{NotOne}) = 1.0
+
+# The predicate to test against.
+Selectors.matcher(::Type{One}, x)    = x === 1
+Selectors.matcher(::Type{NotOne}, x) = x !== 1
+
+# What to do when a test is successful.
+Selectors.runner(::Type{One}, x)    = println("found one")
+Selectors.runner(::Type{NotOne}, x) = println("not found")
+
+# Test our selector with some numbers.
+for i in 0:5
+    Selectors.dispatch(MySelector, i)
+end
+```
+
+The code generated by `Selectors.dispatch(Selector, i)` will look similar to the following:
+
+```julia
+function dispatch(::Type{MySelector}, i::Int)
+    if matcher(One, i)
+        runner(One, i)
+    elseif matcher(NotOne, i)
+        runner(NotOne, i)
+    end
+end
+```
+
+which would be further simplified after inlining [`matcher`](@ref) and [`runner`](@ref) as
+
+```julia
+function dispatch(::Type{MySelector}, i::Int)
+    if i === 1
+        println("found one")
+    elseif i !== 1
+        println("not found")
+    end
+end
+```
+
+The module provides the following interface for creating selectors:
+
+- [`order`](@ref)
+- [`matcher`](@ref)
+- [`runner`](@ref)
+- [`strict`](@ref)
+- [`disable`](@ref)
+- [`dispatch`](@ref)
+
+"""
+module Selectors
+
+using Compat
+
+"""
+Root selector type. Each user-defined selector must subtype from this, i.e.
+
+```julia
+abstract MySelector <: Selectors.AbstractSelector
+
+abstract First  <: MySelector
+abstract Second <: MySelector
+```
+"""
+abstract AbstractSelector
+
+"""
+Define the precedence of each case in a selector, i.e.
+
+```julia
+Selectors.order(::Type{First})  = 1.0
+Selectors.order(::Type{Second}) = 2.0
+```
+
+Note that the return type must be `Float64`. Defining multiple case types to have the same
+order will result in undefined behaviour.
+"""
+function order end
+
+"""
+Define the matching test for each case in a selector, i.e.
+
+```julia
+Selectors.matcher(::Type{First}, x)  = x == 1
+Selectors.matcher(::Type{Second}, x) = true
+```
+
+Note that the return type must be `Bool`.
+
+To match against multiple cases use the [`Selectors.strict`](@ref) function.
+"""
+function matcher end
+
+"""
+Define the code that will run when a particular [`Selectors.matcher`](@ref) test returns
+`true`, i.e.
+
+```julia
+Selectors.runner(::Type{First}, x)  = println("`x` is equal to `1`.")
+Selectors.runner(::Type{Second}, x) = println("`x` is not equal to `1`.")
+```
+"""
+function runner end
+
+"""
+Define whether a selector case will "fallthrough" or not when successfully matched against.
+By default matching is strict and does not fallthrough to subsequent selector cases.
+
+```julia
+# Adding a debugging selector case.
+abstract Debug <: MySelector
+
+# Insert prior to all other cases.
+Selectors.order(::Type{Debug}) = 0.0
+
+# Fallthrough to the next case on success.
+Selectors.strict(::Type{Debug}) = false
+
+# We always match, regardless of the value of `x`.
+Selectors.matcher(::Type{Debug}, x) = true
+
+# Print some debugging info.
+Selectors.runner(::Type{Debug}, x) = @show x
+```
+"""
+strict{T <: AbstractSelector}(::Type{T}) = true
+
+"""
+Disable a particular case in a selector so that it is never used.
+
+```julia
+Selectors.disable(::Type{Debug}) = true
+```
+"""
+disable{T <: AbstractSelector}(::Type{T}) = false
+
+"""
+Generated function that builds a specialised selector for each selector type provided, i.e.
+
+```julia
+Selectors.dispatch(MySelector, 1)
+```
+"""
+@generated function dispatch{T <: AbstractSelector}(::Type{T}, x...)
+    out = map(sort(subtypes(T); by = order)) do t
+        ret = strict(t) ? :(@goto END) : nothing
+        disable(t) ? nothing : :($(matcher)($t, x...) && ($(runner)($t, x...); $ret))
+    end
+    Expr(:block, out..., :($(runner)($T, x...); @label END))
+end
+
+end

Merge pull request #76 from JuliaDocs/mh/selectors