scmdoc

Automatically generate documentation from comments in R7RS Scheme code

git clone https://git.8pit.net/scmdoc.git

  1{-# LANGUAGE LambdaCase #-}
  2{-# LANGUAGE OverloadedStrings #-}
  3
  4-- | This module implements a 'Formatter' for Scheme library definitions.
  5--
  6-- For example:
  7--
  8-- > (define-library (math arithmetic)
  9-- >   (export my-proc)
 10-- >
 11-- >   (begin
 12-- >     (define (my-proc x)
 13-- >       (* x 2))))
 14module SchemeDoc.Format.Library (
 15    Library (..),
 16    mkLibrary,
 17    name,
 18    externalId,
 19    exports,
 20    expand,
 21    ExportSpec (..),
 22    LibraryName,
 23)
 24where
 25
 26import Control.Monad (foldM)
 27import Data.List (find)
 28
 29import SchemeDoc.Error
 30import SchemeDoc.Format.Types
 31import SchemeDoc.Format.Util
 32import SchemeDoc.Parser.R7RS
 33import SchemeDoc.Types
 34
 35import qualified Data.Text as T
 36import Text.Blaze.Html
 37import qualified Text.Blaze.Html5 as H
 38
 39-- | An R7RS Scheme library as defined in Section 5.6 of the standard.
 40data Library = Library
 41    { ident :: LibraryName
 42    -- ^ Library name.
 43    , exported :: [ExportSpec]
 44    -- ^ Export declaration of the library.
 45    , decl :: [Sexp]
 46    -- ^ Declarations of the library.
 47    }
 48    deriving (Show)
 49
 50instance Formatable Library where
 51    fmt lib desc = mkDeclaration (name lib) desc $ \n ->
 52        do
 53            H.h1 $ toHtml n
 54            fromMkd desc
 55
 56-- | Parse a Scheme library definition.
 57--
 58-- > <library> → (define-library <library name>
 59-- >     <library declaration>*)
 60--
 61-- where
 62--
 63-- > <library name part> → <identifier> | <uinteger 10>
 64-- >     <library declaration> → (export <export spec>*)
 65-- >     | <import declaration>
 66-- >     | (begin <command or definition>*)
 67-- >     | <includer>
 68-- >     | (include-library-declarations <string>+)
 69-- >     | (cond-expand <cond-expand clause>+)
 70-- >     | (cond-expand <cond-expand clause>+
 71-- >         (else <library declaration>*))
 72mkLibrary :: Sexp -> Either SyntaxError Library
 73mkLibrary (List ((Id "define-library") : libraryName : xs)) = do
 74    libraryName' <- case mkLibName libraryName of
 75        Right n -> pure n
 76        Left err -> Left err
 77
 78    exportSpec <- case findExport xs of
 79        Right e -> pure e
 80        Left err -> Left err
 81
 82    pure $ Library libraryName' exportSpec xs
 83-- TODO: Handling of cond-expand?!
 84mkLibrary e = makeErr e "found no library definition"
 85
 86-- | Name of the 'Library'. Multiple identifiers, within the library
 87-- name, are joined by a single @' '@ character.
 88name :: Library -> T.Text
 89name (Library{ident = n}) = name' n
 90
 91-- | Return the external identifier for the given internal identifier.
 92externalId :: Library -> T.Text -> Maybe T.Text
 93externalId lib n =
 94    external
 95        <$> find (\e -> internal e == n) (exported lib)
 96
 97-- | Whether the 'Library' exports the given **internal** identifier.
 98exports :: Library -> T.Text -> Bool
 99exports lib i =
100    any (\Export{internal = i'} -> i == i') $
101        exported lib
102
103-- Expand an include into a begin expression.
104--
105--  <includer> →
106--      | (include <string>+)
107--      | (include-ci <string>+)
108--
109-- TODO: Support include-ci
110expandIncl :: Sexp -> IO Sexp
111expandIncl (List ((Id "include") : fileNames)) = expandIncl' fileNames
112expandIncl e@(List ((Id "include-ci") : _)) = throwSyntax e "include-ci currently not supported"
113expandIncl e = throwSyntax e "not an include expression"
114
115expandIncl' :: [Sexp] -> IO Sexp
116expandIncl' fileNames = do
117    paths <-
118        mapM
119            ( \case
120                Str s -> pure s
121                e -> throwSyntax e "expected list of strings"
122            )
123            fileNames
124
125    exprs <- mapM (parseFromFile . T.unpack) paths
126    pure $ List (Id "begin" : concat exprs)
127
128-- | Expand the library declaration. Returns all begin blocks, including
129-- includer expressions as expanded begin blocks.
130expand :: Library -> IO [Sexp]
131expand (Library{decl = d}) = foldM expand' [] d
132  where
133    expand' :: [Sexp] -> Sexp -> IO [Sexp]
134    expand' acc e@(List ((Id "begin") : _)) = pure $ e : acc
135    expand' acc e@(List ((Id "include") : _)) = expandIncl e >>= (\x -> pure $ x : acc)
136    expand' acc e@(List ((Id "include-ci") : _)) = expandIncl e >>= (\x -> pure $ x : acc)
137    expand' acc _ = pure acc
138
139------------------------------------------------------------------------
140
141-- | Name of a R7RS Scheme library.
142newtype LibraryName = LibName [T.Text]
143
144instance Show LibraryName where
145    show = T.unpack . name'
146
147name' :: LibraryName -> T.Text
148name' (LibName lst) = T.intercalate " " lst
149
150-- Parses a Scheme library name.
151--
152--  <library name part> → <identifier> | <uinteger 10>
153--
154mkLibName :: Sexp -> Either SyntaxError LibraryName
155mkLibName (List exprs) =
156    LibName
157        <$> mapM
158            ( \case
159                Id i -> Right i
160                -- TODO: Only allow <uinteger 10> in library name
161                Number n -> Right $ T.pack (show n :: String)
162                e -> makeErr e "expected identifier or uinteger"
163            )
164            exprs
165mkLibName e = makeErr e "expected non-empty list"
166
167------------------------------------------------------------------------
168
169-- | Export specification with internal name and external name.
170-- For exports which are not renamed both are the same.
171data ExportSpec = Export {internal :: T.Text, external :: T.Text}
172    deriving (Show)
173
174-- Export declaration for a library declaration.
175--
176--  <export spec> → <identifier>
177--      | (rename <identifier> <identifier>)
178--
179exportDecl :: Sexp -> Either SyntaxError ExportSpec
180exportDecl (List [Id "rename", Id i1, Id i2]) = Right $ Export i1 i2
181exportDecl (Id i) = Right $ Export i i
182exportDecl e = makeErr e "expected identifier or rename spec"
183
184-- Export expression as part of a library declaration.
185--
186--   <export expr> → (export <export spec>*)
187--
188mkExport :: Sexp -> Either SyntaxError [ExportSpec]
189mkExport (List ((Id "export") : items)) =
190    foldr
191        ( \x acc -> case exportDecl x of
192            Right e -> fmap (e :) acc
193            Left e -> Left e
194        )
195        (Right [])
196        items
197mkExport e = makeErr e "expected export list"
198
199-- Find an export expression within a library declaration.
200-- May return an empty list if no export declaration was found.
201findExport :: [Sexp] -> Either SyntaxError [ExportSpec]
202findExport (e@(List ((Id "export") : _)) : xs) = mkExport e >>= (\ex -> (++) ex <$> findExport xs)
203findExport (_ : exprs) = findExport exprs
204findExport [] = Right []