diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# sdpx 
+# sdpx
 
 [![Build Status](https://travis-ci.org/phadej/spdx.svg?branch=master)](https://travis-ci.org/phadej/spdx)
 
diff --git a/dev/Main.hs b/dev/Main.hs
new file mode 100644
--- /dev/null
+++ b/dev/Main.hs
@@ -0,0 +1,55 @@
+module Main (main) where
+
+import Control.Applicative
+import Data.List
+import Data.Maybe
+import Data.Traversable
+import Text.XML.Light
+
+simplify :: Element -> [[[String]]]
+simplify el = 
+  let worksheets = filter isWorksheetElement $ childrenElements el
+      Just tables = traverse tableFromWorksheet worksheets
+  in map tableElements tables
+
+toElem :: Content -> Maybe Element
+toElem (Elem e) = Just e
+toElem _        = Nothing
+
+isElement :: String -> Element -> Bool
+isElement name = (name ==) . qName . elName
+
+isWorksheetElement :: Element -> Bool
+isWorksheetElement = isElement "Worksheet"
+
+childrenElements :: Element -> [Element]
+childrenElements = mapMaybe toElem . elContent
+
+tableFromWorksheet :: Element -> Maybe Element
+tableFromWorksheet = listToMaybe . filter (isElement "Table") . childrenElements
+
+textContent :: Content -> Maybe String
+textContent (Text t) = Just $ cdData t
+textContent _        = Nothing
+
+extractData :: Element -> String
+extractData el = maybe "" id $ do dataEl <- listToMaybe . filter (isElement "Data") . childrenElements $ el
+                                  textEl <- listToMaybe $ elContent dataEl
+                                  text <- textContent textEl
+                                  return text
+
+tableElements :: Element -> [[String]] 
+tableElements = map cells . filter (isElement "Row") . childrenElements
+  where cells = map extractData . filter (isElement "Cell") . childrenElements
+
+extractLicense :: [String] -> (String, String, Bool)
+extractLicense row = (row !! 1, row !! 0, (row !! 4) == "YES")
+
+main :: IO ()
+main = do
+  contents <- readFile "data/spdx_licenselist.xml"
+  let Just el = parseXMLDoc contents
+  let parsed = simplify el
+  let licenses = filter (not . null) $ parsed !! 0
+  f `mapM_` (sort $ map extractLicense licenses)
+  where f (l, n, osi) = putStrLn $ "  , (LicenseId " ++ show l ++ ", " ++ show n ++ ", " ++ show osi ++ ")"
diff --git a/spdx.cabal b/spdx.cabal
--- a/spdx.cabal
+++ b/spdx.cabal
@@ -1,5 +1,5 @@
 name:                spdx
-version:             0.0.1.0
+version:             0.1.0.0
 synopsis:            SPDX license expression language
 description:         Implementation of <http://spdx.org/sites/spdx/files/SPDX-2.0.pdf SPDX> related functionality.
 homepage:            https://github.com/phadej/spdx
@@ -8,19 +8,24 @@
 author:              Oleg Grenrus
 maintainer:          Oleg Grenrus <oleg.grenrus@iki.fi>
 copyright:           (c) 2015 Oleg Grenrus
-category:            Distribution
+category:            Data
 build-type:          Simple
 extra-source-files:  README.md
 cabal-version:       >=1.10
 
+flag developer
+  description:       Whether to build development tools
+  default:           False
+  manual:            True
+
 library
   default-language:    Haskell2010
-  exposed-modules:     Distribution.SPDX,
-                       Distribution.SPDX.LatticeSyntax
-  other-modules:       Distribution.SPDX.Licenses,
-                       Distribution.SPDX.Types,
-                       Distribution.SPDX.Parser,
-                       Distribution.SPDX.Ranges
+  exposed-modules:     Data.SPDX,
+                       Data.SPDX.LatticeSyntax
+  other-modules:       Data.SPDX.Licenses,
+                       Data.SPDX.Types,
+                       Data.SPDX.Parser,
+                       Data.SPDX.Ranges
   other-extensions:    CPP
                        DeriveFunctor,
                        GeneralizedNewtypeDeriving,
@@ -39,7 +44,16 @@
   default-language:    Haskell2010
   hs-source-dirs:      tests
   ghc-options:         -Wall
-  build-depends:       base              >=4.5  && <5,
+  build-depends:       base              >=4.5  && <4.9,
                        tasty             >=0.10 && <0.11,
                        tasty-quickcheck  >=0.8  && <0.9,
                        spdx
+
+executable spdx-data
+  if !flag(developer)
+    buildable:         False
+  main-is:             Main.hs
+  default-language:    Haskell2010
+  hs-source-dirs:      dev
+  build-depends:       base              >=4.5  && <4.9,
+                       xml               >=1.3  && <1.4
diff --git a/src/Data/SPDX.hs b/src/Data/SPDX.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/SPDX.hs
@@ -0,0 +1,70 @@
+-- |
+-- Module      : Data.SPDX
+-- Description : SPDX licenses and expression language
+-- Copyright   : (c) 2015 Oleg Grenrus
+-- License     : BSD3
+-- Maintainer  : Oleg Grenrus <oleg.grenrus@iki.fi>
+--
+module Data.SPDX (
+  -- * Types
+    LicenseId
+  , getLicenseId
+  , LicenseExceptionId
+  , getLicenseExceptionId
+  , LicenseRef(..)
+  , LicenseExpression(..)
+  -- * Data
+  , licenses
+  , licenseIdentifiers
+  , mkLicenseId
+  , isOsiApproved
+  , licenseExceptions
+  -- ** Ranges
+  , licenseRanges
+  , lookupLicenseRange
+  -- * Parsing
+  , parseExpression
+  , unsafeParseExpr
+  -- * Logic
+  , satisfies
+  ) where
+
+import Data.SPDX.Types
+import Data.SPDX.Ranges
+import Data.SPDX.Licenses
+import Data.SPDX.Parser
+import Data.SPDX.LatticeSyntax
+
+data Lic = Lic (Either LicenseRef LicenseId) (Maybe LicenseExceptionId)
+  deriving (Eq, Ord, Show, Read)
+
+exprToLSLic :: LicenseExpression -> LatticeSyntax Lic
+exprToLSLic (ELicense False l e) = LVar (Lic l e)
+exprToLSLic (ELicense True (Right l) e) = foldr1 LJoin $ map (\l' -> LVar $ Lic (Right l') e) $ lookupLicenseRange l
+-- We don't know anything about newer license references
+exprToLSLic (ELicense True (Left l) e) = LVar (Lic (Left l) e)
+exprToLSLic (EConjunction a b) = LMeet (exprToLSLic a) (exprToLSLic b)
+exprToLSLic (EDisjunction a b) = LJoin (exprToLSLic a) (exprToLSLic b)
+
+-- |
+--
+-- @⟦ satisfies a b ⟧ ≡ a ≥ b ≡ a ∧ b = b @
+--
+-- >>> unsafeParseExpr "GPL-3.0" `satisfies` unsafeParseExpr "ISC AND MIT"
+-- False
+--
+-- >>> unsafeParseExpr "Zlib" `satisfies` unsafeParseExpr "ISC AND MIT AND Zlib"
+-- True
+--
+-- >>> unsafeParseExpr "(MIT OR GPL-2.0)" `satisfies` unsafeParseExpr "(ISC AND MIT)"
+-- True
+--
+-- >>> unsafeParseExpr "(MIT AND GPL-2.0)" `satisfies` unsafeParseExpr "(MIT AND GPL-2.0)"
+-- True
+--
+-- >>> unsafeParseExpr "(MIT AND GPL-2.0)" `satisfies` unsafeParseExpr "(ISC AND GPL-2.0)"
+-- False
+satisfies :: LicenseExpression -- ^ package license
+          -> LicenseExpression -- ^ license policy
+          -> Bool
+satisfies a b = exprToLSLic b `preorder` exprToLSLic a
diff --git a/src/Data/SPDX/LatticeSyntax.hs b/src/Data/SPDX/LatticeSyntax.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/SPDX/LatticeSyntax.hs
@@ -0,0 +1,126 @@
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+-- |
+-- Module      : Data.SPDX.LatticeSyntax
+-- Description : General lattice tools
+-- Copyright   : (c) 2015 Oleg Grenrus
+-- License     : BSD3
+-- Maintainer  : Oleg Grenrus <oleg.grenrus@iki.fi>
+--
+-- Inspired by <http://www.well-typed.com/blog/2014/12/simple-smt-solver/ Simple SMT Solver>.
+--
+-- In future this module will probably be moved into separate package.
+module Data.SPDX.LatticeSyntax (LatticeSyntax(..), dual, freeVars, equivalent, preorder, satisfiable) where
+
+import Control.Applicative
+import Control.Monad
+import Control.Monad.Trans.State
+import Data.Data
+import Data.Foldable
+import Data.Traversable
+import Prelude hiding (all, or)
+
+data LatticeSyntax a = LVar a
+                     | LBound Bool
+                     | LJoin (LatticeSyntax a) (LatticeSyntax a)
+                     | LMeet (LatticeSyntax a) (LatticeSyntax a)
+  deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable, Typeable, Data)
+
+instance Applicative LatticeSyntax where
+  pure  = return
+  (<*>) = ap
+
+instance Monad LatticeSyntax where
+  return = LVar
+  LVar x    >>= f = f x
+  LBound b  >>= _ = LBound b
+  LJoin a b >>= f = LJoin (a >>= f) (b >>= f)
+  LMeet a b >>= f = LMeet (a >>= f) (b >>= f)
+
+freeVars :: LatticeSyntax a -> [a]
+freeVars = toList
+
+dual :: LatticeSyntax a -> LatticeSyntax a
+dual (LVar v) = LVar v
+dual (LBound t) = LBound $ not t
+dual (LJoin a b) = LMeet (dual a) (dual b)
+dual (LMeet a b) = LJoin (dual a) (dual b)
+
+-- | Test for equivalence.
+--
+-- >>> equivalent (LMeet (LVar 'a') (LVar 'b')) (LMeet (LVar 'b') (LVar 'a'))
+-- True
+--
+-- >>> equivalent (LVar 'a') (LMeet (LVar 'a') (LVar 'a'))
+-- True
+--
+-- >>> equivalent (LMeet (LVar 'a') (LVar 'b')) (LMeet (LVar 'b') (LVar 'b'))
+-- False
+equivalent :: Eq a => LatticeSyntax a -> LatticeSyntax a -> Bool
+equivalent a b = all (uncurry (==)) . runEval $ p
+  where p = (,) <$> evalLattice a <*> evalLattice b
+
+-- | Test for preorder.
+--
+-- @ a ≤ b ⇔ a ∨ b ≡ b ⇔ a ≡ a ∧ b @
+--
+-- >>> preorder (LVar 'a' `LMeet` LVar 'b') (LVar 'a')
+-- True
+--
+-- >>> preorder (LVar 'a') (LVar 'a' `LMeet` LVar 'b')
+-- False
+preorder :: Eq a => LatticeSyntax a -> LatticeSyntax a -> Bool
+preorder a b = (a `LJoin` b) `equivalent` b
+
+-- | Return `True` if for some variable assigment expression evaluates to `True`.
+satisfiable :: Eq a => LatticeSyntax a -> Bool 
+satisfiable = or . runEval . evalLattice
+
+newtype Eval v a = Eval { unEval :: StateT [(v, Bool)] [] a }
+  deriving (Functor, Applicative, Alternative, Monad, MonadPlus)
+
+runEval :: Eval v a -> [a]
+runEval act = evalStateT (unEval act) []
+
+evalLattice :: Eq v => LatticeSyntax v -> Eval v Bool
+evalLattice (LVar v)    = guess v
+evalLattice (LBound b)  = return b
+evalLattice (LJoin a b) = evalLattice a ||^ evalLattice b
+evalLattice (LMeet a b) = evalLattice a &&^ evalLattice b
+
+guess :: Eq v => v -> Eval v Bool
+guess v = Eval $ do
+  st <- get
+  let remember b = put ((v, b) : st) >> return b
+  case lookup v st of
+    Just b  -> return b
+    Nothing -> remember True <|> remember False
+
+-- From Control.Monad.Extra of extra
+
+-- | Like @if@, but where the test can be monadic.
+ifM :: Monad m => m Bool -> m a -> m a -> m a
+ifM b t f = do b' <- b; if b' then t else f
+
+-- | The lazy '||' operator lifted to a monad. If the first
+--   argument evaluates to 'True' the second argument will not
+--   be evaluated.
+--
+-- > Just True  ||^ undefined  == Just True
+-- > Just False ||^ Just True  == Just True
+-- > Just False ||^ Just False == Just False
+(||^) :: Monad m => m Bool -> m Bool -> m Bool
+(||^) a b = ifM a (return True) b
+
+-- | The lazy '&&' operator lifted to a monad. If the first
+--   argument evaluates to 'False' the second argument will not
+--   be evaluated.
+--
+-- > Just False &&^ undefined  == Just False
+-- > Just True  &&^ Just True  == Just True
+-- > Just True  &&^ Just False == Just False
+(&&^) :: Monad m => m Bool -> m Bool -> m Bool
+(&&^) a b = ifM a b (return False)
diff --git a/src/Data/SPDX/Licenses.hs b/src/Data/SPDX/Licenses.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/SPDX/Licenses.hs
@@ -0,0 +1,352 @@
+module Data.SPDX.Licenses (
+    licenses
+  , licenseIdentifiers
+  , mkLicenseId
+  , isOsiApproved
+  , licenseExceptions
+  ) where
+
+import Data.Foldable
+import Data.Maybe
+
+import Data.SPDX.Types
+
+licenseExceptions :: [LicenseExceptionId]
+licenseExceptions = map LicenseExceptionId [
+  "Autoconf-exception-2.0",
+  "Autoconf-exception-3.0",
+  "Bison-exception-2.2",
+  "Classpath-exception-2.0",
+  "eCos-exception-2.0",
+  "Font-exception-2.0",
+  "GCC-exception-2.0",
+  "GCC-exception-3.1",
+  "WxWindows-exception-3.1"
+  ]
+
+-- | A list of SPDX licenses identifiers.
+--
+-- See <http://spdx.org/licenses/>.
+licenseIdentifiers :: [LicenseId]
+licenseIdentifiers = map fstOf3 licenses
+
+fstOf3 :: (a, b, c) -> a
+fstOf3 (x,_,_) = x
+
+--sndOf3 :: (a, b, c) -> b
+--sndOf3 (_,x,_) = x
+
+trdOf3 :: (a, b, c) -> c
+trdOf3 (_,_,x) = x
+
+-- | Lookup `LicenseId` by string representation
+mkLicenseId :: String -> Maybe LicenseId
+mkLicenseId str = find ((== str) . getLicenseId) licenseIdentifiers
+
+-- | Whether license is OSI approved
+--
+-- See <http://opensource.org/licenses/alphabetical>
+isOsiApproved :: LicenseId -> Bool
+isOsiApproved l = trdOf3 $ fromJust $ find ((==l) . fstOf3) licenses
+
+-- | A list of `LicenseId`, license name and whether the license is OSI approved.
+--
+-- See <http://spdx.org/licenses/>.
+licenses :: [(LicenseId, String, Bool)]
+licenses =
+  [ (LicenseId "AAL", "Attribution Assurance License", True)
+  , (LicenseId "ADSL", "Amazon Digital Services License", False)
+  , (LicenseId "AFL-1.1", "Academic Free License v1.1", True)
+  , (LicenseId "AFL-1.2", "Academic Free License v1.2", True)
+  , (LicenseId "AFL-2.0", "Academic Free License v2.0", True)
+  , (LicenseId "AFL-2.1", "Academic Free License v2.1", True)
+  , (LicenseId "AFL-3.0", "Academic Free License v3.0", True)
+  , (LicenseId "AGPL-1.0", "Affero General Public License v1.0", False)
+  , (LicenseId "AGPL-3.0", "GNU Affero General Public License v3.0", True)
+  , (LicenseId "AMDPLPA", "AMD's plpa_map.c License", False)
+  , (LicenseId "AML", "Apple MIT License", False)
+  , (LicenseId "AMPAS", "Academy of Motion Picture Arts and Sciences BSD", False)
+  , (LicenseId "ANTLR-PD", "ANTLR Software Rights Notice", False)
+  , (LicenseId "APAFML", "Adobe Postscript AFM License", False)
+  , (LicenseId "APL-1.0", "Adaptive Public License 1.0", True)
+  , (LicenseId "APSL-1.0", "Apple Public Source License 1.0", True)
+  , (LicenseId "APSL-1.1", "Apple Public Source License 1.1", True)
+  , (LicenseId "APSL-1.2", "Apple Public Source License 1.2", True)
+  , (LicenseId "APSL-2.0", "Apple Public Source License 2.0", True)
+  , (LicenseId "Abstyles", "Abstyles License", False)
+  , (LicenseId "Adobe-2006", "Adobe Systems Incorporated Source Code License Agreement", False)
+  , (LicenseId "Adobe-Glyph", "Adobe Glyph List License", False)
+  , (LicenseId "Afmparse", "Afmparse License", False)
+  , (LicenseId "Aladdin", "Aladdin Free Public License", False)
+  , (LicenseId "Apache-1.0", "Apache License 1.0", False)
+  , (LicenseId "Apache-1.1", "Apache License 1.1", True)
+  , (LicenseId "Apache-2.0", "Apache License 2.0", True)
+  , (LicenseId "Artistic-1.0", "Artistic License 1.0", True)
+  , (LicenseId "Artistic-1.0-Perl", "Artistic License 1.0 (Perl)", True)
+  , (LicenseId "Artistic-1.0-cl8", "Artistic License 1.0 w/clause 8", True)
+  , (LicenseId "Artistic-2.0", "Artistic License 2.0", True)
+  , (LicenseId "BSD-2-Clause", "BSD 2-clause \"Simplified\" License", True)
+  , (LicenseId "BSD-2-Clause-FreeBSD", "BSD 2-clause FreeBSD License", False)
+  , (LicenseId "BSD-2-Clause-NetBSD", "BSD 2-clause NetBSD License", False)
+  , (LicenseId "BSD-3-Clause", "BSD 3-clause \"New\" or \"Revised\" License", True)
+  , (LicenseId "BSD-3-Clause-Attribution", "BSD with attribution", False)
+  , (LicenseId "BSD-3-Clause-Clear", "BSD 3-clause Clear License", False)
+  , (LicenseId "BSD-3-Clause-LBNL", "Lawrence Berkeley National Labs BSD variant license", False)
+  , (LicenseId "BSD-4-Clause", "BSD 4-clause \"Original\" or \"Old\" License", False)
+  , (LicenseId "BSD-4-Clause-UC", "BSD-4-Clause (University of California-Specific)", False)
+  , (LicenseId "BSD-Protection", "BSD Protection License", False)
+  , (LicenseId "BSL-1.0", "Boost Software License 1.0", True)
+  , (LicenseId "Bahyph", "Bahyph License", False)
+  , (LicenseId "Barr", "Barr License", False)
+  , (LicenseId "Beerware", "Beerware License", False)
+  , (LicenseId "BitTorrent-1.0", "BitTorrent Open Source License v1.0", False)
+  , (LicenseId "BitTorrent-1.1", "BitTorrent Open Source License v1.1", False)
+  , (LicenseId "Borceux", "Borceux license", False)
+  , (LicenseId "CATOSL-1.1", "Computer Associates Trusted Open Source License 1.1", True)
+  , (LicenseId "CC-BY-1.0", "Creative Commons Attribution 1.0", False)
+  , (LicenseId "CC-BY-2.0", "Creative Commons Attribution 2.0", False)
+  , (LicenseId "CC-BY-2.5", "Creative Commons Attribution 2.5", False)
+  , (LicenseId "CC-BY-3.0", "Creative Commons Attribution 3.0", False)
+  , (LicenseId "CC-BY-4.0", "Creative Commons Attribution 4.0", False)
+  , (LicenseId "CC-BY-NC-1.0", "Creative Commons Attribution Non Commercial 1.0", False)
+  , (LicenseId "CC-BY-NC-2.0", "Creative Commons Attribution Non Commercial 2.0", False)
+  , (LicenseId "CC-BY-NC-2.5", "Creative Commons Attribution Non Commercial 2.5", False)
+  , (LicenseId "CC-BY-NC-3.0", "Creative Commons Attribution Non Commercial 3.0", False)
+  , (LicenseId "CC-BY-NC-4.0", "Creative Commons Attribution Non Commercial 4.0", False)
+  , (LicenseId "CC-BY-NC-ND-1.0", "Creative Commons Attribution Non Commercial No Derivatives 1.0", False)
+  , (LicenseId "CC-BY-NC-ND-2.0", "Creative Commons Attribution Non Commercial No Derivatives 2.0", False)
+  , (LicenseId "CC-BY-NC-ND-2.5", "Creative Commons Attribution Non Commercial No Derivatives 2.5", False)
+  , (LicenseId "CC-BY-NC-ND-3.0", "Creative Commons Attribution Non Commercial No Derivatives 3.0", False)
+  , (LicenseId "CC-BY-NC-ND-4.0", "Creative Commons Attribution Non Commercial No Derivatives 4.0", False)
+  , (LicenseId "CC-BY-NC-SA-1.0", "Creative Commons Attribution Non Commercial Share Alike 1.0", False)
+  , (LicenseId "CC-BY-NC-SA-2.0", "Creative Commons Attribution Non Commercial Share Alike 2.0", False)
+  , (LicenseId "CC-BY-NC-SA-2.5", "Creative Commons Attribution Non Commercial Share Alike 2.5", False)
+  , (LicenseId "CC-BY-NC-SA-3.0", "Creative Commons Attribution Non Commercial Share Alike 3.0", False)
+  , (LicenseId "CC-BY-NC-SA-4.0", "Creative Commons Attribution Non Commercial Share Alike 4.0", False)
+  , (LicenseId "CC-BY-ND-1.0", "Creative Commons Attribution No Derivatives 1.0", False)
+  , (LicenseId "CC-BY-ND-2.0", "Creative Commons Attribution No Derivatives 2.0", False)
+  , (LicenseId "CC-BY-ND-2.5", "Creative Commons Attribution No Derivatives 2.5", False)
+  , (LicenseId "CC-BY-ND-3.0", "Creative Commons Attribution No Derivatives 3.0", False)
+  , (LicenseId "CC-BY-ND-4.0", "Creative Commons Attribution No Derivatives 4.0", False)
+  , (LicenseId "CC-BY-SA-1.0", "Creative Commons Attribution Share Alike 1.0", False)
+  , (LicenseId "CC-BY-SA-2.0", "Creative Commons Attribution Share Alike 2.0", False)
+  , (LicenseId "CC-BY-SA-2.5", "Creative Commons Attribution Share Alike 2.5", False)
+  , (LicenseId "CC-BY-SA-3.0", "Creative Commons Attribution Share Alike 3.0", False)
+  , (LicenseId "CC-BY-SA-4.0", "Creative Commons Attribution Share Alike 4.0", False)
+  , (LicenseId "CC0-1.0", "Creative Commons Zero v1.0 Universal", False)
+  , (LicenseId "CDDL-1.0", "Common Development and Distribution License 1.0", True)
+  , (LicenseId "CDDL-1.1", "Common Development and Distribution License 1.1", False)
+  , (LicenseId "CECILL-1.0", "CeCILL Free Software License Agreement v1.0", False)
+  , (LicenseId "CECILL-1.1", "CeCILL Free Software License Agreement v1.1", False)
+  , (LicenseId "CECILL-2.0", "CeCILL Free Software License Agreement v2.0", False)
+  , (LicenseId "CECILL-B", "CeCILL-B Free Software License Agreement", False)
+  , (LicenseId "CECILL-C", "CeCILL-C Free Software License Agreement", False)
+  , (LicenseId "CNRI-Python", "CNRI Python License", True)
+  , (LicenseId "CNRI-Python-GPL-Compatible", "CNRI Python Open Source GPL Compatible License Agreement", False)
+  , (LicenseId "CPAL-1.0", "Common Public Attribution License 1.0", True)
+  , (LicenseId "CPL-1.0", "Common Public License 1.0", True)
+  , (LicenseId "CPOL-1.02", "Code Project Open License 1.02", False)
+  , (LicenseId "CUA-OPL-1.0", "CUA Office Public License v1.0", True)
+  , (LicenseId "Caldera", "Caldera License", False)
+  , (LicenseId "ClArtistic", "Clarified Artistic License", False)
+  , (LicenseId "Condor-1.1", "Condor Public License v1.1", False)
+  , (LicenseId "Crossword", "Crossword License", False)
+  , (LicenseId "Cube", "Cube License", False)
+  , (LicenseId "D-FSL-1.0", "Deutsche Freie Software Lizenz", False)
+  , (LicenseId "DOC", "DOC License", False)
+  , (LicenseId "DSDP", "DSDP License", False)
+  , (LicenseId "Dotseqn", "Dotseqn License", False)
+  , (LicenseId "ECL-1.0", "Educational Community License v1.0", True)
+  , (LicenseId "ECL-2.0", "Educational Community License v2.0", True)
+  , (LicenseId "EFL-1.0", "Eiffel Forum License v1.0", True)
+  , (LicenseId "EFL-2.0", "Eiffel Forum License v2.0", True)
+  , (LicenseId "EPL-1.0", "Eclipse Public License 1.0", True)
+  , (LicenseId "EUDatagrid", "EU DataGrid Software License", True)
+  , (LicenseId "EUPL-1.0", "European Union Public License 1.0", False)
+  , (LicenseId "EUPL-1.1", "European Union Public License 1.1", True)
+  , (LicenseId "Entessa", "Entessa Public License v1.0", True)
+  , (LicenseId "ErlPL-1.1", "Erlang Public License v1.1", False)
+  , (LicenseId "Eurosym", "Eurosym License", False)
+  , (LicenseId "FSFUL", "FSF Unlimited License", False)
+  , (LicenseId "FSFULLR", "FSF Unlimited License (with License Retention)", False)
+  , (LicenseId "FTL", "Freetype Project License", False)
+  , (LicenseId "Fair", "Fair License", True)
+  , (LicenseId "Frameworx-1.0", "Frameworx Open License 1.0", True)
+  , (LicenseId "FreeImage", "FreeImage Public License v1.0", False)
+  , (LicenseId "GFDL-1.1", "GNU Free Documentation License v1.1", False)
+  , (LicenseId "GFDL-1.2", "GNU Free Documentation License v1.2", False)
+  , (LicenseId "GFDL-1.3", "GNU Free Documentation License v1.3", False)
+  , (LicenseId "GL2PS", "GL2PS License", False)
+  , (LicenseId "GPL-1.0", "GNU General Public License v1.0 only", False)
+  , (LicenseId "GPL-2.0", "GNU General Public License v2.0 only", True)
+  , (LicenseId "GPL-3.0", "GNU General Public License v3.0 only", True)
+  , (LicenseId "Giftware", "Giftware License", False)
+  , (LicenseId "Glide", "3dfx Glide License", False)
+  , (LicenseId "Glulxe", "Glulxe License", False)
+  , (LicenseId "HPND", "Historic Permission Notice and Disclaimer", True)
+  , (LicenseId "HaskellReport", "Haskell Language Report License", False)
+  , (LicenseId "IBM-pibs", "IBM PowerPC Initialization and Boot Software", False)
+  , (LicenseId "ICU", "ICU License", False)
+  , (LicenseId "IJG", "Independent JPEG Group License", False)
+  , (LicenseId "IPA", "IPA Font License", True)
+  , (LicenseId "IPL-1.0", "IBM Public License v1.0", True)
+  , (LicenseId "ISC", "ISC License", True)
+  , (LicenseId "ImageMagick", "ImageMagick License", False)
+  , (LicenseId "Imlib2", "Imlib2 License", False)
+  , (LicenseId "Intel", "Intel Open Source License", True)
+  , (LicenseId "Intel-ACPI", "Intel ACPI Software License Agreement", False)
+  , (LicenseId "JSON", "JSON License", False)
+  , (LicenseId "JasPer-2.0", "JasPer License", False)
+  , (LicenseId "LGPL-2.0", "GNU Library General Public License v2 only", True)
+  , (LicenseId "LGPL-2.1", "GNU Lesser General Public License v2.1 only", True)
+  , (LicenseId "LGPL-3.0", "GNU Lesser General Public License v3.0 only", True)
+  , (LicenseId "LPL-1.0", "Lucent Public License Version 1.0", True)
+  , (LicenseId "LPL-1.02", "Lucent Public License v1.02", True)
+  , (LicenseId "LPPL-1.0", "LaTeX Project Public License v1.0", False)
+  , (LicenseId "LPPL-1.1", "LaTeX Project Public License v1.1", False)
+  , (LicenseId "LPPL-1.2", "LaTeX Project Public License v1.2", False)
+  , (LicenseId "LPPL-1.3a", "LaTeX Project Public License 1.3a", False)
+  , (LicenseId "LPPL-1.3c", "LaTeX Project Public License v1.3c", True)
+  , (LicenseId "Latex2e", "Latex2e License", False)
+  , (LicenseId "Leptonica", "Leptonica License", False)
+  , (LicenseId "Libpng", "libpng License", False)
+  , (LicenseId "License Identifier", "Full name of License", False)
+  , (LicenseId "MIT", "MIT License", True)
+  , (LicenseId "MIT-CMU", "CMU License", False)
+  , (LicenseId "MIT-advertising", "Enlightenment License (e16)", False)
+  , (LicenseId "MIT-enna", "enna License", False)
+  , (LicenseId "MIT-feh", "feh License", False)
+  , (LicenseId "MITNFA", "MIT +no-false-attribs license", False)
+  , (LicenseId "MPL-1.0", "Mozilla Public License 1.0", True)
+  , (LicenseId "MPL-1.1", "Mozilla Public License 1.1", True)
+  , (LicenseId "MPL-2.0", "Mozilla Public License 2.0", True)
+  , (LicenseId "MPL-2.0-no-copyleft-exception", "Mozilla Public License 2.0 (no copyleft exception)", True)
+  , (LicenseId "MS-PL", "Microsoft Public License", True)
+  , (LicenseId "MS-RL", "Microsoft Reciprocal License", True)
+  , (LicenseId "MTLL", "Matrix Template Library License", False)
+  , (LicenseId "MakeIndex", "MakeIndex License", False)
+  , (LicenseId "MirOS", "MirOS Licence", True)
+  , (LicenseId "Motosoto", "Motosoto License", True)
+  , (LicenseId "Multics", "Multics License", True)
+  , (LicenseId "Mup", "Mup License", False)
+  , (LicenseId "NASA-1.3", "NASA Open Source Agreement 1.3", True)
+  , (LicenseId "NBPL-1.0", "Net Boolean Public License v1", False)
+  , (LicenseId "NCSA", "University of Illinois/NCSA Open Source License", True)
+  , (LicenseId "NGPL", "Nethack General Public License", True)
+  , (LicenseId "NLPL", "No Limit Public License", False)
+  , (LicenseId "NOSL", "Netizen Open Source License", False)
+  , (LicenseId "NPL-1.0", "Netscape Public License v1.0", False)
+  , (LicenseId "NPL-1.1", "Netscape Public License v1.1", False)
+  , (LicenseId "NPOSL-3.0", "Non-Profit Open Software License 3.0", True)
+  , (LicenseId "NRL", "NRL License", False)
+  , (LicenseId "NTP", "NTP License", True)
+  , (LicenseId "Naumen", "Naumen Public License", True)
+  , (LicenseId "NetCDF", "NetCDF license", False)
+  , (LicenseId "Newsletr", "Newsletr License", False)
+  , (LicenseId "Nokia", "Nokia Open Source License", True)
+  , (LicenseId "Noweb", "Noweb License", False)
+  , (LicenseId "Nunit", "Nunit License", False)
+  , (LicenseId "OCLC-2.0", "OCLC Research Public License 2.0", True)
+  , (LicenseId "ODbL-1.0", "ODC Open Database License v1.0", False)
+  , (LicenseId "OFL-1.0", "SIL Open Font License 1.0", False)
+  , (LicenseId "OFL-1.1", "SIL Open Font License 1.1", True)
+  , (LicenseId "OGTSL", "Open Group Test Suite License", True)
+  , (LicenseId "OLDAP-1.1", "Open LDAP Public License v1.1", False)
+  , (LicenseId "OLDAP-1.2", "Open LDAP Public License v1.2", False)
+  , (LicenseId "OLDAP-1.3", "Open LDAP Public License v1.3", False)
+  , (LicenseId "OLDAP-1.4", "Open LDAP Public License v1.4", False)
+  , (LicenseId "OLDAP-2.0", "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", False)
+  , (LicenseId "OLDAP-2.0.1", "Open LDAP Public License v2.0.1", False)
+  , (LicenseId "OLDAP-2.1", "Open LDAP Public License v2.1", False)
+  , (LicenseId "OLDAP-2.2", "Open LDAP Public License v2.2", False)
+  , (LicenseId "OLDAP-2.2.1", "Open LDAP Public License v2.2.1", False)
+  , (LicenseId "OLDAP-2.2.2", "Open LDAP Public License  2.2.2", False)
+  , (LicenseId "OLDAP-2.3", "Open LDAP Public License v2.3", False)
+  , (LicenseId "OLDAP-2.4", "Open LDAP Public License v2.4", False)
+  , (LicenseId "OLDAP-2.5", "Open LDAP Public License v2.5", False)
+  , (LicenseId "OLDAP-2.6", "Open LDAP Public License v2.6", False)
+  , (LicenseId "OLDAP-2.7", "Open LDAP Public License v2.7", False)
+  , (LicenseId "OLDAP-2.8", "Open LDAP Public License v2.8", False)
+  , (LicenseId "OML", "Open Market License", False)
+  , (LicenseId "OPL-1.0", "Open Public License v1.0", False)
+  , (LicenseId "OSL-1.0", "Open Software License 1.0", True)
+  , (LicenseId "OSL-1.1", "Open Software License 1.1", False)
+  , (LicenseId "OSL-2.0", "Open Software License 2.0", True)
+  , (LicenseId "OSL-2.1", "Open Software License 2.1", True)
+  , (LicenseId "OSL-3.0", "Open Software License 3.0", True)
+  , (LicenseId "OpenSSL", "OpenSSL License", False)
+  , (LicenseId "PDDL-1.0", "ODC Public Domain Dedication & License 1.0", False)
+  , (LicenseId "PHP-3.0", "PHP License v3.0", True)
+  , (LicenseId "PHP-3.01", "PHP License v3.01", False)
+  , (LicenseId "Plexus", "Plexus Classworlds License", False)
+  , (LicenseId "PostgreSQL", "PostgreSQL License", True)
+  , (LicenseId "Python-2.0", "Python License 2.0", True)
+  , (LicenseId "QPL-1.0", "Q Public License 1.0", True)
+  , (LicenseId "Qhull", "Qhull License", False)
+  , (LicenseId "RHeCos-1.1", "Red Hat eCos Public License v1.1", False)
+  , (LicenseId "RPL-1.1", "Reciprocal Public License 1.1", True)
+  , (LicenseId "RPL-1.5", "Reciprocal Public License 1.5", True)
+  , (LicenseId "RPSL-1.0", "RealNetworks Public Source License v1.0", True)
+  , (LicenseId "RSCPL", "Ricoh Source Code Public License", True)
+  , (LicenseId "Rdisc", "Rdisc License", False)
+  , (LicenseId "Ruby", "Ruby License", False)
+  , (LicenseId "SAX-PD", "Sax Public Domain Notice", False)
+  , (LicenseId "SCEA", "SCEA Shared Source License", False)
+  , (LicenseId "SGI-B-1.0", "SGI Free Software License B v1.0", False)
+  , (LicenseId "SGI-B-1.1", "SGI Free Software License B v1.1", False)
+  , (LicenseId "SGI-B-2.0", "SGI Free Software License B v2.0", False)
+  , (LicenseId "SISSL", "Sun Industry Standards Source License v1.1", True)
+  , (LicenseId "SISSL-1.2", "Sun Industry Standards Source License v1.2", False)
+  , (LicenseId "SMLNJ", "Standard ML of New Jersey License", False)
+  , (LicenseId "SNIA", "SNIA Public License 1.1", False)
+  , (LicenseId "SPL-1.0", "Sun Public License v1.0", True)
+  , (LicenseId "SWL", "Scheme Widget Library (SWL) Software License Agreement", False)
+  , (LicenseId "Saxpath", "Saxpath License", False)
+  , (LicenseId "SimPL-2.0", "Simple Public License 2.0", True)
+  , (LicenseId "Sleepycat", "Sleepycat License", True)
+  , (LicenseId "SugarCRM-1.1.3", "SugarCRM Public License v1.1.3", False)
+  , (LicenseId "TCL", "TCL/TK License", False)
+  , (LicenseId "TMate", "TMate Open Source License", False)
+  , (LicenseId "TORQUE-1.1", "TORQUE v2.5+ Software License v1.1", False)
+  , (LicenseId "TOSL", "Trusster Open Source License", False)
+  , (LicenseId "Unicode-TOU", "Unicode Terms of Use", False)
+  , (LicenseId "Unlicense", "The Unlicense", False)
+  , (LicenseId "VOSTROM", "VOSTROM Public License for Open Source", False)
+  , (LicenseId "VSL-1.0", "Vovida Software License v1.0", True)
+  , (LicenseId "Vim", "Vim License", False)
+  , (LicenseId "W3C", "W3C Software Notice and License (2002-12-31)", True)
+  , (LicenseId "W3C-19980720", "W3C Software Notice and License (1998-07-20)", False)
+  , (LicenseId "WTFPL", "Do What The F*ck You Want To Public License", False)
+  , (LicenseId "Watcom-1.0", "Sybase Open Watcom Public License 1.0", True)
+  , (LicenseId "Wsuipa", "Wsuipa License", False)
+  , (LicenseId "X11", "X11 License", False)
+  , (LicenseId "XFree86-1.1", "XFree86 License 1.1", False)
+  , (LicenseId "XSkat", "XSkat License", False)
+  , (LicenseId "Xerox", "Xerox License", False)
+  , (LicenseId "Xnet", "X.Net License", True)
+  , (LicenseId "YPL-1.0", "Yahoo! Public License v1.0", False)
+  , (LicenseId "YPL-1.1", "Yahoo! Public License v1.1", False)
+  , (LicenseId "ZPL-1.1", "Zope Public License 1.1", False)
+  , (LicenseId "ZPL-2.0", "Zope Public License 2.0", True)
+  , (LicenseId "ZPL-2.1", "Zope Public License 2.1", False)
+  , (LicenseId "Zed", "Zed License", False)
+  , (LicenseId "Zend-2.0", "Zend License v2.0", False)
+  , (LicenseId "Zimbra-1.3", "Zimbra Public License v1.3", False)
+  , (LicenseId "Zimbra-1.4", "Zimbra Public License v1.4", False)
+  , (LicenseId "Zlib", "zlib License", True)
+  , (LicenseId "bzip2-1.0.5", "bzip2 and libbzip2 License v1.0.5", False)
+  , (LicenseId "bzip2-1.0.6", "bzip2 and libbzip2 License v1.0.6", False)
+  , (LicenseId "diffmark", "diffmark license", False)
+  , (LicenseId "dvipdfm", "dvipdfm License", False)
+  , (LicenseId "eGenix", "eGenix.com Public License 1.1.0", False)
+  , (LicenseId "gSOAP-1.3b", "gSOAP Public License v1.3b", False)
+  , (LicenseId "gnuplot", "gnuplot License", False)
+  , (LicenseId "iMatix", "iMatix Standard Function Library Agreement", False)
+  , (LicenseId "libtiff", "libtiff License", False)
+  , (LicenseId "mpich2", "mpich2 License", False)
+  , (LicenseId "psfrag", "psfrag License", False)
+  , (LicenseId "psutils", "psutils License", False)
+  , (LicenseId "xinetd", "xinetd License", False)
+  , (LicenseId "xpp", "XPP License", False)
+  , (LicenseId "zlib-acknowledgement", "zlib/libpng License with Acknowledgement", False)
+  ]
diff --git a/src/Data/SPDX/Parser.hs b/src/Data/SPDX/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/SPDX/Parser.hs
@@ -0,0 +1,87 @@
+{-# LANGUAGE CPP #-}
+module Data.SPDX.Parser (parseExpression, unsafeParseExpr) where
+
+#ifndef MIN_VERSION_base
+#define MIN_VERSION_base(x,y,z) 0
+#endif
+
+#if !MIN_VERSION_base(4, 8, 0)
+import Control.Applicative
+#endif
+
+import Data.Char
+import Text.ParserCombinators.ReadP
+
+import Data.SPDX.Types
+import Data.SPDX.Licenses (licenseIdentifiers, licenseExceptions)
+
+license :: ReadP LicenseId
+license = choice (map f licenseIdentifiers)
+  where f l = l <$ string (getLicenseId l)
+
+licenseException :: ReadP LicenseExceptionId
+licenseException = choice (map f licenseExceptions)
+  where f l = l <$ string (getLicenseExceptionId l)
+
+elicense :: ReadP LicenseExpression
+elicense = (\l -> ELicense False (Right l) Nothing) <$> license
+
+elicenseWith :: ReadP LicenseExpression
+elicenseWith = (\l e -> ELicense False (Right l) (Just e)) <$> license <* skipSpaces1 <* string "WITH" <* skipSpaces1 <*> licenseException
+
+elicenseAndNewer :: ReadP LicenseExpression
+elicenseAndNewer = (\l -> ELicense True (Right l) Nothing) <$> license <* char '+'
+
+elicenseWithAndNewer :: ReadP LicenseExpression
+elicenseWithAndNewer = (\l e -> ELicense True (Right l) (Just e)) <$> license <* char '+' <* string " WITH " <*> licenseException
+
+elicenseRef :: ReadP LicenseExpression
+elicenseRef = (\licId -> ELicense False (Left $ LicenseRef Nothing licId) Nothing) <$ string "LicenseRef-" <*> idString
+
+elicenseDocRef :: ReadP LicenseExpression
+elicenseDocRef = f <$ string "DocumentRef-" <*> idString <* char ':' <* string "LicenseRef-" <*> idString
+  where f docId licId = ELicense False (Left $ LicenseRef (Just docId) licId) Nothing
+
+idString :: ReadP String
+idString = munch1 p
+  where p '.' = True
+        p '-' = True
+        p c   = isAlphaNum c
+
+skipSpaces1 :: ReadP ()
+skipSpaces1 = () <$ char ' ' <* skipSpaces
+
+parens :: ReadP a -> ReadP a
+parens = between (char '(') (skipSpaces <* char ')')
+
+terminal :: ReadP LicenseExpression
+terminal = choice [ elicense
+                  , elicenseWith
+                  , elicenseAndNewer
+                  , elicenseWithAndNewer
+                  , elicenseRef
+                  , elicenseDocRef
+                  , parens expression
+                  ]
+
+conjunction :: ReadP LicenseExpression
+conjunction = chainr1 terminal (EConjunction <$ skipSpaces1 <* string "AND" <* skipSpaces1)
+
+disjunction :: ReadP LicenseExpression
+disjunction = chainr1 conjunction (EDisjunction <$ skipSpaces1 <* string "OR" <* skipSpaces1)
+
+expression :: ReadP LicenseExpression
+expression = skipSpaces *> disjunction
+
+-- | Parse SPDX License Expression
+--
+-- >>> parseExpression "LGPL-2.1 OR MIT"
+-- [EDisjunction (ELicense False (Right (LicenseId "LGPL-2.1")) Nothing) (ELicense False (Right (LicenseId "MIT")) Nothing)]
+parseExpression :: String -> [LicenseExpression]
+parseExpression = map fst . readP_to_S (expression <* skipSpaces <* eof)
+
+unsafeParseExpr :: String -> LicenseExpression
+unsafeParseExpr s = f . parseExpression $ s
+  where f []      = error $ "Failed parse of license expression: " ++ s
+        f [l]     = l
+        f (_:_:_) = error $ "Ambigious parse of license expression: " ++ s
diff --git a/src/Data/SPDX/Ranges.hs b/src/Data/SPDX/Ranges.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/SPDX/Ranges.hs
@@ -0,0 +1,47 @@
+module Data.SPDX.Ranges (licenseRanges, lookupLicenseRange) where
+
+import Data.Char
+import Data.List
+import Data.Maybe
+
+import Data.SPDX.Types
+import Data.SPDX.Licenses
+
+licenseRanges :: [[LicenseId]]
+licenseRanges = filter longerThanSingleton . map f $ prefixes
+  where f p = filter (\l -> p `isPrefixOf` getLicenseId l && restIsNumber (getLicenseId l) p) licenseIdentifiers
+
+-- | Lookup newer licenses we know about
+--
+-- >>> lookupLicenseRange $ fromJust $ mkLicenseId "MIT"
+-- [LicenseId "MIT"]
+--
+-- >>> lookupLicenseRange $ fromJust $ mkLicenseId "GPL-2.0"
+-- [LicenseId "GPL-2.0",LicenseId "GPL-3.0"]
+--
+-- >>> lookupLicenseRange $ fromJust $ mkLicenseId "LGPL-2.0"
+-- [LicenseId "LGPL-2.0",LicenseId "LGPL-2.1",LicenseId "LGPL-3.0"]
+lookupLicenseRange :: LicenseId -> [LicenseId]
+lookupLicenseRange l = fromMaybe [l] $ lookup l ranges'
+
+ranges' :: [(LicenseId, [LicenseId])]
+ranges' = map (\r -> (head r, r)) . filter (not . nullOrSingleton) . concatMap tails $ licenseRanges
+
+nullOrSingleton :: [a] -> Bool
+nullOrSingleton []      = True
+nullOrSingleton [_]     = True
+nullOrSingleton (_:_:_) = False
+
+restIsNumber :: String -> String -> Bool
+restIsNumber l p = all (\c -> isDigit c || c == 'a' || c == 'b' || c == 'c' || c == '.') (drop (length p) l)
+
+pref :: String -> String
+pref = reverse . dropWhile (/= '-') . reverse
+
+prefixes :: [String]
+prefixes = nub (filter (not . null) $ map (pref . getLicenseId) licenseIdentifiers)
+
+longerThanSingleton :: [a] -> Bool
+longerThanSingleton []      = False
+longerThanSingleton [_]     = False
+longerThanSingleton (_:_:_) = True
diff --git a/src/Data/SPDX/Types.hs b/src/Data/SPDX/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/SPDX/Types.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+module Data.SPDX.Types (
+    LicenseId(..)
+  , getLicenseId
+  , LicenseExceptionId(..)
+  , getLicenseExceptionId
+  , LicenseRef(..)
+  , LicenseExpression(..)
+  ) where
+
+import Data.Data
+import GHC.Generics
+
+data LicenseRef = LicenseRef
+  { lrDocument :: !(Maybe String)
+  , lrLicense  :: !String
+  }
+  deriving (Eq, Ord, Show, Read, Typeable, Data, Generic)
+
+-- | Opaque license identifier type.
+newtype LicenseId = LicenseId String
+  deriving (Eq, Ord, Show, Read, Typeable, Data, Generic)
+
+getLicenseId :: LicenseId -> String
+getLicenseId (LicenseId l) = l
+
+-- | Opaque license exception identifier type.
+newtype LicenseExceptionId = LicenseExceptionId String
+  deriving (Eq, Ord, Show, Read, Typeable, Data, Generic)
+
+getLicenseExceptionId :: LicenseExceptionId -> String
+getLicenseExceptionId (LicenseExceptionId l) = l
+
+data LicenseExpression = ELicense !Bool !(Either LicenseRef LicenseId) !(Maybe LicenseExceptionId)
+                       | EConjunction !LicenseExpression !LicenseExpression
+                       | EDisjunction !LicenseExpression !LicenseExpression
+  deriving (Eq, Ord, Show, Read, Typeable, Data, Generic)
diff --git a/src/Distribution/SPDX.hs b/src/Distribution/SPDX.hs
deleted file mode 100644
--- a/src/Distribution/SPDX.hs
+++ /dev/null
@@ -1,65 +0,0 @@
--- |
--- Module      : Distribution.SPDX
--- Description : SPDX licenses and expression language
--- Copyright   : (c) 2015 Oleg Grenrus
--- License     : BSD3
--- Maintainer  : Oleg Grenrus <oleg.grenrus@iki.fi>
---
-module Distribution.SPDX (
-  -- * Types
-    LicenseId
-  , LicenseExceptionId
-  , LicenseRef(..)
-  , LicenseExpression(..)
-  -- * Data
-  , licenseIdentifiers
-  , licenseExceptions
-  -- ** Ranges
-  , licenseRanges
-  , lookupLicenseRange
-  -- * Parsing
-  , parseExpression
-  , unsafeParseExpr
-  -- * Logic
-  , satisfies
-  ) where
-
-import Distribution.SPDX.Types
-import Distribution.SPDX.Ranges
-import Distribution.SPDX.Licenses
-import Distribution.SPDX.Parser
-import Distribution.SPDX.LatticeSyntax
-
-data Lic = Lic (Either LicenseRef String) (Maybe String)
-  deriving (Eq, Ord, Show, Read)
-
-exprToLSLic :: LicenseExpression -> LatticeSyntax Lic
-exprToLSLic (ELicense False l e) = LVar (Lic l e)
-exprToLSLic (ELicense True (Right l) e) = foldr1 LJoin $ map (\l' -> LVar $ Lic (Right l') e) $ lookupLicenseRange l
--- We don't know anything about newer license references
-exprToLSLic (ELicense True (Left l) e) = LVar (Lic (Left l) e)
-exprToLSLic (EConjunction a b) = LMeet (exprToLSLic a) (exprToLSLic b)
-exprToLSLic (EDisjunction a b) = LJoin (exprToLSLic a) (exprToLSLic b)
-
--- |
---
--- @⟦ satisfies a b ⟧ ≡ a ≥ b ≡ a ∧ b = b @
---
--- >>> unsafeParseExpr "GPL-3.0" `satisfies` unsafeParseExpr "ISC AND MIT"
--- False
---
--- >>> unsafeParseExpr "Zlib" `satisfies` unsafeParseExpr "ISC AND MIT AND Zlib"
--- True
---
--- >>> unsafeParseExpr "(MIT OR GPL-2.0)" `satisfies` unsafeParseExpr "(ISC AND MIT)"
--- True
---
--- >>> unsafeParseExpr "(MIT AND GPL-2.0)" `satisfies` unsafeParseExpr "(MIT AND GPL-2.0)"
--- True
---
--- >>> unsafeParseExpr "(MIT AND GPL-2.0)" `satisfies` unsafeParseExpr "(ISC AND GPL-2.0)"
--- False
-satisfies :: LicenseExpression -- ^ package license
-          -> LicenseExpression -- ^ license policy
-          -> Bool
-satisfies a b = exprToLSLic b `preorder` exprToLSLic a
diff --git a/src/Distribution/SPDX/LatticeSyntax.hs b/src/Distribution/SPDX/LatticeSyntax.hs
deleted file mode 100644
--- a/src/Distribution/SPDX/LatticeSyntax.hs
+++ /dev/null
@@ -1,101 +0,0 @@
-{-# LANGUAGE DeriveFunctor #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE DeriveFoldable #-}
-{-# LANGUAGE DeriveTraversable #-}
-{-# LANGUAGE DeriveDataTypeable #-}
--- |
--- Module      : Distribution.SPDX.LatticeSyntax
--- Description : General lattice tools
--- Copyright   : (c) 2015 Oleg Grenrus
--- License     : BSD3
--- Maintainer  : Oleg Grenrus <oleg.grenrus@iki.fi>
---
--- Inspired by <http://www.well-typed.com/blog/2014/12/simple-smt-solver/ Simple SMT Solver>.
---
--- In future this module will probably be moved into separate package.
-module Distribution.SPDX.LatticeSyntax (LatticeSyntax(..), dual, freeVars, equivalent, preorder, satisfiable) where
-
-import Control.Applicative
-import Control.Monad
-import Control.Monad.Trans.State
-import Data.Data
-import Data.Foldable
-import Data.Traversable
-import Prelude hiding (all, or)
-
-data LatticeSyntax a = LVar a
-                     | LBound Bool
-                     | LJoin (LatticeSyntax a) (LatticeSyntax a)
-                     | LMeet (LatticeSyntax a) (LatticeSyntax a)
-  deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable, Typeable, Data)
-
-instance Applicative LatticeSyntax where
-  pure  = return
-  (<*>) = ap
-
-instance Monad LatticeSyntax where
-  return = LVar
-  LVar x    >>= f = f x
-  LBound b  >>= _ = LBound b
-  LJoin a b >>= f = LJoin (a >>= f) (b >>= f)
-  LMeet a b >>= f = LMeet (a >>= f) (b >>= f)
-
-freeVars :: LatticeSyntax a -> [a]
-freeVars = toList
-
-dual :: LatticeSyntax a -> LatticeSyntax a
-dual (LVar v) = LVar v
-dual (LBound t) = LBound $ not t
-dual (LJoin a b) = LMeet (dual a) (dual b)
-dual (LMeet a b) = LJoin (dual a) (dual b)
-
--- | Test for equivalence.
---
--- >>> equivalent (LMeet (LVar 'a') (LVar 'b')) (LMeet (LVar 'b') (LVar 'a'))
--- True
---
--- >>> equivalent (LVar 'a') (LMeet (LVar 'a') (LVar 'a'))
--- True
---
--- >>> equivalent (LMeet (LVar 'a') (LVar 'b')) (LMeet (LVar 'b') (LVar 'b'))
--- False
-equivalent :: Eq a => LatticeSyntax a -> LatticeSyntax a -> Bool
-equivalent a b = all (uncurry (==)) . runEval $ p
-  where p = (,) <$> evalLattice a <*> evalLattice b
-
--- | Test for preorder.
---
--- @ a ≤ b ⇔ a ∨ b ≡ b ⇔ a ≡ a ∧ b@
---
--- >>> preorder (LVar 'a' `LMeet` LVar 'b') (LVar 'a')
--- True
---
--- >>> preorder (LVar 'a') (LVar 'a' `LMeet` LVar 'b')
--- False
-preorder :: Eq a => LatticeSyntax a -> LatticeSyntax a -> Bool
-preorder a b = (a `LJoin` b) `equivalent` b
-
--- | Return `True` if for some variable assigment expression evaluates to `True`.
-satisfiable :: Eq a => LatticeSyntax a -> Bool 
-satisfiable = or . runEval . evalLattice
-
-newtype Eval v a = Eval { unEval :: StateT [(v, Bool)] [] a }
-  deriving (Functor, Applicative, Alternative, Monad, MonadPlus)
-
-runEval :: Eval v a -> [a]
-runEval act = evalStateT (unEval act) []
-
-evalLattice :: Eq v => LatticeSyntax v -> Eval v Bool
-evalLattice (LVar v)    = guess v
-evalLattice (LBound b)  = return b
-evalLattice (LJoin a b) = (||) <$> evalLattice a <*> evalLattice b
-evalLattice (LMeet a b) = (&&) <$> evalLattice a <*> evalLattice b
-
-guess :: Eq v => v -> Eval v Bool
-guess v = Eval $ do
-  st <- get
-  let remember b = put ((v, b) : st) >> return b
-  case lookup v st of
-    Just b  -> return b
-    Nothing -> remember True <|> remember False
-
diff --git a/src/Distribution/SPDX/Licenses.hs b/src/Distribution/SPDX/Licenses.hs
deleted file mode 100644
--- a/src/Distribution/SPDX/Licenses.hs
+++ /dev/null
@@ -1,322 +0,0 @@
-module Distribution.SPDX.Licenses (LicenseId, LicenseExceptionId, licenseIdentifiers, licenseExceptions) where
-
-import Distribution.SPDX.Types
-
-import Data.List
-
-licenseExceptions :: [LicenseExceptionId]
-licenseExceptions = [
-  "Autoconf-exception-2.0",
-  "Autoconf-exception-3.0",
-  "Bison-exception-2.2",
-  "Classpath-exception-2.0",
-  "eCos-exception-2.0",
-  "Font-exception-2.0",
-  "GCC-exception-2.0",
-  "GCC-exception-3.1",
-  "WxWindows-exception-3.1"
-  ]
-
--- | A list of SPDX licenses identifiers.
---
--- See <http://spdx.org/licenses/>.
-licenseIdentifiers :: [LicenseId]
-licenseIdentifiers = sort $ [
-  "Glide",
-  "Abstyles",
-  "AFL-1.1",
-  "AFL-1.2",
-  "AFL-2.0",
-  "AFL-2.1",
-  "AFL-3.0",
-  "AMPAS",
-  "APL-1.0",
-  "Adobe-Glyph",
-  "APAFML",
-  "Adobe-2006",
-  "AGPL-1.0",
-  "Afmparse",
-  "Aladdin",
-  "ADSL",
-  "AMDPLPA",
-  "ANTLR-PD",
-  "Apache-1.0",
-  "Apache-1.1",
-  "Apache-2.0",
-  "AML",
-  "APSL-1.0",
-  "APSL-1.1",
-  "APSL-1.2",
-  "APSL-2.0",
-  "Artistic-1.0",
-  "Artistic-1.0-Perl",
-  "Artistic-1.0-cl8",
-  "Artistic-2.0",
-  "AAL",
-  "Bahyph",
-  "Barr",
-  "Beerware",
-  "BitTorrent-1.0",
-  "BitTorrent-1.1",
-  "BSL-1.0",
-  "Borceux",
-  "BSD-2-Clause",
-  "BSD-2-Clause-FreeBSD",
-  "BSD-2-Clause-NetBSD",
-  "BSD-3-Clause",
-  "BSD-3-Clause-Clear",
-  "BSD-4-Clause",
-  "BSD-Protection",
-  "BSD-3-Clause-Attribution",
-  "BSD-4-Clause-UC",
-  "bzip2-1.0.5",
-  "bzip2-1.0.6",
-  "Caldera",
-  "CECILL-1.0",
-  "CECILL-1.1",
-  "CECILL-2.0",
-  "CECILL-B",
-  "CECILL-C",
-  "ClArtistic",
-  "MIT-CMU",
-  "CNRI-Jython",
-  "CNRI-Python",
-  "CNRI-Python-GPL-Compatible",
-  "CPOL-1.02",
-  "CDDL-1.0",
-  "CDDL-1.1",
-  "CPAL-1.0",
-  "CPL-1.0",
-  "CATOSL-1.1",
-  "Condor-1.1",
-  "CC-BY-1.0",
-  "CC-BY-2.0",
-  "CC-BY-2.5",
-  "CC-BY-3.0",
-  "CC-BY-4.0",
-  "CC-BY-ND-1.0",
-  "CC-BY-ND-2.0",
-  "CC-BY-ND-2.5",
-  "CC-BY-ND-3.0",
-  "CC-BY-ND-4.0",
-  "CC-BY-NC-1.0",
-  "CC-BY-NC-2.0",
-  "CC-BY-NC-2.5",
-  "CC-BY-NC-3.0",
-  "CC-BY-NC-4.0",
-  "CC-BY-NC-ND-1.0",
-  "CC-BY-NC-ND-2.0",
-  "CC-BY-NC-ND-2.5",
-  "CC-BY-NC-ND-3.0",
-  "CC-BY-NC-ND-4.0",
-  "CC-BY-NC-SA-1.0",
-  "CC-BY-NC-SA-2.0",
-  "CC-BY-NC-SA-2.5",
-  "CC-BY-NC-SA-3.0",
-  "CC-BY-NC-SA-4.0",
-  "CC-BY-SA-1.0",
-  "CC-BY-SA-2.0",
-  "CC-BY-SA-2.5",
-  "CC-BY-SA-3.0",
-  "CC-BY-SA-4.0",
-  "CC0-1.0",
-  "Crossword",
-  "CUA-OPL-1.0",
-  "Cube",
-  "D-FSL-1.0",
-  "diffmark",
-  "WTFPL",
-  "DOC",
-  "Dotseqn",
-  "DSDP",
-  "dvipdfm",
-  "EPL-1.0",
-  "ECL-1.0",
-  "ECL-2.0",
-  "eGenix",
-  "EFL-1.0",
-  "EFL-2.0",
-  "MIT-advertising",
-  "MIT-enna",
-  "Entessa",
-  "ErlPL-1.1",
-  "EUDatagrid",
-  "EUPL-1.0",
-  "EUPL-1.1",
-  "Eurosym",
-  "Fair",
-  "MIT-feh",
-  "Frameworx-1.0",
-  "FreeImage",
-  "FTL",
-  "FSFUL",
-  "FSFULLR",
-  "Giftware",
-  "GL2PS",
-  "Glulxe",
-  "AGPL-3.0",
-  "GFDL-1.1",
-  "GFDL-1.2",
-  "GFDL-1.3",
-  "GPL-1.0",
-  "GPL-2.0",
-  "GPL-3.0",
-  "LGPL-2.1",
-  "LGPL-3.0",
-  "LGPL-2.0",
-  "gnuplot",
-  "gSOAP-1.3b",
-  "HaskellReport",
-  "HPND",
-  "IBM-pibs",
-  "IPL-1.0",
-  "ICU",
-  "ImageMagick",
-  "iMatix",
-  "Imlib2",
-  "IJG",
-  "Intel-ACPI",
-  "Intel",
-  "IPA",
-  "ISC",
-  "JasPer-2.0",
-  "JSON",
-  "LPPL-1.3a",
-  "LPPL-1.0",
-  "LPPL-1.1",
-  "LPPL-1.2",
-  "LPPL-1.3c",
-  "Latex2e",
-  "BSD-3-Clause-LBNL",
-  "Leptonica",
-  "Libpng",
-  "libtiff",
-  "LPL-1.02",
-  "LPL-1.0",
-  "MakeIndex",
-  "MTLL",
-  "MS-PL",
-  "MS-RL",
-  "MirOS",
-  "MITNFA",
-  "MIT",
-  "Motosoto",
-  "MPL-1.0",
-  "MPL-1.1",
-  "MPL-2.0",
-  "MPL-2.0-no-copyleft-exception",
-  "mpich2",
-  "Multics",
-  "Mup",
-  "NASA-1.3",
-  "Naumen",
-  "NBPL-1.0",
-  "NetCDF",
-  "NGPL",
-  "NOSL",
-  "NPL-1.0",
-  "NPL-1.1",
-  "Newsletr",
-  "NLPL",
-  "Nokia",
-  "NPOSL-3.0",
-  "Noweb",
-  "NRL",
-  "NTP",
-  "Nunit",
-  "OCLC-2.0",
-  "ODbL-1.0",
-  "PDDL-1.0",
-  "OGTSL",
-  "OLDAP-2.2.2",
-  "OLDAP-1.1",
-  "OLDAP-1.2",
-  "OLDAP-1.3",
-  "OLDAP-1.4",
-  "OLDAP-2.0",
-  "OLDAP-2.0.1",
-  "OLDAP-2.1",
-  "OLDAP-2.2",
-  "OLDAP-2.2.1",
-  "OLDAP-2.3",
-  "OLDAP-2.4",
-  "OLDAP-2.5",
-  "OLDAP-2.6",
-  "OLDAP-2.7",
-  "OLDAP-2.8",
-  "OML",
-  "OPL-1.0",
-  "OSL-1.0",
-  "OSL-1.1",
-  "OSL-2.0",
-  "OSL-2.1",
-  "OSL-3.0",
-  "OpenSSL",
-  "PHP-3.0",
-  "PHP-3.01",
-  "Plexus",
-  "PostgreSQL",
-  "psfrag",
-  "psutils",
-  "Python-2.0",
-  "QPL-1.0",
-  "Qhull",
-  "Rdisc",
-  "RPSL-1.0",
-  "RPL-1.1",
-  "RPL-1.5",
-  "RHeCos-1.1",
-  "RSCPL",
-  "RSA-MD",
-  "Ruby",
-  "SAX-PD",
-  "Saxpath",
-  "SCEA",
-  "SWL",
-  "SGI-B-1.0",
-  "SGI-B-1.1",
-  "SGI-B-2.0",
-  "OFL-1.0",
-  "OFL-1.1",
-  "SimPL-2.0",
-  "Sleepycat",
-  "SNIA",
-  "SMLNJ",
-  "SugarCRM-1.1.3",
-  "SISSL",
-  "SISSL-1.2",
-  "SPL-1.0",
-  "Watcom-1.0",
-  "TCL",
-  "Unlicense",
-  "TMate",
-  "TORQUE-1.1",
-  "TOSL",
-  "Unicode-TOU",
-  "UPL-1.0",
-  "NCSA",
-  "Vim",
-  "VOSTROM",
-  "VSL-1.0",
-  "W3C-19980720",
-  "W3C",
-  "Wsuipa",
-  "Xnet",
-  "X11",
-  "Xerox",
-  "XFree86-1.1",
-  "xinetd",
-  "xpp",
-  "XSkat",
-  "YPL-1.0",
-  "YPL-1.1",
-  "Zed",
-  "Zend-2.0",
-  "Zimbra-1.3",
-  "Zimbra-1.4",
-  "Zlib",
-  "zlib-acknowledgement",
-  "ZPL-1.1",
-  "ZPL-2.0",
-  "ZPL-2.1"
-  ]
diff --git a/src/Distribution/SPDX/Parser.hs b/src/Distribution/SPDX/Parser.hs
deleted file mode 100644
--- a/src/Distribution/SPDX/Parser.hs
+++ /dev/null
@@ -1,81 +0,0 @@
-{-# LANGUAGE CPP #-}
-module Distribution.SPDX.Parser (parseExpression, unsafeParseExpr) where
-
-#if !MIN_VERSION_base(4, 8, 0)
-import Control.Applicative
-#endif
-
-import Data.Char
-import Text.ParserCombinators.ReadP
-
-import Distribution.SPDX.Types
-import Distribution.SPDX.Licenses (licenseIdentifiers, licenseExceptions)
-
-license :: ReadP LicenseId
-license = choice (map string licenseIdentifiers)
-
-licenseException :: ReadP LicenseExceptionId
-licenseException = choice (map string licenseExceptions)
-
-elicense :: ReadP LicenseExpression
-elicense = (\l -> ELicense False (Right l) Nothing) <$> license
-
-elicenseWith :: ReadP LicenseExpression
-elicenseWith = (\l e -> ELicense False (Right l) (Just e)) <$> license <* skipSpaces1 <* string "WITH" <* skipSpaces1 <*> licenseException
-
-elicenseAndNewer :: ReadP LicenseExpression
-elicenseAndNewer = (\l -> ELicense True (Right l) Nothing) <$> license <* char '+'
-
-elicenseWithAndNewer :: ReadP LicenseExpression
-elicenseWithAndNewer = (\l e -> ELicense True (Right l) (Just e)) <$> license <* char '+' <* string " WITH " <*> licenseException
-
-elicenseRef :: ReadP LicenseExpression
-elicenseRef = (\licId -> ELicense False (Left $ LicenseRef Nothing licId) Nothing) <$ string "LicenseRef-" <*> idString
-
-elicenseDocRef :: ReadP LicenseExpression
-elicenseDocRef = f <$ string "DocumentRef-" <*> idString <* char ':' <* string "LicenseRef-" <*> idString
-  where f docId licId = ELicense False (Left $ LicenseRef (Just docId) licId) Nothing
-
-idString :: ReadP String
-idString = munch1 p
-  where p '.' = True
-        p '-' = True
-        p c   = isAlphaNum c
-
-skipSpaces1 :: ReadP ()
-skipSpaces1 = () <$ char ' ' <* skipSpaces
-
-parens :: ReadP a -> ReadP a
-parens = between (char '(') (skipSpaces <* char ')')
-
-terminal :: ReadP LicenseExpression
-terminal = choice [ elicense
-                  , elicenseWith
-                  , elicenseAndNewer
-                  , elicenseWithAndNewer
-                  , elicenseRef
-                  , elicenseDocRef
-                  , parens expression
-                  ]
-
-conjunction :: ReadP LicenseExpression
-conjunction = chainr1 terminal (EConjunction <$ skipSpaces1 <* string "AND" <* skipSpaces1)
-
-disjunction :: ReadP LicenseExpression
-disjunction = chainr1 conjunction (EDisjunction <$ skipSpaces1 <* string "OR" <* skipSpaces1)
-
-expression :: ReadP LicenseExpression
-expression = skipSpaces *> disjunction
-
--- | Parse SPDX License Expression
---
--- >>> parseExpression "LGPL-2.1 OR MIT"
--- [EDisjunction (ELicense False "LGPL-2.1" Nothing) (ELicense False "MIT" Nothing)]
-parseExpression :: String -> [LicenseExpression]
-parseExpression = map fst . readP_to_S (expression <* skipSpaces <* eof)
-
-unsafeParseExpr :: String -> LicenseExpression
-unsafeParseExpr s = f . parseExpression $ s
-  where f []      = error $ "Failed parse of license expression: " ++ s
-        f [l]     = l
-        f (_:_:_) = error $ "Ambigious parse of license expression: " ++ s
diff --git a/src/Distribution/SPDX/Ranges.hs b/src/Distribution/SPDX/Ranges.hs
deleted file mode 100644
--- a/src/Distribution/SPDX/Ranges.hs
+++ /dev/null
@@ -1,47 +0,0 @@
-module Distribution.SPDX.Ranges (licenseRanges, lookupLicenseRange) where
-
-import Data.Char
-import Data.List
-import Data.Maybe
-
-import Distribution.SPDX.Types
-import Distribution.SPDX.Licenses
-
-licenseRanges :: [[LicenseId]]
-licenseRanges = filter longerThanSingleton . map f $ prefixes
-  where f p = filter (\l -> p `isPrefixOf` l && restIsNumber l p) licenseIdentifiers
-
--- | Lookup newer licenses we know about
---
--- >>> lookupLicenseRange "MIT"
--- ["MIT"]
---
--- >>> lookupLicenseRange "GPL-2.0"
--- ["GPL-2.0","GPL-3.0"]
---
--- >>> lookupLicenseRange "LGPL-2.0"
--- ["LGPL-2.0","LGPL-2.1","LGPL-3.0"]
-lookupLicenseRange :: LicenseId -> [LicenseId]
-lookupLicenseRange l = fromMaybe [l] $ lookup l ranges'
-
-ranges' :: [(LicenseId, [LicenseId])]
-ranges' = map (\r -> (head r, r)) . filter (not . nullOrSingleton) . concatMap tails $ licenseRanges
-
-nullOrSingleton :: [a] -> Bool
-nullOrSingleton []      = True
-nullOrSingleton [_]     = True
-nullOrSingleton (_:_:_) = False
-
-restIsNumber :: String -> String -> Bool
-restIsNumber l p = all (\c -> isDigit c || c == 'a' || c == 'b' || c == 'c' || c == '.') (drop (length p) l)
-
-pref :: String -> String
-pref = reverse . dropWhile (/= '-') . reverse
-
-prefixes :: [String]
-prefixes = nub (filter (not . null) $ map pref licenseIdentifiers)
-
-longerThanSingleton :: [a] -> Bool
-longerThanSingleton []      = False
-longerThanSingleton [_]     = False
-longerThanSingleton (_:_:_) = True
diff --git a/src/Distribution/SPDX/Types.hs b/src/Distribution/SPDX/Types.hs
deleted file mode 100644
--- a/src/Distribution/SPDX/Types.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE DeriveGeneric #-}
-module Distribution.SPDX.Types where
-
-import Data.Data
-import GHC.Generics
-
-data LicenseRef = LicenseRef
-  { lrDocument :: !(Maybe String)
-  , lrLicense  :: !String
-  }
-  deriving (Eq, Ord, Show, Read, Typeable, Data, Generic)
-
-type LicenseId = String
-type LicenseExceptionId = String
-
-data LicenseExpression = ELicense !Bool !(Either LicenseRef LicenseId) !(Maybe LicenseExceptionId)
-                       | EConjunction !LicenseExpression !LicenseExpression
-                       | EDisjunction !LicenseExpression !LicenseExpression
-  deriving (Eq, Ord, Show, Read, Typeable, Data, Generic)
diff --git a/tests/Tests.hs b/tests/Tests.hs
--- a/tests/Tests.hs
+++ b/tests/Tests.hs
@@ -5,8 +5,8 @@
 import Test.Tasty
 import Test.Tasty.QuickCheck as QC
 
-import Distribution.SPDX
-import Distribution.SPDX.LatticeSyntax
+import Data.SPDX
+import Data.SPDX.LatticeSyntax
 
 main :: IO ()
 main = defaultMain tests
@@ -35,19 +35,29 @@
                                , rangeUnit
                                ]
 
-simpleExprGen :: Gen String
+simpleExprGen :: Gen LicenseId
 simpleExprGen = elements licenseIdentifiers
 
 latticeSyntaxGen :: Gen (LatticeSyntax Char)
 latticeSyntaxGen = sized gen
-  where var = elements "abcdef"
-        gen 0 = LVar <$> var
-        gen n = oneof [ LVar <$> var
+  where var   = LVar <$> elements "abcdef"
+        gen 0 = var
+        gen n = oneof [ var
                       , LMeet <$> gen' <*> gen'
                       , LJoin <$> gen' <*> gen'
                       ]
           where gen' = gen (n `div` 2)
 
+exprGen :: Gen LicenseExpression
+exprGen = sized gen
+  where sim   = ELicense <$> arbitrary <*> (Right <$> simpleExprGen) <*> (pure Nothing)
+        gen 0 = sim
+        gen n = oneof [ sim
+                      , EDisjunction <$> gen' <*> gen'
+                      , EConjunction <$> gen' <*> gen'
+                      ]
+          where gen' = gen (n `div` 2)
+
 simpleUnits :: TestTree
 simpleUnits = testGroup "Simple License Expressions"
   [ invalidExpr "Invalid-Identifier"
@@ -70,7 +80,7 @@
   ]
 
 rangeUnit :: TestTree
-rangeUnit = QC.testProperty "calculated license ranges" $ once $ property $ sort licenseRanges == sort ranges
+rangeUnit = QC.testProperty "calculated license ranges" $ once $ property $ sort (map (map getLicenseId) licenseRanges) == sort ranges
 
 lsProps :: TestTree
 lsProps = testGroup "LatticeSyntax"
@@ -82,13 +92,35 @@
   , QC.testProperty "preorder reflexive" $ forAll latticeSyntaxGen $ \a -> a `preorder` a
   ]
 
+osiLicenseIds :: [LicenseId]
+osiLicenseIds = filter isOsiApproved licenseIdentifiers
+
+osiLicenseExpr :: LicenseExpression
+osiLicenseExpr = foldr1 EConjunction $ map (\l -> ELicense False (Right l) Nothing) osiLicenseIds
+
+isOsiApprovedExpr :: LicenseExpression -> Bool
+isOsiApprovedExpr (ELicense _ (Left _) _) = False
+isOsiApprovedExpr (ELicense _ _ (Just _)) = False
+isOsiApprovedExpr (ELicense False (Right e) Nothing) = isOsiApproved e
+isOsiApprovedExpr (ELicense True (Right e2) Nothing) = any isOsiApproved $ lookupLicenseRange e2
+isOsiApprovedExpr (EConjunction e1 e2) = isOsiApprovedExpr e1 && isOsiApprovedExpr e2
+isOsiApprovedExpr (EDisjunction e1 e2) = isOsiApprovedExpr e1 || isOsiApprovedExpr e2
+
+isOsiApprovedExpr' :: LicenseExpression -> Bool
+isOsiApprovedExpr' e = e `satisfies` osiLicenseExpr
+
+scaleGen :: (Int -> Int) -> Gen a -> Gen a
+scaleGen f g = sized (\n -> resize (f n) g)
+
 qcProps :: TestTree
 qcProps = testGroup "By Quickcheck"
-  [ QC.testProperty "licence identifiers are valid licenses" $ forAll simpleExprGen $ valid
+  [ QC.testProperty "licence identifiers are valid licenses" $ forAll simpleExprGen $ valid . getLicenseId
+  , QC.testProperty "satisfies osi checked" $ forAll (scaleGen (`div` 2) exprGen) $
+      \e -> isOsiApprovedExpr e == isOsiApprovedExpr' e -- Skip
   , lsProps
   ]
 
-ranges :: [[LicenseId]]
+ranges :: [[String]]
 ranges = [
   [
     "AFL-1.1",
