packages feed

cryptol-3.4.0: src/Cryptol/TypeCheck/Docstrings.hs

-- |
-- Module      :  Cryptol.TypeCheck.Docstrings
-- Copyright   :  (c) 2013-2025 Galois, Inc.
-- License     :  BSD3
-- Maintainer  :  cryptol@galois.com
-- Stability   :  provisional
-- Portability :  portable

{-# LANGUAGE Safe #-}

{-# LANGUAGE OverloadedStrings #-}
module Cryptol.TypeCheck.Docstrings where

import Cryptol.ModuleSystem.Interface
import Cryptol.ModuleSystem.Name
import Cryptol.Parser.AST (ImpName (..))
import Cryptol.TypeCheck.AST (Decl(..), Module, ModuleG(..), Pragma(..),
                              Submodule(..),  groupDecls)
import Cryptol.TypeCheck.PP
import Cryptol.TypeCheck.Type
import Cryptol.Utils.Ident (identText)
import Cryptol.Utils.Panic (panic)

import           Data.Map  (Map)
import qualified Data.Map  as Map
import           Data.Maybe (fromMaybe, maybeToList)
import           Data.Text (Text)
import qualified Data.Text as T

data DocItem = DocItem
  { docModContext :: ImpName Name -- ^ The module scope to run repl commands in
  , docFor        :: DocFor -- ^ The name the documentation is attached to
  , docText       :: [Text] -- ^ The text of the attached docstring, if any
  }

data DocFor
  = DocForMod (ImpName Name)
  | DocForDef Name -- definitions that aren't modules

instance PP DocFor where
  ppPrec p x =
    case x of
      DocForMod m -> ppPrec p m
      DocForDef n -> ppPrec p n 


gatherModuleDocstrings ::
  Map Name (ImpName Name) ->
  Module ->
  [DocItem]
gatherModuleDocstrings nameToModule m =
  [DocItem
    { docModContext = ImpTop (mName m)
    , docFor = DocForMod (ImpTop (mName m))
    , docText = mDoc m
    }
  ] ++
  -- mParams m
  -- mParamTypes m
  -- mParamFuns m
  [DocItem
    { docModContext = lookupModuleName n
    , docFor = DocForDef n
    , docText = maybeToList (tsDoc t)
    } | (n, t) <- Map.assocs (mTySyns m)] ++
  [DocItem
    { docModContext = lookupModuleName n
    , docFor = DocForDef n
    , docText = maybeToList (ntDoc t)
    } | (n, t) <- Map.assocs (mNominalTypes m)] ++
  [DocItem
    { docModContext = lookupModuleName (dName d)
    , docFor = DocForDef (dName d)
    , docText = maybeToList (dDoc d <> exhaustBoolProp d)
    } | g <- mDecls m, d <- groupDecls g] ++
  [DocItem
    { docModContext = ImpNested n
    , docFor = DocForMod (ImpNested n)
    , docText = ifsDoc (smIface s)
    } | (n, s) <- Map.assocs (mSubmodules m)] ++
  [DocItem
    { docModContext = ImpTop (mName m)
    , docFor = DocForMod (ImpNested n)
    , docText = maybeToList (mpnDoc s)
    } | (n, s) <- Map.assocs (mSignatures m)]
  where
    lookupModuleName n =
      case Map.lookup n nameToModule of
        Just x -> x
        Nothing -> panic "gatherModuleDocstrings" ["No owning module for name:", show (pp n)]

    exhaustBoolProp d =
      if (null . extractCodeBlocks . fromMaybe "" . dDoc) d &&
         (tIsBit . sType . dSignature) d &&
         PragmaProperty `elem` dPragmas d
      then Just $ "```\n" <> ":exhaust " <> (identText . nameIdent) (dName d) <> " // implicit" <> "\n```"
      else Nothing

-- | Extract the code blocks from the body of a docstring.
--
-- A single code block starts with at least 3 backticks followed by an
-- optional language specifier of \"cryptol\". This allows other kinds
-- of code blocks to be included (and ignored) in docstrings. Longer
-- backtick sequences can be used when a code block needs to be able to
-- contain backtick sequences.
--
-- @
-- /**
--  * A docstring example
--  *
--  * ```cryptol
--  * :check example
--  * ```
--  */
-- @
extractCodeBlocks :: Text -> [[Text]]
extractCodeBlocks raw = go [] (T.lines raw)
  where
    go finished [] = reverse finished
    go finished (x:xs)
      | (spaces, x1) <- T.span (' ' ==) x
      , (ticks, x2) <- T.span ('`' ==) x1
      , 3 <= T.length ticks
      , not (T.elem '`' x2)
      , let info = T.strip x2
      = if info `elem` ["", "repl"] -- supported languages "unlabeled" and repl
        then keep finished (T.length spaces) (T.length ticks) [] xs
        else skip finished ticks xs
      | otherwise = go finished xs

    -- process a code block that we're keeping
    keep finished _ _ acc [] = go (reverse acc : finished) [] -- unterminated code fences are defined to be terminated by github
    keep finished indentLen ticksLen acc (x:xs)
      | x1 <- T.dropWhile (' ' ==) x
      , (ticks, x2) <- T.span ('`' ==) x1
      , ticksLen <= T.length ticks
      , T.all (' ' ==) x2
      = go (reverse acc : finished) xs

      | otherwise =
        let x' =
              case T.span (' ' ==) x of
                (spaces, x1)
                  | T.length spaces <= indentLen -> x1
                  | otherwise -> T.drop indentLen x
        in keep finished indentLen ticksLen (x' : acc) xs

    -- process a code block that we're skipping
    skip finished _ [] = go finished []
    skip finished close (x:xs)
      | close == x = go finished xs
      | otherwise = skip finished close xs