diff --git a/Control/Monad/BrainFuck.hs b/Control/Monad/BrainFuck.hs
new file mode 100644
--- /dev/null
+++ b/Control/Monad/BrainFuck.hs
@@ -0,0 +1,129 @@
+{-# LANGUAGE EmptyDataDecls #-}
+
+module Control.Monad.BrainFuck where
+
+import qualified Control.Monad as M
+
+newtype BrainFuck a = BrainFuck (DataPointer -> ([Char], DataPointer, a))
+
+type DataPointer = Integer
+
+-- | Retrieve the inner function
+func :: BrainFuck f -> DataPointer -> ([Char], DataPointer, f)
+func (BrainFuck f) = f
+
+-- | Evaluate the monad and get a brainfuck program
+brainfuck :: BrainFuck f -> String
+brainfuck (BrainFuck f) = bytes where (bytes, _, _) = f 0
+
+instance Monad BrainFuck where
+	return ret = BrainFuck $ \loc -> ([], loc, ret)
+	a >>= b = BrainFuck $ \start -> let
+		(left, mid, val) = func a start
+		(right, end, ret) = func (b val) mid
+		in (left ++ right, end, ret)
+
+next, prev, incr, decr, output, input, open, close :: BrainFuck ()
+-- | move data pointer right
+next = cmd' succ '>'
+-- | move data pointer left
+prev = cmd' pred '<'
+-- | increment data
+incr = cmd '+'
+-- | decrement data
+decr = cmd '-'
+-- | output byte at data pointer
+output = cmd '.'
+-- | input byte, storing at data pointer
+input = cmd ','
+-- | if byte at data pointer is zero, jump to command after close
+open = cmd '['
+-- | if byte at data pointer is nonzero, jump to command after matching open
+close = cmd ']'
+
+add, sub :: Int -> BrainFuck ()
+add = multi incr
+sub = multi decr
+
+-- | Adds an arbitrary character to the program.
+-- Should not be used directly.
+cmd :: Char -> BrainFuck ()
+cmd = cmd' id
+
+-- | Adds an arbitrary character to the program,
+-- and updates the data pointer.
+-- Should not be used directly.
+cmd' :: (DataPointer -> DataPointer) -> Char -> BrainFuck ()
+cmd' f x =  BrainFuck $ \loc -> ([x], f loc, ())
+
+-- | Run an action multiple times.
+multi :: BrainFuck () -> Int -> BrainFuck ()
+multi c n = do
+	_ <- sequence (replicate n c)
+	return ()
+
+-- | Gets the current address of the data pointer.
+addr :: BrainFuck DataPointer
+addr = BrainFuck $ \loc -> ([], loc, loc)
+
+-- | Moves the data pointer to a specific address.
+setAddr :: Integer -> BrainFuck ()
+setAddr n = do
+	a <- addr
+	if a > n
+		then prev >> setAddr n
+		else if a < n
+			then next >> setAddr n
+			else return ()
+
+-- | The loop is only entered if the byte at the data pointer is not zero.
+--
+-- On entry, the loop body is run, and then it loops when
+-- the byte at the data pointer is not zero.
+loopUnless0 :: BrainFuck () -> BrainFuck ()
+loopUnless0 a = do
+	open
+	a
+	close
+
+data Constants
+
+-- | Let's run programs with data cells 0-8 reserved to always contain
+-- the numbers 0 to 8.
+--
+-- These constants will make brainfuck quite a bit easier to use!
+brainfuckConstants :: (Constants -> BrainFuck a) -> String
+brainfuckConstants a = brainfuck $ do
+	M.forM_ [0..8] $ \n -> do
+		add n
+		next
+	a (undefined :: Constants)
+
+-- | Sets data pointer to point to one of the Constants.
+at :: Constants -> Integer -> BrainFuck ()
+at _ = setAddr
+
+-- | Run an action in a loop, until it sets its data pointer to 0.
+loop :: Constants -> BrainFuck () -> BrainFuck ()
+loop constants a = do
+	here <- addr
+	at constants 1
+	loopUnless0 $ do
+		setAddr here
+		a
+
+-- | Runs an action in an infinite loop.
+forever :: Constants -> BrainFuck () -> BrainFuck ()
+forever constants a = loop constants $ do
+	a
+	at constants 1
+
+-- | Prints out the ASCII table, starting with space, repeatedly.
+-- 
+-- TODO: Only print printable characters.
+demo :: String
+demo = brainfuckConstants $ \constants -> do
+	add 32
+	forever constants $ do
+		add 1
+		output
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright 2014 Joey Hess <id@joeyh.name>.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/brainfuck-monad.cabal b/brainfuck-monad.cabal
new file mode 100644
--- /dev/null
+++ b/brainfuck-monad.cabal
@@ -0,0 +1,23 @@
+Name: brainfuck-monad
+Version: 0.0.1
+Cabal-Version: >= 1.8
+License: BSD3
+Maintainer: Joey Hess <id@joeyh.name>
+Author: Joey Hess
+Stability: Stable
+Copyright: 2014 Joey Hess
+License-File: LICENSE
+Build-Type: Simple
+Category: Utility
+Synopsis: BrainFuck monad
+Description:
+ This is a BrainFuck monad, for generating brainfuck programs.
+
+Library
+  GHC-Options: -Wall
+  Exposed-Modules: Control.Monad.BrainFuck
+  Build-Depends: base (>= 4.5), base < 5
+
+source-repository head
+  type: git
+  location: git://git.kitenet.net/brainfuck-monad.git
