-S
diff --git a/haddock-api/haddock-api.cabal b/haddock-api/haddock-api.cabal
index 3bc222631d..439c058cfc 100644
--- a/haddock-api/haddock-api.cabal
+++ b/haddock-api/haddock-api.cabal
@@ -18,8 +18,10 @@ stability: experimental
data-dir:
resources
data-files:
+ html/solarized.css
html/frames.html
html/haddock-util.js
+ html/highlight.js
html/Classic.theme/haskell_icon.gif
html/Classic.theme/minus.gif
html/Classic.theme/plus.gif
@@ -79,6 +81,12 @@ library
Haddock.Backends.LaTeX
Haddock.Backends.HaddockDB
Haddock.Backends.Hoogle
+ Haddock.Backends.Hyperlinker
+ Haddock.Backends.Hyperlinker.Ast
+ Haddock.Backends.Hyperlinker.Parser
+ Haddock.Backends.Hyperlinker.Renderer
+ Haddock.Backends.Hyperlinker.Types
+ Haddock.Backends.Hyperlinker.Utils
Haddock.ModuleTree
Haddock.Types
Haddock.Doc
@@ -89,6 +97,27 @@ library
Haddock.Convert
Paths_haddock_api
+test-suite spec
+ type: exitcode-stdio-1.0
+ default-language: Haskell2010
+ main-is: Spec.hs
+ ghc-options: -Wall
+
+ hs-source-dirs:
+ test
+ , src
+
+ other-modules:
+ Haddock.Backends.Hyperlinker.ParserSpec
+
+ build-depends:
+ base >= 4.3 && < 4.9
+ , containers
+ , ghc >= 7.10 && < 7.10.2
+
+ , hspec
+ , QuickCheck == 2.*
+
source-repository head
type: git
location: https://github.com/haskell/haddock.git
diff --git a/haddock-api/resources/html/highlight.js b/haddock-api/resources/html/highlight.js
new file mode 100644
index 0000000000..1e903bd0c5
--- /dev/null
+++ b/haddock-api/resources/html/highlight.js
@@ -0,0 +1,27 @@
+
+var highlight = function (on) {
+ return function () {
+ var links = document.getElementsByTagName('a');
+ for (var i = 0; i < links.length; i++) {
+ var that = links[i];
+
+ if (this.href != that.href) {
+ continue;
+ }
+
+ if (on) {
+ that.classList.add("hover-highlight");
+ } else {
+ that.classList.remove("hover-highlight");
+ }
+ }
+ }
+};
+
+window.onload = function () {
+ var links = document.getElementsByTagName('a');
+ for (var i = 0; i < links.length; i++) {
+ links[i].onmouseover = highlight(true);
+ links[i].onmouseout = highlight(false);
+ }
+};
diff --git a/haddock-api/resources/html/solarized.css b/haddock-api/resources/html/solarized.css
new file mode 100644
index 0000000000..e83dc5ec70
--- /dev/null
+++ b/haddock-api/resources/html/solarized.css
@@ -0,0 +1,55 @@
+body {
+ background-color: #fdf6e3;
+}
+
+.hs-identifier {
+ color: #073642;
+}
+
+.hs-identifier.hs-var {
+}
+
+.hs-identifier.hs-type {
+ color: #5f5faf;
+}
+
+.hs-keyword {
+ color: #af005f;
+}
+
+.hs-string, .hs-char {
+ color: #cb4b16;
+}
+
+.hs-number {
+ color: #268bd2;
+}
+
+.hs-operator {
+ color: #d33682;
+}
+
+.hs-glyph, .hs-special {
+ color: #dc322f;
+}
+
+.hs-comment {
+ color: #8a8a8a;
+}
+
+.hs-pragma {
+ color: #2aa198;
+}
+
+.hs-cpp {
+ color: #859900;
+}
+
+a:link, a:visited {
+ text-decoration: none;
+ border-bottom: 1px solid #eee8d5;
+}
+
+a:hover, a.hover-highlight {
+ background-color: #eee8d5;
+}
diff --git a/haddock-api/src/Haddock.hs b/haddock-api/src/Haddock.hs
index 3e58aba386..350a73ead4 100644
--- a/haddock-api/src/Haddock.hs
+++ b/haddock-api/src/Haddock.hs
@@ -30,6 +30,7 @@ import Haddock.Backends.Xhtml
import Haddock.Backends.Xhtml.Themes (getThemes)
import Haddock.Backends.LaTeX
import Haddock.Backends.Hoogle
+import Haddock.Backends.Hyperlinker
import Haddock.Interface
import Haddock.Parser
import Haddock.Types
@@ -45,6 +46,7 @@ import Data.List (isPrefixOf)
import Control.Exception
import Data.Maybe
import Data.IORef
+import Data.Map (Map)
import qualified Data.Map as Map
import System.IO
import System.Exit
@@ -158,6 +160,7 @@ haddockWithGhc ghc args = handleTopExceptions $ do
_ -> return flags
unless (Flag_NoWarnings `elem` flags) $ do
+ hypSrcWarnings flags
forM_ (warnings args) $ \warning -> do
hPutStrLn stderr warning
@@ -226,13 +229,16 @@ renderStep dflags flags qual pkgs interfaces = do
let
ifaceFiles = map snd pkgs
installedIfaces = concatMap ifInstalledIfaces ifaceFiles
- srcMap = Map.fromList [ (ifPackageKey if_, x) | ((_, Just x), if_) <- pkgs ]
- render dflags flags qual interfaces installedIfaces srcMap
+ extSrcMap = Map.fromList $ do
+ ((_, Just path), ifile) <- pkgs
+ iface <- ifInstalledIfaces ifile
+ return (instMod iface, path)
+ render dflags flags qual interfaces installedIfaces extSrcMap
-- | Render the interfaces with whatever backend is specified in the flags.
-render :: DynFlags -> [Flag] -> QualOption -> [Interface] -> [InstalledInterface] -> SrcMap -> IO ()
-render dflags flags qual ifaces installedIfaces srcMap = do
+render :: DynFlags -> [Flag] -> QualOption -> [Interface] -> [InstalledInterface] -> Map Module FilePath -> IO ()
+render dflags flags qual ifaces installedIfaces extSrcMap = do
let
title = fromMaybe "" (optTitle flags)
@@ -243,6 +249,7 @@ render dflags flags qual ifaces installedIfaces srcMap = do
opt_index_url = optIndexUrl flags
odir = outputDir flags
opt_latex_style = optLaTeXStyle flags
+ opt_source_css = optSourceCssFile flags
visibleIfaces = [ i | i <- ifaces, OptHide `notElem` ifaceOptions i ]
@@ -256,10 +263,25 @@ render dflags flags qual ifaces installedIfaces srcMap = do
pkgNameVer = modulePackageInfo dflags flags pkgMod
(srcBase, srcModule, srcEntity, srcLEntity) = sourceUrls flags
- srcMap' = maybe srcMap (\path -> Map.insert pkgKey path srcMap) srcEntity
+
+ srcModule'
+ | Flag_HyperlinkedSource `elem` flags = Just hypSrcModuleUrlFormat
+ | otherwise = srcModule
+
+ srcMap = mkSrcMap $ Map.union
+ (Map.map SrcExternal extSrcMap)
+ (Map.fromList [ (ifaceMod iface, SrcLocal) | iface <- ifaces ])
+
+ pkgSrcMap = Map.mapKeys modulePackageKey extSrcMap
+ pkgSrcMap'
+ | Flag_HyperlinkedSource `elem` flags =
+ Map.insert pkgKey hypSrcModuleNameUrlFormat pkgSrcMap
+ | Just srcNameUrl <- srcEntity = Map.insert pkgKey srcNameUrl pkgSrcMap
+ | otherwise = pkgSrcMap
+
-- TODO: Get these from the interface files as with srcMap
srcLMap' = maybe Map.empty (\path -> Map.singleton pkgKey path) srcLEntity
- sourceUrls' = (srcBase, srcModule, srcMap', srcLMap')
+ sourceUrls' = (srcBase, srcModule', pkgSrcMap', srcLMap')
libDir <- getHaddockLibDir flags
prologue <- getPrologue dflags flags
@@ -308,6 +330,9 @@ render dflags flags qual ifaces installedIfaces srcMap = do
ppLaTeX title pkgStr visibleIfaces odir (fmap _doc prologue) opt_latex_style
libDir
+ when (Flag_HyperlinkedSource `elem` flags) $ do
+ ppHyperlinkedSource odir libDir opt_source_css pretty srcMap ifaces
+
-- | From GHC 7.10, this function has a potential to crash with a
-- nasty message such as @expectJust getPackageDetails@ because
-- package name and versions can no longer reliably be extracted in
@@ -476,6 +501,35 @@ shortcutFlags flags = do
++ "Ported to use the GHC API by David Waern 2006-2008\n"
+-- | Generate some warnings about potential misuse of @--hyperlinked-source@.
+hypSrcWarnings :: [Flag] -> IO ()
+hypSrcWarnings flags = do
+
+ when (hypSrc && any isSourceUrlFlag flags) $
+ hPutStrLn stderr $ concat
+ [ "Warning: "
+ , "--source-* options are ignored when "
+ , "--hyperlinked-source is enabled."
+ ]
+
+ when (not hypSrc && any isSourceCssFlag flags) $
+ hPutStrLn stderr $ concat
+ [ "Warning: "
+ , "source CSS file is specified but "
+ , "--hyperlinked-source is disabled."
+ ]
+
+ where
+ hypSrc = Flag_HyperlinkedSource `elem` flags
+ isSourceUrlFlag (Flag_SourceBaseURL _) = True
+ isSourceUrlFlag (Flag_SourceModuleURL _) = True
+ isSourceUrlFlag (Flag_SourceEntityURL _) = True
+ isSourceUrlFlag (Flag_SourceLEntityURL _) = True
+ isSourceUrlFlag _ = False
+ isSourceCssFlag (Flag_SourceCss _) = True
+ isSourceCssFlag _ = False
+
+
updateHTMLXRefs :: [(DocPaths, InterfaceFile)] -> IO ()
updateHTMLXRefs packages = do
writeIORef html_xrefs_ref (Map.fromList mapping)
diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker.hs b/haddock-api/src/Haddock/Backends/Hyperlinker.hs
new file mode 100644
index 0000000000..248a8a549e
--- /dev/null
+++ b/haddock-api/src/Haddock/Backends/Hyperlinker.hs
@@ -0,0 +1,64 @@
+module Haddock.Backends.Hyperlinker
+ ( ppHyperlinkedSource
+ , module Haddock.Backends.Hyperlinker.Types
+ , module Haddock.Backends.Hyperlinker.Utils
+ ) where
+
+
+import Haddock.Types
+import Haddock.Backends.Hyperlinker.Renderer
+import Haddock.Backends.Hyperlinker.Types
+import Haddock.Backends.Hyperlinker.Utils
+
+import Text.XHtml hiding ((>))
+
+import Data.Maybe
+import System.Directory
+import System.FilePath
+
+
+-- | Generate hyperlinked source for given interfaces.
+--
+-- Note that list of interfaces should also contain interfaces normally hidden
+-- when generating documentation. Otherwise this could lead to dead links in
+-- produced source.
+ppHyperlinkedSource :: FilePath -- ^ Output directory
+ -> FilePath -- ^ Resource directory
+ -> Maybe FilePath -- ^ Custom CSS file path
+ -> Bool -- ^ Flag indicating whether to pretty-print HTML
+ -> SrcMap -- ^ Paths to sources
+ -> [Interface] -- ^ Interfaces for which we create source
+ -> IO ()
+ppHyperlinkedSource outdir libdir mstyle pretty srcs ifaces = do
+ createDirectoryIfMissing True srcdir
+ let cssFile = fromMaybe (defaultCssFile libdir) mstyle
+ copyFile cssFile $ srcdir > srcCssFile
+ copyFile (libdir > "html" > highlightScript) $
+ srcdir > highlightScript
+ mapM_ (ppHyperlinkedModuleSource srcdir pretty srcs) ifaces
+ where
+ srcdir = outdir > hypSrcDir
+
+-- | Generate hyperlinked source for particular interface.
+ppHyperlinkedModuleSource :: FilePath -> Bool -> SrcMap -> Interface
+ -> IO ()
+ppHyperlinkedModuleSource srcdir pretty srcs iface =
+ case ifaceTokenizedSrc iface of
+ Just tokens -> writeFile path . html . render' $ tokens
+ Nothing -> return ()
+ where
+ render' = render (Just srcCssFile) (Just highlightScript) srcs
+ html = if pretty then renderHtml else showHtml
+ path = srcdir > hypSrcModuleFile (ifaceMod iface)
+
+-- | Name of CSS file in output directory.
+srcCssFile :: FilePath
+srcCssFile = "style.css"
+
+-- | Name of highlight script in output and resource directory.
+highlightScript :: FilePath
+highlightScript = "highlight.js"
+
+-- | Path to default CSS file.
+defaultCssFile :: FilePath -> FilePath
+defaultCssFile libdir = libdir > "html" > "solarized.css"
diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Ast.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Ast.hs
new file mode 100644
index 0000000000..71b7366355
--- /dev/null
+++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Ast.hs
@@ -0,0 +1,195 @@
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE RecordWildCards #-}
+
+
+module Haddock.Backends.Hyperlinker.Ast (enrich) where
+
+
+import Haddock.Backends.Hyperlinker.Types
+
+import qualified GHC
+
+import Control.Applicative
+import Data.Data
+import Data.Maybe
+
+
+-- | Add more detailed information to token stream using GHC API.
+enrich :: GHC.RenamedSource -> [Token] -> [RichToken]
+enrich src =
+ map $ \token -> RichToken
+ { rtkToken = token
+ , rtkDetails = enrichToken token detailsMap
+ }
+ where
+ detailsMap = concatMap ($ src)
+ [ variables
+ , types
+ , decls
+ , binds
+ , imports
+ ]
+
+-- | A map containing association between source locations and "details" of
+-- this location.
+--
+-- For the time being, it is just a list of pairs. However, looking up things
+-- in such structure has linear complexity. We cannot use any hashmap-like
+-- stuff because source locations are not ordered. In the future, this should
+-- be replaced with interval tree data structure.
+type DetailsMap = [(GHC.SrcSpan, TokenDetails)]
+
+lookupBySpan :: Span -> DetailsMap -> Maybe TokenDetails
+lookupBySpan tspan = listToMaybe . map snd . filter (matches tspan . fst)
+
+enrichToken :: Token -> DetailsMap -> Maybe TokenDetails
+enrichToken (Token typ _ spn) dm
+ | typ `elem` [TkIdentifier, TkOperator] = lookupBySpan spn dm
+enrichToken _ _ = Nothing
+
+-- | Obtain details map for variables ("normally" used identifiers).
+variables :: GHC.RenamedSource -> DetailsMap
+variables =
+ everything (<|>) (var `combine` rec)
+ where
+ var term = case cast term of
+ (Just (GHC.L sspan (GHC.HsVar name))) ->
+ pure (sspan, RtkVar name)
+ (Just (GHC.L _ (GHC.RecordCon (GHC.L sspan name) _ _))) ->
+ pure (sspan, RtkVar name)
+ _ -> empty
+ rec term = case cast term of
+ Just (GHC.HsRecField (GHC.L sspan name) (_ :: GHC.LHsExpr GHC.Name) _) ->
+ pure (sspan, RtkVar name)
+ _ -> empty
+
+-- | Obtain details map for types.
+types :: GHC.RenamedSource -> DetailsMap
+types =
+ everything (<|>) ty
+ where
+ ty term = case cast term of
+ (Just (GHC.L sspan (GHC.HsTyVar name))) ->
+ pure (sspan, RtkType name)
+ _ -> empty
+
+-- | Obtain details map for identifier bindings.
+--
+-- That includes both identifiers bound by pattern matching or declared using
+-- ordinary assignment (in top-level declarations, let-expressions and where
+-- clauses).
+binds :: GHC.RenamedSource -> DetailsMap
+binds =
+ everything (<|>) (fun `combine` pat `combine` tvar)
+ where
+ fun term = case cast term of
+ (Just (GHC.FunBind (GHC.L sspan name) _ _ _ _ _ :: GHC.HsBind GHC.Name)) ->
+ pure (sspan, RtkBind name)
+ _ -> empty
+ pat term = case cast term of
+ (Just (GHC.L sspan (GHC.VarPat name))) ->
+ pure (sspan, RtkBind name)
+ (Just (GHC.L _ (GHC.ConPatIn (GHC.L sspan name) recs))) ->
+ [(sspan, RtkVar name)] ++ everything (<|>) rec recs
+ (Just (GHC.L _ (GHC.AsPat (GHC.L sspan name) _))) ->
+ pure (sspan, RtkBind name)
+ _ -> empty
+ rec term = case cast term of
+ (Just (GHC.HsRecField (GHC.L sspan name) (_ :: GHC.LPat GHC.Name) _)) ->
+ pure (sspan, RtkVar name)
+ _ -> empty
+ tvar term = case cast term of
+ (Just (GHC.L sspan (GHC.UserTyVar name))) ->
+ pure (sspan, RtkBind name)
+ (Just (GHC.L _ (GHC.KindedTyVar (GHC.L sspan name) _))) ->
+ pure (sspan, RtkBind name)
+ _ -> empty
+
+-- | Obtain details map for top-level declarations.
+decls :: GHC.RenamedSource -> DetailsMap
+decls (group, _, _, _) = concatMap ($ group)
+ [ concat . map typ . concat . map GHC.group_tyclds . GHC.hs_tyclds
+ , everything (<|>) fun . GHC.hs_valds
+ , everything (<|>) (con `combine` ins)
+ ]
+ where
+ typ (GHC.L _ t) = case t of
+ GHC.DataDecl name _ _ _ -> pure . decl $ name
+ GHC.SynDecl name _ _ _ -> pure . decl $ name
+ GHC.FamDecl fam -> pure . decl $ GHC.fdLName fam
+ GHC.ClassDecl{..} -> [decl tcdLName] ++ concatMap sig tcdSigs
+ fun term = case cast term of
+ (Just (GHC.FunBind (GHC.L sspan name) _ _ _ _ _ :: GHC.HsBind GHC.Name))
+ | GHC.isExternalName name -> pure (sspan, RtkDecl name)
+ _ -> empty
+ con term = case cast term of
+ (Just cdcl) ->
+ map decl (GHC.con_names cdcl) ++ everything (<|>) fld cdcl
+ Nothing -> empty
+ ins term = case cast term of
+ (Just (GHC.DataFamInstD inst)) -> pure . tyref $ GHC.dfid_tycon inst
+ (Just (GHC.TyFamInstD (GHC.TyFamInstDecl (GHC.L _ eqn) _))) ->
+ pure . tyref $ GHC.tfe_tycon eqn
+ _ -> empty
+ fld term = case cast term of
+ Just field -> map decl $ GHC.cd_fld_names field
+ Nothing -> empty
+ sig (GHC.L _ (GHC.TypeSig names _ _)) = map decl names
+ sig _ = []
+ decl (GHC.L sspan name) = (sspan, RtkDecl name)
+ tyref (GHC.L sspan name) = (sspan, RtkType name)
+
+-- | Obtain details map for import declarations.
+--
+-- This map also includes type and variable details for items in export and
+-- import lists.
+imports :: GHC.RenamedSource -> DetailsMap
+imports src@(_, imps, _, _) =
+ everything (<|>) ie src ++ mapMaybe (imp . GHC.unLoc) imps
+ where
+ ie term = case cast term of
+ (Just (GHC.IEVar v)) -> pure $ var v
+ (Just (GHC.IEThingAbs t)) -> pure $ typ t
+ (Just (GHC.IEThingAll t)) -> pure $ typ t
+ (Just (GHC.IEThingWith t vs)) -> [typ t] ++ map var vs
+ _ -> empty
+ typ (GHC.L sspan name) = (sspan, RtkType name)
+ var (GHC.L sspan name) = (sspan, RtkVar name)
+ imp idecl | not . GHC.ideclImplicit $ idecl =
+ let (GHC.L sspan name) = GHC.ideclName idecl
+ in Just (sspan, RtkModule name)
+ imp _ = Nothing
+
+-- | Check whether token stream span matches GHC source span.
+--
+-- Currently, it is implemented as checking whether "our" span is contained
+-- in GHC span. The reason for that is because GHC span are generally wider
+-- and may spread across couple tokens. For example, @(>>=)@ consists of three
+-- tokens: @(@, @>>=@, @)@, but GHC source span associated with @>>=@ variable
+-- contains @(@ and @)@. Similarly, qualified identifiers like @Foo.Bar.quux@
+-- are tokenized as @Foo@, @.@, @Bar@, @.@, @quux@ but GHC source span
+-- associated with @quux@ contains all five elements.
+matches :: Span -> GHC.SrcSpan -> Bool
+matches tspan (GHC.RealSrcSpan aspan)
+ | saspan <= stspan && etspan <= easpan = True
+ where
+ stspan = (posRow . spStart $ tspan, posCol . spStart $ tspan)
+ etspan = (posRow . spEnd $ tspan, posCol . spEnd $ tspan)
+ saspan = (GHC.srcSpanStartLine aspan, GHC.srcSpanStartCol aspan)
+ easpan = (GHC.srcSpanEndLine aspan, GHC.srcSpanEndCol aspan)
+matches _ _ = False
+
+-- | Perform a query on each level of a tree.
+--
+-- This is stolen directly from SYB package and copied here to not introduce
+-- additional dependencies.
+everything :: (r -> r -> r) -> (forall a. Data a => a -> r)
+ -> (forall a. Data a => a -> r)
+everything k f x = foldl k (f x) (gmapQ (everything k f) x)
+
+-- | Combine two queries into one using alternative combinator.
+combine :: Alternative f => (forall a. Data a => a -> f r)
+ -> (forall a. Data a => a -> f r)
+ -> (forall a. Data a => a -> f r)
+combine f g x = f x <|> g x
diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs
new file mode 100644
index 0000000000..e206413e27
--- /dev/null
+++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs
@@ -0,0 +1,204 @@
+module Haddock.Backends.Hyperlinker.Parser (parse) where
+
+
+import Data.Char
+import Data.List
+import Data.Maybe
+
+import Haddock.Backends.Hyperlinker.Types
+
+
+-- | Turn source code string into a stream of more descriptive tokens.
+--
+-- Result should retain original file layout (including comments, whitespace,
+-- etc.), i.e. the following "law" should hold:
+--
+-- @concat . map 'tkValue' . 'parse' = id@
+parse :: String -> [Token]
+parse = tokenize . tag . chunk
+
+-- | Split raw source string to more meaningful chunks.
+--
+-- This is the initial stage of tokenization process. Each chunk is either
+-- a comment (including comment delimiters), a whitespace string, preprocessor
+-- macro (and all its content until the end of a line) or valid Haskell lexeme.
+chunk :: String -> [String]
+chunk [] = []
+chunk str@(c:_)
+ | isSpace c =
+ let (space, mcpp, rest) = spanSpaceOrCpp str
+ in [space] ++ maybeToList mcpp ++ chunk rest
+chunk str
+ | "--" `isPrefixOf` str = chunk' $ spanToNewline str
+ | "{-" `isPrefixOf` str = chunk' $ chunkComment 0 str
+ | otherwise = case lex str of
+ (tok:_) -> chunk' tok
+ [] -> [str]
+ where
+ chunk' (c, rest) = c:(chunk rest)
+
+-- | Split input to "first line" string and the rest of it.
+--
+-- Ideally, this should be done simply with @'break' (== '\n')@. However,
+-- Haskell also allows line-unbreaking (or whatever it is called) so things
+-- are not as simple and this function deals with that.
+spanToNewline :: String -> (String, String)
+spanToNewline [] = ([], [])
+spanToNewline ('\\':'\n':str) =
+ let (str', rest) = spanToNewline str
+ in ('\\':'\n':str', rest)
+spanToNewline str@('\n':_) = ("", str)
+spanToNewline (c:str) =
+ let (str', rest) = spanToNewline str
+ in (c:str', rest)
+
+-- | Split input to whitespace string, (optional) preprocessor directive and
+-- the rest of it.
+--
+-- Again, using something like @'span' 'isSpace'@ would be nice to chunk input
+-- to whitespace. The problem is with /#/ symbol - if it is placed at the very
+-- beginning of a line, it should be recognized as preprocessor macro. In any
+-- other case, it is ordinary Haskell symbol and can be used to declare
+-- operators. Hence, while dealing with whitespace we also check whether there
+-- happens to be /#/ symbol just after a newline character - if that is the
+-- case, we begin treating the whole line as preprocessor macro.
+spanSpaceOrCpp :: String -> (String, Maybe String, String)
+spanSpaceOrCpp ('\n':'#':str) =
+ let (str', rest) = spanToNewline str
+ in ("\n", Just $ '#':str', rest)
+spanSpaceOrCpp (c:str')
+ | isSpace c =
+ let (space, mcpp, rest) = spanSpaceOrCpp str'
+ in (c:space, mcpp, rest)
+spanSpaceOrCpp str = ("", Nothing, str)
+
+-- | Split input to comment content (including delimiters) and the rest.
+--
+-- Again, some more logic than simple 'span' is required because of Haskell
+-- comment nesting policy.
+chunkComment :: Int -> String -> (String, String)
+chunkComment _ [] = ("", "")
+chunkComment depth ('{':'-':str) =
+ let (c, rest) = chunkComment (depth + 1) str
+ in ("{-" ++ c, rest)
+chunkComment depth ('-':'}':str)
+ | depth == 1 = ("-}", str)
+ | otherwise =
+ let (c, rest) = chunkComment (depth - 1) str
+ in ("-}" ++ c, rest)
+chunkComment depth (e:str) =
+ let (c, rest) = chunkComment depth str
+ in (e:c, rest)
+
+-- | Assign source location for each chunk in given stream.
+tag :: [String] -> [(Span, String)]
+tag =
+ reverse . snd . foldl aux (Position 1 1, [])
+ where
+ aux (pos, cs) str =
+ let pos' = foldl move pos str
+ in (pos', (Span pos pos', str):cs)
+ move pos '\n' = pos { posRow = posRow pos + 1, posCol = 1 }
+ move pos _ = pos { posCol = posCol pos + 1 }
+
+-- | Turn unrecognised chunk stream to more descriptive token stream.
+tokenize :: [(Span, String)] -> [Token]
+tokenize =
+ map aux
+ where
+ aux (sp, str) = Token
+ { tkType = classify str
+ , tkValue = str
+ , tkSpan = sp
+ }
+
+-- | Classify given string as appropriate Haskell token.
+--
+-- This method is based on Haskell 98 Report lexical structure description:
+-- https://www.haskell.org/onlinereport/lexemes.html
+--
+-- However, this is probably far from being perfect and most probably does not
+-- handle correctly all corner cases.
+classify :: String -> TokenType
+classify str
+ | "--" `isPrefixOf` str = TkComment
+ | "{-#" `isPrefixOf` str = TkPragma
+ | "{-" `isPrefixOf` str = TkComment
+classify str@(c:_)
+ | isSpace c = TkSpace
+ | isDigit c = TkNumber
+ | c `elem` special = TkSpecial
+ | str `elem` glyphs = TkGlyph
+ | all (`elem` symbols) str = TkOperator
+ | c == '#' = TkCpp
+ | c == '"' = TkString
+ | c == '\'' = TkChar
+classify str
+ | str `elem` keywords = TkKeyword
+ | isIdentifier str = TkIdentifier
+ | otherwise = TkUnknown
+
+keywords :: [String]
+keywords =
+ [ "as"
+ , "case"
+ , "class"
+ , "data"
+ , "default"
+ , "deriving"
+ , "do"
+ , "else"
+ , "hiding"
+ , "if"
+ , "import"
+ , "in"
+ , "infix"
+ , "infixl"
+ , "infixr"
+ , "instance"
+ , "let"
+ , "module"
+ , "newtype"
+ , "of"
+ , "qualified"
+ , "then"
+ , "type"
+ , "where"
+ , "forall"
+ , "family"
+ , "mdo"
+ ]
+
+glyphs :: [String]
+glyphs =
+ [ ".."
+ , ":"
+ , "::"
+ , "="
+ , "\\"
+ , "|"
+ , "<-"
+ , "->"
+ , "@"
+ , "~"
+ , "~#"
+ , "=>"
+ , "-"
+ , "!"
+ ]
+
+special :: [Char]
+special = "()[]{},;`"
+
+-- TODO: Add support for any Unicode symbol or punctuation.
+-- source: http://stackoverflow.com/questions/10548170/what-characters-are-permitted-for-haskell-operators
+symbols :: [Char]
+symbols = "!#$%&*+./<=>?@\\^|-~:"
+
+isIdentifier :: String -> Bool
+isIdentifier (s:str)
+ | (isLower' s || isUpper s) && all isAlphaNum' str = True
+ where
+ isLower' c = isLower c || c == '_'
+ isAlphaNum' c = isAlphaNum c || c == '_' || c == '\''
+isIdentifier _ = False
diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs
new file mode 100644
index 0000000000..5037421a22
--- /dev/null
+++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs
@@ -0,0 +1,169 @@
+module Haddock.Backends.Hyperlinker.Renderer (render) where
+
+
+import Haddock.Backends.Hyperlinker.Types
+import Haddock.Backends.Hyperlinker.Utils
+
+import qualified GHC
+import qualified Name as GHC
+import qualified Unique as GHC
+
+import System.FilePath.Posix ((>))
+
+import Data.List
+import Data.Maybe
+import Data.Monoid
+import qualified Data.Map as Map
+
+import Text.XHtml (Html, HtmlAttr, (!))
+import qualified Text.XHtml as Html
+
+
+type StyleClass = String
+
+
+render :: Maybe FilePath -> Maybe FilePath -> SrcMap -> [RichToken]
+ -> Html
+render mcss mjs srcs tokens = header mcss mjs <> body srcs tokens
+
+
+data TokenGroup
+ = GrpNormal Token
+ | GrpRich TokenDetails [Token]
+
+
+-- | Group consecutive tokens pointing to the same element.
+--
+-- We want to render qualified identifiers as one entity. For example,
+-- @Bar.Baz.foo@ consists of 5 tokens (@Bar@, @.@, @Baz@, @.@, @foo@) but for
+-- better user experience when highlighting and clicking links, these tokens
+-- should be regarded as one identifier. Therefore, before rendering we must
+-- group consecutive elements pointing to the same 'GHC.Name' (note that even
+-- dot token has it if it is part of qualified name).
+groupTokens :: [RichToken] -> [TokenGroup]
+groupTokens [] = []
+groupTokens ((RichToken tok Nothing):rest) = (GrpNormal tok):(groupTokens rest)
+groupTokens ((RichToken tok (Just det)):rest) =
+ let (grp, rest') = span same rest
+ in (GrpRich det (tok:(map rtkToken grp))):(groupTokens rest')
+ where
+ same (RichToken _ (Just det')) = det == det'
+ same _ = False
+
+
+body :: SrcMap -> [RichToken] -> Html
+body srcs tokens =
+ Html.body . Html.pre $ hypsrc
+ where
+ hypsrc = mconcat . map (tokenGroup srcs) . groupTokens $ tokens
+
+
+header :: Maybe FilePath -> Maybe FilePath -> Html
+header mcss mjs
+ | isNothing mcss && isNothing mjs = Html.noHtml
+header mcss mjs =
+ Html.header $ css mcss <> js mjs
+ where
+ css Nothing = Html.noHtml
+ css (Just cssFile) = Html.thelink Html.noHtml !
+ [ Html.rel "stylesheet"
+ , Html.thetype "text/css"
+ , Html.href cssFile
+ ]
+ js Nothing = Html.noHtml
+ js (Just scriptFile) = Html.script Html.noHtml !
+ [ Html.thetype "text/javascript"
+ , Html.src scriptFile
+ ]
+
+
+tokenGroup :: SrcMap -> TokenGroup -> Html
+tokenGroup _ (GrpNormal tok) =
+ tokenSpan tok ! attrs
+ where
+ attrs = [ multiclass . tokenStyle . tkType $ tok ]
+tokenGroup srcs (GrpRich det tokens) =
+ externalAnchor det . internalAnchor det . hyperlink srcs det $ content
+ where
+ content = mconcat . map (richToken det) $ tokens
+
+
+richToken :: TokenDetails -> Token -> Html
+richToken det tok =
+ tokenSpan tok ! [ multiclass style ]
+ where
+ style = (tokenStyle . tkType) tok ++ richTokenStyle det
+
+
+tokenSpan :: Token -> Html
+tokenSpan = Html.thespan . Html.toHtml . tkValue
+
+
+richTokenStyle :: TokenDetails -> [StyleClass]
+richTokenStyle (RtkVar _) = ["hs-var"]
+richTokenStyle (RtkType _) = ["hs-type"]
+richTokenStyle _ = []
+
+tokenStyle :: TokenType -> [StyleClass]
+tokenStyle TkIdentifier = ["hs-identifier"]
+tokenStyle TkKeyword = ["hs-keyword"]
+tokenStyle TkString = ["hs-string"]
+tokenStyle TkChar = ["hs-char"]
+tokenStyle TkNumber = ["hs-number"]
+tokenStyle TkOperator = ["hs-operator"]
+tokenStyle TkGlyph = ["hs-glyph"]
+tokenStyle TkSpecial = ["hs-special"]
+tokenStyle TkSpace = []
+tokenStyle TkComment = ["hs-comment"]
+tokenStyle TkCpp = ["hs-cpp"]
+tokenStyle TkPragma = ["hs-pragma"]
+tokenStyle TkUnknown = []
+
+multiclass :: [StyleClass] -> HtmlAttr
+multiclass = Html.theclass . intercalate " "
+
+externalAnchor :: TokenDetails -> Html -> Html
+externalAnchor (RtkDecl name) content =
+ Html.anchor content ! [ Html.name $ externalAnchorIdent name ]
+externalAnchor _ content = content
+
+internalAnchor :: TokenDetails -> Html -> Html
+internalAnchor (RtkBind name) content =
+ Html.anchor content ! [ Html.name $ internalAnchorIdent name ]
+internalAnchor _ content = content
+
+externalAnchorIdent :: GHC.Name -> String
+externalAnchorIdent = hypSrcNameUrl
+
+internalAnchorIdent :: GHC.Name -> String
+internalAnchorIdent = ("local-" ++) . show . GHC.getKey . GHC.nameUnique
+
+hyperlink :: SrcMap -> TokenDetails -> Html -> Html
+hyperlink srcs details = case rtkName details of
+ Left name ->
+ if GHC.isInternalName name
+ then internalHyperlink name
+ else externalNameHyperlink srcs name
+ Right name -> externalModHyperlink srcs name
+
+internalHyperlink :: GHC.Name -> Html -> Html
+internalHyperlink name content =
+ Html.anchor content ! [ Html.href $ "#" ++ internalAnchorIdent name ]
+
+externalNameHyperlink :: SrcMap -> GHC.Name -> Html -> Html
+externalNameHyperlink (srcs, _) name content = case Map.lookup mdl srcs of
+ Just SrcLocal -> Html.anchor content !
+ [ Html.href $ hypSrcModuleNameUrl mdl name ]
+ Just (SrcExternal path) -> Html.anchor content !
+ [ Html.href $ path > hypSrcModuleNameUrl mdl name ]
+ Nothing -> content
+ where
+ mdl = GHC.nameModule name
+
+externalModHyperlink :: SrcMap -> GHC.ModuleName -> Html -> Html
+externalModHyperlink (_, srcs) name content = case Map.lookup name srcs of
+ Just SrcLocal -> Html.anchor content !
+ [ Html.href $ hypSrcModuleUrl' name ]
+ Just (SrcExternal path) -> Html.anchor content !
+ [ Html.href $ path > hypSrcModuleUrl' name ]
+ Nothing -> content
diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs
new file mode 100644
index 0000000000..5f4dbc8cc5
--- /dev/null
+++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs
@@ -0,0 +1,85 @@
+module Haddock.Backends.Hyperlinker.Types where
+
+
+import qualified GHC
+
+import Data.Map (Map)
+import qualified Data.Map as Map
+
+
+data Token = Token
+ { tkType :: TokenType
+ , tkValue :: String
+ , tkSpan :: Span
+ }
+
+data Position = Position
+ { posRow :: !Int
+ , posCol :: !Int
+ }
+
+data Span = Span
+ { spStart :: Position
+ , spEnd :: Position
+ }
+
+data TokenType
+ = TkIdentifier
+ | TkKeyword
+ | TkString
+ | TkChar
+ | TkNumber
+ | TkOperator
+ | TkGlyph
+ | TkSpecial
+ | TkSpace
+ | TkComment
+ | TkCpp
+ | TkPragma
+ | TkUnknown
+ deriving (Show, Eq)
+
+
+data RichToken = RichToken
+ { rtkToken :: Token
+ , rtkDetails :: Maybe TokenDetails
+ }
+
+data TokenDetails
+ = RtkVar GHC.Name
+ | RtkType GHC.Name
+ | RtkBind GHC.Name
+ | RtkDecl GHC.Name
+ | RtkModule GHC.ModuleName
+ deriving (Eq)
+
+
+rtkName :: TokenDetails -> Either GHC.Name GHC.ModuleName
+rtkName (RtkVar name) = Left name
+rtkName (RtkType name) = Left name
+rtkName (RtkBind name) = Left name
+rtkName (RtkDecl name) = Left name
+rtkName (RtkModule name) = Right name
+
+
+-- | Path for making cross-package hyperlinks in generated sources.
+--
+-- Used in 'SrcMap' to determine whether module originates in current package
+-- or in an external package.
+data SrcPath
+ = SrcExternal FilePath
+ | SrcLocal
+
+-- | Mapping from modules to cross-package source paths.
+--
+-- This mapping is actually a pair of maps instead of just one map. The reason
+-- for this is because when hyperlinking modules in import lists we have no
+-- 'GHC.Module' available. On the other hand, we can't just use map with
+-- 'GHC.ModuleName' as indices because certain modules may have common name
+-- but originate in different packages. Hence, we use both /rich/ and /poor/
+-- versions, where the /poor/ is just projection of /rich/ one cached in pair
+-- for better performance.
+type SrcMap = (Map GHC.Module SrcPath, Map GHC.ModuleName SrcPath)
+
+mkSrcMap :: Map GHC.Module SrcPath -> SrcMap
+mkSrcMap srcs = (srcs, Map.mapKeys GHC.moduleName srcs)
diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs
new file mode 100644
index 0000000000..db2bfc76e8
--- /dev/null
+++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs
@@ -0,0 +1,47 @@
+module Haddock.Backends.Hyperlinker.Utils
+ ( hypSrcDir, hypSrcModuleFile, hypSrcModuleFile'
+ , hypSrcModuleUrl, hypSrcModuleUrl', hypSrcNameUrl, hypSrcModuleNameUrl
+ , hypSrcModuleUrlFormat, hypSrcModuleNameUrlFormat,
+ ) where
+
+
+import Haddock.Backends.Xhtml.Utils
+
+import GHC
+import System.FilePath.Posix ((>))
+
+
+hypSrcDir :: FilePath
+hypSrcDir = "src"
+
+hypSrcModuleFile :: Module -> FilePath
+hypSrcModuleFile = hypSrcModuleFile' . moduleName
+
+hypSrcModuleFile' :: ModuleName -> FilePath
+hypSrcModuleFile' mdl = spliceURL'
+ Nothing (Just mdl) Nothing Nothing moduleFormat
+
+hypSrcModuleUrl :: Module -> String
+hypSrcModuleUrl = hypSrcModuleFile
+
+hypSrcModuleUrl' :: ModuleName -> String
+hypSrcModuleUrl' = hypSrcModuleFile'
+
+hypSrcNameUrl :: Name -> String
+hypSrcNameUrl name = spliceURL
+ Nothing Nothing (Just name) Nothing nameFormat
+
+hypSrcModuleNameUrl :: Module -> Name -> String
+hypSrcModuleNameUrl mdl name = hypSrcModuleUrl mdl ++ "#" ++ hypSrcNameUrl name
+
+hypSrcModuleUrlFormat :: String
+hypSrcModuleUrlFormat = hypSrcDir > moduleFormat
+
+hypSrcModuleNameUrlFormat :: String
+hypSrcModuleNameUrlFormat = hypSrcModuleUrlFormat ++ "#" ++ nameFormat
+
+moduleFormat :: String
+moduleFormat = "%{MODULE}.html"
+
+nameFormat :: String
+nameFormat = "%{NAME}"
diff --git a/haddock-api/src/Haddock/Backends/Xhtml/Utils.hs b/haddock-api/src/Haddock/Backends/Xhtml/Utils.hs
index cbcbbd6da8..5166549ad1 100644
--- a/haddock-api/src/Haddock/Backends/Xhtml/Utils.hs
+++ b/haddock-api/src/Haddock/Backends/Xhtml/Utils.hs
@@ -14,7 +14,7 @@ module Haddock.Backends.Xhtml.Utils (
renderToString,
namedAnchor, linkedAnchor,
- spliceURL,
+ spliceURL, spliceURL',
groupId,
(<+>), (<=>), char,
@@ -29,7 +29,6 @@ module Haddock.Backends.Xhtml.Utils (
) where
-import Haddock.GhcUtils
import Haddock.Utils
import Data.Maybe
@@ -38,18 +37,31 @@ import Text.XHtml hiding ( name, title, p, quote )
import qualified Text.XHtml as XHtml
import GHC ( SrcSpan(..), srcSpanStartLine, Name )
-import Module ( Module )
+import Module ( Module, ModuleName, moduleName, moduleNameString )
import Name ( getOccString, nameOccName, isValOcc )
+-- | Replace placeholder string elements with provided values.
+--
+-- Used to generate URL for customized external paths, usually provided with
+-- @--source-module@, @--source-entity@ and related command-line arguments.
+--
+-- >>> spliceURL Nothing mmod mname Nothing "output/%{MODULE}.hs#%{NAME}"
+-- "output/Foo.hs#foo"
spliceURL :: Maybe FilePath -> Maybe Module -> Maybe GHC.Name ->
Maybe SrcSpan -> String -> String
-spliceURL maybe_file maybe_mod maybe_name maybe_loc = run
+spliceURL mfile mmod = spliceURL' mfile (moduleName <$> mmod)
+
+
+-- | Same as 'spliceURL' but takes 'ModuleName' instead of 'Module'.
+spliceURL' :: Maybe FilePath -> Maybe ModuleName -> Maybe GHC.Name ->
+ Maybe SrcSpan -> String -> String
+spliceURL' maybe_file maybe_mod maybe_name maybe_loc = run
where
file = fromMaybe "" maybe_file
mdl = case maybe_mod of
Nothing -> ""
- Just m -> moduleString m
+ Just m -> moduleNameString m
(name, kind) =
case maybe_name of
diff --git a/haddock-api/src/Haddock/Interface/Create.hs b/haddock-api/src/Haddock/Interface/Create.hs
index 7491a01e94..0599151e3c 100644
--- a/haddock-api/src/Haddock/Interface/Create.hs
+++ b/haddock-api/src/Haddock/Interface/Create.hs
@@ -21,6 +21,9 @@ import Haddock.GhcUtils
import Haddock.Utils
import Haddock.Convert
import Haddock.Interface.LexParseRn
+import Haddock.Backends.Hyperlinker.Types
+import Haddock.Backends.Hyperlinker.Ast as Hyperlinker
+import Haddock.Backends.Hyperlinker.Parser as Hyperlinker
import qualified Data.Map as M
import Data.Map (Map)
@@ -122,6 +125,8 @@ createInterface tm flags modMap instIfaceMap = do
mkAliasMap dflags $ tm_renamed_source tm
modWarn = moduleWarning dflags gre warnings
+ tokenizedSrc <- mkMaybeTokenizedSrc flags tm
+
return $! Interface {
ifaceMod = mdl
, ifaceOrigFilename = msHsFilePath ms
@@ -145,6 +150,7 @@ createInterface tm flags modMap instIfaceMap = do
, ifaceFamInstances = fam_instances
, ifaceHaddockCoverage = coverage
, ifaceWarningMap = warningMap
+ , ifaceTokenizedSrc = tokenizedSrc
}
mkAliasMap :: DynFlags -> Maybe RenamedSource -> M.Map Module ModuleName
@@ -861,6 +867,30 @@ seqList :: [a] -> ()
seqList [] = ()
seqList (x : xs) = x `seq` seqList xs
+mkMaybeTokenizedSrc :: [Flag] -> TypecheckedModule
+ -> ErrMsgGhc (Maybe [RichToken])
+mkMaybeTokenizedSrc flags tm
+ | Flag_HyperlinkedSource `elem` flags = case renamedSource tm of
+ Just src -> do
+ tokens <- liftGhcToErrMsgGhc . liftIO $ mkTokenizedSrc summary src
+ return $ Just tokens
+ Nothing -> do
+ liftErrMsg . tell . pure $ concat
+ [ "Warning: Cannot hyperlink module \""
+ , moduleNameString . ms_mod_name $ summary
+ , "\" because renamed source is not available"
+ ]
+ return Nothing
+ | otherwise = return Nothing
+ where
+ summary = pm_mod_summary . tm_parsed_module $ tm
+
+mkTokenizedSrc :: ModSummary -> RenamedSource -> IO [RichToken]
+mkTokenizedSrc ms src =
+ Hyperlinker.enrich src . Hyperlinker.parse <$> rawSrc
+ where
+ rawSrc = readFile $ msHsFilePath ms
+
-- | Find a stand-alone documentation comment by its name.
findNamedDoc :: String -> [HsDecl Name] -> ErrMsgM (Maybe HsDocString)
findNamedDoc name = search
diff --git a/haddock-api/src/Haddock/InterfaceFile.hs b/haddock-api/src/Haddock/InterfaceFile.hs
index 4b39d31546..d5762ce8e2 100644
--- a/haddock-api/src/Haddock/InterfaceFile.hs
+++ b/haddock-api/src/Haddock/InterfaceFile.hs
@@ -14,7 +14,7 @@
-- Reading and writing the .haddock interface file
-----------------------------------------------------------------------------
module Haddock.InterfaceFile (
- InterfaceFile(..), ifPackageKey,
+ InterfaceFile(..), ifModule, ifPackageKey,
readInterfaceFile, nameCacheFromGhc, freshNameCache, NameCacheAccessor,
writeInterfaceFile, binaryInterfaceVersion, binaryInterfaceVersionCompatibility
) where
@@ -51,11 +51,14 @@ data InterfaceFile = InterfaceFile {
}
-ifPackageKey :: InterfaceFile -> PackageKey
-ifPackageKey if_ =
+ifModule :: InterfaceFile -> Module
+ifModule if_ =
case ifInstalledIfaces if_ of
[] -> error "empty InterfaceFile"
- iface:_ -> modulePackageKey $ instMod iface
+ iface:_ -> instMod iface
+
+ifPackageKey :: InterfaceFile -> PackageKey
+ifPackageKey = modulePackageKey . ifModule
binaryInterfaceMagic :: Word32
diff --git a/haddock-api/src/Haddock/Options.hs b/haddock-api/src/Haddock/Options.hs
index e847333eda..f84989efde 100644
--- a/haddock-api/src/Haddock/Options.hs
+++ b/haddock-api/src/Haddock/Options.hs
@@ -21,6 +21,7 @@ module Haddock.Options (
optContentsUrl,
optIndexUrl,
optCssFile,
+ optSourceCssFile,
sourceUrls,
wikiUrls,
optDumpInterfaceFile,
@@ -66,6 +67,8 @@ data Flag
| Flag_WikiEntityURL String
| Flag_LaTeX
| Flag_LaTeXStyle String
+ | Flag_HyperlinkedSource
+ | Flag_SourceCss String
| Flag_Help
| Flag_Verbosity String
| Flag_Version
@@ -116,6 +119,10 @@ options backwardsCompat =
Option ['U'] ["use-unicode"] (NoArg Flag_UseUnicode) "use Unicode in HTML output",
Option [] ["hoogle"] (NoArg Flag_Hoogle)
"output for Hoogle; you may want --package-name and --package-version too",
+ Option [] ["hyperlinked-source"] (NoArg Flag_HyperlinkedSource)
+ "generate highlighted and hyperlinked source code (for use with --html)",
+ Option [] ["source-css"] (ReqArg Flag_SourceCss "FILE")
+ "use custom CSS file instead of default one in hyperlinked source",
Option [] ["source-base"] (ReqArg Flag_SourceBaseURL "URL")
"URL for a source code link on the contents\nand index pages",
Option ['s'] (if backwardsCompat then ["source", "source-module"] else ["source-module"])
@@ -239,6 +246,8 @@ optIndexUrl flags = optLast [ url | Flag_UseIndex url <- flags ]
optCssFile :: [Flag] -> Maybe FilePath
optCssFile flags = optLast [ str | Flag_CSS str <- flags ]
+optSourceCssFile :: [Flag] -> Maybe FilePath
+optSourceCssFile flags = optLast [ str | Flag_SourceCss str <- flags ]
sourceUrls :: [Flag] -> (Maybe String, Maybe String, Maybe String, Maybe String)
sourceUrls flags =
diff --git a/haddock-api/src/Haddock/Types.hs b/haddock-api/src/Haddock/Types.hs
index 14995098d0..6dd6450619 100644
--- a/haddock-api/src/Haddock/Types.hs
+++ b/haddock-api/src/Haddock/Types.hs
@@ -36,6 +36,8 @@ import OccName
import Outputable
import Control.Monad (ap)
+import Haddock.Backends.Hyperlinker.Types
+
-----------------------------------------------------------------------------
-- * Convenient synonyms
-----------------------------------------------------------------------------
@@ -49,7 +51,6 @@ type SubMap = Map Name [Name]
type DeclMap = Map Name [LHsDecl Name]
type InstMap = Map SrcSpan Name
type FixMap = Map Name Fixity
-type SrcMap = Map PackageKey FilePath
type DocPaths = (FilePath, Maybe FilePath) -- paths to HTML and sources
@@ -125,6 +126,10 @@ data Interface = Interface
-- | Warnings for things defined in this module.
, ifaceWarningMap :: !WarningMap
+
+ -- | Tokenized source code of module (avaliable if Haddock is invoked with
+ -- source generation flag).
+ , ifaceTokenizedSrc :: !(Maybe [RichToken])
}
type WarningMap = Map Name (Doc Name)
@@ -266,7 +271,6 @@ unrenameDocForDecl (doc, fnArgsDoc) =
-- | Type of environment used to cross-reference identifiers in the syntax.
type LinkEnv = Map Name Module
-
-- | Extends 'Name' with cross-reference information.
data DocName
= Documented Name Module
diff --git a/haddock-api/test/Haddock/Backends/Hyperlinker/ParserSpec.hs b/haddock-api/test/Haddock/Backends/Hyperlinker/ParserSpec.hs
new file mode 100644
index 0000000000..8cd2690e56
--- /dev/null
+++ b/haddock-api/test/Haddock/Backends/Hyperlinker/ParserSpec.hs
@@ -0,0 +1,98 @@
+module Haddock.Backends.Hyperlinker.ParserSpec (main, spec) where
+
+
+import Test.Hspec
+import Test.QuickCheck
+
+import Haddock.Backends.Hyperlinker.Parser
+import Haddock.Backends.Hyperlinker.Types
+
+
+main :: IO ()
+main = hspec spec
+
+
+spec :: Spec
+spec = do
+ describe "parse" parseSpec
+
+
+parseSpec :: Spec
+parseSpec = do
+
+ it "is total" $
+ property $ \src -> length (parse src) `shouldSatisfy` (>= 0)
+
+ it "retains file layout" $
+ property $ \src -> concatMap tkValue (parse src) == src
+
+ context "when parsing single-line comments" $ do
+
+ it "should ignore content until the end of line" $
+ "-- some very simple comment\nidentifier"
+ `shouldParseTo`
+ [TkComment, TkSpace, TkIdentifier]
+
+ it "should allow endline escaping" $
+ "-- first line\\\nsecond line\\\nand another one"
+ `shouldParseTo`
+ [TkComment]
+
+ context "when parsing multi-line comments" $ do
+
+ it "should support nested comments" $
+ "{- comment {- nested -} still comment -} {- next comment -}"
+ `shouldParseTo`
+ [TkComment, TkSpace, TkComment]
+
+ it "should distinguish compiler pragma" $
+ "{- comment -}{-# LANGUAGE GADTs #-}{- comment -}"
+ `shouldParseTo`
+ [TkComment, TkPragma, TkComment]
+
+ it "should recognize preprocessor directives" $ do
+ "\n#define foo bar" `shouldParseTo` [TkSpace, TkCpp]
+ "x # y" `shouldParseTo`
+ [TkIdentifier, TkSpace, TkOperator, TkSpace,TkIdentifier]
+
+ it "should distinguish basic language constructs" $ do
+ "(* 2) <$> (\"abc\", foo)" `shouldParseTo`
+ [ TkSpecial, TkOperator, TkSpace, TkNumber, TkSpecial
+ , TkSpace, TkOperator, TkSpace
+ , TkSpecial, TkString, TkSpecial, TkSpace, TkIdentifier, TkSpecial
+ ]
+ "let foo' = foo in foo' + foo'" `shouldParseTo`
+ [ TkKeyword, TkSpace, TkIdentifier
+ , TkSpace, TkGlyph, TkSpace
+ , TkIdentifier, TkSpace, TkKeyword, TkSpace
+ , TkIdentifier, TkSpace, TkOperator, TkSpace, TkIdentifier
+ ]
+ "square x = y^2 where y = x" `shouldParseTo`
+ [ TkIdentifier, TkSpace, TkIdentifier
+ , TkSpace, TkGlyph, TkSpace
+ , TkIdentifier, TkOperator, TkNumber
+ , TkSpace, TkKeyword, TkSpace
+ , TkIdentifier, TkSpace, TkGlyph, TkSpace, TkIdentifier
+ ]
+
+ it "should parse do-notation syntax" $ do
+ "do { foo <- getLine; putStrLn foo }" `shouldParseTo`
+ [ TkKeyword, TkSpace, TkSpecial, TkSpace
+ , TkIdentifier, TkSpace, TkGlyph, TkSpace
+ , TkIdentifier, TkSpecial, TkSpace
+ , TkIdentifier, TkSpace, TkIdentifier, TkSpace, TkSpecial
+ ]
+
+ unlines
+ [ "do"
+ , " foo <- getLine"
+ , " putStrLn foo"
+ ] `shouldParseTo`
+ [ TkKeyword, TkSpace, TkIdentifier
+ , TkSpace, TkGlyph, TkSpace, TkIdentifier, TkSpace
+ , TkIdentifier, TkSpace, TkIdentifier, TkSpace
+ ]
+
+
+shouldParseTo :: String -> [TokenType] -> Expectation
+str `shouldParseTo` tokens = map tkType (parse str) `shouldBe` tokens
diff --git a/haddock-api/test/Spec.hs b/haddock-api/test/Spec.hs
new file mode 100644
index 0000000000..a824f8c30c
--- /dev/null
+++ b/haddock-api/test/Spec.hs
@@ -0,0 +1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/haddock.cabal b/haddock.cabal
index ed570f5368..8fa9f33d94 100644
--- a/haddock.cabal
+++ b/haddock.cabal
@@ -29,6 +29,8 @@ extra-source-files:
haddock-api/src/haddock.sh
html-test/src/*.hs
html-test/ref/*.html
+ hypsrc-test/src/*.hs
+ hypsrc-test/ref/*.html
latex-test/src/Simple/*.hs
latex-test/ref/Simple/*.tex
latex-test/ref/Simple/*.sty
@@ -101,6 +103,12 @@ executable haddock
Haddock.Backends.LaTeX
Haddock.Backends.HaddockDB
Haddock.Backends.Hoogle
+ Haddock.Backends.Hyperlinker
+ Haddock.Backends.Hyperlinker.Ast
+ Haddock.Backends.Hyperlinker.Parser
+ Haddock.Backends.Hyperlinker.Renderer
+ Haddock.Backends.Hyperlinker.Types
+ Haddock.Backends.Hyperlinker.Utils
Haddock.ModuleTree
Haddock.Types
Haddock.Doc
@@ -119,6 +127,14 @@ test-suite html-test
hs-source-dirs: html-test
build-depends: base, directory, process, filepath, Cabal
+test-suite hypsrc-test
+ type: exitcode-stdio-1.0
+ default-language: Haskell2010
+ main-is: run.hs
+ hs-source-dirs: hypsrc-test
+ build-depends: base, directory, process, filepath, Cabal
+ ghc-options: -Wall -fwarn-tabs
+
test-suite latex-test
type: exitcode-stdio-1.0
default-language: Haskell2010
diff --git a/hypsrc-test/Utils.hs b/hypsrc-test/Utils.hs
new file mode 100644
index 0000000000..e15fabee86
--- /dev/null
+++ b/hypsrc-test/Utils.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE CPP #-}
+
+
+module Utils
+ ( baseDir, rootDir
+ , srcDir, refDir, outDir, refDir', outDir'
+ , haddockPath
+ , stripLocalAnchors, stripLocalLinks, stripLocalReferences
+ ) where
+
+
+import Data.List
+
+import System.FilePath
+
+
+baseDir, rootDir :: FilePath
+baseDir = takeDirectory __FILE__
+rootDir = baseDir > ".."
+
+srcDir, refDir, outDir, refDir', outDir' :: FilePath
+srcDir = baseDir > "src"
+refDir = baseDir > "ref"
+outDir = baseDir > "out"
+refDir' = refDir > "src"
+outDir' = outDir > "src"
+
+haddockPath :: FilePath
+haddockPath = rootDir > "dist" > "build" > "haddock" > "haddock"
+
+
+replaceBetween :: Eq a => [a] -> a -> [a] -> [a] -> [a]
+replaceBetween _ _ _ [] = []
+replaceBetween pref end val html@(x:xs') = case stripPrefix pref html of
+ Just strip -> pref ++ val ++ (replaceBetween' . dropWhile (/= end)) strip
+ Nothing -> x:(replaceBetween' xs')
+ where
+ replaceBetween' = replaceBetween pref end val
+
+stripLocalAnchors :: String -> String
+stripLocalAnchors = replaceBetween " String
+stripLocalLinks = replaceBetween " String
+stripLocalReferences = stripLocalLinks . stripLocalAnchors
diff --git a/hypsrc-test/accept.hs b/hypsrc-test/accept.hs
new file mode 100755
index 0000000000..4606b2dfb0
--- /dev/null
+++ b/hypsrc-test/accept.hs
@@ -0,0 +1,27 @@
+#!/usr/bin/env runhaskell
+{-# LANGUAGE CPP #-}
+
+
+import System.Directory
+import System.FilePath
+import System.Environment
+
+import Utils
+
+
+main :: IO ()
+main = do
+ args <- getArgs
+ files <- filter isHtmlFile <$> getDirectoryContents outDir'
+ let files' = if args == ["--all"] || args == ["-a"]
+ then files
+ else filter ((`elem` args) . takeBaseName) files
+ mapM_ copy files'
+ where
+ isHtmlFile = (== ".html") . takeExtension
+
+
+copy :: FilePath -> IO ()
+copy file = do
+ content <- stripLocalReferences <$> readFile (outDir' > file)
+ writeFile (refDir' > file) content
diff --git a/hypsrc-test/ref/src/Classes.html b/hypsrc-test/ref/src/Classes.html
new file mode 100644
index 0000000000..a5a3d243f4
--- /dev/null
+++ b/hypsrc-test/ref/src/Classes.html
@@ -0,0 +1,931 @@
+
+module Classes where
+
+
+ class Foo a where
+ bar :: a -> Int
+ baz :: Int -> ( a , a )
+
+ instance Foo Int where
+ bar = id
+ baz x = ( x , x )
+
+ instance Foo [ a ] where
+ bar = length
+ baz _ = ( [ ] , [ ] )
+
+
+ class Foo a => Foo' a where
+ quux :: ( a , a ) -> a
+ quux ( x , y ) = norf [ x , y ]
+
+ norf :: [ a ] -> a
+ norf = quux . baz . sum . map bar
+
+ instance Foo' Int where
+ norf = sum
+
+ instance Foo' [ a ] where
+ quux = uncurry ( ++ )
+
+
+ class Plugh p where
+ plugh :: p a a -> p b b -> p ( a -> b ) ( b -> a )
+
+ instance Plugh Either where
+ plugh ( Left a ) _ = Right $ const a
+ plugh ( Right a ) _ = Right $ const a
+ plugh _ ( Left b ) = Left $ const b
+ plugh _ ( Right b ) = Left $ const b
+
diff --git a/hypsrc-test/ref/src/Constructors.html b/hypsrc-test/ref/src/Constructors.html
new file mode 100644
index 0000000000..96be3627f1
--- /dev/null
+++ b/hypsrc-test/ref/src/Constructors.html
@@ -0,0 +1,834 @@
+
+module Constructors where
+
+
+ data Foo
+ = Bar
+ | Baz
+ | Quux Foo Int
+
+ newtype Norf = Norf ( Foo , [ Foo ] , Foo )
+
+
+ bar , baz , quux :: Foo
+ bar = Bar
+ baz = Baz
+ quux = Quux quux 0
+
+
+ unfoo :: Foo -> Int
+ unfoo Bar = 0
+ unfoo Baz = 0
+ unfoo ( Quux foo n ) = 42 * n + unfoo foo
+
+
+ unnorf :: Norf -> [ Foo ]
+ unnorf ( Norf ( Bar , xs , Bar ) ) = xs
+ unnorf ( Norf ( Baz , xs , Baz ) ) = reverse xs
+ unnorf _ = undefined
+
+
+ unnorf' :: Norf -> Int
+ unnorf' x @ ( Norf ( f1 @ ( Quux _ n ) , _ , f2 @ ( Quux f3 _ ) ) ) =
+ x' + n * unfoo f1 + aux f3
+ where
+ aux fx = unfoo f2 * unfoo fx * unfoo f3
+ x' = sum . map unfoo . unnorf $ x
+
diff --git a/hypsrc-test/ref/src/Identifiers.html b/hypsrc-test/ref/src/Identifiers.html
new file mode 100644
index 0000000000..14cfbd8b8c
--- /dev/null
+++ b/hypsrc-test/ref/src/Identifiers.html
@@ -0,0 +1,845 @@
+
+module Identifiers where
+
+
+ foo , bar , baz :: Int -> Int -> Int
+ foo x y = x + x * bar y x * y + y
+ bar x y = y + x - baz x y - x + y
+ baz x y = x * y * y * y * x
+
+ quux :: Int -> Int
+ quux x = foo ( bar x x ) ( bar x x )
+
+ norf :: Int -> Int -> Int -> Int
+ norf x y z
+ | x < 0 = quux x
+ | y < 0 = quux y
+ | z < 0 = quux z
+ | otherwise = norf ( - x ) ( - y ) ( - z )
+
+
+ main :: IO ( )
+ main = do
+ putStrLn . show $ foo x y
+ putStrLn . show $ quux z
+ putStrLn . show $ Identifiers . norf x y z
+ where
+ x = 10
+ y = 20
+ z = 30
+
diff --git a/hypsrc-test/ref/src/Literals.html b/hypsrc-test/ref/src/Literals.html
new file mode 100644
index 0000000000..f2e4749b4f
--- /dev/null
+++ b/hypsrc-test/ref/src/Literals.html
@@ -0,0 +1,382 @@
+
+module Literals where
+
+
+ str :: String
+ str = "str literal"
+
+ num :: Num a => a
+ num = 0 + 1 + 1010011 * 41231 + 12131
+
+ frac :: Fractional a => a
+ frac = 42.0000001
+
+ list :: [ [ [ [ a ] ] ] ]
+ list = [ [ ] , [ [ ] ] , [ [ [ ] ] ] ]
+
+ pair :: ( ( ) , ( ( ) , ( ) , ( ) ) , ( ) )
+ pair = ( ( ) , ( ( ) , ( ) , ( ) ) , ( ) )
+
diff --git a/hypsrc-test/ref/src/Operators.html b/hypsrc-test/ref/src/Operators.html
new file mode 100644
index 0000000000..beefda58fa
--- /dev/null
+++ b/hypsrc-test/ref/src/Operators.html
@@ -0,0 +1,777 @@
+
+module Operators where
+
+
+ ( +++ ) :: [ a ] -> [ a ] -> [ a ]
+ a +++ b = a ++ b ++ a
+
+ ( $$$ ) :: [ a ] -> [ a ] -> [ a ]
+ a $$$ b = b +++ a
+
+ ( *** ) :: [ a ] -> [ a ] -> [ a ]
+ ( *** ) a [ ] = a
+ ( *** ) a ( _ : b ) = a +++ ( a *** b )
+
+ ( */\* ) :: [ [ a ] ] -> [ a ] -> [ a ]
+ a */\* b = concatMap ( *** b ) a
+
+ ( **/\** ) :: [ [ a ] ] -> [ [ a ] ] -> [ [ a ] ]
+ a **/\** b = zipWith ( */\* ) [ a +++ b ] ( a $$$ b )
+
+
+ ( #.# ) :: a -> b -> ( c -> ( a , b ) )
+ a #.# b = const $ ( a , b )
+
diff --git a/hypsrc-test/ref/src/Records.html b/hypsrc-test/ref/src/Records.html
new file mode 100644
index 0000000000..0751782a2f
--- /dev/null
+++ b/hypsrc-test/ref/src/Records.html
@@ -0,0 +1,887 @@
+
+{-# LANGUAGE NamedFieldPuns #-}
+ {-# LANGUAGE RecordWildCards #-}
+
+
+ module Records where
+
+
+ data Point = Point
+ { x :: ! Int
+ , y :: ! Int
+ }
+
+
+ point :: Int -> Int -> Point
+ point x y = Point { x = x , y = y }
+
+
+ lengthSqr :: Point -> Int
+ lengthSqr ( Point { x = x , y = y } ) = x * x + y * y
+
+ lengthSqr' :: Point -> Int
+ lengthSqr' ( Point { x , y } ) = y * y + x * x
+
+
+ translateX , translateY :: Point -> Int -> Point
+ translateX p d = p { x = x p + d }
+ translateY p d = p { y = y p + d }
+
+ translate :: Int -> Int -> Point -> Point
+ translate x y p =
+ aux p
+ where
+ ( dx , dy ) = ( x , y )
+ aux Point { .. } = p { x = x + dx , y = y + dy }
+
diff --git a/hypsrc-test/ref/src/Types.html b/hypsrc-test/ref/src/Types.html
new file mode 100644
index 0000000000..bdb68ed679
--- /dev/null
+++ b/hypsrc-test/ref/src/Types.html
@@ -0,0 +1,937 @@
+
+{-# LANGUAGE TypeFamilies #-}
+
+
+ module Types where
+
+
+ data Quux = Bar | Baz
+
+ newtype Foo = Foo ( )
+
+ type FooQuux = ( Foo , Quux )
+ type QuuxFoo = ( Quux , Foo )
+
+
+ data family Norf a b
+
+ data instance Norf Foo Quux = NFQ Foo Quux
+ data instance Norf Quux Foo = NQF Quux Foo
+
+
+ type family Norf' a b
+
+ type instance Norf' Foo Quux = ( Foo , Quux )
+ type instance Norf' Quux Foo = ( Quux , Foo )
+
+
+ norf1 :: Norf Foo Quux -> Int
+ norf1 ( NFQ ( Foo ( ) ) Bar ) = 0
+ norf1 ( NFQ ( Foo ( ) ) Baz ) = 1
+
+ norf2 :: Norf Quux Foo -> Int
+ norf2 ( NQF Bar ( Foo ( ) ) ) = 0
+ norf2 ( NQF Baz ( Foo ( ) ) ) = 1
+
+
+ norf1' :: Norf' Foo Quux -> Int
+ norf1' ( Foo ( ) , Bar ) = 0
+ norf1' ( Foo ( ) , Baz ) = 1
+
+ norf2' :: Norf' Quux Foo -> Int
+ norf2' ( Bar , Foo ( ) ) = 0
+ norf2' ( Baz , Foo ( ) ) = 1
+
diff --git a/hypsrc-test/run.hs b/hypsrc-test/run.hs
new file mode 100755
index 0000000000..853c4f091e
--- /dev/null
+++ b/hypsrc-test/run.hs
@@ -0,0 +1,122 @@
+#!/usr/bin/env runhaskell
+{-# LANGUAGE CPP #-}
+
+
+import Control.Monad
+
+import Data.List
+import Data.Maybe
+
+import System.Directory
+import System.Environment
+import System.Exit
+import System.FilePath
+import System.Process
+
+import Distribution.Verbosity
+import Distribution.Simple.Utils hiding (die)
+
+import Utils
+
+
+main :: IO ()
+main = do
+ haddockAvailable <- doesFileExist haddockPath
+ unless haddockAvailable $ die "Haddock exectuable not available"
+
+ (args, mods) <- partition ("-" `isPrefixOf`) <$> getArgs
+ let args' = filter (\arg -> not $ arg == "--all" || arg == "-a") args
+ mods' <- map (srcDir >) <$> case args of
+ [] -> getAllSrcModules
+ _ -> return $ map (++ ".hs") mods
+
+ putHaddockVersion
+ putGhcVersion
+
+ putStrLn "Running tests..."
+ runHaddock $
+ [ "--odir=" ++ outDir
+ , "--no-warnings"
+ , "--hyperlinked-source"
+ , "--pretty-html"
+ ] ++ args' ++ mods'
+
+ forM_ mods' $ check True
+
+
+check :: Bool -> FilePath -> IO ()
+check strict mdl = do
+ hasReference <- doesFileExist refFile
+ if hasReference
+ then do
+ ref <- readFile refFile
+ out <- readFile outFile
+ compareOutput strict mdl ref out
+ else do
+ putStrLn $ "Pass: " ++ mdl ++ " (no reference file)"
+ where
+ refFile = refDir' > takeBaseName mdl ++ ".html"
+ outFile = outDir' > takeBaseName mdl ++ ".html"
+
+
+compareOutput :: Bool -> FilePath -> String -> String -> IO ()
+compareOutput strict mdl ref out = do
+ if ref' == out'
+ then putStrLn $ "Pass: " ++ mdl
+ else do
+ putStrLn $ "Fail: " ++ mdl
+ diff mdl ref' out'
+ when strict $ die "Aborting further tests."
+ where
+ ref' = stripLocalReferences ref
+ out' = stripLocalReferences out
+
+
+diff :: FilePath -> String -> String -> IO ()
+diff mdl ref out = do
+ colorDiffPath <- findProgramLocation silent "colordiff"
+ let cmd = fromMaybe "diff" colorDiffPath
+
+ writeFile refFile ref
+ writeFile outFile out
+
+ result <- system $ cmd ++ " " ++ refFile ++ " " ++ outFile
+ unless (result == ExitSuccess) $ die "Failed to run `diff` command."
+ where
+ refFile = outDir > takeBaseName mdl ++ ".ref.nolinks"
+ outFile = outDir > takeBaseName mdl ++ ".nolinks"
+
+
+
+getAllSrcModules :: IO [FilePath]
+getAllSrcModules =
+ filter isHaskellFile <$> getDirectoryContents srcDir
+ where
+ isHaskellFile = (== ".hs") . takeExtension
+
+
+putHaddockVersion :: IO ()
+putHaddockVersion = do
+ putStrLn "Haddock version:"
+ runHaddock ["--version"]
+ putStrLn ""
+
+
+putGhcVersion :: IO ()
+putGhcVersion = do
+ putStrLn "GHC version:"
+ runHaddock ["--ghc-version"]
+ putStrLn ""
+
+
+runHaddock :: [String] -> IO ()
+runHaddock args = do
+ menv <- Just <$> getEnvironment
+ handle <- runProcess haddockPath args Nothing menv Nothing Nothing Nothing
+ waitForSuccess handle $ "Failed to invoke haddock with " ++ show args
+
+
+waitForSuccess :: ProcessHandle -> String -> IO ()
+waitForSuccess handle msg = do
+ result <- waitForProcess handle
+ unless (result == ExitSuccess) $ die msg
diff --git a/hypsrc-test/src/Classes.hs b/hypsrc-test/src/Classes.hs
new file mode 100644
index 0000000000..bddb9939a0
--- /dev/null
+++ b/hypsrc-test/src/Classes.hs
@@ -0,0 +1,38 @@
+module Classes where
+
+
+class Foo a where
+ bar :: a -> Int
+ baz :: Int -> (a, a)
+
+instance Foo Int where
+ bar = id
+ baz x = (x, x)
+
+instance Foo [a] where
+ bar = length
+ baz _ = ([], [])
+
+
+class Foo a => Foo' a where
+ quux :: (a, a) -> a
+ quux (x, y) = norf [x, y]
+
+ norf :: [a] -> a
+ norf = quux . baz . sum . map bar
+
+instance Foo' Int where
+ norf = sum
+
+instance Foo' [a] where
+ quux = uncurry (++)
+
+
+class Plugh p where
+ plugh :: p a a -> p b b -> p (a -> b) (b -> a)
+
+instance Plugh Either where
+ plugh (Left a) _ = Right $ const a
+ plugh (Right a) _ = Right $ const a
+ plugh _ (Left b) = Left $ const b
+ plugh _ (Right b) = Left $ const b
diff --git a/hypsrc-test/src/Constructors.hs b/hypsrc-test/src/Constructors.hs
new file mode 100644
index 0000000000..8cb465359b
--- /dev/null
+++ b/hypsrc-test/src/Constructors.hs
@@ -0,0 +1,35 @@
+module Constructors where
+
+
+data Foo
+ = Bar
+ | Baz
+ | Quux Foo Int
+
+newtype Norf = Norf (Foo, [Foo], Foo)
+
+
+bar, baz, quux :: Foo
+bar = Bar
+baz = Baz
+quux = Quux quux 0
+
+
+unfoo :: Foo -> Int
+unfoo Bar = 0
+unfoo Baz = 0
+unfoo (Quux foo n) = 42 * n + unfoo foo
+
+
+unnorf :: Norf -> [Foo]
+unnorf (Norf (Bar, xs, Bar)) = xs
+unnorf (Norf (Baz, xs, Baz)) = reverse xs
+unnorf _ = undefined
+
+
+unnorf' :: Norf -> Int
+unnorf' x@(Norf (f1@(Quux _ n), _, f2@(Quux f3 _))) =
+ x' + n * unfoo f1 + aux f3
+ where
+ aux fx = unfoo f2 * unfoo fx * unfoo f3
+ x' = sum . map unfoo . unnorf $ x
diff --git a/hypsrc-test/src/Identifiers.hs b/hypsrc-test/src/Identifiers.hs
new file mode 100644
index 0000000000..173c3ba7b3
--- /dev/null
+++ b/hypsrc-test/src/Identifiers.hs
@@ -0,0 +1,28 @@
+module Identifiers where
+
+
+foo, bar, baz :: Int -> Int -> Int
+foo x y = x + x * bar y x * y + y
+bar x y = y + x - baz x y - x + y
+baz x y = x * y * y * y * x
+
+quux :: Int -> Int
+quux x = foo (bar x x) (bar x x)
+
+norf :: Int -> Int -> Int -> Int
+norf x y z
+ | x < 0 = quux x
+ | y < 0 = quux y
+ | z < 0 = quux z
+ | otherwise = norf (-x) (-y) (-z)
+
+
+main :: IO ()
+main = do
+ putStrLn . show $ foo x y
+ putStrLn . show $ quux z
+ putStrLn . show $ Identifiers.norf x y z
+ where
+ x = 10
+ y = 20
+ z = 30
diff --git a/hypsrc-test/src/Literals.hs b/hypsrc-test/src/Literals.hs
new file mode 100644
index 0000000000..997b661561
--- /dev/null
+++ b/hypsrc-test/src/Literals.hs
@@ -0,0 +1,17 @@
+module Literals where
+
+
+str :: String
+str = "str literal"
+
+num :: Num a => a
+num = 0 + 1 + 1010011 * 41231 + 12131
+
+frac :: Fractional a => a
+frac = 42.0000001
+
+list :: [[[[a]]]]
+list = [[], [[]], [[[]]]]
+
+pair :: ((), ((), (), ()), ())
+pair = ((), ((), (), ()), ())
diff --git a/hypsrc-test/src/Operators.hs b/hypsrc-test/src/Operators.hs
new file mode 100644
index 0000000000..8e86ab0b71
--- /dev/null
+++ b/hypsrc-test/src/Operators.hs
@@ -0,0 +1,22 @@
+module Operators where
+
+
+(+++) :: [a] -> [a] -> [a]
+a +++ b = a ++ b ++ a
+
+($$$) :: [a] -> [a] -> [a]
+a $$$ b = b +++ a
+
+(***) :: [a] -> [a] -> [a]
+(***) a [] = a
+(***) a (_:b) = a +++ (a *** b)
+
+(*/\*) :: [[a]] -> [a] -> [a]
+a */\* b = concatMap (*** b) a
+
+(**/\**) :: [[a]] -> [[a]] -> [[a]]
+a **/\** b = zipWith (*/\*) [a +++ b] (a $$$ b)
+
+
+(#.#) :: a -> b -> (c -> (a, b))
+a #.# b = const $ (a, b)
diff --git a/hypsrc-test/src/Polymorphism.hs b/hypsrc-test/src/Polymorphism.hs
new file mode 100644
index 0000000000..a74ac4921b
--- /dev/null
+++ b/hypsrc-test/src/Polymorphism.hs
@@ -0,0 +1,66 @@
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+
+module Polymorphism where
+
+
+foo :: a -> a -> a
+foo = undefined
+
+foo' :: forall a. a -> a -> a
+foo' = undefined
+
+bar :: a -> b -> (a, b)
+bar = undefined
+
+bar' :: forall a b. a -> b -> (a, b)
+bar' = undefined
+
+baz :: a -> (a -> [a -> a] -> b) -> b
+baz = undefined
+
+baz' :: forall a b. a -> (a -> [a -> a] -> b) -> b
+baz' = undefined
+
+quux :: a -> (forall a. a -> a) -> a
+quux = undefined
+
+quux' :: forall a. a -> (forall a. a -> a) -> a
+quux' = undefined
+
+
+num :: Num a => a -> a -> a
+num = undefined
+
+num' :: forall a. Num a => a -> a -> a
+num' = undefined
+
+eq :: (Eq a, Eq b) => [a] -> [b] -> (a, b)
+eq = undefined
+
+eq' :: forall a b. (Eq a, Eq b) => [a] -> [b] -> (a, b)
+eq' = undefined
+
+mon :: Monad m => (a -> m a) -> m a
+mon = undefined
+
+mon' :: forall m a. Monad m => (a -> m a) -> m a
+mon' = undefined
+
+
+norf :: a -> (forall a. Ord a => a -> a) -> a
+norf = undefined
+
+norf' :: forall a. a -> (forall a. Ord a => a -> a) -> a
+norf' = undefined
+
+
+plugh :: forall a. a -> a
+plugh x = x :: a
+
+thud :: forall a b. (a -> b) -> a -> (a, b)
+thud f x =
+ (x :: a, y) :: (a, b)
+ where
+ y = (f :: a -> b) x :: b
diff --git a/hypsrc-test/src/Records.hs b/hypsrc-test/src/Records.hs
new file mode 100644
index 0000000000..40a01121f2
--- /dev/null
+++ b/hypsrc-test/src/Records.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE RecordWildCards #-}
+
+
+module Records where
+
+
+data Point = Point
+ { x :: !Int
+ , y :: !Int
+ }
+
+
+point :: Int -> Int -> Point
+point x y = Point { x = x, y = y }
+
+
+lengthSqr :: Point -> Int
+lengthSqr (Point { x = x, y = y }) = x * x + y * y
+
+lengthSqr' :: Point -> Int
+lengthSqr' (Point { x, y }) = y * y + x * x
+
+
+translateX, translateY :: Point -> Int -> Point
+translateX p d = p { x = x p + d }
+translateY p d = p { y = y p + d }
+
+translate :: Int -> Int -> Point -> Point
+translate x y p =
+ aux p
+ where
+ (dx, dy) = (x, y)
+ aux Point{..} = p { x = x + dx, y = y + dy }
diff --git a/hypsrc-test/src/Types.hs b/hypsrc-test/src/Types.hs
new file mode 100644
index 0000000000..b63a825b95
--- /dev/null
+++ b/hypsrc-test/src/Types.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE TypeFamilies #-}
+
+
+module Types where
+
+
+data Quux = Bar | Baz
+
+newtype Foo = Foo ()
+
+type FooQuux = (Foo, Quux)
+type QuuxFoo = (Quux, Foo)
+
+
+data family Norf a b
+
+data instance Norf Foo Quux = NFQ Foo Quux
+data instance Norf Quux Foo = NQF Quux Foo
+
+
+type family Norf' a b
+
+type instance Norf' Foo Quux = (Foo, Quux)
+type instance Norf' Quux Foo = (Quux, Foo)
+
+
+norf1 :: Norf Foo Quux -> Int
+norf1 (NFQ (Foo ()) Bar) = 0
+norf1 (NFQ (Foo ()) Baz) = 1
+
+norf2 :: Norf Quux Foo -> Int
+norf2 (NQF Bar (Foo ())) = 0
+norf2 (NQF Baz (Foo ())) = 1
+
+
+norf1' :: Norf' Foo Quux -> Int
+norf1' (Foo (), Bar) = 0
+norf1' (Foo (), Baz) = 1
+
+norf2' :: Norf' Quux Foo -> Int
+norf2' (Bar, Foo ()) = 0
+norf2' (Baz, Foo ()) = 1