diff --git a/Peg.hs b/Peg.hs
--- a/Peg.hs
+++ b/Peg.hs
@@ -14,7 +14,7 @@
     You should have received a copy of the GNU General Public License
     along with peg.  If not, see <http://www.gnu.org/licenses/>.
 -}
-{-# LANGUAGE CPP, PatternGuards #-}
+{-# LANGUAGE CPP, PatternGuards, DeriveDataTypeable, ScopedTypeVariables #-}
 #ifdef MAIN
 module Main where
 #else
@@ -31,7 +31,7 @@
 import Text.Parsec.Language (haskellDef)
 import Data.List
 import Data.Ord
-import System.Console.Haskeline
+import System.Console.Haskeline hiding (throwIO, handle)
 import System.Environment
 import System.FilePath
 import System.IO
@@ -42,27 +42,22 @@
 import qualified Data.Set as S
 import Data.Map (Map)
 import qualified Data.Map as M
-
-lexer = P.makeTokenParser haskellDef
-
-integer = P.integer lexer
-float = P.float lexer
-naturalOrFloat = P.naturalOrFloat lexer
-natural = P.natural lexer
-whiteSpace = P.whiteSpace lexer
-charLiteral = P.charLiteral lexer
-stringLiteral = P.stringLiteral lexer
+import Control.Exception hiding (try)
+import Data.Typeable
 
-probe s x = trace (s ++ show x) x
+-------------------- Data Types --------------------
 
 type Stack = [Value]
+type Env = Map String (Peg ())
 data PegState = PegState { psStack :: Stack,
                            psArgStack :: Stack,
-                           psWords :: Map String (Peg ()),
+                           psWords :: Env,
                            psAvoid :: Set Stack }
-type Peg = StateT PegState (LogicT (Either Stack))
+type Peg = StateT PegState (LogicT IO)
+data PegException = PegException Stack Stack deriving (Show, Typeable)
+instance Exception PegException
 data Rule = Rule { getRule :: Stack -> Peg Stack }
-data Value = F Double | I Integer | C Char | L Stack | W String deriving (Show, Eq, Ord)
+data Value = F Double | I Integer | C Char | L Stack | W String | Io deriving (Show, Eq, Ord)
 
 isWord (W _) = True
 isWord _ = False
@@ -91,9 +86,20 @@
 
 isString = isJust . toString
 
-up :: Stack -> Peg ()
-up = lift . lift . Left
+isIo Io = True
+isIo _ = False
 
+-------------------- Debug --------------------
+
+probe s x = trace (s ++ show x) x
+
+traceStack :: Peg ()
+traceStack = do
+  s <- psStack <$> get
+  trace (showStack s) $ return ()
+
+-------------------- Peg Monad Operations --------------------
+
 -- | pop an argument from the stack, push onto argument stack
 getArg' check st = do
   force
@@ -105,7 +111,7 @@
       if check x
         then return ()
         else if st x
-               then pushArg x >> done
+               then pushStack x >> done
                else mzero
       pushArg x
 
@@ -114,6 +120,7 @@
 
 pushStack x = modify (\(PegState s a m xx) -> PegState (x:s) a m xx)
 appendStack x = modify (\(PegState s a m xx) -> PegState (x++s) a m xx)
+
 popStack :: Peg Value
 popStack = do PegState (x:s) a m xx <- get
               put $ PegState s a m xx
@@ -123,7 +130,7 @@
 -- | can't go any further, bail
 done = do
   st <- get
-  up $ reverse (psArgStack st) ++ psStack st
+  liftIO . throwIO $ PegException (psStack st) (psArgStack st)
 
 pushArg x = modify (\(PegState s a m xx) -> PegState s (x:a) m xx)
 popArg = do PegState s (x:a) m xx <- get
@@ -142,12 +149,7 @@
     (W w : _) -> popStack >> doWord w -- >> traceStack
     _ -> return ()
 
-traceStack :: Peg ()
-traceStack = do
-  s <- psStack <$> get
-  trace (showStack s) $ return ()
-
-wordMap = foldl' (flip (uncurry $ M.insertWith mplus)) M.empty
+-------------------- Converters --------------------
 
 op2i f = do
   getArgNS isInt
@@ -168,6 +170,11 @@
   F x <- popArg
   pushStack . F . f $ x
 
+opfi f = do
+  getArg isFloat
+  F x <- popArg
+  pushStack . I . f $ x
+
 reli f = do
   getArgNS isInt
   getArgNS isInt
@@ -194,16 +201,42 @@
   getArg anything
   pushStack . W . show . f =<< popArg
 
+-------------------- Helpers for builtins --------------------
+
+(f ||. g) x = f x || g x
+(f &&. g) x = f x && g x
+
 anything (W "]") = False
 anything (W "[") = False
 anything _ = True
 
-brac (W "]") = True
-brac _ = False
+unpackList = do
+  getArg (isList ||. (== W "]"))
+  x <- popArg
+  case x of
+    W "]" -> return ()
+    L l -> do pushStack $ W "["
+              appendStack l
 
-(f ||. g) x = f x || g x
-(f &&. g) x = f x && g x
+bind n l = modify $ \(PegState s a w xx) -> PegState s a (M.insertWith interleave n (f l) w) xx
+  where f l = do force
+                 w <- popArg
+                 force >> appendStack l >> force
+                 pushArg w
 
+unbind n = modify $ \(PegState s a w xx) -> PegState s a (M.delete n w) xx
+
+gatherList n l (w@(W "]") : s) = gatherList (n+1) (w:l) s
+gatherList n l (w@(W "[") : s)
+  | n <= 0 = Right (l,s)
+  | otherwise = gatherList (n-1) (w:l) s
+gatherList n l (w:s) = gatherList n (w:l) s
+gatherList n l [] = Left l
+
+wordMap = foldl' (flip (uncurry $ M.insertWith mplus)) M.empty
+
+-------------------- Built-ins --------------------
+
 builtins = wordMap [
   ("+", op2i (+)),
   ("-", op2i (-)),
@@ -274,7 +307,7 @@
                put $ PegState s' a w xx
                pushStack . L . reverse $ l),
   ("pushr", do getArg anything
-               getArg (isList ||. brac)
+               getArg (isList ||. (== W "]"))
                x <- popArg
                case x of
                  -- toss it over the fence
@@ -282,53 +315,32 @@
                              pushStack (W "]")
                  L l -> do x <- popArg
                            pushStack $ L (x:l)),
-  ("popr", do getArg (isList ||. brac)
+  ("popr", do unpackList
+              -- reach across the fence
+              pushArg $ W "]"
+              getArg (anything ||. (== W "["))
               x <- popArg
-              case x of
-                -- reach across the fence
-                W "]" -> do pushArg (W "]")
-                            getArg (anything ||. (== W "["))
-                            x <- popArg
-                            popArg
-                            guard $ x /= W "["
-                            pushStack (W "]")
-                            pushStack x
-                -- unpack the list and force it
-                L l -> do pushStack $ W "["
-                          pushArg $ W "]"
-                          appendStack l
-                          getArg (anything ||. (== W "["))
-                          x <- popArg
-                          guard $ x /= W "["
-                          popArg >>= pushStack
-                          pushStack x
-                _ -> mzero),
+              guard $ x /= W "["
+              popArg >>= pushStack
+              pushStack x),
+  ("dupnull?", do unpackList
+                  -- take a peek across the fence
+                  pushArg $ W "]"
+                  getArg (anything ||. (== W "["))
+                  x <- popArg
+                  pushStack x
+                  popArg >>= pushStack
+                  pushStack . W . show $ x == W "["),
   (".", do getArg isList
-           getArg (isList ||. brac)
+           getArg (isList ||. (== W "]"))
            x <- popArg
            case x of
              -- remove the fence
              W "]" -> do L l <- popArg
-                         pushArg $ W "]"
                          appendStack l
-                         popArg >>= pushStack
+                         pushStack $ W "]"
              L x -> do L y <- popArg
                        pushStack . L $ y ++ x),
-  ("dupnull?", do getArg (isList ||. brac)
-                  x <- popArg
-                  case x of
-                    -- take a peek across the fence
-                    W "]" -> do pushArg $ W "]"
-                                force
-                                y <- peekStack
-                                popArg >>= pushStack
-                                pushStack . W . show $ y == W "["
-                    L l -> do pushStack $ W "["
-                              appendStack l
-                              force
-                              x <- peekStack
-                              pushStack $ W "]"
-                              pushStack . W . show $ x == W "["),
   ("assert", getArgNS (== W "True") >> popArg >> force),
   ("deny", getArgNS (== W "False") >> popArg >> force),
   ("\\/", do getArg anything
@@ -342,6 +354,7 @@
   ("list?", is_type isList),
   ("char?", is_type isChar),
   ("string?", is_type isString),
+  ("io?", is_type isIo),
   ("eq?", do getArg anything
              getArg anything
              x <- popArg
@@ -358,8 +371,10 @@
                 unbind s),
   ("$", do getArg isList
            L l <- popArg
+           w <- popArg -- temporarily remove $ from the arg stack
            appendStack l
-           force),
+           force
+           pushArg w),
   ("seq", do getArg anything
              force
              pushStack =<< popArg),
@@ -370,33 +385,51 @@
               Just s <- toString <$> popArg
               let Right x = parseStack s
               appendStack x
-              force)]
-{-
-runIO (L [W "IO", L (W op : args), L k] : s) =
-  case (op, args) of
-    ("getChar", []) -> getChar >>= runIO . (++s) . (:k) . C
-    ("putChar", [C c]) -> putChar c >> runIO (k ++ s)
-    ("return", [x]) -> runIO (x : k ++ s)
--}
-bind n l = modify $ \(PegState s a w xx) -> PegState s a (M.insertWith interleave n (f l) w) xx
-  where f l = do force
-                 w <- popArg
-                 force >> appendStack l >> force
-                 pushArg w
-
-unbind n = modify $ \(PegState s a w xx) -> PegState s a (M.delete n w) xx
+              force),
+  ("realToFrac", do getArg (isInt ||. isFloat)
+                    a <- popArg
+                    case a of
+                      I x -> pushStack . F . realToFrac $ x
+                      F x -> pushStack a),
+  ("round", opfi round),
+  ("floor", opfi floor),
+  ("ceiling", opfi ceiling),
+  ("getChar", do getArg isIo
+                 pushStack =<< popArg
+                 liftIO getChar >>= pushStack . C),
+  ("putChar", do getArg isChar
+                 getArg isIo
+                 io <- popArg
+                 C c <- popArg
+                 liftIO $ putChar c
+                 pushStack io),
+  ("getLine", do getArg isIo
+                 pushStack =<< popArg
+                 liftIO getLine >>= pushStack . L . map C),
+  ("putStr", do getArg isString
+                getArg isIo
+                io <- popArg
+                Just s <- toString <$> popArg
+                liftIO $ putStr s
+                pushStack io),
+  ("putStrLn", do getArg isString
+                  getArg isIo
+                  io <- popArg
+                  Just s <- toString <$> popArg
+                  liftIO $ putStrLn s
+                  pushStack io)]
 
-peekStack = do
-  (x:_) <- psStack <$> get
-  return x
+-------------------- Parsing --------------------
 
-gatherList n l (w@(W "]") : s) = gatherList (n+1) (w:l) s
-gatherList n l (w@(W "[") : s)
-  | n <= 0 = Right (l,s)
-  | otherwise = gatherList (n-1) (w:l) s
-gatherList n l (w:s) = gatherList n (w:l) s
-gatherList n l [] = Left l
+lexer = P.makeTokenParser haskellDef
 
+integer = P.integer lexer
+float = P.float lexer
+naturalOrFloat = P.naturalOrFloat lexer
+natural = P.natural lexer
+whiteSpace = P.whiteSpace lexer
+charLiteral = P.charLiteral lexer
+stringLiteral = P.stringLiteral lexer
 
 word :: Parser String
 word = (:) <$> (letter <|> oneOf ":_?") <*> many (alphaNum <|> oneOf "?_'#")
@@ -406,8 +439,6 @@
         fmap (:[]) (oneOf "[]{};") <|>
         (string "-")
 
---list = char '[' >> stackExpr <* char ']'
-
 number = do m <- optionMaybe (char '-')
             let f = maybe (either I F)
                           (const $ either (I . negate) (F . negate)) m
@@ -436,6 +467,7 @@
         loop (C x : s) = loop s . (' ':) . shows x
         loop (F x : s) = loop s . (' ':) . shows x
         loop (W x : s) = loop s . ((' ':x) ++)
+        loop (Io : s) = loop s . (" IO" ++)
         loop (L [] : s) = loop s . (" [ ]" ++)
         loop (L x : s) = case toString (L x) of
                            Just str -> loop s . (' ':) . shows str
@@ -443,13 +475,11 @@
 
 parseStack = parse stackExpr ""
 
-evalStack' fs m src = do
-  s <- fs <$> parseStack src
-  return . observeManyT 8 $ do
-    PegState s _ m _ <- execStateT force $ PegState s [] m S.empty
-    return (s, m)
+-------------------- Main --------------------
 
-evalStack fs m = fmap (either (\s -> [(s, m)]) id) . evalStack' fs m
+evalStack (s, m) = observeManyT 8 $ do
+  PegState s _ m _ <- execStateT force $ PegState s [] m S.empty
+  return (s, m)
 
 hGetLines h = do
   e <- hIsEOF h
@@ -464,37 +494,48 @@
   let files = filter ((==".peg").takeExtension) args
   m <- foldM (\m f -> do
           l <- getLinesFromFile f
-          case load [] m l of
-            Left e -> print e >> return m
-            Right m' -> return m') builtins files
-  runInputT defaultSettings (evalLoop True [] m)
+          load [] m l) builtins files
+  runInputT defaultSettings $ evalLoop (Right []) m
 
 load :: Stack
-  -> Map String (Peg ())
+  -> Env
   -> [String]
-  -> Either ParseError (Map String (Peg ()))
-load s m [] = Right m
+  -> IO Env
+load s m [] = return m
 load s m (input:r) =
-  case head <$> evalStack (++s) m input of
-    Left e -> Left e
-    Right (s', m') -> load s' m' r
+  case parseStack input of
+    Left e -> print e >> return m
+    Right s -> handle (\(_ :: PegException) -> load s m r) $ do
+      (s', m') : _ <- evalStack (s, m)
+      load s' m' r
 
-ifNotNull f [] = []
-ifNotNull f x = f x
+-- I/O ideas
+-- I/O token: dup I/O --> spawn thread, pop I/O --> kill thread
+-- x0 x1 x2      IO x3 x4  IO x5
+-- | main thread | thread 0 | thread 1 ...
+-- .. IO [ x ] dip
+--   <------|
+-- send to thread n-1
 
-evalLoop :: Bool -> Stack -> Map String (Peg ()) -> InputT IO ()
-evalLoop n s m = do
-  minput <- getInputLineWithInitial ": " .
-              (if n then (flip (,) "" . ifNotNull (++" "))
-                    else ((,) "") . (" "++)) $ showStack s
+makeIOReal = map (\x -> if x == W "IO" then Io else x)
+
+evalLoop :: Either (Stack, Stack) Stack -> Env -> InputT IO ()
+evalLoop p m = do
+  let text = case p of
+               Left (s, a) -> (showStack s, ' ' : showStack (reverse a))
+               Right [] -> ("", "")
+               Right s -> (showStack s ++ " ", "")
+  minput <- getInputLineWithInitial ": " text
   case minput of
     Nothing -> return ()
     Just "" -> return ()
-    Just input -> case evalStack' id m input of
-      Left e -> outputStrLn (show e) >> evalLoop n s m
-      Right x -> case x of
-        Left s' -> evalLoop False s' m
-        Right [] -> evalLoop n [W "no"] m
-        Right ((s',m'):r) -> do
-          mapM_ (outputStrLn . showStack . fst) r
-          evalLoop True s' m'
+    Just input -> case parseStack input of
+      Left e -> outputStrLn (show e) >> evalLoop p m
+      Right s -> do
+        x' <- liftIO . handle (\(PegException s a) -> return (Left (s, a))) $ Right <$> evalStack (makeIOReal s, m)
+        case x' of
+          Left s' -> evalLoop (Left s') m
+          Right [] -> evalLoop (Right [W "no"]) m
+          Right ((s',m'):r) -> do
+            mapM_ (outputStrLn . showStack . fst) r
+            evalLoop (Right s') m'
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,16 +4,22 @@
 Overview
 --------
 
-Peg is a lazy non-deterministic concatenative programming language.
+Peg is a lazy non-deterministic concatenative programming language inspired by Haskell, Joy, and Prolog.
 
 In contrast to most concatenative programming languages, Peg starts evaluation from the right, evaluating arguments to the left as needed.
 
-For example, even though the word 'no' can never be resolved:
+For example, even though the word `no` can never be resolved:
 
     no 1 2 + --> no 3
 
 This is because `+` requires only two arguments on the stack.
 
+Another demonstration of laziness:
+
+    0 [1+] iterate
+
+This creates the infinite stack `.. 3 2 1 0`.  Try `pop`ing a few times to see how it works.
+
 Branching is accomplished with the choice operator `\/`. Both paths are followed non-deterministically.  Paths are terminated when a word cannot be resolved.
 
 Multiple definitions for a word cause the definitions to be substituted non-deterministically.  This allows words (even built-in words) to be extended for new types.
@@ -30,6 +36,11 @@
 
     [1 2 3 +] popr --> [1] 5
 
+A string literal is a stack consisting only of characters.  They are read and displayed backwards from stacks, to make them readable.
+
+    ['o' 'l' 'l' 'e' 'h'] --> "hello"
+    "hello" 0 pushr --> ['o' 'l' 'l' 'e' 'h' 0]
+
 Peg is flat, in that any expression can be divided on white space (except inside a literal), the pieces evaluated independently, and when the results are concatenated, evaluate to an equivalent expression to the original expression.
 
 Example:
@@ -39,7 +50,11 @@
     ] popr         --> ] popr
     [ 3 ] popr     --> [ ] 3
 
-Built-in words
+Instead of using a monad to implement pure functional I/O, Peg simply uses a token representing the state of the world, `IO`.  Words that perform I/O must require `IO` as an argument.  If the word does not put it back, it will destroy the world.
+
+`IO` can only be introduced from the top-level, by typing `IO`.  In other places, such as definitions and `read`, `IO` is parsed as a word with no special meaning.
+
+Built-in Words
 --------------
 
 The format below is:
@@ -68,7 +83,7 @@
 
 `False` `deny` --> -- opposite of assert
 
-`x` `y` `\/` --> 'x' \/ 'y' -- continues execution non-deterministically with `x` and `y`
+`x` `y` `\/` --> `x` \/ `y` -- continues execution non-deterministically with `x` and `y`
 
 `int?`, `float?`, `word?`, `list?`, `char?`, `string?` -- test type of argument, returning `True` or `False`
  
@@ -86,8 +101,26 @@
 
 `"x"` `read` --> `x` -- convert string representation of `x` into `x`, opposite of `show`
 
-`+`, `-`, `*`, `div`, `^`, `^^`, `**`, `exp`, `sqrt`, `log`, `logBase`, `sin`, `tan`, `cos`, `asin`, `atan`, `acos`, `sinh`, `tanh`, `cosh`, `asinh`, `atanh`, `acosh`, `<`, `<=`, `>`, `>=` -- numeric and comparison words defined as in Haskell Prelude
+`+`, `-`, `*`, `div`, `^`, `^^`, `**`, `exp`, `sqrt`, `log`, `logBase`, `sin`, `tan`, `cos`, `asin`, `atan`, `acos`, `sinh`, `tanh`, `cosh`, `asinh`, `atanh`, `acosh`, `<`, `<=`, `>`, `>=`, `realToFrac`, `round`, `floor`, `ceiling` -- numeric and comparison words defined as in Haskell Prelude
 
+`getChar`, `putChar`, `getLine`, `putStr`, `putStrLn` -- similar to Haskell Prelude.  Instead of running in IO monad, they require `IO` as the first argument, putting it back after executing
+
+A simple IO example:
+
+    IO "What's your name?" putStrLn getLine "!" "Hello " splice putStrLn
+
+Peg supports a curly bracket notation to allow for case statements and do-notation.  Curly braces trivially reduce to a nested stack.
+
+`{` --> `[` `[`
+
+`;` --> `]` `[`
+
+`}` --> `]` `]`
+
+Usage with `case`:
+
+    b {1 a; 2 b} case --> 2
+
 Library: lib.peg
 ----------------
 
@@ -95,4 +128,42 @@
 
 `foldr` and `foldl` are swapped from the Haskell definitions, because "lists" are stacks, and elements are added to the right side of a stack.  Similarly for `scanr` and `scanl`.
 
-Most of the Haskell Prelude is implemented, except words that aren't very useful or are replaced by a built-in word.  I'm still working on IO.
+Most of the Haskell Prelude is implemented, except words that aren't very useful or are replaced by a built-in word.
+
+Running the Peg Interpreter
+---------------------------
+
+Build the interpreter using Cabal (cabal configure; cabal build)
+
+Just call the `peg` executable with source files to be loaded (such as lib.peg) as arguments.
+
+The interpreter evaluates the input after pressing `Enter`.  The results will be printed after the next prompt, allowing you to edit the results.  If the cursor is not on the right, a word did not have enough arguments to be evaluated; the cursor will be placed so that you can provide the missing arguments.  If there are multiple results, up to 8 results will be printed, but only the first will appear at the prompt.  If there are no results, the result `no` is shown, which is equivalent (defined in `lib.peg`).
+
+[Haskeline](http://hackage.haskell.org/package/haskeline) provides the line editing interface.  Clearing the input and pressing `Enter` will exit the interpreter.
+
+Future
+------
+
+### I/O
+
+I have tried modeling I/O after Haskell's monad approach, but monads seem to be better suited to applicative languages, despite being possible in a concatenative language.
+
+I have implemented a different method of performing I/O in a pure functional way, as described above (I/O words require an `IO` token.)  This may allow more flexible use of I/O than a monad; threads could be spawned with `IO dup` and ended with `IO pop`.  Also, more complex operations may be possible, requiring multiple `IO`s.
+
+### Type System
+
+The current idea is to use explicit type checks (such as `int?`) instead of introducing a different syntax for type annotations.  This would allow the type system to be extended using the base language, and support dependent typing.  It would also allow optional run time typing.
+
+The interpreter is currently dynamically typed, but I would like to make the compiler support static type checking, by proving that the result of a computation cannot be `no`.  The compiler could also optimize away types and most non-determinism.  I do realize that, in general, static type checking will be undecidable.  The compiler will be designed to resolve undecidable types interactively with the user.
+
+The language would not change significantly.  Product types are built from stacks, such as `[1 2 Ratio]`, and sum types are created using `\/`, such as `[1 Left] \/ ['a' Right]`, using undefined words at the top of the stack as tags.  Constructors can be created as the matching lowercase word, such as `x left --> [x Left]`.  Using the same word as the tag results in an infinitely nested stack, so the values can never be retrieved.
+
+I have done some work on types in `types.peg`, which extends some words to operate on type tags.
+
+### Modules
+
+I will need to add a module system to allow encapsulation.
+
+### Compiler
+
+The compiler will first target C, to allow easy portability.  I am interested in running Peg code in embedded systems, especially because it is difficult to use other high-level languages such as Haskell on most microcontrollers.
diff --git a/lib.peg b/lib.peg
--- a/lib.peg
+++ b/lib.peg
@@ -192,7 +192,7 @@
 [[not] . span] "break" :def
 
 -- ( a a enumFromTo -> [A] )
-[?over - 1+ swap [[1+] iterate] pushl swap take] "enumFromTo" :def
+[over - 1+ swap [[1+] iterate] pushl swap take] "enumFromTo" :def
 
 -- ( [A] length -> Int )
 [0 length'] "length" :def
@@ -362,3 +362,11 @@
 --[[return] pushl singleton [[]] [IO] splice] "return" :def
 --[swap popr {popr [head swap . [>>=] .] dip [IO] pushl pushl : IO} case] ">>=" :def
 
+
+-- I/O
+
+-- ( IO a print --> IO ) -- prints representation of a to stdout
+[show putStrLn] "print" :def
+
+-- ( IO readLn --> IO a ) -- parses input from stdin to a
+[getLine read] "readLn" :def
diff --git a/peg.cabal b/peg.cabal
--- a/peg.cabal
+++ b/peg.cabal
@@ -6,7 +6,7 @@
 -- The package version. See the Haskell package versioning policy
 -- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for
 -- standards guiding when and how versions should be incremented.
-Version:             0.1
+Version:             0.2
 
 -- A short (one-line) description of the package.
 Synopsis:            a lazy non-deterministic concatenative programming language
