1{-# LANGUAGE LambdaCase #-}2{-# LANGUAGE OverloadedStrings #-}34-- | This is the high-level interface for rendering Scheme source5-- documentation to a suitable output format. Currently, only HTML6-- is supported as an output format.7--8-- The API, provided by this module, is structured around R7RS9-- Scheme 'L.Library' declarations and generates documentation10-- for all exported identifiers of a Scheme library.11module SchemeDoc (DocLib, findDocLibs, docDecls, docFmt, mkDoc, findUndocumented)12where1314import SchemeDoc.Error15import SchemeDoc.Format.Formatter16import qualified SchemeDoc.Format.Library as L17import qualified SchemeDoc.Format.Record as R18import SchemeDoc.Format.Types19import SchemeDoc.Types2021import qualified Data.Text as T22import Text.Blaze.Html23import Text.Blaze.Html.Renderer.String24import qualified Text.Blaze.Html5 as H25import qualified Text.Blaze.Html5.Attributes as A2627-- | A documented Scheme library, i.e. a Scheme 'L.Library' declaration28-- which is proceded by a 'DocComment'.29type DocLib = (T.Text, L.Library)3031-- | Find all documented Scheme 'L.Library' declarations in a Scheme source.32findDocLibs :: [Sexp] -> Either SyntaxError [DocLib]33findDocLibs exprs = foldr fn (Right []) (findDocumented exprs)34 where35 fn (s, e@(List ((Id "define-library") : _))) acc =36 case L.mkLibrary e of37 Right lib -> fmap ((s, lib) :) acc38 Left err -> Left err39 fn _ acc = acc4041-- | Find all documented 'Component's of a Scheme 'L.Library'. Performs42-- file system access to expand includes and may return an 'ErrParser'43-- exception.44--45-- Returns the recognized Components and a list of S-expressions which46-- were preceded by a 'DocComment' but for which no suitable 'Formatter'47-- was found.48docDecls :: DocLib -> IO ([Component], [Sexp])49docDecls (_, lib) = do50 sexprs <- L.expand lib51 pure $ findComponents defFormatter (findDocumented sexprs)5253-- | Format a documented 'L.Library', with regards to its 'Component's54-- (obtained via 'docDecls') as an 'Html' document.55docFmt :: DocLib -> [Component] -> Html56docFmt (libDesc, lib) comps =57 let html = format lib comps58 in declFmt (fmt lib libDesc) Nothing >> html5960-- | Render an 'Html' document with the given title, stylesheet, and body.61mkDoc :: String -> String -> Html -> String62mkDoc title css hbody = renderHtml $ H.docTypeHtml $ do63 H.head $ do64 H.meta ! A.charset "UTF-8"65 H.meta66 ! A.name "viewport"67 ! A.content "width=device-width, initial-scale=1"6869 H.link70 ! A.rel "stylesheet"71 ! A.href (stringValue css)72 H.title $ toHtml title73 H.body hbody7475-- | The name of all internal identifiers which are exported by the76-- 'L.Library' but not documented, i.e. not preceded by a 'DocComment'.77findUndocumented :: L.Library -> [Component] -> [T.Text]78findUndocumented lib comps =79 filter (\i -> not $ member i comps) $80 map L.internal (L.exported lib)81 where82 member :: T.Text -> [Component] -> Bool83 member ident =84 any85 ( \case86 D d -> declId d == ident87 S _ -> False88 )8990------------------------------------------------------------------------9192-- Filter out all non-documented S-expressions and attempt to perform93-- pseudo macro expansion for documented S-expression.94filterAndExpand :: [Sexp] -> [Sexp]95filterAndExpand = snd . walk filterAndExpand' (False, [])96 where97 filterAndExpand' :: (Bool, [Sexp]) -> Sexp -> Walk (Bool, [Sexp])98 filterAndExpand' (False, acc) c@(DocComment _) = Recur (True, acc ++ [c])99 filterAndExpand' (True, acc) expr =100 -- Attempt to expand the expression. If expansion was successful101 -- strip the preceding DocComment from the accumulated value and102 -- filter the expanded S-expressions.103 --104 -- TODO: Generalize this and use it for Library expansion too.105 case R.expand expr of106 Just e -> Rise (False, init acc ++ filterAndExpand [e])107 Nothing -> Recur (False, acc ++ [expr])108 filterAndExpand' b _ = Recur b109110-- Find all S-expressions which are preceded by a documentation comment.111findDocumented :: [Sexp] -> [Documented]112findDocumented = toPairLst . filterAndExpand113 where114 toPairLst :: [Sexp] -> [Documented]115 toPairLst [] = []116 toPairLst ((DocComment s) : expr : xs) = (s, expr) : toPairLst xs117 toPairLst _ = error "unreachable"