diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -20,22 +20,3 @@
 http://hackage.haskell.org/package/prednote
 
 prednote is licensed under the BSD license; see the LICENSE file.
-
-## Versioning
-
-prednote releases are numbered in accordance with the Haskell
-Package Versioning Policy.
-
-prednote does not set its dependencies in accordance with the
-Package Versioning Policy, as I do not set upper bounds.  prednote
-is guaranteed to build with the *minimum* versions specified in the
-cabal file.  I also include a dependencies.txt file that
-documents more recent dependencies that are also known to work.
-
-If you find that prednote does not build due to dependency problems:
-1) please let me know at omari@smileystation.com; 2) feel free to
-add appropriate upper bounds or patches to the package as
-appropriate; and 3) feel free to add command-line contraints to your
-cabal command to get it to build.
-
-
diff --git a/changelog b/changelog
--- a/changelog
+++ b/changelog
@@ -1,3 +1,7 @@
+0.24.0.0
+
+  * complete change in API and internals.
+
 0.22.0.2
 
   * updates for new Rainbow API
diff --git a/current-versions.txt b/current-versions.txt
--- a/current-versions.txt
+++ b/current-versions.txt
@@ -1,7 +1,7 @@
 This package was tested to work with these dependency
 versions and compiler version.
 These are the default versions fetched by cabal install.
-Tested as of: 2014-04-13 22:50:48.223408 UTC
+Tested as of: 2014-07-13 14:54:13.902743 UTC
 Path to compiler: ghc-7.8.2
 Compiler description: 7.8.2
 
@@ -33,17 +33,13 @@
     transformers-0.3.0.0
     unix-2.7.0.1
 
-/home/massysett/prednote/sunlight-22866/db:
-    QuickCheck-2.7.3
-    contravariant-0.4.4
-    prednote-0.22.0.2
-    primitive-0.5.2.1
-    rainbow-0.14.0.0
-    random-1.0.1.1
+/home/massysett/prednote/library/sunlight-25515/db:
+    contravariant-0.6
+    prednote-0.24.0.0
+    rainbow-0.14.0.2
     split-0.2.2
-    tagged-0.7.1
     terminfo-0.4.0.0
-    text-1.1.0.1
-    tf-random-0.5
-    transformers-compat-0.1.1.1
+    text-1.1.1.3
+    transformers-0.4.1.0
+    transformers-compat-0.3.3.4
 
diff --git a/lib/Data/Prednote.hs b/lib/Data/Prednote.hs
deleted file mode 100644
--- a/lib/Data/Prednote.hs
+++ /dev/null
@@ -1,9 +0,0 @@
-module Data.Prednote
-  ( module Data.Prednote.Predbox
-  , module Data.Prednote.Expressions
-  , module Data.Prednote.Test
-  ) where
-
-import Data.Prednote.Predbox
-import Data.Prednote.Expressions
-import Data.Prednote.Test
diff --git a/lib/Data/Prednote/Expressions.hs b/lib/Data/Prednote/Expressions.hs
deleted file mode 100644
--- a/lib/Data/Prednote/Expressions.hs
+++ /dev/null
@@ -1,87 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
--- | Handles parsing of both infix and RPN Predbox expressions.
-module Data.Prednote.Expressions
-  ( ExprDesc(..)
-  , Error
-  , Token
-  , operand
-  , opAnd
-  , opOr
-  , opNot
-  , openParen
-  , closeParen
-  , parseExpression
-  ) where
-
-import Data.Either (partitionEithers)
-import Data.Functor.Contravariant
-import qualified Data.Text as X
-import qualified Data.Prednote.Expressions.Infix as I
-import qualified Data.Prednote.Expressions.RPN as R
-import qualified Data.Prednote.Predbox as P
-
--- | A single type for both RPN tokens and infix tokens.
-newtype Token a = Token { unToken :: I.InfixToken a }
-
-instance Contravariant Token where
-  contramap f = Token . contramap f . unToken
-
-type Error = X.Text
-
--- | Creates Operands from Predbox.
-operand :: P.Predbox a -> Token a
-operand p = Token (I.TokRPN (R.TokOperand p))
-
--- | The And operator
-opAnd :: Token a
-opAnd = Token (I.TokRPN (R.TokOperator R.OpAnd))
-
--- | The Or operator
-opOr :: Token a
-opOr = Token (I.TokRPN (R.TokOperator R.OpOr))
-
--- | The Not operator
-opNot :: Token a
-opNot = Token (I.TokRPN (R.TokOperator R.OpNot))
-
--- | Open parentheses
-openParen :: Token a
-openParen = Token (I.TokParen I.Open)
-
--- | Close parentheses
-closeParen :: Token a
-closeParen = Token (I.TokParen I.Close)
-
--- | Is this an infix or RPN expression?
-data ExprDesc
-  = Infix
-  | RPN
-  deriving (Eq, Show)
-
-toksToRPN :: [Token a] -> Maybe [R.RPNToken a]
-toksToRPN toks
-  = let toEither t = case unToken t of
-          I.TokRPN tok -> Right tok
-          _ -> Left ()
-    in case partitionEithers . map toEither $ toks of
-        ([], xs) -> return xs
-        _ -> Nothing
-
--- | Parses expressions. Fails if the expression is nonsensical in
--- some way (for example, unbalanced parentheses, parentheses in an
--- RPN expression, or multiple stack values remaining.) Works by first
--- changing infix expressions to RPN ones.
-parseExpression
-  :: ExprDesc
-  -> [Token a]
-  -> Either Error (P.Predbox a)
-parseExpression e toks = do
-  rpnToks <- case e of
-    Infix -> maybe (Left "unbalanced parentheses\n") Right
-             . I.createRPN
-             . map unToken
-             $ toks
-    RPN -> maybe (Left "parentheses in an RPN expression\n") Right
-           $ toksToRPN toks
-  R.parseRPN rpnToks
diff --git a/lib/Data/Prednote/Expressions/Infix.hs b/lib/Data/Prednote/Expressions/Infix.hs
deleted file mode 100644
--- a/lib/Data/Prednote/Expressions/Infix.hs
+++ /dev/null
@@ -1,132 +0,0 @@
-module Data.Prednote.Expressions.Infix
-  ( InfixToken (..)
-  , Paren(..)
-  , createRPN
-  ) where
-
-import Data.Functor.Contravariant
-import qualified Data.Prednote.Expressions.RPN as R
-import qualified Data.Foldable as Fdbl
-
-data InfixToken a
-  = TokRPN (R.RPNToken a)
-  | TokParen Paren
-
-instance Contravariant InfixToken where
-  contramap f t = case t of
-    TokRPN r -> TokRPN . contramap f $ r
-    TokParen p -> TokParen p
-
-data Paren = Open | Close
-
--- | Values on the operator stack.
-data OpStackVal
-  = StkOp R.Operator
-  | StkOpenParen
-
--- In the shunting yard algorithm, the output sequence is a queue. The
--- first values to go into the output sequence are the first to be
--- processed by the RPN parser. In this module, the output sequence is
--- implemented as a list stack, which means it must be reversed upon
--- output (this is done in the createRPN function.)
-
-processInfixToken
-  :: ([OpStackVal], [R.RPNToken a])
-  -> InfixToken a
-  -> Maybe ([OpStackVal], [R.RPNToken a])
-processInfixToken (os, ts) t = case t of
-  TokRPN tok -> return $ processRPNToken (os, ts) tok
-  TokParen p -> processParen (os, ts) p
-
-
--- | If the token is a binary operator A, then:
---
--- If A is left associative, while there is an operator B of higher or
--- equal precedence than A at the top of the stack, pop B off the
--- stack and append it to the output.
---
--- If A is right associative, while there is an operator B of higher
--- precedence than A at the top of the stack, pop B off the stack and
--- append it to the output.
---
--- Push A onto the stack.
---
--- If a token is an operand, append it to the postfix output.
---
--- And has higher precedence than Or.
-processRPNToken
-  :: ([OpStackVal], [R.RPNToken a])
-  -> R.RPNToken a
-  -> ([OpStackVal], [R.RPNToken a])
-processRPNToken (os, ts) t = case t of
-  p@(R.TokOperand _) -> (os, p:ts)
-  R.TokOperator d -> case d of
-    R.OpNot -> (StkOp R.OpNot : os, ts)
-    R.OpAnd -> (StkOp R.OpAnd : os, ts)
-    R.OpOr ->
-      let (os', ts') = popper os ts
-      in (StkOp R.OpOr : os', ts')
-
--- | Pops operators from the operator stack and places then in the
--- output queue, as long as there is an And operator on the top of the
--- operator stack.
-popper :: [OpStackVal] -> [R.RPNToken a] -> ([OpStackVal], [R.RPNToken a])
-popper os ts = case os of
-  [] -> (os, ts)
-  x:xs -> case x of
-    StkOp R.OpAnd ->
-      let os' = xs
-          ts' = R.TokOperator R.OpAnd : ts
-      in popper os' ts'
-    _ -> (os, ts)
-
--- | Pops operators off the operator stack and onto the output stack
--- as long as the top of the operator stack is not an open
--- parenthesis. When an open parenthesis is encountered, pop that too,
--- but not onto the output stack. Fails if the stack has no open
--- parentheses.
-popThroughOpen
-  :: ([OpStackVal], [R.RPNToken a])
-  -> Maybe ([OpStackVal], [R.RPNToken a])
-popThroughOpen (os, ts) = case os of
-  [] -> Nothing
-  v:vs -> case v of
-    StkOp op -> popThroughOpen (vs, R.TokOperator op : ts)
-    StkOpenParen -> return (vs, ts)
-
--- | Places an open parenthesis on the top of the operator stack. For
--- Close parenthesis, pops operators off the operator stack through
--- the next open parenthesis on the operator stack.
-processParen
-  :: ([OpStackVal], [R.RPNToken a])
-  -> Paren
-  -> Maybe ([OpStackVal], [R.RPNToken a])
-processParen (os, ts) p = case p of
-  Open -> Just (StkOpenParen : os, ts)
-  Close -> popThroughOpen (os, ts)
-
--- | Creates an RPN expression from an infix one. Fails only if there
--- are mismatched parentheses. It is possible to create a nonsensical
--- RPN expression; the RPN parser must catch this.
-createRPN
-  :: Fdbl.Foldable f
-  => f (InfixToken a)
-  -- ^ The input tokens, with the beginning of the expression on the
-  -- left side of the sequence.
-
-  -> Maybe [R.RPNToken a]
-  -- ^ The output sequence of tokens, with the beginning of the
-  -- expression on the left side of the list.
-createRPN ts = do
-  (stack, toks) <- Fdbl.foldlM processInfixToken ([], []) ts
-  fmap reverse $ popRemainingOperators stack toks
-
--- | Pops remaining items off operator stack. Fails if there is an
--- open paren left on the stack, as this indicates mismatched
--- parenthesis.
-popRemainingOperators :: [OpStackVal] -> [R.RPNToken a] -> Maybe [R.RPNToken a]
-popRemainingOperators os ts = case os of
-  [] -> return ts
-  x:xs -> case x of
-    StkOp op -> popRemainingOperators xs (R.TokOperator op : ts)
-    StkOpenParen -> Nothing
diff --git a/lib/Data/Prednote/Expressions/RPN.hs b/lib/Data/Prednote/Expressions/RPN.hs
deleted file mode 100644
--- a/lib/Data/Prednote/Expressions/RPN.hs
+++ /dev/null
@@ -1,85 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
--- | Postfix, or RPN, expression parsing.
---
--- This module parses RPN expressions where the operands are
--- predicates and the operators are one of @and@, @or@, or @not@,
--- where @and@ and @or@ are binary and @not@ is unary.
-module Data.Prednote.Expressions.RPN where
-
-import Data.Functor.Contravariant
-import qualified Data.Foldable as Fdbl
-import qualified Data.Prednote.Predbox as P
-import Data.Prednote.Predbox ((&&&), (|||))
-import Data.Monoid ((<>))
-import Data.Text (Text)
-import qualified Data.Text as X
-import qualified System.Console.Rainbow as C
-
-type Error = Text
-
-data RPNToken a
-  = TokOperand (P.Predbox a)
-  | TokOperator Operator
-
-instance Contravariant RPNToken where
-  contramap f t = case t of
-    TokOperand p -> TokOperand . contramap f $ p
-    TokOperator o -> TokOperator o
-
-data Operator
-  = OpAnd
-  | OpOr
-  | OpNot
-  deriving Show
-
-pushOperand :: P.Predbox a -> [P.Predbox a] -> [P.Predbox a]
-pushOperand p ts = p : ts
-
-pushOperator
-  :: Operator
-  -> [P.Predbox a]
-  -> Either Error [P.Predbox a]
-pushOperator o ts = case o of
-  OpAnd -> case ts of
-    x:y:zs -> return $ (y &&& x) : zs
-    _ -> Left $ err "and"
-  OpOr -> case ts of
-    x:y:zs -> return $ (y ||| x) : zs
-    _ -> Left $ err "or"
-  OpNot -> case ts of
-    x:zs -> return $ P.not x : zs
-    _ -> Left $ err "not"
-  where
-    err x = "insufficient operands to apply \"" <> x
-            <> "\" operator\n"
-
-pushToken
-  :: [P.Predbox a]
-  -> RPNToken a
-  -> Either Error [P.Predbox a]
-pushToken ts t = case t of
-  TokOperand p -> return $ pushOperand p ts
-  TokOperator o -> pushOperator o ts
-
-
--- | Parses an RPN expression and returns the resulting Predbox. Fails if
--- there are no operands left on the stack or if there are multiple
--- operands left on the stack; the stack must contain exactly one
--- operand in order to succeed.
-parseRPN
-  :: Fdbl.Foldable f
-  => f (RPNToken a)
-  -> Either Error (P.Predbox a)
-parseRPN ts = do
-  trees <- Fdbl.foldlM pushToken [] ts
-  case trees of
-    [] -> Left $ "bad expression: no operands left on the stack\n"
-    x:[] -> return x
-    xs -> Left
-      $ "bad expression: multiple operands left on the stack:\n"
-      <> ( X.concat
-           . concat
-           . map C.text
-           . concatMap (P.showPredbox 4 0)
-           $ xs )
-
diff --git a/lib/Data/Prednote/Predbox.hs b/lib/Data/Prednote/Predbox.hs
deleted file mode 100644
--- a/lib/Data/Prednote/Predbox.hs
+++ /dev/null
@@ -1,662 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
--- | Trees of predicates.
---
--- Exports names which conflict with Prelude names, so you probably
--- want to import this module qualified.
-
-module Data.Prednote.Predbox
-
-  ( -- * The Predbox tree
-    Label
-  , Hide
-  , Predbox(..)
-  , Node(..)
-
-  -- * Creating Predbox.
-  -- | All functions create Predbox that are shown by default.
-  , predicate
-  , and
-  , or
-  , not
-  , (&&&)
-  , (|||)
-  , always
-  , never
-
-  -- * Controlling whether Predbox are shown in the results
-  , hide
-  , show
-  , hideTrue
-  , hideFalse
-
-  -- * Renaming Predbox
-  , rename
-
-  -- * Result
-  , Result(..)
-  , RNode(..)
-
-  -- * Showing and evaluating Predbox
-  , evaluate
-  , evaluateNode
-  , IndentAmt
-  , Level
-  , ShowAll
-  , showResult
-  , showTopResult
-  , showPredbox
-  , filter
-  , verboseFilter
-
-  -- * Helpers for building common Predbox
-  -- ** Non-overloaded
-
-  -- | Each of these functions builds a Predbox that compares two
-  -- items.  The predicate in the Predbox is applied to an item that
-  -- is considered to be the left hand side of the comparison.  The
-  -- left hand side side can change; the right hand side is baked
-  -- into the Predbox.
-  --
-  -- For example, to build a Predbox that returns True if an item is
-  -- greater than 5:
-  --
-  -- >>> :set -XOverloadedStrings
-  -- >>> let p = compareBy "5" "integer" (`Prelude.compare` (5 :: Integer)) GT
-  -- >>> rBool . evaluate p $ 6
-  -- True
-  -- >>> rBool . evaluate p $ 4
-  -- False
-  , compareBy
-  , compareByMaybe
-  , greaterBy
-  , lessBy
-  , equalBy
-  , greaterEqBy
-  , lessEqBy
-  , notEqBy
-
-  -- ** Overloaded
-  , compare
-  , greater
-  , less
-  , equal
-  , greaterEq
-  , lessEq
-  , notEq
-  , parseComparer
-
-  ) where
-
-
--- # Imports
-
-import Data.Functor.Contravariant hiding (Predicate)
-import Data.Text (Text)
-import qualified Data.Text as X
-import Data.Monoid ((<>), mconcat, mempty)
-import Data.String (fromString)
-import qualified System.Console.Rainbow as R
-import Prelude hiding (not, and, or, compare, filter, show)
-import qualified Prelude
-
--- # Predbox type
-
-type Label = Text
-
--- | Determines whether a result is shown by default.
-type Hide = Bool
-
--- | A predicate. Each Predbox contains a tree of Node.
-data Predbox a = Predbox
-  { pLabel :: Label
-  -- ^ Label used when showing the results
-
-  , pHide :: (Bool -> Hide)
-  -- ^ As results are computed, this function is applied to the
-  -- result. If this function returns False, then this Predbox will not
-  -- be shown by default in the results.
-
-  , pNode :: Node a
-
-  }
-
-data Node a
-  = And [Predbox a]
-  -- ^ Conjunction. If any Predbox in the list is False, the result is
-  -- False. If the list is empty, the result is True.
-
-  | Or [Predbox a]
-  -- ^ Disjunction. If at least one Predbox in the list is True, the
-  -- result it True. If the list is empty, the result is False.
-
-  | Not (Predbox a)
-  -- ^ Negation
-
-  | Predicate (a -> Bool)
-  -- ^ Most basic building block.
-
--- | Renames the top level of the Predbox. The function you pass will be
--- applied to the old name.
-rename :: (Text -> Text) -> Predbox a -> Predbox a
-rename f p = p { pLabel = f (pLabel p) }
-
--- | Always True
-always :: Predbox a
-always = Predbox "always True" (const False) (Predicate (const True))
-
--- | Always False
-never :: Predbox a
-never = Predbox "always False" (const False) (Predicate (const False))
-
--- | Creates and labels predicates.
-predicate :: Label -> (a -> Bool) -> Predbox a
-predicate l = Predbox l (const False) . Predicate
-
--- | Creates And Predbox using a generic name
-and :: [Predbox a] -> Predbox a
-and = Predbox "and" (const False) . And
-
--- | Creates Or Predbox using a generic name
-or :: [Predbox a] -> Predbox a
-or = Predbox "or" (const False) . Or
-
--- | Creates Not Predbox using a generic name
-not :: Predbox a -> Predbox a
-not = Predbox "not" (const False) . Not
-
--- | Changes a Predbox so it is always hidden by default.
-hide :: Predbox a -> Predbox a
-hide p = p { pHide = const True }
-
--- | Changes a Predbox so it is always shown by default.
-show :: Predbox a -> Predbox a
-show p = p { pHide = const False }
-
--- | Changes a Predbox so that it is hidden if its result is True.
-hideTrue :: Predbox a -> Predbox a
-hideTrue p = p { pHide = id }
-
--- | Changes a Predbox so that it is hidden if its result is False.
-hideFalse :: Predbox a -> Predbox a
-hideFalse p = p { pHide = Prelude.not }
-
--- | Forms a Predbox using 'and'; assigns a generic label.
-(&&&) :: Predbox a -> Predbox a -> Predbox a
-(&&&) x y = Predbox "and" (const False) (And [x, y])
-infixr 3 &&&
-
--- | Forms a Predbox using 'or'; assigns a generic label.
-(|||) :: Predbox a -> Predbox a -> Predbox a
-(|||) x y = Predbox "or" (const False) (Or [x, y])
-infixr 2 |||
-
-instance Contravariant Predbox where
-  contramap f (Predbox l d n) = Predbox l d $ contramap f n
-
-instance Contravariant Node where
-  contramap f n = case n of
-    And ls -> And $ map (contramap f) ls
-    Or ls -> Or $ map (contramap f) ls
-    Not o -> Not $ contramap f o
-    Predicate g -> Predicate $ \b -> g (f b)
-
--- # Result
-
--- | The result from evaluating a Predbox.
-data Result = Result
-  { rLabel :: Label
-  -- ^ The label from the original Predbox
-
-  , rBool :: Bool
-  -- ^ The boolean result from evaluating the node. If the node is an
-  -- predicate, this is the result of applying the predicate function to
-  -- the subject. Otherwise, this is the result of application of the
-  -- appropriate boolean operation to the child nodes.
-
-  , rHide :: Hide
-  -- ^ Is this result hidden in the result by default? Hiding only
-  -- affects presentation; it does not affect how this Predbox affects
-  -- any parent Predbox.
-  , rNode :: RNode
-  } deriving (Eq, Show)
-
-data RNode
-  = RAnd [Result]
-  | ROr [Result]
-  | RNot Result
-  | RPredicate Bool
-  deriving (Eq, Show)
-
--- | Applies a Predbox to a particular value, known as the subject.
-evaluate :: Predbox a -> a -> Result
-evaluate (Predbox l d n) a = Result l r d' rn
-  where
-    rn = evaluateNode n a
-    r = case rn of
-      RAnd ls -> all rBool ls
-      ROr ls -> any rBool ls
-      RNot x -> Prelude.not . rBool $ x
-      RPredicate b -> b
-    d' = d r
-
-evaluateNode :: Node a -> a -> RNode
-evaluateNode n a = case n of
-  And ls -> RAnd (map (flip evaluate a) ls)
-  Or ls -> ROr (map (flip evaluate a) ls)
-  Not l -> RNot (flip evaluate a l)
-  Predicate f -> RPredicate (f a)
-
--- # Types and functions for showing
-
--- | The number of spaces to use for each level of indentation.
-type IndentAmt = Int
-
--- | How many levels of indentation to use. Typically you will start
--- this at zero. It is incremented by one for each level as functions
--- descend through the tree.
-type Level = Int
-
--- | Indents text, and adds a newline to the end.
-indent :: IndentAmt -> Level -> [R.Chunk] -> [R.Chunk]
-indent amt lvl cs = idt : (cs ++ [nl])
-  where
-    idt = fromString (replicate (lvl * amt) ' ')
-    nl = fromString "\n"
-
--- # Showing Predbox
-
--- | Creates a plain Chunk from a Text.
-plain :: Text -> R.Chunk
-plain = R.Chunk mempty . (:[])
-
--- | Shows a Predbox tree without evaluating it.
-showPredbox :: IndentAmt -> Level -> Predbox a -> [R.Chunk]
-showPredbox amt lvl (Predbox l _ pd) = case pd of
-  And ls -> indent amt lvl [plain ("and - " <> l)]
-            <> mconcat (map (showPredbox amt (lvl + 1)) ls)
-  Or ls -> indent amt lvl [plain ("or - " <> l)]
-           <> mconcat (map (showPredbox amt (lvl + 1)) ls)
-  Not t -> indent amt lvl [plain ("not - " <> l)]
-           <> showPredbox amt (lvl + 1) t
-  Predicate _ -> indent amt lvl [plain ("predicate - " <> l)]
-
-instance Show (Predbox a) where
-  show = X.unpack
-       . X.concat
-       . concat
-       . map R.text
-       . showPredbox 2 0
-
-
-filter :: Predbox a -> [a] -> [a]
-filter pd as
-  = map fst
-  . Prelude.filter (rBool . snd)
-  . zip as
-  . map (evaluate pd)
-  $ as
-
-
--- # Showing Result
-
-labelBool :: Text -> Bool -> [R.Chunk]
-labelBool t b = [open, trueFalse, close, blank, txt]
-  where
-    trueFalse = 
-      if b then "TRUE" <> R.f_green else "FALSE" <> R.f_red
-    open = "["
-    close = "]"
-    blank = plain (X.replicate blankLen " ")
-    blankLen = X.length "discard"
-               - (sum . map X.length . R.text $ trueFalse) + 1
-    txt = plain t
-
-type ShowAll = Bool
-
--- | Shows a Result in a pretty way with colors and indentation.
-showResult
-  :: IndentAmt
-  -- ^ Indent each level by this many spaces
-
-  -> ShowAll
-  -- ^ If True, shows all Predbox, even ones where 'rHide' is
-  -- True. Otherwise, respects 'rHide' and does not show hidden Predbox.
-
-  -> Level
-  -- ^ How deep in the tree we are; this increments by one for each
-  -- level of descent.
-
-  -> Result
-  -- ^ The result to show
-
-  -> [R.Chunk]
-showResult amt sa lvl (Result lbl rslt hd nd)
-  | hd && Prelude.not sa = []
-  | otherwise = firstLine ++ restLines
-  where
-    firstLine = indent amt lvl $ labelBool lbl rslt
-    restLines = case nd of
-      RAnd ls -> f False ls
-      ROr ls -> f True ls
-      RNot r -> showResult amt sa (lvl + 1) r
-      RPredicate _ -> []
-    f stopOn ls = concatMap sr ls' ++ end
-      where
-        ls' = takeThrough ((== stopOn) . rBool) ls
-        sr = showResult amt sa (lvl + 1)
-        end = if ls' `shorter` ls
-              then indent amt (lvl + 1) ["(short circuit)"]
-              else []
-
--- | @shorter x y@ is True if list x is shorter than list y. Lazier
--- than taking the length of each list and comparing the results.
-shorter :: [a] -> [a] -> Bool
-shorter [] [] = False
-shorter (_:_) [] = False
-shorter [] (_:_) = True
-shorter (_:xs) (_:ys) = shorter xs ys
-
--- | For instance,
---
--- > takeThrough odd [2,4,6,7,8] == [2,4,6,7]
-takeThrough :: (a -> Bool) -> [a] -> [a]
-takeThrough _ [] = []
-takeThrough f (x:xs) = x : if f x then [] else takeThrough f xs
-
--- | Shows the top of a Result tree and all the child Results. Adds a
--- short label at the top of the tree.
-showTopResult
-  :: X.Text
-  -- ^ Label to add to the top of the tree.
-  -> IndentAmt
-  -- ^ Indent each level by this many spaces
-  -> Level
-  -- ^ Indent the top by this many levels
-  -> ShowAll
-  -- ^ If True, shows all Predbox, even ones where 'rHide' is
-  -- True. Otherwise, respects 'rHide' and does not show hidden Predbox.
-
-  -> Result
-  -- ^ The result to show
-  -> [R.Chunk]
-showTopResult txt i lvl sd r = showResult i sd lvl r'
-  where
-    r' = r { rLabel = rLabel r <> " - " <> txt }
-
-
--- | Filters a list. Also returns chunks describing the process.
-verboseFilter
-  :: (a -> X.Text)
-  -- ^ How to describe each subject
-
-  -> IndentAmt
-  -- ^ Indent each level by this many spaces
-
-  -> ShowAll
-  -- ^ If True, shows all Predbox, even ones where 'rHide' is
-  -- True. Otherwise, respects 'rHide' and does not show hidden Predbox.
-
-  -> Predbox a
-  -- ^ Used to perform the filtering
-
-  -> [a]
-  -> ([R.Chunk], [a])
-
-verboseFilter desc amt sa pd as = (chks, as')
-  where
-    rs = map (evaluate pd) as
-    subjAndRslts = zip as rs
-    mkChks (subj, rslt) = showTopResult (desc subj) amt 0 sa rslt
-    chks = concatMap mkChks subjAndRslts
-    as' = map fst . Prelude.filter (rBool . snd) $ subjAndRslts
-
--- # Comparisons
-
--- | Build a Predbox that compares items.
-compareBy
-  :: Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> Text
-  -- ^ Description of the type of thing that is being matched
-
-  -> (a -> Ordering)
-  -- ^ How to compare an item against the right hand side. Return LT
-  -- if the item is less than the right hand side; GT if greater; EQ
-  -- if equal to the right hand side.
-
-  -> Ordering
-  -- ^ When subjects are compared, this ordering must be the result in
-  -- order for the Predbox to be True; otherwise it is False. The subject
-  -- will be on the left hand side.
-
-  -> Predbox a
-
-compareBy itemDesc typeDesc cmp ord = Predbox l (const False) (Predicate f)
-  where
-    l = typeDesc <> " is " <> cmpDesc <> " " <> itemDesc
-    cmpDesc = case ord of
-      LT -> "less than"
-      GT -> "greater than"
-      EQ -> "equal to"
-    f subj = cmp subj == ord
-
--- | Overloaded version of 'compareBy'.
-compare
-  :: (Show a, Ord a)
-  => Text
-  -- ^ Description of the type of thing being matched
-
-  -> a
-  -- ^ The right hand side of the comparison.
-
-  -> Ordering
-  -- ^ When subjects are compared, this ordering must be the result in
-  -- order for the Predbox to be True; otherwise it is False. The subject
-  -- will be on the left hand side.
-
-  -> Predbox a
-compare typeDesc a ord = compareBy itemDesc typeDesc cmp ord
-  where
-    itemDesc = X.pack . Prelude.show $ a
-    cmp item = Prelude.compare item a
-
--- | Builds a Predbox for items that might fail to return a comparison.
-compareByMaybe
-  :: Text
-  -- ^ How to show the item being compared
-
-  -> Text
-  -- ^ Description of type of thing being matched
-
-  -> (a -> Maybe Ordering)
-  -- ^ How to compare against right hand side. If Nothing, a Predbox that
-  -- always returns False is returned.
-
-  -> Ordering
-  -- ^ Ordering that must result for the Predbox to be True
-
-  -> Predbox a
-
-compareByMaybe itemDesc typeDesc cmp ord =
-  Predbox l (const False) (Predicate f)
-  where
-    l = typeDesc <> " is " <> cmpDesc <> " " <> itemDesc
-    cmpDesc = case ord of
-      LT -> "less than"
-      GT -> "greater than"
-      EQ -> "equal to"
-    f subj = case cmp subj of
-      Nothing -> False
-      Just ord' -> ord == ord'
-
-greater
-  :: (Show a, Ord a)
-  => Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> a
-  -- ^ The right hand side of the comparison.
-
-  -> Predbox a
-greater d a = compare d a GT
-
-less
-  :: (Show a, Ord a)
-  => Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> a
-  -- ^ The right hand side of the comparison.
-
-  -> Predbox a
-less d a = compare d a LT
-
-equal
-  :: (Show a, Ord a)
-  => Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> a
-  -- ^ The right hand side of the comparison.
-
-  -> Predbox a
-equal d a = compare d a EQ
-
-greaterEq
-  :: (Show a, Ord a)
-  => Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> a
-  -- ^ The right hand side of the comparison.
-
-  -> Predbox a
-greaterEq d a = greater d a ||| equal d a
-
-lessEq
-  :: (Show a, Ord a)
-  => Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> a
-  -- ^ The right hand side of the comparison.
-
-  -> Predbox a
-lessEq d a = less d a ||| equal d a
-
-notEq
-  :: (Show a, Ord a)
-  => Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> a
-  -- ^ The right hand side of the comparison.
-
-  -> Predbox a
-notEq d a = not $ equal d a
-
-greaterBy
-  :: Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> Text
-  -- ^ Description of the type of thing that is being matched
-
-  -> (a -> Ordering)
-  -- ^ How to compare two items
-
-  -> Predbox a
-greaterBy iD tD cmp = compareBy iD tD cmp GT
-
-lessBy
-  :: Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> Text
-  -- ^ Description of the type of thing that is being matched
-
-  -> (a -> Ordering)
-  -- ^ How to compare two items
-
-  -> Predbox a
-lessBy iD tD cmp = compareBy iD tD cmp LT
-
-equalBy
-  :: Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> Text
-  -- ^ Description of the type of thing that is being matched
-
-  -> (a -> Ordering)
-  -- ^ How to compare two items
-
-  -> Predbox a
-equalBy iD tD cmp = compareBy iD tD cmp EQ
-
-greaterEqBy
-  :: Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> Text
-  -- ^ Description of the type of thing that is being matched
-
-  -> (a -> Ordering)
-  -- ^ How to compare two items
-
-  -> Predbox a
-greaterEqBy iD tD cmp =
-  greaterBy iD tD cmp ||| equalBy iD tD cmp
-
-lessEqBy
-  :: Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> Text
-  -- ^ Description of the type of thing that is being matched
-
-  -> (a -> Ordering)
-  -- ^ How to compare two items
-
-  -> Predbox a
-lessEqBy iD tD cmp =
-  lessBy iD tD cmp ||| equalBy iD tD cmp
-
-notEqBy
-  :: Text
-  -- ^ How to show the item being compared; used to describe the Predbox
-
-  -> Text
-  -- ^ Description of the type of thing that is being matched
-
-  -> (a -> Ordering)
-  -- ^ How to compare two items
-
-  -> Predbox a
-notEqBy iD tD cmp =
-  not $ equalBy iD tD cmp
-
--- | Parses a string to find the correct comparer; returns the correct
--- function to build a Predbox.
-
-parseComparer
-  :: Text
-  -- ^ The string with the comparer to be parsed
-  -> (Ordering -> Predbox a)
-  -- ^ A function that, when given an ordering, returns a Predbox
-  -> Maybe (Predbox a)
-  -- ^ If an invalid comparer string is given, Nothing; otherwise, the
-  -- Predbox.
-parseComparer t f
-  | t == ">" = Just (f GT)
-  | t == "<" = Just (f LT)
-  | t == "=" = Just (f EQ)
-  | t == "==" = Just (f EQ)
-  | t == ">=" = Just (f GT ||| f EQ)
-  | t == "<=" = Just (f LT ||| f EQ)
-  | t == "/=" = Just (not $ f EQ)
-  | t == "!=" = Just (not $ f EQ)
-  | otherwise = Nothing
-
diff --git a/lib/Data/Prednote/Test.hs b/lib/Data/Prednote/Test.hs
deleted file mode 100644
--- a/lib/Data/Prednote/Test.hs
+++ /dev/null
@@ -1,224 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
--- | Helps you build tests that run against a series of items.
-module Data.Prednote.Test
-  (
-  -- * Test data types
-    Name
-  , Verbosity(..)
-  , TrueVerbosity
-  , FalseVerbosity
-  , TestVisibility(..)
-  , TestVerbosity(..)
-  , Pass
-  , Test(..)
-  , TestResult(..)
-
-  -- * Pre-built tests
-  , eachSubjectMustBeTrue
-  , nSubjectsMustBeTrue
-
-  -- * Running and showing tests
-  , evalTest
-  , showTestResult
-
-  ) where
-
-import Control.Arrow (first)
-import Data.Functor.Contravariant
-import Data.Maybe (fromMaybe)
-import Data.Monoid ((<>), mempty)
-import qualified Data.Text as X
-import Data.Text (Text)
-
-import qualified System.Console.Rainbow as R
-import qualified Data.Prednote.Predbox as Pt
-
--- # Types
-
--- | How verbose to be when showing the results of running a Predbox on a
--- single subject.
-data Verbosity
-  = HideAll
-  -- ^ Do not show any results from the Predbox
-
-  | ShowDefaults
-  -- ^ Show results according to the default settings provided in the
-  -- Result itself
-
-  | ShowAll
-  -- ^ Show all Result
-  deriving (Eq, Show)
-
--- | Use this verbosity for subjects that are True
-type TrueVerbosity = Verbosity
-
--- | Use this verbosity for subjects that are False
-type FalseVerbosity = Verbosity
-
--- | Determines whether to show any of the results from a single test.
-data TestVisibility
-  = HideTest
-  -- ^ Do not show any results from this test
-
-  | ShowFirstLine TrueVerbosity FalseVerbosity
-  -- ^ Show the first line, which indicates whether the test passed or
-  -- failed and gives the label for the test. Whether to show
-  -- individual subjects is determined by the TrueVerbosity and
-  -- FalseVerbosity.
-
-  deriving (Eq, Show)
-
--- | Determines which TestVisibility to use for a particular test.
-data TestVerbosity = TestVerbosity
-  { onPass :: TestVisibility
-    -- ^ Use this TestVisibility when the test passes
-  , onFail :: TestVisibility
-    -- ^ Use this TestVisibility when the test fails
-  } deriving (Eq, Show)
-
-type Pass = Bool
-
--- | The name of a test or of a group.
-type Name = Text
-
--- | A single test.
-data Test a = Test
-  { testName :: Name
-  , testPass :: [Pt.Result] -> Pass
-  -- ^ Applied to the results of all applications of testFunc;
-  -- determines whether the test passes or fails.
-
-  , testFunc :: a -> Pt.Result
-  -- ^ This function is applied to each subject.
-
-  , testVerbosity :: TestVerbosity
-  -- ^ Default verbosity for the test.
-  }
-
-instance Contravariant Test where
-  contramap f t = t { testFunc = testFunc t . f }
-
-data TestResult a = TestResult
-  { resultName :: Name
-  , resultPass :: Pass
-  , resultSubjects :: [(a, Pt.Result)]
-  , resultDefaultVerbosity :: TestVerbosity
-  }
-
-instance Functor TestResult where
-  fmap f t = t { resultSubjects = map (first f) . resultSubjects $ t }
-
--- # Showing tests
-
--- | Creates a plain Chunk from a Text.
-plain :: X.Text -> R.Chunk
-plain = R.Chunk mempty . (:[])
-
-showTestTitle :: Name -> Pass -> [R.Chunk]
-showTestTitle n p = [open, passFail, close, blank, txt, nl]
-  where
-    nl = plain "\n"
-    passFail =
-      if p
-      then "PASS" <> R.f_green
-      else "FAIL" <> R.f_red
-    open = plain "["
-    close = plain "]"
-    blank = plain (X.singleton ' ')
-    txt = plain n
-
--- | Evaluates a test for a given list of subjects.
-evalTest :: Test a -> [a] -> TestResult a
-evalTest (Test n fPass fSubj vy) ls = TestResult n p ss vy
-  where
-    p = fPass results
-    results = map fSubj ls
-    ss = zip ls results
-
--- | Shows a result with indenting.
-showTestResult
-  :: Pt.IndentAmt
-  -- ^ Indent each level by this many spaces
-
-  -> (a -> Text)
-  -- ^ Shows each subject. The function should return a single-line
-  -- text without a trailing newline.
-
-  -> Maybe TestVerbosity
-  -- ^ If Just, use this TestVerbosity when showing the test. If
-  -- Nothing, use the default verbosity.
-
-  -> TestResult a
-  -- ^ The result to show
-
-  -> [R.Chunk]
-showTestResult amt swr mayVb (TestResult n p ss dfltVb) =
-  let vb = fromMaybe dfltVb mayVb
-      tv = if p then onPass vb else onFail vb
-      firstLine = showTestTitle n p
-  in case tv of
-      HideTest -> []
-      ShowFirstLine trueV falseV ->
-        firstLine
-        ++ concatMap (showSubject p amt swr (trueV, falseV)) ss
-
-showSubject
-  :: Pass
-  -> Pt.IndentAmt
-  -> (a -> Text)
-  -> (TrueVerbosity, FalseVerbosity)
-  -> (a, Pt.Result)
-  -> [R.Chunk]
-showSubject p amt swr (tv, fv) (a, r) =
-  let txt = swr a
-      vb = if p then tv else fv
-  in case vb of
-      HideAll -> []
-      ShowDefaults -> Pt.showTopResult txt amt 1 False r
-      ShowAll -> Pt.showTopResult txt amt 1 True r
-
--- # Pre-built tests
-
--- | The test passes if each subject returns True.
-eachSubjectMustBeTrue :: Pt.Predbox a -> Name -> Test a
-eachSubjectMustBeTrue pd nm = Test nm pass f vy
-  where
-    vy = TestVerbosity
-      { onPass = ShowFirstLine HideAll HideAll
-      , onFail = ShowFirstLine HideAll ShowDefaults }
-    pass = all Pt.rBool
-    f = Pt.evaluate pd
-
-
--- | The test passes if at least a given number of subjects are True.
-nSubjectsMustBeTrue
-  :: Pt.Predbox a
-  -> Name
-  -> Int
-  -- ^ The number of subjects that must be True. This should be a
-  -- positive number.
-  -> Test a
-nSubjectsMustBeTrue pd nm i = Test nm pass f vy
-  where
-    pass = atLeast i . filter Pt.rBool
-    f = Pt.evaluate pd
-    vy = TestVerbosity
-      { onPass = ShowFirstLine HideAll HideAll
-      , onFail = ShowFirstLine HideAll HideAll }
-
-
--- # Basement
-
--- | Returns True if the list has at least this many elements. Lazier
--- than taking the length of the list.
-atLeast :: Int -> [a] -> Bool
-atLeast i as
-  | i < 0 = error "atLeast: negative length parameter"
-  | otherwise = go 0 as
-  where
-    go _ [] = i == 0
-    go soFar (_:xs) =
-      let nFound = soFar + 1
-      in if nFound == i then True else go nFound xs
-
diff --git a/lib/Prednote.hs b/lib/Prednote.hs
new file mode 100644
--- /dev/null
+++ b/lib/Prednote.hs
@@ -0,0 +1,117 @@
+-- | This module provides everything you need for most uses of Prednote.
+-- The core type of Prednote is the 'Pred', which is a rose
+-- 'Tree' of predicates along with some additional information, such
+-- as a 'plan', which shows you how the 'Pred' will be evaluated
+-- without actually having to apply it to a particular subject.  When
+-- evaluating a 'Pred', you can also display a 'report' describing the
+-- evaluation process.
+--
+-- This module builds 'Pred' with 'report's that make sparing use of
+-- color; for example, 'True' results have @[TRUE]@ indicated in
+-- green, @[FALSE]@ in red, and /short circuits/ (that is, 'Pred' that
+-- were evaluated without evaluating all their child 'Pred') indicated
+-- in yellow.  They are also nicely indented to indicate the structure
+-- of the 'Tree'.
+--
+-- If you want more control over how your results are formatted,
+-- examine "Prednote.Core" and "Prednote.Format".
+-- "Prednote.Comparisons" builds on this module to provide 'Pred' to
+-- use for common comparisons (such as greater than, less than, etc.)
+-- "Prednote.Expressions" helps you parse infix or postfix (i.e. RPN)
+-- expressions.
+--
+-- This module exports some names that conflict with Prelude names, so
+-- you might want to do something like
+--
+-- > import qualified Prednote as P
+module Prednote
+  ( -- * Pred
+    Pred
+
+    -- * Visibility
+
+    -- | Upon evaluation, each 'Pred' has a visibility, indicated with
+    -- 'Visible'.  It can be either 'shown' or 'hidden'.  The
+    -- visibility of a 'Pred' does not affect any of the results, nor
+    -- does it affect how the 'Pred' is shown in the 'plan'; rather,
+    -- it affects only how the result of the 'Pred' is shown in the
+    -- 'report'.  If a 'Pred' is 'hidden', its value and the value of
+    -- its children is not shown in the 'report'.
+
+  , C.Visible
+  , C.shown
+  , C.hidden
+  , P.visibility
+  , P.reveal
+  , P.hide
+  , P.showTrue
+  , P.showFalse
+
+    -- * Predicates
+
+    -- | These 'Pred' have no child 'Pred'.
+
+  , P.predicate
+  , P.true
+  , P.false
+  , P.same
+
+  -- * Combinators
+
+  -- | These functions combine one more more 'Pred' to create a new
+  -- 'Pred'; the argument 'Pred' become children of the new 'Pred'.
+
+  , P.all
+  , (&&&)
+  , P.any
+  , (|||)
+  , P.not
+
+  -- ** Fanout
+
+  -- | These functions allow you to take a single subject and split it
+  -- into multiple subjects, applying a 'Pred' to each subject that
+  -- results.  As a simple example, this allows you to build a 'Pred'
+  -- ['Int'] that combines 'Pred' that test individual 'Int' along
+  -- with 'Pred' that examine the entire list of ['Int'].
+
+  , P.fanAll
+  , P.fanAny
+  , P.fanAtLeast
+
+  -- * Reports and plans
+
+  -- | A 'plan' displays a 'Pred' without evaluating it, while a
+  -- 'report' shows the process through which a 'Pred' was evaluated
+  -- for a particular subject.
+
+  , C.Output
+  , plan
+  , C.evaluate
+  , report
+
+  -- * Evaluation and reporting
+
+  -- | These functions use 'report', 'C.evaluate', or both.
+
+  , C.test
+  , C.testV
+  , C.filter
+  , C.filterV
+  ) where
+
+import qualified Prednote.Prebuilt as P
+import Prednote.Prebuilt ((&&&), (|||))
+import Prednote.Core (Pred)
+import qualified Prednote.Core as C
+import System.Console.Rainbow
+import Data.Tree
+
+-- | Indents and formats static labels for display.  This is a 'plan'
+-- for how the 'Pred' would be applied.
+plan :: Pred a -> [Chunk]
+plan = C.plan 0
+
+-- | Indents and formats output for display.
+report :: Tree C.Output -> [Chunk]
+report = C.report 0
diff --git a/lib/Prednote/Comparisons.hs b/lib/Prednote/Comparisons.hs
new file mode 100644
--- /dev/null
+++ b/lib/Prednote/Comparisons.hs
@@ -0,0 +1,315 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Prednote.Comparisons where
+
+import Prednote.Prebuilt
+import Prednote.Format
+import qualified Prednote.Core as C
+import Data.Text (Text)
+import qualified Data.Text as X
+import Prelude hiding (compare, not)
+import qualified Prelude
+
+-- | Build a Pred that compares items.  The idea is that the item on
+-- the right hand side is baked into the 'Pred' and that the 'Pred'
+-- compares this single right-hand side to each left-hand side item.
+compareBy
+  :: Text
+  -- ^ Description of the type of thing that is being matched
+
+  -> Text
+  -- ^ Description of the right-hand side
+
+  -> (a -> Text)
+  -- ^ Describes the left-hand side
+
+  -> (a -> Ordering)
+  -- ^ How to compare the left-hand side to the right-hand side.
+  -- Return LT if the item is less than the right hand side; GT if
+  -- greater; EQ if equal to the right hand side.
+
+  -> Ordering
+  -- ^ When subjects are compared, this ordering must be the result in
+  -- order for the Predbox to be True; otherwise it is False. The subject
+  -- will be on the left hand side.
+
+  -> C.Pred a
+
+compareBy typeDesc rhsDesc lhsDesc get ord = predicate stat dyn pd
+  where
+    stat = typeDesc <+> "is" <+> ordDesc <+> rhsDesc
+    ordDesc = case ord of
+      EQ -> "equal to"
+      LT -> "less than"
+      GT -> "greater than"
+    dyn a = typeDesc <+> lhsDesc a <+> "is" <+> ordDesc <+> rhsDesc
+    pd a = get a == ord
+
+-- | Overloaded version of 'compareBy'.
+
+compare
+  :: (Show a, Ord a)
+  => Text
+  -- ^ Description of the type of thing that is being matched
+
+  -> a
+  -- ^ Right-hand side
+
+  -> Ordering
+  -- ^ When subjects are compared, this ordering must be the result in
+  -- order for the Predbox to be True; otherwise it is False. The subject
+  -- will be on the left hand side.
+
+  -> C.Pred a
+compare typeDesc rhs ord =
+  compareBy typeDesc (X.pack . show $ rhs) (X.pack . show)
+            (`Prelude.compare` rhs) ord
+
+-- | Builds a 'Pred' that tests items for equality.
+
+equalBy
+  :: Text
+  -- ^ Description of the type of thing that is being matched
+
+  -> Text
+  -- ^ Description of the right-hand side
+
+  -> (a -> Text)
+  -- ^ Describes the left-hand side
+
+  -> (a -> Bool)
+  -- ^ How to compare an item against the right hand side.  Return
+  -- 'True' if the items are equal; 'False' otherwise.
+
+  -> C.Pred a
+equalBy typeDesc rhsDesc lhsDesc get = predicate stat dyn get
+  where
+    stat = typeDesc <+> "is equal to" <+> rhsDesc
+    dyn a = typeDesc <+> lhsDesc a <+> "is equal to" <+> rhsDesc
+
+-- | Overloaded version of 'equalBy'.
+
+equal
+  :: (Eq a, Show a)
+  => Text
+  -- ^ Description of the type of thing that is being matched
+
+  -> a
+  -- ^ Right-hand side
+
+  -> C.Pred a
+equal typeDesc rhs = equalBy typeDesc (X.pack . show $ rhs)
+                             (X.pack . show) (== rhs)
+
+
+-- | Builds a 'Pred' for items that might fail to return a comparison.
+compareByMaybe
+  :: Text
+  -- ^ Description of the type of thing that is being matched
+
+  -> Text
+  -- ^ Description of the right-hand side
+
+  -> (a -> Text)
+  -- ^ Describes the left-hand side
+
+  -> (a -> Maybe Ordering)
+  -- ^ How to compare an item against the right hand side. Return LT if
+  -- the item is less than the right hand side; GT if greater; EQ if
+  -- equal to the right hand side.
+
+  -> Ordering
+  -- ^ When subjects are compared, this ordering must be the result in
+  -- order for the Predbox to be True; otherwise it is False. The subject
+  -- will be on the left hand side.
+
+  -> C.Pred a
+
+compareByMaybe typeDesc rhsDesc lhsDesc get ord = predicate stat dyn fn
+  where
+    stat = typeDesc <+> "is" <+> ordDesc <+> rhsDesc
+    dyn a = typeDesc <+> lhsDesc a <+> "is" <+> ordDesc <+> rhsDesc
+    ordDesc = case ord of
+      EQ -> "equal to"
+      LT -> "less than"
+      GT -> "greater than"
+    fn a = case get a of
+      Nothing -> False
+      Just o -> o == ord
+
+greater
+  :: (Show a, Ord a)
+
+  => Text
+  -- ^ Description of the type of thing being matched
+
+  -> a
+  -- ^ Right-hand side
+
+  -> C.Pred a
+greater typeDesc rhs = compare typeDesc rhs GT
+
+less
+  :: (Show a, Ord a)
+
+  => Text
+  -- ^ Description of the type of thing being matched
+
+  -> a
+  -- ^ Right-hand side
+
+  -> C.Pred a
+less typeDesc rhs = compare typeDesc rhs LT
+
+greaterEq
+  :: (Show a, Ord a)
+  => Text
+  -- ^ Description of the type of thing being matched
+
+  -> a
+  -- ^ Right-hand side
+
+  -> C.Pred a
+greaterEq t r = greater t r ||| equal t r
+
+lessEq
+  :: (Show a, Ord a)
+  => Text
+  -- ^ Description of the type of thing being matched
+
+  -> a
+  -- ^ Right-hand side
+
+  -> C.Pred a
+lessEq t r = less t r ||| equal t r
+
+notEq
+  :: (Show a, Eq a)
+  => Text
+  -- ^ Description of the type of thing being matched
+
+  -> a
+  -- ^ Right-hand side
+
+  -> C.Pred a
+notEq t r = not $ equal t r
+
+greaterBy
+  :: Text
+  -- ^ Description of the type of thing being matched
+
+  -> Text
+  -- ^ Description of right-hand side
+
+  -> (a -> Text)
+  -- ^ Describes the left-hand side
+
+  -> (a -> Ordering)
+  -- ^ How to compare an item against the right hand side. Return LT
+  -- if the item is less than the right hand side; GT if greater; EQ
+  -- if equal to the right hand side.
+
+  -> C.Pred a
+greaterBy dT dR dL get = compareBy dT dR dL get GT
+
+
+lessBy
+  :: Text
+  -- ^ Description of the type of thing being matched
+
+  -> Text
+  -- ^ Description of right-hand side
+
+  -> (a -> Text)
+  -- ^ Describes the left-hand side
+
+  -> (a -> Ordering)
+  -- ^ How to compare an item against the right hand side. Return LT
+  -- if the item is less than the right hand side; GT if greater; EQ
+  -- if equal to the right hand side.
+
+  -> C.Pred a
+lessBy dT dR dL get = compareBy dT dR dL get LT
+
+greaterEqBy
+  :: Text
+  -- ^ Description of the type of thing being matched
+
+  -> Text
+  -- ^ Description of right-hand side
+
+  -> (a -> Text)
+  -- ^ Describes the left-hand side
+
+  -> (a -> Ordering)
+  -- ^ How to compare an item against the right hand side. Return LT
+  -- if the item is less than the right hand side; GT if greater; EQ
+  -- if equal to the right hand side.
+
+  -> C.Pred a
+greaterEqBy dT dR dL f = greaterBy dT dR dL f ||| equalBy dT dR dL f'
+  where
+    f' = fmap (== EQ) f
+
+lessEqBy
+  :: Text
+  -- ^ Description of the type of thing being matched
+
+  -> Text
+  -- ^ Description of right-hand side
+
+  -> (a -> Text)
+  -- ^ Describes the left-hand side
+
+  -> (a -> Ordering)
+  -- ^ How to compare an item against the right hand side. Return LT
+  -- if the item is less than the right hand side; GT if greater; EQ
+  -- if equal to the right hand side.
+
+  -> C.Pred a
+lessEqBy dT dR dL f = lessBy dT dR dL f ||| equalBy dT dR dL f'
+  where
+    f' = fmap (== EQ) f
+
+notEqBy
+  :: Text
+  -- ^ Description of the type of thing being matched
+
+  -> Text
+  -- ^ Description of right-hand side
+
+  -> (a -> Text)
+  -- ^ Describes the left-hand side
+
+  -> (a -> Bool)
+  -- ^ How to compare an item against the right hand side.  Return
+  -- 'True' if equal; 'False' otherwise.
+
+  -> C.Pred a
+notEqBy dT dR dL = not . equalBy dT dR dL
+
+
+-- | Parses a string that contains text, such as @>=@, which indicates
+-- which comparer to use.  Returns the comparer.
+parseComparer
+  :: Text
+  -- ^ The string with the comparer to be parsed
+
+  -> (Ordering -> C.Pred a)
+  -- ^ A function that, when given an ordering, returns a 'C.Pred'.
+  -- Typically you will get this by partial application of 'compare',
+  -- 'compareBy', or 'compareByMaybe'.
+
+  -> Maybe (C.Pred a)
+  -- ^ If an invalid comparer string is given, Nothing; otherwise, the
+  -- 'C.Pred'.
+parseComparer t f
+  | t == ">" = Just (f GT)
+  | t == "<" = Just (f LT)
+  | t == "=" = Just (f EQ)
+  | t == "==" = Just (f EQ)
+  | t == ">=" = Just (f GT ||| f EQ)
+  | t == "<=" = Just (f LT ||| f EQ)
+  | t == "/=" = Just (not $ f EQ)
+  | t == "!=" = Just (not $ f EQ)
+  | otherwise = Nothing
+
diff --git a/lib/Prednote/Core.hs b/lib/Prednote/Core.hs
new file mode 100644
--- /dev/null
+++ b/lib/Prednote/Core.hs
@@ -0,0 +1,325 @@
+{-# LANGUAGE BangPatterns #-}
+-- | 'Pred' core functions.  If your needs are simple, "Prednote.Prebuilt"
+-- is easier to use.  However, the types and functions in this module
+-- give you more control.
+--
+-- Each function in this module that returns a 'Pred' returns one with
+-- the following characteristics:
+--
+-- * No 'static' name
+--
+-- Upon evaluation:
+--
+-- * 'visible' is always 'shown'
+--
+-- * 'short' is either 'Nothing' or @'Just' ('const' [])@
+--
+-- * 'dynamic' is always @'const' []@
+--
+-- Thus, the 'Pred' created by this module are rather bare-bones, but
+-- you can modify them as you see fit; "Prednote.Prebuilt" already
+-- does this for you.
+--
+-- This module exports some names that conflict with Prelude names, so
+-- you might want to do something like
+--
+-- > import qualified Prednote.Pred.Core as P
+
+module Prednote.Core where
+
+import System.Console.Rainbow
+import Prelude hiding (filter, not)
+import qualified Prelude
+import Data.Functor.Contravariant (Contravariant(..))
+import Data.Tree
+import qualified Data.Text as X
+import Data.Maybe
+
+-- | Indicates how to display text.  This function is applied to an
+-- 'Int' that is the level of indentation; each level of descent
+-- through a tree of 'Pred' increments this 'Int' by one.  Because the
+-- function returns a list of 'Chunk', you can use multiple colors.
+-- Typically this function will indent text accordingly, with a
+-- newline at the end.
+type Chunker = Int -> [Chunk]
+
+-- | A rose tree of predicates.
+data Pred a = Pred
+  { static :: Tree Chunker
+    -- ^ A tree of static names, allowing you to identify the 'Pred'
+    -- without applying it to a subject.
+
+  , evaluate :: a -> Tree Output
+    -- ^ Evaluates a 'Pred' by applying it to a subject.
+  }
+
+instance Contravariant Pred where
+  contramap f (Pred s e) = Pred s (e . f)
+
+-- | The result of evaluating a 'Pred'.
+data Output = Output
+  { result :: Bool
+  , visible :: Visible
+    -- ^ Results that are not 'Visible' are not shown by the 'report'
+    -- function.
+  , short :: Maybe Chunker
+    -- ^ Indicates whether there was a short circuit when evaluating
+    -- this 'Pred'.  A short circuit occurs when the 'Pred' does not
+    -- need to evaluate all of its children in order to reach a
+    -- result.  If 'Nothing', there was no short circuit; otherwise,
+    -- this is a 'Just' with a 'Chunker' providing a way to display
+    -- the short circuit.
+
+  , dynamic :: Chunker
+    -- ^ The dynamic label; this indicates how 'report' will show the
+    -- 'Pred' to the user after it has been evaluated.
+  }
+
+instance Show Output where
+  show (Output r v _ _) = "output - result: " ++ show r
+    ++ " visible: " ++ (show . unVisible $ v)
+
+-- | Is this result visible?  If not, 'Prednote.report' will not show it.
+newtype Visible = Visible { unVisible :: Bool }
+  deriving (Eq, Ord, Show)
+
+-- | Shown by 'Prednote.report'
+shown :: Visible
+shown = Visible True
+
+-- | Hidden by 'Prednote.report'
+hidden :: Visible
+hidden = Visible False
+
+-- | No 'Pred' in the list may be 'False' for 'all' to be 'True'.  An
+-- empty list of 'Pred' yields a 'Pred' that always returns 'True'.
+-- May short circuit.
+all :: [Pred a] -> Pred a
+all ls = Pred st' ev
+  where
+    st' = Node (const []) . map static $ ls
+    ev a = go [] ls
+      where
+        go soFar [] = Node (Output True shown Nothing (const []))
+          (reverse soFar)
+        go soFar (x:xs) =
+          let tree = evaluate x a
+              r = result . rootLabel $ tree
+              shrt = case xs of
+                [] -> Nothing
+                _ -> Just (const [])
+              out = Output r shown shrt (const [])
+              cs = reverse (tree:soFar)
+          in case xs of
+              [] -> Node out cs
+              _ | Prelude.not r -> Node out cs
+                | otherwise -> go cs xs
+
+
+-- | At least one 'Pred' in the list must be 'True' for the resulting
+-- 'Pred' to be 'True'.  An empty list of 'Pred' yields a 'Pred' that
+-- always returns 'False'.  May short circuit.
+any :: [Pred a] -> Pred a
+any ls = Pred st' ev
+  where
+    st' = Node (const []) . map static $ ls
+    ev a = go [] ls
+      where
+        go soFar [] = Node (Output False shown Nothing (const []))
+          (reverse soFar)
+        go soFar (x:xs) =
+          let tree = evaluate x a
+              r = result . rootLabel $ tree
+              shrt = case xs of
+                [] -> Nothing
+                _ -> Just (const [])
+              out = Output r shown shrt (const [])
+              cs = reverse (tree:soFar)
+          in case xs of
+              [] -> Node out cs
+              _ | r -> Node out cs
+                | otherwise -> go cs xs
+
+
+-- | Negates the child 'Pred'.  Never short circuits.
+not :: Pred a -> Pred a
+not pd = Pred st' ev
+  where
+    st' = Node (const []) [static pd]
+    ev a = Node nd [c]
+      where
+        nd = Output res shown Nothing (const [])
+        (res, c) = (Prelude.not r, t)
+          where
+            t = evaluate pd a
+            r = result . rootLabel $ t
+
+-- | Fanout.  May short circuit.
+fan
+  :: ([Bool] -> (Bool, Visible, Maybe Int))
+  -- ^ This function is applied to a list of the 'result' from
+  -- evaluating the child 'Pred' on each fanout item.  The function
+  -- must return a triple, with the 'Bool' indicating success or
+  -- failure, 'Visible' for visibility, and 'Maybe' 'Int' to indicate
+  -- whether a short circuit occurred; this must be 'Nothing' if there
+  -- was no short circuit, or 'Just' with an 'Int' to indicate a short
+  -- circuit, with the 'Int' indicating that a short circuit occurred
+  -- after examining the given number of elements.
+  --
+  -- The resulting 'Pred' always short circuits if the previous
+  -- function returns a 'Just' 'Int' with the 'Int' being less than
+  -- zero.  Otherwise, the resulting 'Pred' short circuits if
+  -- the 'Int' is less than the number of elements returned by the
+  -- fanout function.
+
+  -> (a -> [b])
+  -- ^ Fanout function
+
+  -> Pred b
+  -> Pred a
+fan get fn pd = Pred st' ev
+  where
+    st' = Node (const []) [static pd]
+    ev a = Node nd cs
+      where
+        nd = Output r v shrt (const [])
+        (r, v, mayInt) = get bools
+        shrt = case mayInt of
+          Nothing -> Nothing
+          Just s | s < 0 -> Just (const [])
+                 | cs `shorter` allcs -> Just (const [])
+                 | otherwise -> Nothing
+        bs = fn a
+        allcs = map (evaluate pd) bs
+        bools = map (result . rootLabel) allcs
+        cs = case mayInt of
+          Nothing -> allcs
+          Just i -> take i allcs
+
+
+-- | Fanout all.  The resulting 'Pred' is 'True' if no child item
+-- returns 'False'; an empty list of child items returns 'True'.  May
+-- short circuit.
+fanAll
+  :: (a -> [b])
+  -- ^ Fanout function
+
+  -> Pred b
+  -> Pred a
+fanAll = fan get
+  where
+    get = go 0
+      where
+        go !c ls = case ls of
+          [] -> (True, shown, Just c)
+          x:xs
+            | Prelude.not x -> (False, shown, Just (c + 1))
+            | otherwise -> go (c + 1) xs
+
+-- | Fanout any.  The resulting 'Pred' is 'True' if at least one child
+-- item returns 'True'; an empty list of child items returns 'False'.
+-- May short circuit.
+fanAny
+  :: (a -> [b])
+  -- ^ Fanout function
+
+  -> Pred b
+  -> Pred a
+fanAny = fan get
+  where
+    get = go 0
+      where
+        go !c ls = case ls of
+          [] -> (False, shown, Just c)
+          x:xs
+            | x -> (True, shown, Just (c + 1))
+            | otherwise -> go (c + 1) xs
+
+-- | Fanout at least.  The resulting 'Pred' is 'True' if at least the
+-- given number of child items return 'True'.  May short circuit.
+fanAtLeast
+  :: Int
+  -- ^ Find at least this many.  If this number is less than or equal
+  -- to zero, 'fanAtLeast' will always return 'True'.
+
+  -> (a -> [b])
+  -- ^ Fanout function
+
+  -> Pred b
+  -> Pred a
+fanAtLeast i = fan get
+  where
+    get = go 0 0
+      where
+        go !found !c ls
+          | found >= i = (True, shown, Just c)
+          | otherwise = case ls of
+              [] -> (False, shown, Just c)
+              x:xs -> go fnd' (c + 1) xs
+                where
+                  fnd' | x = found + 1
+                       | otherwise = found
+
+-- | Indents and formats output for display.
+report
+  :: Int
+  -- ^ Start at this level of indentation.
+  -> Tree Output
+  -> [Chunk]
+report l (Node n cs)
+  | (== hidden) . visible $ n = []
+  | otherwise = this ++ concatMap (report (l + 1)) cs ++ shrt
+  where
+    this = dynamic n l
+    shrt = maybe [] ($ (l + 1)) . short $ n
+
+-- | Indents and formats static labels for display.  This is a 'plan'
+-- for how the 'Pred' would be applied.
+plan
+  :: Int
+  -- ^ Start at this level of indentation.
+  -> Pred a
+  -> [Chunk]
+plan lvl pd = go lvl (static pd)
+  where
+    go l (Node n cs) = this ++ concatMap (go (l + 1)) cs
+      where
+        this = n l
+
+instance Show (Pred a) where
+  show = X.unpack . X.concat . concat . map text
+    . plan 0
+
+-- | Applies a 'Pred' to a single subject and returns the 'result'.
+test :: Pred a -> a -> Bool
+test p = result . rootLabel . evaluate p
+
+-- | Like 'test' but also returns the accompanying 'report'.
+testV :: Pred a -> a -> (Bool, [Chunk])
+testV p a = (result . rootLabel $ t, report 0 t)
+  where
+    t = evaluate p a
+
+-- | Like 'Prelude.filter'.
+filter :: Pred a -> [a] -> [a]
+filter p = Prelude.filter (test p)
+
+-- | Like 'filter' but also returns a list of 'report', with one
+-- 'report' for each list item.
+filterV :: Pred a -> [a] -> ([a], [Chunk])
+filterV p as = (mapMaybe fltr (zip as rslts), cks)
+  where
+    fltr (a, r)
+      | result . rootLabel $ r = Just a
+      | otherwise = Nothing
+    rslts = map (evaluate p) as
+    cks = concatMap (report 0) rslts
+
+-- | @shorter x y@ is True if list x is shorter than list y. Lazier
+-- than taking the length of each list and comparing the results.
+shorter :: [a] -> [a] -> Bool
+shorter [] [] = False
+shorter (_:_) [] = False
+shorter [] (_:_) = True
+shorter (_:xs) (_:ys) = shorter xs ys
+
diff --git a/lib/Prednote/Expressions.hs b/lib/Prednote/Expressions.hs
new file mode 100644
--- /dev/null
+++ b/lib/Prednote/Expressions.hs
@@ -0,0 +1,87 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Handles parsing of both infix and RPN Predbox expressions.
+module Prednote.Expressions
+  ( ExprDesc(..)
+  , Error
+  , Token
+  , operand
+  , opAnd
+  , opOr
+  , opNot
+  , openParen
+  , closeParen
+  , parseExpression
+  ) where
+
+import Data.Either (partitionEithers)
+import Data.Functor.Contravariant
+import qualified Data.Text as X
+import qualified Prednote.Expressions.Infix as I
+import qualified Prednote.Expressions.RPN as R
+import Prednote.Core (Pred)
+
+-- | A single type for both RPN tokens and infix tokens.
+newtype Token a = Token { unToken :: I.InfixToken a }
+
+instance Contravariant Token where
+  contramap f = Token . contramap f . unToken
+
+type Error = X.Text
+
+-- | Creates Operands from Predbox.
+operand :: Pred a -> Token a
+operand p = Token (I.TokRPN (R.TokOperand p))
+
+-- | The And operator
+opAnd :: Token a
+opAnd = Token (I.TokRPN (R.TokOperator R.OpAnd))
+
+-- | The Or operator
+opOr :: Token a
+opOr = Token (I.TokRPN (R.TokOperator R.OpOr))
+
+-- | The Not operator
+opNot :: Token a
+opNot = Token (I.TokRPN (R.TokOperator R.OpNot))
+
+-- | Open parentheses
+openParen :: Token a
+openParen = Token (I.TokParen I.Open)
+
+-- | Close parentheses
+closeParen :: Token a
+closeParen = Token (I.TokParen I.Close)
+
+-- | Is this an infix or RPN expression?
+data ExprDesc
+  = Infix
+  | RPN
+  deriving (Eq, Show)
+
+toksToRPN :: [Token a] -> Maybe [R.RPNToken a]
+toksToRPN toks
+  = let toEither t = case unToken t of
+          I.TokRPN tok -> Right tok
+          _ -> Left ()
+    in case partitionEithers . map toEither $ toks of
+        ([], xs) -> return xs
+        _ -> Nothing
+
+-- | Parses expressions. Fails if the expression is nonsensical in
+-- some way (for example, unbalanced parentheses, parentheses in an
+-- RPN expression, or multiple stack values remaining.) Works by first
+-- changing infix expressions to RPN ones.
+parseExpression
+  :: ExprDesc
+  -> [Token a]
+  -> Either Error (Pred a)
+parseExpression e toks = do
+  rpnToks <- case e of
+    Infix -> maybe (Left "unbalanced parentheses\n") Right
+             . I.createRPN
+             . map unToken
+             $ toks
+    RPN -> maybe (Left "parentheses in an RPN expression\n") Right
+           $ toksToRPN toks
+  R.parseRPN rpnToks
diff --git a/lib/Prednote/Expressions/Infix.hs b/lib/Prednote/Expressions/Infix.hs
new file mode 100644
--- /dev/null
+++ b/lib/Prednote/Expressions/Infix.hs
@@ -0,0 +1,132 @@
+module Prednote.Expressions.Infix
+  ( InfixToken (..)
+  , Paren(..)
+  , createRPN
+  ) where
+
+import Data.Functor.Contravariant
+import qualified Prednote.Expressions.RPN as R
+import qualified Data.Foldable as Fdbl
+
+data InfixToken a
+  = TokRPN (R.RPNToken a)
+  | TokParen Paren
+
+instance Contravariant InfixToken where
+  contramap f t = case t of
+    TokRPN r -> TokRPN . contramap f $ r
+    TokParen p -> TokParen p
+
+data Paren = Open | Close
+
+-- | Values on the operator stack.
+data OpStackVal
+  = StkOp R.Operator
+  | StkOpenParen
+
+-- In the shunting yard algorithm, the output sequence is a queue. The
+-- first values to go into the output sequence are the first to be
+-- processed by the RPN parser. In this module, the output sequence is
+-- implemented as a list stack, which means it must be reversed upon
+-- output (this is done in the createRPN function.)
+
+processInfixToken
+  :: ([OpStackVal], [R.RPNToken a])
+  -> InfixToken a
+  -> Maybe ([OpStackVal], [R.RPNToken a])
+processInfixToken (os, ts) t = case t of
+  TokRPN tok -> return $ processRPNToken (os, ts) tok
+  TokParen p -> processParen (os, ts) p
+
+
+-- | If the token is a binary operator A, then:
+--
+-- If A is left associative, while there is an operator B of higher or
+-- equal precedence than A at the top of the stack, pop B off the
+-- stack and append it to the output.
+--
+-- If A is right associative, while there is an operator B of higher
+-- precedence than A at the top of the stack, pop B off the stack and
+-- append it to the output.
+--
+-- Push A onto the stack.
+--
+-- If a token is an operand, append it to the postfix output.
+--
+-- And has higher precedence than Or.
+processRPNToken
+  :: ([OpStackVal], [R.RPNToken a])
+  -> R.RPNToken a
+  -> ([OpStackVal], [R.RPNToken a])
+processRPNToken (os, ts) t = case t of
+  p@(R.TokOperand _) -> (os, p:ts)
+  R.TokOperator d -> case d of
+    R.OpNot -> (StkOp R.OpNot : os, ts)
+    R.OpAnd -> (StkOp R.OpAnd : os, ts)
+    R.OpOr ->
+      let (os', ts') = popper os ts
+      in (StkOp R.OpOr : os', ts')
+
+-- | Pops operators from the operator stack and places then in the
+-- output queue, as long as there is an And operator on the top of the
+-- operator stack.
+popper :: [OpStackVal] -> [R.RPNToken a] -> ([OpStackVal], [R.RPNToken a])
+popper os ts = case os of
+  [] -> (os, ts)
+  x:xs -> case x of
+    StkOp R.OpAnd ->
+      let os' = xs
+          ts' = R.TokOperator R.OpAnd : ts
+      in popper os' ts'
+    _ -> (os, ts)
+
+-- | Pops operators off the operator stack and onto the output stack
+-- as long as the top of the operator stack is not an open
+-- parenthesis. When an open parenthesis is encountered, pop that too,
+-- but not onto the output stack. Fails if the stack has no open
+-- parentheses.
+popThroughOpen
+  :: ([OpStackVal], [R.RPNToken a])
+  -> Maybe ([OpStackVal], [R.RPNToken a])
+popThroughOpen (os, ts) = case os of
+  [] -> Nothing
+  v:vs -> case v of
+    StkOp op -> popThroughOpen (vs, R.TokOperator op : ts)
+    StkOpenParen -> return (vs, ts)
+
+-- | Places an open parenthesis on the top of the operator stack. For
+-- Close parenthesis, pops operators off the operator stack through
+-- the next open parenthesis on the operator stack.
+processParen
+  :: ([OpStackVal], [R.RPNToken a])
+  -> Paren
+  -> Maybe ([OpStackVal], [R.RPNToken a])
+processParen (os, ts) p = case p of
+  Open -> Just (StkOpenParen : os, ts)
+  Close -> popThroughOpen (os, ts)
+
+-- | Creates an RPN expression from an infix one. Fails only if there
+-- are mismatched parentheses. It is possible to create a nonsensical
+-- RPN expression; the RPN parser must catch this.
+createRPN
+  :: Fdbl.Foldable f
+  => f (InfixToken a)
+  -- ^ The input tokens, with the beginning of the expression on the
+  -- left side of the sequence.
+
+  -> Maybe [R.RPNToken a]
+  -- ^ The output sequence of tokens, with the beginning of the
+  -- expression on the left side of the list.
+createRPN ts = do
+  (stack, toks) <- Fdbl.foldlM processInfixToken ([], []) ts
+  fmap reverse $ popRemainingOperators stack toks
+
+-- | Pops remaining items off operator stack. Fails if there is an
+-- open paren left on the stack, as this indicates mismatched
+-- parenthesis.
+popRemainingOperators :: [OpStackVal] -> [R.RPNToken a] -> Maybe [R.RPNToken a]
+popRemainingOperators os ts = case os of
+  [] -> return ts
+  x:xs -> case x of
+    StkOp op -> popRemainingOperators xs (R.TokOperator op : ts)
+    StkOpenParen -> Nothing
diff --git a/lib/Prednote/Expressions/RPN.hs b/lib/Prednote/Expressions/RPN.hs
new file mode 100644
--- /dev/null
+++ b/lib/Prednote/Expressions/RPN.hs
@@ -0,0 +1,79 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- | Postfix, or RPN, expression parsing.
+--
+-- This module parses RPN expressions where the operands are
+-- predicates and the operators are one of @and@, @or@, or @not@,
+-- where @and@ and @or@ are binary and @not@ is unary.
+module Prednote.Expressions.RPN where
+
+import Data.Functor.Contravariant
+import qualified Data.Foldable as Fdbl
+import qualified Prednote.Prebuilt as P
+import Prednote.Core (Pred)
+import Prednote.Prebuilt ((&&&), (|||))
+import Data.Monoid ((<>))
+import Data.Text (Text)
+import qualified Data.Text as X
+
+data RPNToken a
+  = TokOperand (Pred a)
+  | TokOperator Operator
+
+instance Contravariant RPNToken where
+  contramap f t = case t of
+    TokOperand p -> TokOperand . contramap f $ p
+    TokOperator o -> TokOperator o
+
+data Operator
+  = OpAnd
+  | OpOr
+  | OpNot
+  deriving Show
+
+pushOperand :: Pred a -> [Pred a] -> [Pred a]
+pushOperand p ts = p : ts
+
+pushOperator
+  :: Operator
+  -> [Pred a]
+  -> Either Text [Pred a]
+pushOperator o ts = case o of
+  OpAnd -> case ts of
+    x:y:zs -> return $ (y &&& x) : zs
+    _ -> Left $ err "and"
+  OpOr -> case ts of
+    x:y:zs -> return $ (y ||| x) : zs
+    _ -> Left $ err "or"
+  OpNot -> case ts of
+    x:zs -> return $ P.not x : zs
+    _ -> Left $ err "not"
+  where
+    err x = "insufficient operands to apply \"" <> x
+            <> "\" operator\n"
+
+pushToken
+  :: [Pred a]
+  -> RPNToken a
+  -> Either Text [Pred a]
+pushToken ts t = case t of
+  TokOperand p -> return $ pushOperand p ts
+  TokOperator o -> pushOperator o ts
+
+
+-- | Parses an RPN expression and returns the resulting Predbox. Fails if
+-- there are no operands left on the stack or if there are multiple
+-- operands left on the stack; the stack must contain exactly one
+-- operand in order to succeed.
+parseRPN
+  :: Fdbl.Foldable f
+  => f (RPNToken a)
+  -> Either Text (Pred a)
+parseRPN ts = do
+  trees <- Fdbl.foldlM pushToken [] ts
+  case trees of
+    [] -> Left $ "bad expression: no operands left on the stack\n"
+    x:[] -> return x
+    xs -> Left . X.pack
+      $ "bad expression: multiple operands left on the stack:\n"
+      <> concatMap show xs
+
diff --git a/lib/Prednote/Format.hs b/lib/Prednote/Format.hs
new file mode 100644
--- /dev/null
+++ b/lib/Prednote/Format.hs
@@ -0,0 +1,110 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- | Functions used to format text.  Typically you won't need these
+-- unless you want tailored control over how your 'Prednote.Core.Pred'
+-- are formatted.
+module Prednote.Format where
+
+import System.Console.Rainbow
+import Data.Text (Text)
+import qualified Data.Text as X
+import qualified Prednote.Core as C
+import qualified Data.Tree as E
+import Data.Monoid
+
+-- # Labels and indentation
+
+-- | A colorful label for 'True' values.
+lblTrue :: [Chunk]
+lblTrue = ["[", f_green <> "TRUE", "]"]
+
+-- | A colorful label for 'False' values.
+lblFalse :: [Chunk]
+lblFalse = ["[", f_red <> "FALSE", "]"]
+
+-- | Indent amount.
+indentAmt :: Int
+indentAmt = 2
+
+-- | Prefixes the given 'Text' with colorful text to indicate 'True'
+-- or 'False' as appropriate.
+lblLine :: Bool -> Text -> [Chunk]
+lblLine b t = lbl ++ [" ", fromText t]
+  where
+    lbl | b = lblTrue
+        | otherwise = lblFalse
+
+-- | Indents the given list of 'Chunk' by the given 'Int' multipled by
+-- 'indentAmt'.  Appends a newline.
+indent :: [Chunk] -> Int -> [Chunk]
+indent cs i = spaces : cs ++ [fromText "\n"]
+  where
+    spaces = fromText . X.replicate (indentAmt * i)
+      . X.singleton $ ' '
+
+-- | A label for a short circuit.
+shortCir :: Int -> [Chunk]
+shortCir = indent ["[", f_yellow <> "short circuit", "]"]
+
+-- | Indents a 'Text' by the given 'Int' multiplied by
+-- 'indentAmt'.
+indentTxt :: Text -> Int -> [Chunk]
+indentTxt = indent . (:[]) . fromText
+
+-- | Append two 'Text', with an intervening space if both 'Text' are
+-- not empty.
+(<+>) :: Text -> Text -> Text
+l <+> r
+  | full l && full r = l <> " " <> r
+  | otherwise = l <> r
+  where
+    full = Prelude.not . X.null
+
+-- | Create a new 'C.Pred' with a different static label.
+rename :: Text -> C.Pred a -> C.Pred a
+rename x p = p { C.static = (C.static p)
+  { E.rootLabel = indentTxt x } }
+
+-- | Creates a new 'C.Pred' with a result differing from the original
+-- 'C.Pred'.
+changeOutput
+  :: (a -> C.Output -> C.Output)
+  -- ^ Function to modify the 'C.Output'
+
+  -> C.Pred a
+  -- ^ Modify the 'C.Output' of this 'C.Pred'
+
+  -> C.Pred a
+changeOutput f p = p { C.evaluate = e' }
+  where
+    e' a = t'
+      where
+        t = C.evaluate p a
+        t' = t { E.rootLabel = f a (E.rootLabel t) }
+
+-- | Creates a new 'C.Pred' with a different dynamic label.
+speak
+  :: (a -> Text)
+  -- ^ New dynamic label.  Do not indicate whether the result is
+  -- 'True' or 'False'; this is done for you.
+
+  -> C.Pred a
+
+  -> C.Pred a
+speak f = changeOutput g
+  where
+    g a o = o { C.dynamic = dyn }
+      where
+        dyn = indent $ lblLine (C.result o) (f a)
+
+
+-- | Creates a new 'C.Pred' with any short circuits having a colorful
+-- label.
+speakShort :: C.Pred a -> C.Pred a
+speakShort p = p { C.evaluate = e' }
+  where
+    e' a = t { E.rootLabel = (E.rootLabel t)
+      { C.short = fmap (const shortCir) shrt } }
+      where
+        t = C.evaluate p a
+        shrt = C.short . E.rootLabel $ t
+
diff --git a/lib/Prednote/Prebuilt.hs b/lib/Prednote/Prebuilt.hs
new file mode 100644
--- /dev/null
+++ b/lib/Prednote/Prebuilt.hs
@@ -0,0 +1,151 @@
+{-# LANGUAGE OverloadedStrings, BangPatterns #-}
+
+-- | Functions to work with 'Pred'.  This module works with 'Text' and
+-- produces 'Pred' that make sparing use of color.  For more control
+-- over the 'Pred' produced, use "Prednote.Pred.Core".
+--
+-- Exports some names that conflict with Prelude names, so you might
+-- want to do something like
+--
+-- > import qualified Prednote.Pred as P
+module Prednote.Prebuilt where
+
+import qualified Prednote.Core as C
+import Prednote.Format
+import qualified Data.Tree as E
+import qualified Data.Text as X
+import Data.Text (Text)
+import Data.Monoid
+import Prelude hiding (and, or, not, filter, compare, any, all)
+import qualified Prelude
+
+-- # Predicate
+
+-- | Builds predicates.
+predicate
+  :: Text
+  -- ^ Static label
+
+  -> (a -> Text)
+  -- ^ Computes the dynamic label.  Do not indicate whether the result
+  -- is 'True' or 'False'; this is automatically done for you.
+
+  -> (a -> Bool)
+  -- ^ Predicate function
+
+  -> C.Pred a
+
+predicate r s f = rename r . speak s $ C.Pred (E.Node (const []) []) ev
+  where
+    ev a = E.Node (C.Output (f a) C.shown Nothing (const [])) []
+
+-- | Always returns 'True' and is always visible.
+true :: C.Pred a
+true = predicate l (const l) (const True)
+  where
+    l = "always True"
+
+-- | Always returns 'False' and is always visible.
+false :: C.Pred a
+false = predicate l (const l) (const False)
+  where
+    l = "always False"
+
+-- | Returns the subject as is; is always visible.
+same :: C.Pred Bool
+same = predicate l (const l) id
+  where
+    l = "same as subject"
+
+-- # Visibility
+
+-- | Creates a 'C.Pred' with its visibility modified.
+visibility
+  :: (Bool -> C.Visible)
+  -- ^ When applied to the 'C.result' of the 'C.Pred', this function
+  -- returns the desired visibility.
+  -> C.Pred a
+  -> C.Pred a
+visibility f (C.Pred s e) = C.Pred s e'
+  where
+    e' a = g (e a)
+    g (E.Node n cs) = E.Node n { C.visible = f (C.result n) } cs
+
+-- | Creates a 'C.Pred' that is always shown.
+reveal :: C.Pred a -> C.Pred a
+reveal = visibility (const C.shown)
+
+-- | Creates a 'C.Pred' that is always hidden.
+hide :: C.Pred a -> C.Pred a
+hide = visibility (const C.hidden)
+
+-- | Creates a 'C.Pred' that is shown only if its 'C.result' is
+-- 'True'.
+showTrue :: C.Pred a -> C.Pred a
+showTrue = visibility (\b -> if b then C.shown else C.hidden)
+
+-- | Creates a 'C.Pred' that is shown only if its 'C.result' is
+-- 'False'.
+showFalse :: C.Pred a -> C.Pred a
+showFalse = visibility (\b -> if Prelude.not b then C.shown else C.hidden)
+
+-- # Conjunction and disjunction, negation
+
+-- | No child 'Pred' may be 'False'.  An empty list of child 'Pred'
+-- returns 'True'.  Always visible.
+all :: [C.Pred a] -> C.Pred a
+all = speakShort . rename l . speak (const l) . C.all
+  where
+    l = "all"
+
+-- | Creates 'all' 'Pred' that are always visible.
+(&&&) :: C.Pred a -> C.Pred a -> C.Pred a
+l &&& r = all [l, r]
+
+infixr 3 &&&
+
+-- | At least one child 'Pred' must be 'True'.  An empty list of child
+-- 'Pred' returns 'False'.  Always visible.
+any :: [C.Pred a] -> C.Pred a
+any = speakShort . rename l . speak (const l) . C.any
+  where
+    l = "any"
+
+
+-- | Creates 'any' 'Pred' that are always visible.
+(|||) :: C.Pred a -> C.Pred a -> C.Pred a
+l ||| r = any [l, r]
+
+infixr 2 |||
+
+-- | Negation.  Always visible.
+not :: C.Pred a -> C.Pred a
+not = rename l . speak (const l) . C.not
+  where
+    l = "not"
+
+-- | No fanned-out item may be 'False'.  An empty list of child items
+-- returns 'True'.
+fanAll :: (a -> [b]) -> C.Pred b -> C.Pred a
+fanAll f = speakShort . rename l . speak (const l) . C.fanAll f
+  where
+    l = "fanout all"
+
+
+
+-- | At least one fanned-out item must be 'True'.  An empty list of
+-- child items returns 'False'.
+fanAny :: (a -> [b]) -> C.Pred b -> C.Pred a
+fanAny f = speakShort . rename l . speak (const l) . C.fanAny f
+  where
+    l = "fanout any"
+
+
+-- | At least the given number of child items must be 'True'.
+fanAtLeast :: Int -> (a -> [b]) -> C.Pred b -> C.Pred a
+fanAtLeast i f = speakShort . rename l . speak (const l)
+  . C.fanAtLeast i f
+  where
+    l = "fanout - at least " <> X.pack (show i) <>
+      " fanned-out subject(s) must be True"
+
diff --git a/minimum-versions.txt b/minimum-versions.txt
--- a/minimum-versions.txt
+++ b/minimum-versions.txt
@@ -1,7 +1,7 @@
 This package was tested to work with these dependency
 versions and compiler version.
 These are the minimum versions given in the .cabal file.
-Tested as of: 2014-04-13 22:50:48.223408 UTC
+Tested as of: 2014-07-13 14:54:13.902743 UTC
 Path to compiler: ghc-7.4.1
 Compiler description: 7.4.1
 
@@ -33,12 +33,10 @@
     time-1.4
     unix-2.5.1.0
 
-/home/massysett/prednote/sunlight-22866/db:
-    QuickCheck-2.6
+/home/massysett/prednote/library/sunlight-25515/db:
     contravariant-0.2.0.1
-    prednote-0.22.0.2
+    prednote-0.24.0.0
     rainbow-0.14.0.0
-    random-1.0.1.1
     split-0.2.2
     terminfo-0.4.0.0
     text-0.11.2.0
diff --git a/prednote-test.hs b/prednote-test.hs
deleted file mode 100644
--- a/prednote-test.hs
+++ /dev/null
@@ -1,90 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-module Main where
-
-import Control.Applicative
-import qualified Test.QuickCheck as Q
-import Test.QuickCheck.All (quickCheckAll)
-import Test.QuickCheck.Function
-import Test.QuickCheck (Arbitrary, Gen, arbitrary)
-import qualified Data.Prednote.Predbox as P
-import Data.Prednote.Predbox ((&&&), (|||))
-import qualified Data.Text as X
-import qualified System.Exit as Exit
-
-instance Arbitrary X.Text where
-  arbitrary = fmap X.pack (Q.listOf (Q.choose ('!', '~')))
-
-instance Q.CoArbitrary a => Arbitrary (P.Predbox a) where
-  arbitrary = P.Predbox <$> arbitrary <*> arbitrary <*> arbitrary
-
-instance Q.CoArbitrary a => Arbitrary (P.Node a) where
-  arbitrary = Q.sized tree
-    where
-      tree 0 = fmap P.Predicate arbitrary
-      tree n = Q.oneof
-        [ fmap P.And (Q.listOf subtree)
-        , fmap P.Or (Q.listOf subtree)
-        , fmap P.Not subtree ]
-        where
-          subtree = P.Predbox <$> arbitrary <*> arbitrary
-                    <*> tree (n `div` 2)
-
--- | And is commutative
-prop_andCommutative :: a -> P.Predbox a -> P.Predbox a -> Bool
-prop_andCommutative a p1 p2 = P.rBool r1 == P.rBool r2
-  where
-    r1 = P.evaluate (p1 &&& p2) a
-    r2 = P.evaluate (p2 &&& p1) a
-
--- | And is associative
-prop_andAssociative :: a -> P.Predbox a -> P.Predbox a -> P.Predbox a -> Bool
-prop_andAssociative a p1 p2 p3 = P.rBool r1 == P.rBool r2
-  where
-    r1 = P.evaluate (p1 &&& (p2 &&& p3)) a
-    r2 = P.evaluate ((p1 &&& p2) &&& p3) a
-    
--- | Or is commutative
-prop_orCommutative :: a -> P.Predbox a -> P.Predbox a -> Bool
-prop_orCommutative a p1 p2 = P.rBool r1 == P.rBool r2
-  where
-    r1 = P.evaluate (p1 ||| p2) a
-    r2 = P.evaluate (p2 ||| p1) a
-
--- | Or is associative
-prop_orAssociative :: a -> P.Predbox a -> P.Predbox a -> P.Predbox a -> Bool
-prop_orAssociative a p1 p2 p3 = P.rBool r1 == P.rBool r2
-  where
-    r1 = P.evaluate (p1 ||| (p2 ||| p3)) a
-    r2 = P.evaluate ((p1 ||| p2) ||| p3) a
-
--- | Anything or'd with True is True
-prop_orWithTrue :: a -> P.Predbox a -> Bool
-prop_orWithTrue a p1 = P.rBool r1
-  where
-    r1 = P.evaluate (p1 ||| P.always) a
-
--- | Anything and'ed with False is False
-prop_andWithFalse :: a -> P.Predbox a -> Bool
-prop_andWithFalse a p1 = not $ P.rBool r1
-  where
-    r1 = P.evaluate (p1 &&& P.never) a
-
--- | And Distributivity
-prop_andDistributivity :: a -> P.Predbox a -> P.Predbox a -> P.Predbox a -> Bool
-prop_andDistributivity x a b c = P.rBool r1 == P.rBool r2
-  where
-    r1 = P.evaluate (a &&& (b ||| c)) x
-    r2 = P.evaluate ((a &&& b) ||| (a &&& c)) x
-
-prop_orDistributivity :: a -> P.Predbox a -> P.Predbox a -> P.Predbox a -> Bool
-prop_orDistributivity x a b c = P.rBool r1 == P.rBool r2
-  where
-    r1 = P.evaluate (a ||| (b &&& c)) x
-    r2 = P.evaluate ((a ||| b) &&& (a ||| c)) x
-
-runTests = $quickCheckAll
-
-main :: IO ()
-main = do
-  b <- runTests
-  if b then Exit.exitSuccess else Exit.exitFailure
diff --git a/prednote.cabal b/prednote.cabal
--- a/prednote.cabal
+++ b/prednote.cabal
@@ -1,6 +1,22 @@
-name:                prednote
-version:             0.22.0.2
-synopsis:            Build and evaluate trees of predicates
+-- This Cabal file generated using the Cartel library.
+-- Cartel is available at:
+-- http://www.github.com/massysett/cartel
+--
+-- Script name used to generate: genCabal.hs
+-- Generated on: 2014-07-13 10:54:10.054497 EDT
+-- Cartel library version: 0.10.0.2
+name: prednote
+version: 0.24.0.0
+cabal-version: >= 1.14
+build-type: Simple
+license: BSD3
+license-file: LICENSE
+copyright: Copyright 2013-2014 Omari Norman
+author: Omari Norman
+maintainer: omari@smileystation.com
+stability: Experimental
+homepage: http://www.github.com/massysett/prednote
+bug-reports: http://www.github.com/massysett/prednote/issues
 description:
   Build and evaluate trees of predicates. For example, you might build
   a predicate of the type Int -> Bool. You do this by assembling
@@ -10,55 +26,41 @@
   prednote also provides modules to test several subjects against a
   given predicate, and to parse infix or RPN expressions into a tree of
   predicates.
-
-homepage:            http://github.com/massysett/prednote
-license:             BSD3
-license-file:        LICENSE
-author:              Omari Norman
-maintainer:          omari@smileystation.com
-copyright:           2013-2014 Omari Norman
-category:            Data
-build-type:          Simple
-cabal-version:       >=1.8
+  .
+  tests are packaged separately in the prednote-tests package.
+category: Data
+tested-with: GHC == 7.4.1, GHC == 7.6.3, GHC == 7.8.2
 extra-source-files:
-  README.md, minimum-versions.txt, current-versions.txt,
-  sunlight-test.hs, changelog
-
-tested-with: GHC ==7.4.1, GHC ==7.6.3, GHC ==7.8.2
+    README.md
+  , minimum-versions.txt
+  , current-versions.txt
+  , sunlight-test.hs
+  , changelog
 
 source-repository head
-    type: git
-    location: git://github.com/massysett/prednote.git
-
-library
+  type: git
+  location: git://github.com/massysett/prednote.git
+  branch: master
 
+Library
   exposed-modules:
-      Data.Prednote
-    , Data.Prednote.Predbox
-    , Data.Prednote.Expressions
-    , Data.Prednote.Expressions.Infix
-    , Data.Prednote.Expressions.RPN
-    , Data.Prednote.Test
-
-  build-depends:
-      base >= 4.5.0.0 && < 5
-    , contravariant >= 0.2.0.1
-    , rainbow >=0.14.0.0 && < 0.15
-    , split >=0.2.2
-    , text >= 0.11.2.0
-
-  ghc-options: -Wall
-  hs-source-dirs: lib
-
-Test-Suite prednote-test
-  type: exitcode-stdio-1.0
-  main-is: prednote-test.hs
-  hs-source-dirs: . lib
-
+      Prednote
+    , Prednote.Comparisons
+    , Prednote.Core
+    , Prednote.Expressions
+    , Prednote.Expressions.Infix
+    , Prednote.Expressions.RPN
+    , Prednote.Format
+    , Prednote.Prebuilt
   build-depends:
-      base >=4.5.0.0 && < 5
-    , contravariant >= 0.2.0.1
-    , QuickCheck >=2.6
-    , rainbow >=0.14.0.0 && < 0.15
-    , text >= 0.11.2.0
-
+      base ((> 4.5.0.0 || == 4.5.0.0) && < 5)
+    , contravariant ((> 0.2.0.1 || == 0.2.0.1) && < 0.7)
+    , rainbow ((> 0.14.0.0 || == 0.14.0.0) && < 0.15)
+    , split ((> 0.2.2 || == 0.2.2) && < 0.3)
+    , text ((> 0.11.2.0 || == 0.11.2.0) && < 1.2)
+    , containers ((> 0.4.2.1 || == 0.4.2.1) && < 0.6)
+  hs-source-dirs:
+      lib
+  ghc-options:
+      -Wall
+  default-language: Haskell2010
diff --git a/sunlight-test.hs b/sunlight-test.hs
--- a/sunlight-test.hs
+++ b/sunlight-test.hs
@@ -9,7 +9,7 @@
   , tiDefault = [ ("7.4.1", "ghc-7.4.1", "ghc-pkg-7.4.1")
                 , ("7.6.3", "ghc-7.6.3", "ghc-pkg-7.6.3")
                 , ("7.8.2", "ghc-7.8.2", "ghc-pkg-7.8.2") ]
-  , tiTest = [("dist/build/prednote-test/prednote-test", [])]
+  , tiTest = []
   }
 
 main = runTests inputs
