diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,4 @@
+0.1.0.0
+=======
+
+-   Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Abhinav Gupta
+
+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 Abhinav Gupta 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/Language/Thrift/Parser.hs b/Language/Thrift/Parser.hs
new file mode 100644
--- /dev/null
+++ b/Language/Thrift/Parser.hs
@@ -0,0 +1,293 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings          #-}
+module Language.Thrift.Parser
+    ( ThriftParser
+    , runThriftParser
+
+    , program
+    , header
+    , definition
+    , typeDefinition
+    , typedef
+    , enum
+    , enumDef
+    , senum
+    , struct
+    , union
+    , exception
+    , fieldRequiredness
+    , fieldType
+    , field
+    , constant
+    , constantValue
+    , service
+    , function
+    , typeAnnotations
+    ) where
+
+import Control.Applicative
+import Control.Monad
+import Control.Monad.State     (StateT)
+import Data.Text               (Text)
+import Text.Trifecta
+import Text.Parser.Token.Style (emptyIdents)
+
+import qualified Control.Monad.State as State
+import qualified Data.Text           as Text
+
+import qualified Language.Thrift.Types as T
+
+
+newtype ParserState = ParserState
+  { parserLastDocstring :: T.Docstring
+  } deriving (Show, Ord, Eq)
+
+newtype ThriftParser a = ThriftParser (StateT ParserState Parser a)
+    deriving
+      ( Functor
+      , Applicative
+      , Alternative
+      , Monad
+      , MonadPlus
+      , Parsing
+      , CharParsing
+      )
+
+lastDocstring :: ThriftParser T.Docstring
+lastDocstring = ThriftParser $ do
+    s <- State.gets parserLastDocstring
+    State.put (ParserState Nothing)
+    return s
+
+runThriftParser :: ThriftParser a -> Parser a
+runThriftParser (ThriftParser p) = State.evalStateT p (ParserState Nothing)
+
+instance TokenParsing ThriftParser where
+    someSpace = skipSome $
+        readDocstring <|> skipComments <|> skipSpace
+      where
+        skipSpace = choice [
+            newline *> clearDocstring
+          , ThriftParser someSpace
+          ]
+
+        skipComments = choice [
+              char '#'   *> skipLine
+            , text "//"  *> skipLine
+            , text "/*"  *> skipCStyleComment
+            ] *> clearDocstring
+        skipLine = skipMany (satisfy (/= '\n')) <* newline
+        skipCStyleComment = choice [
+            text "*/"              *> pure ()
+          , skipSome (noneOf "/*") *> skipCStyleComment
+          , oneOf "/*"             *> skipCStyleComment
+          ]
+
+        -- TODO this is really ugly. use some sort of docstring parser instead
+        clearDocstring = ThriftParser $ State.put (ParserState Nothing)
+        readDocstring = text "/**" *> loop []
+          where
+            saveDocstring s = unless (Text.null s') $
+                ThriftParser . State.put . ParserState . Just $ s'
+              where
+                s' = sanitizeDocstring s
+            loop chunks = choice [
+                text "*/" *> optional (newline <|> space) *>
+                saveDocstring (Text.strip . Text.concat $ reverse chunks)
+              , Text.pack      <$> some (noneOf "/*") >>= loop . (:chunks)
+              , Text.singleton <$>        oneOf "/*"  >>= loop . (:chunks)
+              ]
+            sanitizeDocstring =
+              Text.unlines . map (Text.dropWhile (`elem` "* ")) . Text.lines
+
+idStyle :: IdentifierStyle ThriftParser
+idStyle = (emptyIdents :: IdentifierStyle ThriftParser)
+    { _styleStart = letter <|> char '_'
+    , _styleLetter = alphaNum <|> oneOf "_."
+    }
+
+reserved :: Text -> ThriftParser ()
+reserved = reserveText idStyle
+
+program :: ThriftParser (T.Program T.Docstring)
+program = whiteSpace >> T.Program <$> many header <*> many definition
+
+literal :: ThriftParser Text
+literal = stringLiteral <|> stringLiteral'
+
+identifier :: ThriftParser Text
+identifier = ident idStyle
+
+header :: ThriftParser T.Header
+header = choice [
+    reserved "include" >> T.Include <$> literal
+  , reserved "namespace" >>
+      T.Namespace <$> (text "*" <|> identifier) <*> identifier
+  , reserved "cpp_namespace" >> T.Namespace "cpp" <$> identifier
+  , reserved "php_namespace" >> T.Namespace "php" <$> identifier
+  , reserved "py_module" >> T.Namespace "py" <$> identifier
+  , reserved "perl_package" >> T.Namespace "perl" <$> identifier
+  , reserved "ruby_namespace" >> T.Namespace "rb" <$> identifier
+  , reserved "java_package" >> T.Namespace "java" <$> identifier
+  , reserved "cocoa_package" >> T.Namespace "cocoa" <$> identifier
+  , reserved "csharp_namespace" >> T.Namespace "csharp" <$> identifier
+  ]
+
+docstring :: ThriftParser (T.Docstring -> a) -> ThriftParser a
+docstring p = lastDocstring >>= \s -> p <*> pure s
+
+definition :: ThriftParser (T.Definition T.Docstring)
+definition = choice [constant, typeDefinition, service]
+
+typeDefinition :: ThriftParser (T.Definition T.Docstring)
+typeDefinition =
+  T.TypeDefinition
+    <$> choice [typedef, enum, senum, struct, union, exception]
+    <*> typeAnnotations
+
+typedef :: ThriftParser (T.Type T.Docstring)
+typedef = reserved "typedef" >>
+    docstring (T.Typedef <$> fieldType <*> identifier)
+
+enum :: ThriftParser (T.Type T.Docstring)
+enum = reserved "enum" >>
+    docstring (T.Enum <$> identifier <*> braces (many enumDef))
+
+struct :: ThriftParser (T.Type T.Docstring)
+struct = reserved "struct" >>
+    docstring (T.Struct <$> identifier <*> braces (many field))
+
+union :: ThriftParser (T.Type T.Docstring)
+union = reserved "union" >>
+    docstring (T.Union <$> identifier <*> braces (many field))
+
+exception :: ThriftParser (T.Type T.Docstring)
+exception = reserved "exception" >>
+     docstring (T.Exception <$> identifier <*> braces (many field))
+
+fieldRequiredness :: ThriftParser T.FieldRequiredness
+fieldRequiredness = choice [
+    reserved "required" *> pure T.Required
+  , reserved "optional" *> pure T.Optional
+  ]
+
+field :: ThriftParser (T.Field T.Docstring)
+field = docstring $
+  T.Field
+    <$> optional (integer <* symbolic ':')
+    <*> optional fieldRequiredness
+    <*> fieldType
+    <*> identifier
+    <*> optional (equals *> constantValue)
+    <*> typeAnnotations
+    <*  optionalSep
+
+equals :: ThriftParser ()
+equals = void $ symbolic '='
+
+enumDef :: ThriftParser (T.EnumDef T.Docstring)
+enumDef = docstring $
+  T.EnumDef
+    <$> identifier
+    <*> optional (equals *> integer)
+    <*> typeAnnotations
+    <*  optionalSep
+
+senum :: ThriftParser (T.Type T.Docstring)
+senum = reserved "senum" >> docstring
+    (T.Senum <$> identifier <*> braces (many (literal <* optionalSep)))
+
+constant :: ThriftParser (T.Definition T.Docstring)
+constant = do
+  reserved "const"
+  docstring $
+    T.ConstDefinition
+        <$> fieldType
+        <*> (identifier <* equals)
+        <*> constantValue
+        <*  optionalSep
+
+constantValue :: ThriftParser T.ConstValue
+constantValue = choice [
+    either T.ConstInt T.ConstFloat <$> integerOrDouble
+  , T.ConstLiteral <$> literal
+  , T.ConstIdentifier <$> identifier
+  , T.ConstList <$> constList
+  , T.ConstMap <$> constMap
+  ]
+
+constList :: ThriftParser [T.ConstValue]
+constList = brackets $ commaSep (constantValue <* optionalSep)
+
+constMap :: ThriftParser [(T.ConstValue, T.ConstValue)]
+constMap = braces $ commaSep constantValuePair
+
+constantValuePair :: ThriftParser (T.ConstValue, T.ConstValue)
+constantValuePair =
+    (,) <$> (constantValue <* colon)
+        <*> (constantValue <* optionalSep)
+
+fieldType :: ThriftParser T.FieldType
+fieldType = choice [
+    baseType
+  , containerType
+  , T.DefinedType <$> identifier
+  ]
+
+baseType :: ThriftParser T.FieldType
+baseType =
+    choice [reserved s *> (v <$> typeAnnotations) | (s, v) <- bases]
+  where
+    bases = [
+        ("string", T.StringType)
+      , ("binary", T.BinaryType)
+      , ("slist", T.SListType)
+      , ("bool", T.BoolType)
+      , ("byte", T.ByteType)
+      , ("i16", T.I16Type)
+      , ("i32", T.I32Type)
+      , ("i64", T.I64Type)
+      , ("double", T.DoubleType)
+      ]
+
+containerType :: ThriftParser T.FieldType
+containerType =
+    choice [mapType, setType, listType] <*> typeAnnotations
+  where
+    mapType = reserved "map" >>
+        angles (T.MapType <$> (fieldType <* comma) <*> fieldType)
+    setType = reserved "set" >> angles (T.SetType <$> fieldType)
+    listType = reserved "list" >> angles (T.ListType <$> fieldType)
+
+service :: ThriftParser (T.Definition T.Docstring)
+service = do
+  reserved "service"
+  docstring $
+    T.ServiceDefinition
+        <$> identifier
+        <*> optional (reserved "extends" *> identifier)
+        <*> braces (many function)
+        <*> typeAnnotations
+
+function :: ThriftParser (T.Function T.Docstring)
+function = docstring $
+    T.Function
+        <$> ((reserved "oneway" *> pure True) <|> pure False)
+        <*> ((reserved "void" *> pure Nothing) <|> Just <$> fieldType)
+        <*> identifier
+        <*> parens (many field)
+        <*> optional (reserved "throws" *> parens (many field))
+        <*> typeAnnotations
+        <*  optionalSep
+
+typeAnnotations :: ThriftParser [T.TypeAnnotation]
+typeAnnotations = parens (many typeAnnotation) <|> pure []
+
+typeAnnotation :: ThriftParser T.TypeAnnotation
+typeAnnotation =
+    T.TypeAnnotation
+        <$> identifier
+        <*> (equals *> literal <* optionalSep)
+
+optionalSep :: ThriftParser ()
+optionalSep = void $ optional (comma <|> semi)
diff --git a/Language/Thrift/Types.hs b/Language/Thrift/Types.hs
new file mode 100644
--- /dev/null
+++ b/Language/Thrift/Types.hs
@@ -0,0 +1,154 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric      #-}
+module Language.Thrift.Types
+    ( Program(..)
+    , Header(..)
+    , Definition(..)
+    , Type(..)
+    , FieldRequiredness(..)
+    , Field(..)
+    , EnumDef(..)
+    , ConstValue(..)
+    , FieldType(..)
+    , Function(..)
+    , TypeAnnotation(..)
+    , Docstring
+    ) where
+
+import Data.Data    (Data, Typeable)
+import Data.Text    (Text)
+import GHC.Generics (Generic)
+
+data Program srcAnnot = Program
+    { programHeaders     :: [Header]
+    , programDefinitions :: [Definition srcAnnot]
+    }
+    deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+data Header
+    = Include    { includePath :: Text }
+    | Namespace  { namespaceLanguage, namespaceName :: Text }
+  deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+data Definition srcAnnot
+    = ConstDefinition
+        { constType     :: FieldType
+        , constName     :: Text
+        , constValue    :: ConstValue
+        , constSrcAnnot :: srcAnnot
+        }
+    | TypeDefinition
+        { typeDefinition  :: Type srcAnnot
+        , typeAnnotations :: [TypeAnnotation]
+        }
+    | ServiceDefinition
+        { serviceName        :: Text
+        , serviceExtends     :: Maybe Text
+        , serviceFunctions   :: [Function srcAnnot]
+        , serviceAnnotations :: [TypeAnnotation]
+        , serviceSrcAnnot    :: srcAnnot
+        }
+  deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+data Type srcAnnot
+    = Typedef
+        { typedefType     :: FieldType
+        , typedefName     :: Text
+        , typedefSrcAnnot :: srcAnnot
+        }
+    | Enum
+        { enumName     :: Text
+        , enumValues   :: [EnumDef srcAnnot]
+        , enumSrcAnnot :: srcAnnot
+        }
+    | Struct
+        { structName     :: Text
+        , structFields   :: [Field srcAnnot]
+        , structSrcAnnot :: srcAnnot
+        }
+    | Union
+        { unionName     :: Text
+        , unionFields   :: [Field srcAnnot]
+        , unionSrcAnnot :: srcAnnot
+        }
+    | Exception
+        { exceptionName     :: Text
+        , exceptionFields   :: [Field srcAnnot]
+        , exceptionSrcAnnot :: srcAnnot
+        }
+    | Senum
+        { senumName     :: Text
+        , senumValues   :: [Text]
+        , senumSrcAnnot :: srcAnnot
+        }
+  deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+data FieldRequiredness = Required | Optional
+  deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+data Field srcAnnot = Field
+    { fieldIdentifier   :: Maybe Integer
+    , fieldRequiredNess :: Maybe FieldRequiredness
+    , fieldType         :: FieldType
+    , fieldName         :: Text
+    , fieldDefault      :: Maybe ConstValue
+    , fieldAnnotations  :: [TypeAnnotation]
+    , fieldSrcAnnot     :: srcAnnot
+    }
+  deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+data EnumDef srcAnnot = EnumDef
+    { enumDefName        :: Text
+    , enumDefValue       :: Maybe Integer
+    , enumDefAnnotations :: [TypeAnnotation]
+    , enumDefSrcAnnot    :: srcAnnot
+    }
+  deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+data ConstValue
+    = ConstInt Integer
+    | ConstFloat Double
+    | ConstLiteral Text
+    | ConstIdentifier Text
+    | ConstList [ConstValue]
+    | ConstMap [(ConstValue, ConstValue)]
+  deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+data FieldType
+    = DefinedType Text
+
+    -- Base types
+    | StringType [TypeAnnotation]
+    | BinaryType [TypeAnnotation]
+    | SListType [TypeAnnotation]
+    | BoolType [TypeAnnotation]
+    | ByteType [TypeAnnotation]
+    | I16Type [TypeAnnotation]
+    | I32Type [TypeAnnotation]
+    | I64Type [TypeAnnotation]
+    | DoubleType [TypeAnnotation]
+
+    -- Container types
+    | MapType FieldType FieldType [TypeAnnotation]
+    | SetType FieldType [TypeAnnotation]
+    | ListType FieldType [TypeAnnotation]
+  deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+data Function srcAnnot = Function
+    { functionOneWay      :: Bool
+    , functionReturnType  :: Maybe FieldType
+    , functionName        :: Text
+    , functionParameters  :: [Field srcAnnot]
+    , functionExceptions  :: Maybe [Field srcAnnot]
+    , functionAnnotations :: [TypeAnnotation]
+    , functionSrcAnnot    :: srcAnnot
+    }
+  deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+data TypeAnnotation = TypeAnnotation
+    { typeAnnotationName  :: Text
+    , typeAnnotationValue :: Text
+    }
+  deriving (Show, Ord, Eq, Data, Typeable, Generic)
+
+type Docstring = Maybe Text
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+`language-thrift` provides a parser for the [Thrift IDL format][]. The parser
+is written using [`trifecta`][] to provide helpful error messages when parsing
+fails. The parser keeps track of Javadoc-style comments (`/** ... */`) and
+associates them with the type, service, function, or field above which they
+were added.
+
+Documentation is sparse; most of the types are self-explanatory.
+Haddock-generated docs are available on [Hackage][] and [here][].
+
+  [Thrift IDL format]: http://thrift.apache.org/docs/idl
+  [`trifecta`]: http://hackage.haskell.org/package/trifecta
+  [Hackage]: http://hackage.haskell.org/package/language-thrift
+  [here]: http://abhinavg.net/language-thrift/
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/language-thrift.cabal b/language-thrift.cabal
new file mode 100644
--- /dev/null
+++ b/language-thrift.cabal
@@ -0,0 +1,32 @@
+name          : language-thrift
+version       : 0.1.0.0
+synopsis      : Parser for the Thrift IDL format.
+homepage      : https://github.com/abhinav/language-thrift
+license       : BSD3
+license-file  : LICENSE
+author        : Abhinav Gupta
+maintainer    : Abhinav Gupta <mail@abhinavg.net>
+category      : Language
+build-type    : Simple
+cabal-version : >=1.10
+description   :
+    This package provides a parser for the
+    <http://thrift.apache.org/docs/idl Thrift IDL format>.
+extra-source-files:
+    README.md
+    CHANGES.md
+--  examples/*.hs   TODO: Add examples
+
+library
+  exposed-modules  : Language.Thrift.Parser
+                   , Language.Thrift.Types
+  build-depends    : base     >= 4.7  && < 4.8
+                   , mtl
+                   , text     >= 1.2
+                   , parsers  >= 0.12 && < 0.13
+                   , trifecta >= 1.5  && < 1.6
+  default-language : Haskell2010
+
+source-repository head
+  type: git
+  location: git://github.com/abhinav/language-thrift.git
