diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Changelog for cmake-syntax
+
+## v0.1.0.0
+
+Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Georg Rudoy (c) 2019
+
+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 Georg Rudoy 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# cmake-syntax
+
+[![Build Status][travis-badge]][travis]
+
+A simple parser for the CMake files.
+
+[travis]:        <https://travis-ci.org/0xd34df00d/can-i-haz>
+[travis-badge]:  <https://travis-ci.org/0xd34df00d/can-i-haz.svg?branch=master>
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/cmake-syntax.cabal b/cmake-syntax.cabal
new file mode 100644
--- /dev/null
+++ b/cmake-syntax.cabal
@@ -0,0 +1,61 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.2.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 26bcc743c2398b409fec017473b905cf77d41b0b8ec9abe5cf6b0ae91e461e2c
+
+name:           cmake-syntax
+version:        0.1.0.0
+synopsis:       Parser for the CMake syntax (CMakeLists.txt and .cmake files)
+description:    Please see the README on GitHub at <https://github.com/0xd34df00d/cmake-syntax#readme>
+category:       Language
+homepage:       https://github.com/0xd34df00d/cmake-syntax#readme
+bug-reports:    https://github.com/0xd34df00d/cmake-syntax/issues
+author:         Georg Rudoy
+maintainer:     0xd34df00d@gmail.com
+copyright:      2019 Georg Rudoy
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/0xd34df00d/cmake-syntax
+
+library
+  exposed-modules:
+      Language.CMake.AST
+      Language.CMake.Parser
+  other-modules:
+      Paths_cmake_syntax
+  hs-source-dirs:
+      src
+  ghc-options: -Wall
+  build-depends:
+      base >=4.7 && <5
+    , bytestring
+    , raw-strings-qq
+    , trifecta
+  default-language: Haskell2010
+
+test-suite cmake-syntax-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_cmake_syntax
+  hs-source-dirs:
+      test
+  ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , bytestring
+    , cmake-syntax
+    , hspec
+    , raw-strings-qq
+    , trifecta
+  default-language: Haskell2010
diff --git a/src/Language/CMake/AST.hs b/src/Language/CMake/AST.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/CMake/AST.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Language.CMake.AST where
+
+import qualified Data.ByteString.Char8 as BS
+import Data.String
+
+newtype File = File { fileElements :: [FileElement] } deriving (Eq, Show)
+
+data FileElement
+  = CommandElement { commandInvocation :: CommandInvocation }
+  | NonCommandElement
+  deriving (Eq, Show)
+
+data CommandInvocation = CommandInvocation
+  { commandId :: BS.ByteString
+  , commandArgs :: [Argument]
+  }
+  deriving (Eq, Show)
+
+data LiteralElem
+  = LiteralString { literalString :: BS.ByteString }
+  | VariableReference { variableName :: BS.ByteString }
+  deriving (Eq, Show)
+
+newtype Literal = Literal { literalParts :: [LiteralElem] }
+  deriving (Eq, Show, Semigroup, Monoid)
+
+instance IsString Literal where
+  fromString = Literal . pure . LiteralString . BS.pack
+
+newtype Argument = Argument { argumentLiteral :: Literal }
+  deriving (Eq, Show, Semigroup, Monoid)
+
+instance IsString Argument where
+  fromString = Argument . fromString
diff --git a/src/Language/CMake/Parser.hs b/src/Language/CMake/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/CMake/Parser.hs
@@ -0,0 +1,83 @@
+{-# LANGUAGE RecordWildCards, QuasiQuotes #-}
+
+module Language.CMake.Parser
+( fileParser
+) where
+
+import qualified Data.ByteString.Char8 as BS
+import Control.Applicative
+import Data.Char
+import Data.Functor
+import Data.Maybe
+import Data.String
+import Text.RawString.QQ
+import Text.Trifecta
+
+import Language.CMake.AST hiding(commandInvocation)
+
+fileParser :: Parser File
+fileParser = File <$> many fileElement <* eof
+
+fileElement :: Parser FileElement
+fileElement = try (many (try bracketComment <|> spaceNonLF) *> lineEnding $> NonCommandElement)
+          <|> (CommandElement <$> commandInvocation) <* many spaceNonLF <* lineEnding
+  where spaceNonLF = void $ satisfy $ \c -> isSpace c && c /= '\n'
+
+lineEnding :: Parser ()
+lineEnding = skipOptional lineComment >> void newline
+
+commandInvocation :: Parser CommandInvocation
+commandInvocation = do
+  spaces
+  commandId <- BS.pack <$> some ('A' ~~ 'Z' <|> 'a' ~~ 'z' <|> char '_' <|> '0' ~~ '9')
+  spaces
+  commandArgs <- between (char '(') (char ')') arguments
+  pure CommandInvocation { .. }
+  where (~~) = satisfyRange
+
+arguments :: Parser [Argument]
+arguments = do
+  arg <- maybeToList <$> optional argument
+  rest <- concat <$> many separatedArguments
+  pure $ arg <> rest
+
+separatedArguments :: Parser [Argument]
+separatedArguments = maybeToList <$> try (some separation *> optional argument)
+                 <|> many separation *> between (char '(') (char ')') arguments
+
+separation :: Parser ()
+separation = void space <|> lineEnding
+
+argument :: Parser Argument
+argument = bracketArgument <|> quotedArgument <|> unquotedArgument
+
+bracketArgument :: Parser Argument
+bracketArgument = do
+  opening <- char '[' *> many (char '=') <* char '['
+  let closing = string $ "]" <> opening <> "]"
+  fromString <$> anyChar `manyTill` try closing
+
+quotedArgument :: Parser Argument
+quotedArgument = fromString . catMaybes <$> many quotedElement `surroundedBy` char '"'
+
+quotedElement :: Parser (Maybe Char)
+quotedElement = Just <$> (noneOf [r|\"|] <|> try escapeSequence)
+            <|> (char '\\' *> newline $> Nothing)
+
+unquotedArgument :: Parser Argument
+unquotedArgument = fromString <$> some (satisfy unElem <|> escapeSequence)
+  where unElem c = c `notElem` [r|()#\"|] && not (isSpace c)
+
+escapeSequence :: Parser Char
+escapeSequence = char '\\' >> (self <|> encoded)
+  where
+    self = oneOf [r|()#" \$@^;|]
+    encoded = char 't' $> '\t'
+          <|> char 'r' $> '\r'
+          <|> char 'n' $> '\n'
+
+bracketComment :: Parser ()
+bracketComment = char '#' >> void bracketArgument
+
+lineComment :: Parser ()
+lineComment = char '#' >> void (many $ notChar '\n')
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,160 @@
+{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+import Data.Char
+import Test.Hspec
+import Text.RawString.QQ
+import Text.Trifecta.Parser
+import Text.Trifecta.Result
+
+import Language.CMake.AST
+import Language.CMake.Parser
+
+instance Eq a => Eq (Result a) where
+  Success a == Success b = a == b
+  Failure _ == Failure _ = True
+  _ == _ = False
+
+(~~>) :: HasCallStack => String -> File -> Expectation
+(~~>) str res = parseString fileParser mempty (str <> "\n") `shouldBe` Success res
+
+dropLeading :: String -> String
+dropLeading str = init $ unlines $ l : (drop numSpaces <$> init ls)
+  where
+    (l:ls) = lines str
+    numSpaces = length $ takeWhile isSpace $ last ls
+
+main :: IO ()
+main = hspec $ do
+  describe "Parsing simple commands" $ do
+    it "without args"
+        $ [r|add_executable()|]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" [] ]
+    it "with leading spaces"
+        $ [r|    add_executable()|]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" [] ]
+    it "empty line"
+        $ [r||]
+      ~~> File [ NonCommandElement ]
+    it "empty line with spaces"
+        $ "   \t  "
+      ~~> File [ NonCommandElement ]
+  describe "Parsing a sequence of commands" $ do
+    it "without args"
+        $ [r|add_executable()
+             add_library()|]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" []
+               , CommandElement $ CommandInvocation "add_library" []
+               ]
+    it "with empty lines"
+        $ [r|add_executable()
+
+             add_library()|]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" []
+               , NonCommandElement
+               , CommandElement $ CommandInvocation "add_library" []
+               ]
+  describe "Parsing comments" $ do
+    it "parses single whole-line comment"
+        $ [r|# this is a comment|]
+      ~~> File [NonCommandElement]
+    it "parses multiple whole-line comments"
+        $ [r|# this is a comment
+             # and this is a continuation|]
+      ~~> File [NonCommandElement, NonCommandElement]
+    it "parses end-of-line comments"
+        $ [r|add_library() # this is a comment|]
+      ~~> File [CommandElement $ CommandInvocation "add_library" []]
+  describe "Bracket comments" $ do
+    it "parses simplest empty bracket comments"
+        $ [r|#[[]]|]
+      ~~> File [NonCommandElement]
+    it "parses zero-eq bracket comments with text"
+        $ [r|#[[ foobar ]]|]
+      ~~> File [NonCommandElement]
+    it "parses non-zero-eq bracket comments with text"
+        $ [r|#[==[ foobar ]==]|]
+      ~~> File [NonCommandElement]
+    it "parses non-zero-eq bracket comments with nested eqs (less)"
+        $ [r|#[==[ ]=] ]==]|]
+      ~~> File [NonCommandElement]
+    it "parses non-zero-eq bracket comments with nested eqs (more)"
+        $ [r|#[==[ ]===] ]==]|]
+      ~~> File [NonCommandElement]
+    it "parses multilinenon-zero-eq bracket comments"
+        $ [r|#[==[ foobar
+             ]=]
+             fdsafsdahljkrew hjkfdshafkjlashjk
+             ]===]
+             ]==]|]
+      ~~> File [NonCommandElement]
+  describe "Parsing simple command arguments" $ do
+    it "parses command with one arg"
+        $ [r|add_executable(foo)|]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo"] ]
+    it "parses command with several simple args"
+        $ [r|add_executable(foo bar baz)|]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo", "bar", "baz"] ]
+  describe "Parsing bracket command arguments" $ do
+    it "parses command with bracket arguments"
+        $ dropLeading
+          [r|add_executable([==[
+            This is a bracket argument
+            ]==])
+            |]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["\nThis is a bracket argument\n"] ]
+    it "parses command with multiline bracket arguments"
+        $ dropLeading
+          [r|add_executable([==[
+            This is a
+            bracket
+            argument
+            ]==])
+            |]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["\nThis is a\nbracket\nargument\n"] ]
+    it "does not escape in bracket arguments"
+        $ dropLeading
+          [r|add_executable([==[
+            This is \t \; \n a \- bracket argument \meh
+            ]==])
+            |]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["\nThis is \\t \\; \\n a \\- bracket argument \\meh\n"] ]
+    it "does not expand variables in bracket arguments"
+        $ dropLeading
+          [r|add_executable([==[
+            This is a ${bracket} argument
+            ]==])
+            |]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["\nThis is a ${bracket} argument\n"] ]
+  describe "Parsing quoted command arguments" $ do
+    it "parses command with one arg"
+        $ [r|add_executable("foo")|]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo"] ]
+    it "parses command with multple args"
+        $ [r|add_executable("foo" "bar")|]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo", "bar"] ]
+    it "does escaping"
+        $ [r|add_executable("foo \n \; bar")|]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo \n ; bar"] ]
+    it "parses command with multiline arg"
+        $ dropLeading
+          [r|add_executable("foo
+             bar
+             baz")
+             |]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo\nbar\nbaz"] ]
+    it "parses command with multiline arg and final line feed"
+        $ dropLeading
+          [r|add_executable("foo
+             bar
+             baz
+             ")
+             |]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo\nbar\nbaz\n"] ]
+    it "parses command with single-line arg spanning multiple lines"
+        $ dropLeading
+          [r|add_executable("foo \
+             bar \
+             baz")
+             |]
+      ~~> File [ CommandElement $ CommandInvocation "add_executable" ["foo bar baz"] ]
