diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2013, Tobias Dammers
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * 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.
+
+    * Neither the name of Tobias Dammers nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+OWNER 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/Text/Parcom/ByteString.hs b/Text/Parcom/ByteString.hs
new file mode 100644
--- /dev/null
+++ b/Text/Parcom/ByteString.hs
@@ -0,0 +1,6 @@
+module Text.Parcom.ByteString
+( module Text.Parcom.ByteString.Strict
+)
+where
+
+import Text.Parcom.ByteString.Strict
diff --git a/Text/Parcom/ByteString/Strict.hs b/Text/Parcom/ByteString/Strict.hs
new file mode 100644
--- /dev/null
+++ b/Text/Parcom/ByteString/Strict.hs
@@ -0,0 +1,14 @@
+{-#LANGUAGE MultiParamTypeClasses #-}
+module Text.Parcom.ByteString.Strict
+where
+
+import Prelude hiding (head, null, tail)
+import Data.ByteString
+import Data.Word8
+import Text.Parcom.Stream
+import Text.Parcom.Word8
+
+instance Stream ByteString Word8 where
+    peek = head
+    atEnd = null
+    consume = tail
diff --git a/Text/Parcom/Combinators.hs b/Text/Parcom/Combinators.hs
new file mode 100644
--- /dev/null
+++ b/Text/Parcom/Combinators.hs
@@ -0,0 +1,78 @@
+module Text.Parcom.Combinators
+( choice, namedChoice
+, before, between
+, many, many1, manySepBy
+, times
+, skip
+)
+where
+
+import Text.Parcom.Core
+import Text.Parcom.Internal
+
+-- | Walk a list of options, return the first one that succeeds.
+choice :: (Stream s t) => [Parcom s t a] -> Parcom s t a
+choice xs = foldl (<|>) empty xs <?> "I tried to make a choice, but couldn't"
+
+-- | Like @choice@, but each choice tagged with a human-readable name for
+-- better error reporting.
+namedChoice :: (Stream s t) => [(String, Parcom s t a)] -> Parcom s t a
+namedChoice xs = choice (map snd xs) <?> (formatOptionList . map fst) xs
+
+-- | Match two consecutive parser, return the first parser's result iff both
+-- succeed.
+before :: (Stream s t) => Parcom s t a -> Parcom s t b -> Parcom s t a
+before p q = do { v <- p; q; return v }
+
+-- | Match three consecutive parsers, return the middle parser's result iff
+-- all three match. Parsers are given in the order inner, left, right.
+between :: (Stream s t) => Parcom s t a -> Parcom s t l -> Parcom s t r -> Parcom s t a
+between p l r = do { l; v <- p; r; return v }
+
+-- | Match zero or more occurrences of a parser
+many :: (Stream s t) => Parcom s t a -> Parcom s t [a]
+many p =
+    handle p f m
+    where
+        f e = return []
+        m x = do
+            xs <- many p
+            return (x:xs)
+
+-- | Match one or more occurrences of a parser
+many1 :: (Stream s t) => Parcom s t a -> Parcom s t [a]
+many1 p = do
+    xs <- many p
+    if null xs
+        then fail "Expected at least one item"
+        else return xs
+
+-- | Given an item parser and a separator parser, keep parsing until the
+-- separator or the item fails.
+manySepBy :: (Stream s t) => Parcom s t a -> Parcom s t b -> Parcom s t [a]
+manySepBy p s = go
+    where
+        go = do
+            -- try an item
+            handle p f m
+            where
+                -- item does not parse: return empty list (no matches)
+                f e = return []
+                -- item does parse: keep the item, try the separator.
+                m x = handle s
+                    -- separator does not parse: return the one item we have
+                    (\e -> return [x])
+                    -- separator does parse: recurse and prepend our item
+                    (\_ -> go >>= \xs -> return (x:xs))
+
+-- | Run the given parser n times, returning all the results as a list.
+times :: (Stream s t) => Int -> Parcom s t a -> Parcom s t [a]
+times 0 p = return []
+times n p = do
+    x <- p
+    xs <- times (n - 1) p
+    return (x:xs)
+
+-- | Ignore the result of a parser.
+skip :: Parcom s t a -> Parcom s t ()
+skip p = p >> return ()
diff --git a/Text/Parcom/Core.hs b/Text/Parcom/Core.hs
new file mode 100644
--- /dev/null
+++ b/Text/Parcom/Core.hs
@@ -0,0 +1,161 @@
+{-#LANGUAGE MultiParamTypeClasses #-}
+module Text.Parcom.Core
+( ParcomError (..)
+, SourcePosition (..)
+, Parcom
+, parse
+, peek, next, atEnd
+, try, handle
+, notFollowedBy
+, (<?>), (<|>), empty
+, Stream, Token
+)
+where
+
+import qualified Text.Parcom.Stream as Stream
+import Text.Parcom.Stream (Stream, Token)
+import Control.Monad (liftM, ap)
+import Control.Applicative
+
+data SourcePosition =
+    SourcePosition
+        { posFileName :: String
+        , posLine :: Int
+        , posColumn :: Int
+        }
+        deriving (Show)
+
+data ParcomError =
+    ParcomError
+        { peErrorDescription :: String
+        , peSourcePosition :: SourcePosition
+        }
+        deriving (Show)
+
+data ParcomState s =
+    ParcomState
+        { psSourcePosition :: SourcePosition
+        , psStream :: s
+        }
+
+newtype Parcom s t a = Parcom { runParcom :: ParcomState s -> (Either ParcomError a, ParcomState s) }
+
+instance Monad (Parcom s t) where
+    return x = Parcom (\s -> (Right x, s))
+    fail err = Parcom (\s -> (Left $ ParcomError err (psSourcePosition s), s))
+    m >>= f = Parcom $ \s ->
+        let (a, s') = runParcom m s
+        in case a of
+            Left e -> (Left e, s')
+            Right x -> runParcom (f x) s'
+
+instance Functor (Parcom s t) where
+    fmap f xs = xs >>= return . f
+
+instance Applicative (Parcom s t) where
+    pure = return
+    (<*>) = ap
+
+instance Alternative (Parcom s t) where
+    (<|>) = alt
+    empty = fail "empty"
+
+runParser :: (Stream s t, Token t) => Parcom s t a -> String -> s -> (Either ParcomError a, ParcomState s)
+runParser p fn str =
+    runParcom p state
+    where
+        state =
+            ParcomState
+                { psSourcePosition = SourcePosition fn 1 1
+                , psStream = str }
+
+parse :: (Stream s t, Token t) => Parcom s t a -> String -> s -> Either ParcomError a
+parse p fn str = fst $ runParser p fn str
+
+getState :: Parcom s t (ParcomState s)
+getState = Parcom $ \s -> (Right s, s)
+
+useState :: (ParcomState s -> a) -> Parcom s t a
+useState f = Parcom $ \s -> (Right $ f s, s)
+
+setState :: ParcomState s -> Parcom s t ()
+setState s = Parcom $ \_ -> (Right (), s)
+
+modifyState :: (ParcomState s -> ParcomState s) -> Parcom s t ()
+modifyState f = Parcom $ \s -> (Right (), f s)
+
+handle :: Parcom s t a -> (ParcomError -> Parcom s t b) -> (a -> Parcom s t b) -> Parcom s t b
+handle p f t = Parcom $ \s ->
+    let (r', s') = runParcom p s
+    in case r' of
+        -- parse failed: run the error handler
+        Left e -> runParcom (f e) s'
+        -- parse succeeded: run the success handler
+        Right x -> runParcom (t x) s'
+
+
+-- | Backtracking modifier; restores the parser state to the previous situation
+-- if the wrapped parser fails.
+try :: Parcom s t a -> Parcom s t a
+try p = Parcom $ \s ->
+    let (r', s') = runParcom p s
+    in case r' of
+        -- parse failed: return the error and restore the old state
+        Left e -> (Left e, s)
+        -- parse succeeded: return the result and the new state
+        Right x -> (Right x, s')
+
+-- | Return the result of the first parser that succeeds.
+alt :: Parcom s t a -> Parcom s t a -> Parcom s t a
+alt a b = Parcom $ \s ->
+            let (r', s') = runParcom a s
+            in case r' of
+                Left _ -> runParcom b s
+                Right x -> (Right x, s')
+
+-- | Succeeds iff the given parser fails
+notFollowedBy :: (Stream s t) => Parcom s t a -> Parcom s t ()
+notFollowedBy p = Parcom $ \s ->
+    let (r', s') = runParcom p s
+    in case r' of
+        Left _ -> (Right (), s)
+        Right x -> runParcom (fail "something followed that shouldn't") s
+    
+-- | Gets the next token from the stream without consuming it.
+-- Fails at end-of-input.
+peek :: (Stream s t) => Parcom s t t
+peek = do
+    str <- psStream `liftM` getState
+    if Stream.atEnd str
+        then fail "Unexpected end of input"
+        else return (Stream.peek str)
+
+-- | Checks whether end-of-input has been reached.
+atEnd :: (Stream s t) => Parcom s t Bool
+atEnd = useState (Stream.atEnd . psStream)
+
+nextLine :: SourcePosition -> SourcePosition
+nextLine s =
+    s { posLine = posLine s + 1, posColumn = 1 }
+
+nextColumn :: SourcePosition -> SourcePosition
+nextColumn s =
+    s { posColumn = posColumn s + 1 }
+
+-- | Gets the next token from the stream and consumes it.
+-- Fails at end-of-input.
+next :: (Stream s t, Token t) => Parcom s t t
+next = do
+    str <- psStream `liftM` getState
+    if Stream.atEnd str
+        then fail "Unexpected end of input"
+        else do
+            let (t, str') = Stream.pop str
+            modifyState $ \state -> state { psStream = str' }
+            if Stream.isLineDelimiter t
+                then modifyState $ \s -> s { psSourcePosition = nextLine (psSourcePosition s) }
+                else modifyState $ \s -> s { psSourcePosition = nextColumn (psSourcePosition s) }
+            return t
+
+(<?>) :: (Stream s t) => Parcom s t a -> String -> Parcom s t a
+p <?> expected = p <|> fail ("Expected " ++ expected)
diff --git a/Text/Parcom/Internal.hs b/Text/Parcom/Internal.hs
new file mode 100644
--- /dev/null
+++ b/Text/Parcom/Internal.hs
@@ -0,0 +1,8 @@
+module Text.Parcom.Internal
+where
+
+formatOptionList :: [String] -> String
+formatOptionList (x:y:[]) = x ++ " or " ++ y
+formatOptionList (x:[]) = x
+formatOptionList [] = ""
+formatOptionList (x:xs) = x ++ ", " ++ formatOptionList xs
diff --git a/Text/Parcom/Prim.hs b/Text/Parcom/Prim.hs
new file mode 100644
--- /dev/null
+++ b/Text/Parcom/Prim.hs
@@ -0,0 +1,42 @@
+module Text.Parcom.Prim
+( anyToken, oneOf, eof
+, satisfy
+, token, tokens
+)
+where
+
+import Text.Parcom.Core
+import Text.Parcom.Internal
+
+-- | Gets the next token from the stream
+anyToken :: (Stream s t, Token t) => Parcom s t t
+anyToken = next
+
+-- | Succeeds iff end-of-input has been reached
+eof :: (Stream s t, Token t) => Parcom s t ()
+eof = notFollowedBy anyToken <?> "end of input"
+
+-- | Matches one token against a list of possible tokens; returns the
+-- matching token.
+oneOf :: (Stream s t, Token t, Show t, Eq t) => [t] -> Parcom s t t
+oneOf xs = satisfy (`elem` xs) <?> (formatOptionList . map show) xs
+
+-- | Succeeds if the given predicate is met for the next token.
+satisfy :: (Stream s t, Token t) => (t -> Bool) -> Parcom s t t
+satisfy p = do
+    c <- peek
+    if p c
+        then next
+        else fail "Predicate not met"
+
+-- | Exactly match one particular token
+token :: (Stream s t, Token t, Show t, Eq t) => t -> Parcom s t t
+token t = satisfy (== t) <?> show t
+
+-- | Match a series of tokens exactly
+tokens :: (Stream s t, Token t, Eq t, Show t) => [t] -> Parcom s t [t]
+tokens [] = return []
+tokens (x:xs) = do
+    c <- token x
+    cs <- tokens xs
+    return (c:cs)
diff --git a/Text/Parcom/Stream.hs b/Text/Parcom/Stream.hs
new file mode 100644
--- /dev/null
+++ b/Text/Parcom/Stream.hs
@@ -0,0 +1,23 @@
+{-#LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}
+module Text.Parcom.Stream
+where
+
+class Stream s t | s -> t where
+    peek :: s -> t
+    atEnd :: s -> Bool
+    consume :: s -> s
+    pop :: s -> (t, s)
+    consume = snd . pop
+    pop s = (peek s, consume s)
+
+instance Stream [a] a where
+    peek = head
+    atEnd = null
+    consume = tail
+
+class Token t where
+    isLineDelimiter :: t -> Bool
+
+instance Token Char where
+    isLineDelimiter '\n' = True
+    isLineDelimiter _ = False
diff --git a/Text/Parcom/Word8.hs b/Text/Parcom/Word8.hs
new file mode 100644
--- /dev/null
+++ b/Text/Parcom/Word8.hs
@@ -0,0 +1,10 @@
+module Text.Parcom.Word8
+(
+)
+where
+
+import Text.Parcom.Stream
+import Data.Word8
+
+instance Token Word8 where
+    isLineDelimiter = (== 13)
diff --git a/parcom-lib.cabal b/parcom-lib.cabal
new file mode 100644
--- /dev/null
+++ b/parcom-lib.cabal
@@ -0,0 +1,28 @@
+-- Initial parcom-lib.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                parcom-lib
+version:             0.1.0.0
+synopsis:            A simple parser-combinator library, a bit like Parsec but without the frills
+description:         Parcom provides parser combinator functionality in a string-type-agnostic way;
+                     it supports strict ByteStrings (with Word8 tokens) and any list type (with
+                     the element type as the token type) out-of-the-box, including plain old String.
+                     Any other stream-of-tokens type can be hooked into the library; unlike Parsec,
+                     none of the built-in parsers assumes char-like tokens.
+homepage:            https://bitbucket.org/tdammers/parcom-lib
+license:             BSD3
+license-file:        LICENSE
+author:              Tobias Dammers
+maintainer:          tdammers@gmail.com
+-- copyright:           
+category:            Text
+build-type:          Simple
+cabal-version:       >=1.8
+
+library
+  exposed-modules: Text.Parcom.Stream, Text.Parcom.Core, Text.Parcom.Word8, Text.Parcom.ByteString, Text.Parcom.Internal, Text.Parcom.Combinators, Text.Parcom.Prim, Text.Parcom.ByteString.Strict
+  -- other-modules:       
+  build-depends: base ==4.5.*
+               , containers ==0.4.*
+               , bytestring ==0.9.*
+               , word8
