diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+#!/usr/bin/env runhaskell
+import Distribution.Simple
+main :: IO ()
+main = defaultMain
diff --git a/compiler-warnings.cabal b/compiler-warnings.cabal
new file mode 100644
--- /dev/null
+++ b/compiler-warnings.cabal
@@ -0,0 +1,52 @@
+-- This file has been generated from package.yaml by hpack version 0.14.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           compiler-warnings
+version:        0.1.0
+synopsis:       Parser for common compiler warning formats
+category:       Development
+homepage:       https://github.com/yi-editor/compiler-warnings#readme
+bug-reports:    https://github.com/yi-editor/compiler-warnings/issues
+maintainer:     Yi developers <yi-devel@googlegroups.com>
+license:        BSD2
+build-type:     Simple
+cabal-version:  >= 1.10
+
+source-repository head
+  type: git
+  location: https://github.com/yi-editor/compiler-warnings
+
+library
+  hs-source-dirs:
+      src
+  ghc-options: -Wall -ferror-spans
+  build-depends:
+      base >= 4.8 && < 5
+    , binary >= 0.7.5
+    , text >= 1.2
+    , parsec >= 3.1
+  exposed-modules:
+      Text.Warning
+  other-modules:
+      Paths_compiler_warnings
+  default-language: Haskell2010
+
+test-suite tasty
+  type: exitcode-stdio-1.0
+  main-is: Main.hs
+  hs-source-dirs:
+      test
+  ghc-options: -Wall -ferror-spans
+  build-depends:
+      base >= 4.8 && < 5
+    , binary >= 0.7.5
+    , text >= 1.2
+    , parsec >= 3.1
+    , tasty
+    , tasty-th
+    , tasty-hunit
+    , tasty-quickcheck
+    , text
+    , compiler-warnings
+  default-language: Haskell2010
diff --git a/src/Text/Warning.hs b/src/Text/Warning.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Warning.hs
@@ -0,0 +1,146 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Text.Warning
+    ( Warning (..)
+    , parseWarnings
+    ) where
+
+import Data.Binary
+import Data.Char
+import Data.Monoid
+import GHC.Generics
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+
+import qualified Text.ParserCombinators.Parsec as P
+
+type Parser a = P.GenParser Char () a
+
+data Line
+    = StarterLine Warning
+    | WarningTextLine T.Text
+    | GarbageLine T.Text
+    | EmptyLine
+    deriving Show
+
+data Warning = Warning
+    { cmFilePath :: !FilePath
+    , cmStartLine :: !Int
+    , cmStartColumn :: !Int
+    , cmEndLine :: !Int
+    , cmEndColumn :: !Int
+    , cmMessage :: !T.Text
+    } deriving (Eq, Generic, Show)
+
+parseWarnings :: String -> [Warning]
+parseWarnings input =
+    go Nothing [] classifiedLines
+    where
+        go Nothing ws [] = reverse ws
+        go (Just w) ws [] = reverse (w : ws)
+        go (Just w) ws (WarningTextLine t : rest) =
+            go (Just (appendMsg t w)) ws rest
+        go (Just w) ws rest = go Nothing (w : ws) rest
+        go Nothing ws (StarterLine w : rest) = go (Just w) ws rest
+        go _ ws (_ : rest) = go Nothing ws rest
+        classifiedLines = map parseLine (lines input)
+        parseLine "" = EmptyLine
+        parseLine s@(c : _) | isSpace c = WarningTextLine (T.pack s)
+        parseLine s = either
+            (const (GarbageLine (T.pack s)))
+            StarterLine
+            (P.parse (P.choice errorParsers) "" s)
+        errorParsers =
+            [ P.try multilineSpan
+            , P.try onelineSpan
+            , P.try point
+            , P.try pointTypeScript
+            , line
+            ]
+        appendMsg :: T.Text -> Warning -> Warning
+        appendMsg msg2 (Warning fn l1 c1 l2 c2 msg) =
+            Warning fn l1 c1 l2 c2 (msg <> "\n" <> msg2)
+
+point :: P.GenParser Char () Warning
+point = do
+    fn <- filename
+    _ <- P.char ':'
+    l <- number
+    _ <- P.char ':'
+    c <- number
+    _ <- P.char ':'
+    msg <- message
+    return (Warning fn l c l (c + 1) msg)
+
+pointTypeScript :: P.GenParser Char () Warning
+pointTypeScript = do
+    fn <- filename
+    _ <- P.char '('
+    l <- number
+    _ <- P.char ','
+    c <- number
+    _ <- P.string "):"
+    msg <- message
+    return (Warning fn l c l (c + 1) msg)
+
+line :: P.GenParser Char () Warning
+line = do
+    fn <- filename
+    _ <- P.char ':'
+    l <- number
+    _ <- P.char ':'
+    msg <- message
+    return (Warning fn l 1 l (-1) msg)
+
+onelineSpan :: P.GenParser Char () Warning
+onelineSpan = do
+    fn <- filename
+    _ <- P.char ':'
+    l <- number
+    _ <- P.char ':'
+    c1 <- number
+    _ <- P.char '-'
+    c2 <- number
+    _ <- P.char ':'
+    msg <- message
+    return (Warning fn l c1 l c2 msg)
+
+multilineSpan :: P.GenParser Char () Warning
+multilineSpan = do
+    fn <- filename
+    _ <- P.char ':'
+    (l1, c1) <- lineCol
+    _ <- P.char '-'
+    (l2, c2) <- lineCol
+    _ <- P.char ':'
+    msg <- message
+    return (Warning fn l1 c1 l2 c2 msg)
+
+filename :: P.GenParser Char () FilePath
+filename = (:) <$> P.noneOf " ():\t" <*> P.many1 (P.noneOf "():\t")
+
+message :: Parser T.Text
+message = fmap T.pack (P.many P.anyChar)
+
+number :: P.GenParser Char () Int
+number = fmap read (P.many1 P.digit)
+
+lineCol :: P.GenParser Char () (Int, Int)
+lineCol = do
+    _ <- P.char '('
+    l <- number
+    _ <- P.char ','
+    c <- number
+    _ <- P.char ')'
+    return (l, c)
+
+instance Binary Warning where
+    put (Warning fn l1 c1 l2 c2 msg) = do
+        put (TE.encodeUtf8 msg)
+        put fn
+        put l1 >> put c1
+        put l2 >> put c2
+    get = do
+        msg <- fmap TE.decodeUtf8 get
+        Warning <$> get <*> get <*> get <*> get <*> get <*> pure msg
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,173 @@
+#!/usr/bin/env runhaskell
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+import qualified Data.Text as T
+import Data.Binary (encode, decode)
+import Test.Tasty.TH
+import Test.Tasty.HUnit
+import Test.Tasty.QuickCheck
+import Text.Warning
+
+main :: IO ()
+main = $defaultMainGenerator
+
+case_0 :: Assertion
+case_0 =
+    let input = unlines
+            [ "foo:3:5: Fail"
+            ]
+        warnings =
+            [ Warning "foo" 3 5 3 6 " Fail"
+            ]
+    in do parseWarnings input @?= warnings
+
+case_ghc_1 :: Assertion
+case_ghc_1 =
+    let input = unlines
+            [ "modules/Warning.hs:29:5: Not in scope: ‘eit'"
+            ]
+        warnings =
+            [ Warning "modules/Warning.hs" 29 5 29 6 " Not in scope: ‘eit'"
+            ]
+    in do parseWarnings input @?= warnings
+
+case_ghc_1_with_newlines :: Assertion
+case_ghc_1_with_newlines =
+    let input = unlines
+            [ ""
+            , "modules/Warning.hs:29:5: Not in scope: ‘eit'"
+            , ""
+            ]
+        warnings =
+            [ Warning "modules/Warning.hs" 29 5 29 6 " Not in scope: ‘eit'"
+            ]
+    in do parseWarnings input @?= warnings
+
+case_ghc_2 :: Assertion
+case_ghc_2 =
+    let input = unlines
+            [ "modules/Warning.hs:29:5: Not in scope: ‘eit'"
+            -- , ""
+            , "modules/Warning.hs:29:9: Not in scope: ‘her'"
+            ]
+        warnings =
+            [ Warning "modules/Warning.hs" 29 5 29 6 " Not in scope: ‘eit'"
+            , Warning "modules/Warning.hs" 29 9 29 10 " Not in scope: ‘her'"
+            ]
+    in do parseWarnings input @?= warnings
+
+case_ghc_2_with_newlines :: Assertion
+case_ghc_2_with_newlines =
+    let input = unlines
+            [ "modules/Warning.hs:29:5: Not in scope: ‘eit'"
+            , ""
+            , "modules/Warning.hs:29:9: Not in scope: ‘her'"
+            ]
+        warnings =
+            [ Warning "modules/Warning.hs" 29 5 29 6 " Not in scope: ‘eit'"
+            , Warning "modules/Warning.hs" 29 9 29 10 " Not in scope: ‘her'"
+            ]
+    in do parseWarnings input @?= warnings
+
+case_ghc_2_perhaps_you_meant :: Assertion
+case_ghc_2_perhaps_you_meant =
+    let input = unlines
+            [ "modules/Warning.hs:30:5:"
+            , "    Not in scope: ‘eit'"
+            , "    Perhaps you meant ‘pi’ (imported from Prelude)"
+            , ""
+            , "modules/Warning.hs:29:9: Not in scope: ‘her'"
+            ]
+        warnings =
+            [ Warning "modules/Warning.hs" 30 5 30 6
+                (T.intercalate "\n"
+                    [ ""
+                    , "    Not in scope: ‘eit'"
+                    , "    Perhaps you meant ‘pi’ (imported from Prelude)"
+                    ])
+            , Warning "modules/Warning.hs" 29 9 29 10 " Not in scope: ‘her'"
+            ]
+    in do parseWarnings input @?= warnings
+
+case_make :: Assertion
+case_make =
+    let input = unlines
+            [ "ghc -imodules -ferror-spans -Wall -Werror -c modules/Warning.hs"
+            , "modules/Warning.hs:30:5-6:"
+            , "    Not in scope: 'ei'"
+            , "    Perhaps you meant 'pi' (imported from Prelude)"
+            , "     modules/Warning.hs:30:8-11: Not in scope: 'ther'"
+            , "Makefile:6: recipe for target 'modules/Warning.o' failed"
+            , "make: *** [modules/Warning.o] Error 1"
+            ]
+        warnings =
+            [ Warning "modules/Warning.hs" 30 5 30 6
+                (T.intercalate "\n"
+                    [ ""
+                    , "    Not in scope: 'ei'"
+                    , "    Perhaps you meant 'pi' (imported from Prelude)"
+                    , "     modules/Warning.hs:30:8-11: Not in scope: 'ther'"
+                    ])
+            , Warning "Makefile" 6 1 6 (-1) " recipe for target 'modules/Warning.o' failed"
+            ]
+    in do parseWarnings input @?= warnings
+
+case_ghc_multiline :: Assertion
+case_ghc_multiline =
+    let input = unlines
+            [ "[1 of 1] Compiling Main             ( test/Main.hs, .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/tasty/tasty-tmp/Main.o )"
+            , ""
+            , "test/Main.hs:(148,1)-(149,120): warning: [-Worphans]"
+            , "    Orphan instance: instance Arbitrary Warning"
+            , "    To avoid this"
+            , "    move the instance declaration to the module of the class or of the type, or"
+            , "    wrap the type with a newtype and declare the instance on the new type."
+            , "Linking .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/tasty/tasty .."
+            ]
+        warnings =
+            [ Warning "test/Main.hs" 148 1 149 120
+                (T.intercalate "\n"
+                    [ " warning: [-Worphans]"
+                    , "    Orphan instance: instance Arbitrary Warning"
+                    , "    To avoid this"
+                    , "    move the instance declaration to the module of the class or of the type, or"
+                    , "    wrap the type with a newtype and declare the instance on the new type."
+                    ])
+            ]
+    in do parseWarnings input @?= warnings
+
+case_typescript :: Assertion
+case_typescript =
+    let input = "lol.ts(13,9): error TS2304: Cannot find name 'lol'."
+        warnings = [Warning "lol.ts" 13 9 13 10 " error TS2304: Cannot find name 'lol'."]
+    in do parseWarnings input @?= warnings
+
+case_colons_in_error_message :: Assertion
+case_colons_in_error_message =
+    let input = unlines
+            [ "modules/Snippet.hs:75:34-37:"
+            , "     Couldn't match expected type ‘WriterT"
+            , "\t\t\t\t\tR.YiString"
+            , "                                     transformers-0.3.0.0:Data.Functor.Identity.Identity"
+            , "                                     a’"
+            , "                 with actual type ‘a’"
+            ]
+        warnings =
+            [ Warning "modules/Snippet.hs" 75 34 75 37
+                (T.intercalate "\n"
+                    [ ""
+                    , "     Couldn't match expected type ‘WriterT"
+                    , "\t\t\t\t\tR.YiString"
+                    , "                                     transformers-0.3.0.0:Data.Functor.Identity.Identity"
+                    , "                                     a’"
+                    , "                 with actual type ‘a’"
+                    ])
+            ]
+    in do parseWarnings input @?= warnings
+
+prop_binary_roundtrip :: Warning -> Bool
+prop_binary_roundtrip w = decode (encode w) == w
+
+instance Arbitrary Warning where
+    arbitrary = Warning <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> (T.pack <$> arbitrary)
