summaryrefslogtreecommitdiff
path: root/source/Render
diff options
context:
space:
mode:
authoradelon <22380201+adelon@users.noreply.github.com>2026-08-06 17:54:00 +0200
committeradelon <22380201+adelon@users.noreply.github.com>2026-08-06 17:54:00 +0200
commit82328890108bae64b372b8d58620ebc62699de76 (patch)
tree575404c6b425c19259c0ded296f1c8ffb7ff0e2b /source/Render
parent1a25421c2a168d420581358c8733fcd8f36f379b (diff)
Migrate to `Felix` namespaceHEADhotg
Diffstat (limited to 'source/Render')
-rw-r--r--source/Render/Html.hs3963
-rw-r--r--source/Render/Html/Context.hs171
-rw-r--r--source/Render/Html/Export.hs277
-rw-r--r--source/Render/Html/Layout.hs602
-rw-r--r--source/Render/Html/Output.hs471
5 files changed, 0 insertions, 5484 deletions
diff --git a/source/Render/Html.hs b/source/Render/Html.hs
deleted file mode 100644
index c37a278..0000000
--- a/source/Render/Html.hs
+++ /dev/null
@@ -1,3963 +0,0 @@
-{-# LANGUAGE NamedFieldPuns #-}
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
-
-module Render.Html
- ( HtmlRenderIndex
- , HtmlPagePresentation
- , buildRenderIndex
- , htmlPagePresentationSource
- , renderDocument
- , supportScriptAssetContents
- ) where
-
-import Syntax.Abstract
-
-import Base
-import Felix.Source (ResolvedSource)
-import Lucid hiding (Term, for_)
-import Lucid.Base (makeAttributes)
-import Lucid.Math
-import Render.Html.Context
- ( HtmlRenderContext
- , HtmlRenderContextError
- , htmlCurrentPageLabel
- , htmlCurrentSource
- , htmlSourceFragmentHref
- , htmlSourceLabel
- , htmlSourcePageHref
- , htmlSupportScriptHref
- )
-import Render.Html.Layout (renderUrlFragment)
-
-import Control.Monad (unless, when)
-import Data.Char (digitToInt, isAlphaNum, isDigit, isSpace, toUpper)
-import Data.List qualified as List
-import Data.List.NonEmpty qualified as NonEmpty
-import Data.Map.Strict qualified as Map
-import Data.Set qualified as Set
-import Data.Text qualified as Text
-import Data.Text.Lazy qualified as LazyText
-import Report.Location (Location, pattern Nowhere)
-import Syntax.Token (VariableDisplay(..), VariableSuffix(..), displayVariable, tokToText)
-
-
-data HintCategory
- = OperatorHint
- | RelationHint
- | PredicateHint
- | StructOpHint
- deriving (Show, Eq, Ord)
-
-data TemplatePiece
- = Literal Text
- | Slot Int
- deriving (Show, Eq, Ord)
-
-data RenderHint = RenderHint
- { renderHintArity :: Int
- , renderHintTemplate :: [TemplatePiece]
- } deriving (Show, Eq, Ord)
-
-type HintMap = Map (HintCategory, Marker, Int) RenderHint
-type AnchorMap = Map Marker Text
-type BlockRenderInfo = (Int, Block, Text)
-type PreviewMap = Map Marker PreviewEntry
-
-data HtmlRenderIndex = HtmlRenderIndex
- !(Map Marker IndexedReferenceTarget)
-
-data HtmlPagePresentation = HtmlPagePresentation
- { htmlPagePresentationSource :: !ResolvedSource
- , pagePresentationBlockInfos :: ![BlockRenderInfo]
- , pagePresentationAnchors :: !AnchorMap
- , pagePresentationReferencedMarkers :: !(Set Marker)
- }
-
-data IndexedReferenceTarget = IndexedReferenceTarget
- !Int
- !ResolvedSource
- !ReferenceTarget
-
-data ReferenceContext = ReferenceContext
- { referenceAnchors :: AnchorMap
- , referencePreviews :: PreviewMap
- }
-
-data PreviewEntry = PreviewEntry
- { previewMarker :: Marker
- , previewKind :: Text
- , previewTitle :: Maybe Text
- , previewSourceLabel :: Text
- , previewSourceHref :: Text
- , previewReferenceHref :: Text
- , previewId :: Text
- , previewBody :: HintMap -> Html ()
- }
-
-data ReferenceTarget = ReferenceTarget
- { targetMarker :: Marker
- , targetAnchorId :: Text
- , targetKind :: Text
- , targetTitle :: Maybe Text
- , targetBody :: HintMap -> Html ()
- }
-
-data StmtMathFragment
- = StmtMathProse Text
- | StmtMathNode (Html ())
-
-type StmtMathFragments = [StmtMathFragment]
-
-newtype MissingHintMap = MissingHintMap
- { unMissingHintMap :: Map HintCategory (Set Marker)
- } deriving (Show, Eq)
-
-instance Semigroup MissingHintMap where
- MissingHintMap left <> MissingHintMap right =
- MissingHintMap (Map.unionWith (<>) left right)
-
-instance Monoid MissingHintMap where
- mempty = MissingHintMap mempty
-
-proofCollapseThreshold :: Int
-proofCollapseThreshold = 10
-
-referenceGroupThreshold :: Int
-referenceGroupThreshold = 5
-
-
-renderDocument
- :: HtmlRenderContext
- -> Text
- -> HtmlRenderIndex
- -> HtmlPagePresentation
- -> Either HtmlRenderContextError Text
-renderDocument
- context
- hintsSource
- renderIndex
- HtmlPagePresentation
- { pagePresentationBlockInfos = blockInfos
- , pagePresentationAnchors = anchors
- , pagePresentationReferencedMarkers = referencedMarkers
- } = do
- previews <-
- buildPreviewMap
- context
- referencedMarkers
- anchors
- renderIndex
- let result =
- case formatMissingHintWarning missingHints of
- Nothing -> rendered
- Just warningText ->
- trace (Text.unpack warningText) rendered
- hints = parseHints hintsSource
- missingHints =
- collectMissingHints
- hints
- [ block
- | (_index, block, _blockId) <- blockInfos
- ]
- rendered = LazyText.toStrict (renderText (renderPage hints))
- pageLabel = htmlCurrentPageLabel context
- tocBlocks = [(index, blockId, block) | (index, block, blockId) <- blockInfos, includeInToc block]
- referenceContext = ReferenceContext anchors previews
-
- renderPage :: HintMap -> Html ()
- renderPage hintMap = doctypehtml_ do
- head_ do
- meta_ [charset_ "utf-8"]
- title_ (toHtml pageLabel)
- style_ pageStyles
- body_ do
- div_ [class_ "page-layout"] do
- aside_ [class_ "toc-column"] do
- nav_ [class_ "toc"] do
- h2_ [class_ "toc-heading"] "Contents"
- input_
- [ class_ "toc-filter"
- , type_ "search"
- , placeholder_ "Filter labels"
- , makeAttributes "aria-label" "Filter TOC by label"
- ]
- ol_ [class_ "toc-list"] do
- traverse_ renderTocEntry tocBlocks
- main_ do
- h1_ (toHtml pageLabel)
- traverse_ (renderBlock hintMap referenceContext) blockInfos
- renderPreviewStore hintMap previews
- div_
- [ id_ "reference-preview-popup"
- , class_ "reference-preview-popup"
- , makeAttributes "role" "tooltip"
- , makeAttributes "aria-hidden" "true"
- ]
- skip
- script_
- [src_ (htmlSupportScriptHref context)]
- ("" :: Text)
- Right result
-
--- | Index every block target and every page's referenced markers once in
--- deterministic source order. Rendering a page subsequently performs only
--- marker lookups in the shared index.
-buildRenderIndex
- :: NonEmpty (ResolvedSource, [Block])
- -> (HtmlRenderIndex, NonEmpty HtmlPagePresentation)
-buildRenderIndex sourceBlocks =
- ( HtmlRenderIndex
- (Map.fromList
- [ ( targetMarker target
- , IndexedReferenceTarget ordinal source target
- )
- | (ordinal, (source, target)) <-
- zip [1 :: Int ..] orderedTargets
- ])
- , pages
- )
- where
- analysedPages =
- uncurry analysePage <$> sourceBlocks
- pages =
- fst <$> analysedPages
- orderedTargets =
- [ (htmlPagePresentationSource page, target)
- | (page, targets) <- NonEmpty.toList analysedPages
- , target <- targets
- ]
-
- analysePage source blocks =
- ( HtmlPagePresentation
- { htmlPagePresentationSource = source
- , pagePresentationBlockInfos = blockInfos
- , pagePresentationAnchors =
- Map.fromList
- [ (targetMarker, targetAnchorId)
- | ReferenceTarget
- { targetMarker
- , targetAnchorId
- } <- targets
- ]
- , pagePresentationReferencedMarkers =
- foldMap
- (\(_blockInfo, _targets, references) -> references)
- analysedBlocks
- }
- , targets
- )
- where
- analysedBlocks =
- [ ( blockInfo
- , referenceTargetsOfBlockRenderInfo blockInfo
- , collectReferencedMarkersOfBlock block
- )
- | (index, block) <- zip [1 :: Int ..] blocks
- , let blockInfo =
- (index, block, blockAnchorId index block)
- ]
- blockInfos =
- [ blockInfo
- | (blockInfo, _targets, _references) <- analysedBlocks
- ]
- targets =
- concat
- [ blockTargets
- | (_blockInfo, blockTargets, _references) <- analysedBlocks
- ]
-
-
-pageStyles :: Text
-pageStyles = Text.unlines
- [ ":root {"
- , " color-scheme: light dark;"
- , " font-family: Georgia, \"Times New Roman\", serif;"
- , " --page-bg: #ffffff;"
- , " --page-fg: #111111;"
- , " --muted-fg: #666666;"
- , " --subtle-fg: #444444;"
- , " --badge-bg: #f1f1f1;"
- , " --badge-border: #dddddd;"
- , " --badge-fg: #555555;"
- , " --rule-color: #d9d2c2;"
- , " --error-fg: #9f1d1d;"
- , " --error-bg: #fff1f1;"
- , " --toc-active-bg: #f3efe6;"
- , " --toc-active-fg: #1b1b1b;"
- , " --toc-active-accent: #b9aa7a;"
- , " --preview-bg: #fffdf8;"
- , " --preview-border: #cfc3a3;"
- , " --preview-shadow: rgba(0, 0, 0, 0.18);"
- , "}"
- , "html {"
- , " height: 100%;"
- , "}"
- , "body {"
- , " margin: 0;"
- , " height: 100vh;"
- , " overflow: hidden;"
- , " line-height: 1.5;"
- , " background: var(--page-bg);"
- , " color: var(--page-fg);"
- , "}"
- , ".page-layout {"
- , " display: grid;"
- , " grid-template-columns: minmax(16rem, 24rem) minmax(0, 1fr);"
- , " grid-template-rows: minmax(0, 1fr);"
- , " gap: 2rem;"
- , " align-items: stretch;"
- , " box-sizing: border-box;"
- , " margin: 0 auto;"
- , " max-width: 84rem;"
- , " height: 100vh;"
- , " padding: 2rem 1.25rem 3rem;"
- , "}"
- , ".toc-column {"
- , " display: block;"
- , " min-height: 0;"
- , "}"
- , ".toc {"
- , " display: flex;"
- , " flex-direction: column;"
- , " height: 100%;"
- , " min-height: 0;"
- , "}"
- , ".toc-heading {"
- , " margin: 0 0 0.75rem;"
- , " color: var(--muted-fg);"
- , " font-size: 0.9rem;"
- , " letter-spacing: 0.04em;"
- , " text-transform: uppercase;"
- , "}"
- , ".toc-filter {"
- , " box-sizing: border-box;"
- , " width: 100%;"
- , " margin: 0 0 0.9rem;"
- , " padding: 0.45rem 0.55rem;"
- , " border: 1px solid var(--badge-border);"
- , " border-radius: 0.35rem;"
- , " background: var(--page-bg);"
- , " color: var(--page-fg);"
- , " font: inherit;"
- , "}"
- , ".toc-filter::placeholder {"
- , " color: var(--muted-fg);"
- , "}"
- , ".toc-list {"
- , " flex: 1 1 auto;"
- , " min-height: 0;"
- , " overflow-y: auto;"
- , " list-style: none;"
- , " margin: 0;"
- , " padding: 0 0.5rem 0 0;"
- , "}"
- , ".toc-list > li {"
- , " margin: 0 0 0.8rem;"
- , "}"
- , ".toc-list > li > a {"
- , " display: block;"
- , " margin: -0.15rem -0.35rem;"
- , " padding: 0.15rem 0.35rem;"
- , " border-radius: 0.35rem;"
- , " color: inherit;"
- , " text-decoration: none;"
- , " transition: background-color 120ms ease, box-shadow 120ms ease, color 120ms ease;"
- , "}"
- , ".toc-list > li > a:hover,"
- , ".toc-list > li > a:focus-visible {"
- , " text-decoration: underline;"
- , "}"
- , ".toc-list > li > a.is-active {"
- , " background: var(--toc-active-bg);"
- , " box-shadow: inset 0.2rem 0 0 var(--toc-active-accent);"
- , " color: var(--toc-active-fg);"
- , "}"
- , ".toc-list > li > a.is-active > code {"
- , " color: var(--toc-active-fg);"
- , "}"
- , ".toc-list > li > a > span:first-child {"
- , " display: block;"
- , " font-weight: 700;"
- , "}"
- , ".toc-list > li > a > code {"
- , " display: block;"
- , " margin-top: 0.15rem;"
- , " color: var(--muted-fg);"
- , " font-size: 0.9em;"
- , " overflow-wrap: anywhere;"
- , "}"
- , "main {"
- , " min-width: 0;"
- , " min-height: 0;"
- , " overflow-y: auto;"
- , "}"
- , "main > *[id] {"
- , " display: block;"
- , " margin: 0 0 1rem;"
- , " scroll-margin-top: 1rem;"
- , "}"
- , "head- {"
- , " font-weight: 700;"
- , "}"
- , "title- {"
- , " font-weight: 400;"
- , "}"
- , "id-,"
- , "main a[href^=\"#\"],"
- , ".ref-badge {"
- , " display: inline-block;"
- , " padding: 0.02rem 0.35rem;"
- , " border: 1px solid var(--badge-border);"
- , " border-radius: 0.2rem;"
- , " background: var(--badge-bg);"
- , " color: var(--badge-fg);"
- , " font-family: \"SFMono-Regular\", Menlo, Consolas, \"Liberation Mono\", monospace;"
- , " font-size: 0.82em;"
- , " text-decoration: none;"
- , "}"
- , "head- > id- {"
- , " margin-left: 0.45rem;"
- , "}"
- , "main a[href^=\"#\"]:hover,"
- , "main a[href^=\"#\"]:focus-visible,"
- , ".ref-badge.has-preview:hover,"
- , ".ref-badge.has-preview:focus-visible {"
- , " text-decoration: underline;"
- , "}"
- , ".ref-badge.has-preview {"
- , " cursor: help;"
- , "}"
- , ".ref-badge-group {"
- , " user-select: none;"
- , "}"
- , "proof- > p:first-child,"
- , "proof- > details > summary + p {"
- , " display: inline;"
- , " margin: 0;"
- , "}"
- , "proof- proof- {"
- , " display: block;"
- , " margin: 0.5rem 0 0.5rem 1rem;"
- , " padding-left: 0.75rem;"
- , " border-left: 1px solid var(--rule-color);"
- , "}"
- , "proof- > details {"
- , " margin: 0;"
- , "}"
- , "proof- > details > summary {"
- , " cursor: pointer;"
- , " font-weight: 700;"
- , "}"
- , "proof- > details > summary title- {"
- , " font-weight: 400;"
- , "}"
- , "proof- > details > :not(summary) {"
- , " margin-top: 0.5rem;"
- , "}"
- , "inductive- > details {"
- , " margin-top: 0.75rem;"
- , "}"
- , "inductive- > details > summary {"
- , " cursor: pointer;"
- , " font-weight: 700;"
- , "}"
- , "inductive- > details > :not(summary) {"
- , " margin-top: 0.5rem;"
- , "}"
- , ".inductive-derived-facts > li {"
- , " margin: 0.35rem 0;"
- , "}"
- , ".inductive-derived-facts {"
- , " margin: 0;"
- , " padding-left: 1.5rem;"
- , "}"
- , ".reference-preview-store {"
- , " display: none;"
- , "}"
- , ".reference-preview-popup {"
- , " position: fixed;"
- , " z-index: 1000;"
- , " box-sizing: border-box;"
- , " width: 44rem;"
- , " max-width: calc(100vw - 2rem);"
- , " max-height: 34rem;"
- , " max-height: min(34rem, calc(100vh - 2rem));"
- , " overflow: auto;"
- , " overscroll-behavior: contain;"
- , " padding: 0.75rem 0.9rem;"
- , " border: 1px solid var(--preview-border);"
- , " border-radius: 0.45rem;"
- , " background: var(--preview-bg);"
- , " color: var(--page-fg);"
- , " box-shadow: 0 0.75rem 2.25rem var(--preview-shadow);"
- , " opacity: 0;"
- , " pointer-events: none;"
- , " transform: translateY(0.2rem);"
- , " transition: opacity 90ms ease, transform 90ms ease;"
- , "}"
- , ".reference-preview-popup[aria-hidden=\"true\"] {"
- , " visibility: hidden;"
- , "}"
- , ".reference-preview-popup.is-visible {"
- , " opacity: 1;"
- , " pointer-events: auto;"
- , " transform: translateY(0);"
- , "}"
- , ".reference-preview-popup * {"
- , " box-sizing: border-box;"
- , "}"
- , ".reference-preview-template {"
- , " display: flex;"
- , " flex-direction: column;"
- , " gap: 0.45rem;"
- , " width: 100%;"
- , "}"
- , ".reference-preview-heading {"
- , " display: block;"
- , " margin: 0;"
- , " width: 100%;"
- , " color: var(--muted-fg);"
- , " font-size: 0.82rem;"
- , " letter-spacing: 0.035em;"
- , " text-transform: uppercase;"
- , "}"
- , ".reference-preview-heading code {"
- , " color: var(--page-fg);"
- , " font-family: \"SFMono-Regular\", Menlo, Consolas, \"Liberation Mono\", monospace;"
- , " letter-spacing: 0;"
- , " text-transform: none;"
- , "}"
- , ".reference-preview-heading a {"
- , " color: inherit;"
- , " text-decoration: none;"
- , "}"
- , ".reference-preview-heading a:hover,"
- , ".reference-preview-heading a:focus-visible {"
- , " text-decoration: underline;"
- , "}"
- , ".reference-preview-source {"
- , " display: block;"
- , " margin: 0;"
- , " color: var(--subtle-fg);"
- , " font-size: 0.78rem;"
- , " letter-spacing: 0;"
- , " text-transform: none;"
- , "}"
- , ".reference-preview-body {"
- , " display: block;"
- , " clear: both;"
- , " margin: 0;"
- , " width: 100%;"
- , "}"
- , ".reference-preview-body p {"
- , " margin: 0.35rem 0 0;"
- , "}"
- , ".reference-preview-body p:first-child {"
- , " margin-top: 0;"
- , "}"
- , ".reference-preview-group-template {"
- , " display: flex;"
- , " flex-direction: column;"
- , " gap: 1rem;"
- , " margin: 0;"
- , " width: 100%;"
- , "}"
- , ".reference-preview-group-template > .reference-preview-template + .reference-preview-template {"
- , " padding-top: 1rem;"
- , " border-top: 1px solid var(--badge-border);"
- , "}"
- , ".reference-preview-statement {"
- , " display: block;"
- , " width: 100%;"
- , " white-space: normal;"
- , " overflow-wrap: break-word;"
- , "}"
- , ".reference-preview-statement math {"
- , " max-width: 100%;"
- , " overflow-x: auto;"
- , " overflow-y: hidden;"
- , " vertical-align: middle;"
- , "}"
- , "math[display=\"block\"] {"
- , " display: block;"
- , " margin: 0.5rem 0;"
- , "}"
- , "merror {"
- , " color: var(--error-fg);"
- , " background: var(--error-bg);"
- , "}"
- , "@media (prefers-color-scheme: dark) {"
- , " :root {"
- , " --page-bg: #161616;"
- , " --page-fg: #e9e6df;"
- , " --muted-fg: #b7b0a4;"
- , " --subtle-fg: #cfc8bc;"
- , " --badge-bg: #2a2a2a;"
- , " --badge-border: #444444;"
- , " --badge-fg: #d8d3ca;"
- , " --rule-color: #5b5348;"
- , " --error-fg: #ffb0b0;"
- , " --error-bg: #3b1f1f;"
- , " --toc-active-bg: #2b271f;"
- , " --toc-active-fg: #f0ebe1;"
- , " --toc-active-accent: #99865a;"
- , " --preview-bg: #211f1a;"
- , " --preview-border: #776a50;"
- , " --preview-shadow: rgba(0, 0, 0, 0.55);"
- , " }"
- , "}"
- , "@media (max-width: 900px) {"
- , " body {"
- , " height: auto;"
- , " overflow: auto;"
- , " }"
- , " .page-layout {"
- , " grid-template-columns: 1fr;"
- , " grid-template-rows: auto;"
- , " gap: 1.5rem;"
- , " height: auto;"
- , " }"
- , " .toc-column {"
- , " display: none;"
- , " }"
- , " main {"
- , " min-height: auto;"
- , " overflow: visible;"
- , " }"
- , "}"
- ]
-
-supportScriptAssetContents :: Text
-supportScriptAssetContents = tocScript <> "\n" <> referencePreviewScript
-
-
-tocScript :: Text
-tocScript = Text.unlines
- [ "(function () {"
- , " const toc = document.querySelector('.toc');"
- , " const tocList = toc ? toc.querySelector('.toc-list') : null;"
- , " const filterInput = toc ? toc.querySelector('.toc-filter') : null;"
- , " const content = document.querySelector('main');"
- , " if (!toc || !tocList || !content) return;"
- , " const links = Array.from(tocList.querySelectorAll(':scope > li > a[href^=\"#\"]'));"
- , " const blocks = Array.from(content.querySelectorAll(':scope > *[id]'));"
- , " if (!links.length || !blocks.length) return;"
- , " const linkByTarget = new Map(links.map((link) => [decodeURIComponent(link.hash.slice(1)), link]));"
- , " const tocEntries = links.map((link) => ({"
- , " item: link.parentElement,"
- , " link,"
- , " label: ((link.querySelector('code') || link.lastElementChild || link).textContent || '').trim().toLowerCase()"
- , " }));"
- , " let activeTarget = null;"
- , " let rafId = 0;"
- , " let suspendUntil = 0;"
- , ""
- , " const keepActiveLinkVisible = (link) => {"
- , " if (!link || performance.now() < suspendUntil) return;"
- , " const item = link.parentElement;"
- , " if (item && item.hidden) return;"
- , " const tocRect = tocList.getBoundingClientRect();"
- , " const linkRect = link.getBoundingClientRect();"
- , " const comfortTop = tocRect.top + tocRect.height * 0.2;"
- , " const comfortBottom = tocRect.bottom - tocRect.height * 0.2;"
- , " if (linkRect.top < comfortTop || linkRect.bottom > comfortBottom) {"
- , " link.scrollIntoView({ block: 'nearest', inline: 'nearest' });"
- , " }"
- , " };"
- , ""
- , " const applyFilter = () => {"
- , " const query = filterInput ? filterInput.value.trim().toLowerCase() : '';"
- , " for (const { item, label } of tocEntries) {"
- , " if (!item) continue;"
- , " item.hidden = query !== '' && !label.includes(query);"
- , " }"
- , " if (activeTarget) {"
- , " keepActiveLinkVisible(linkByTarget.get(activeTarget));"
- , " }"
- , " };"
- , ""
- , " const setActiveTarget = (target) => {"
- , " if (!target || target === activeTarget) return;"
- , " const previous = activeTarget ? linkByTarget.get(activeTarget) : null;"
- , " if (previous) {"
- , " previous.classList.remove('is-active');"
- , " previous.removeAttribute('aria-current');"
- , " }"
- , " activeTarget = target;"
- , " const next = linkByTarget.get(target);"
- , " if (!next) return;"
- , " next.classList.add('is-active');"
- , " next.setAttribute('aria-current', 'location');"
- , " keepActiveLinkVisible(next);"
- , " };"
- , ""
- , " const firstTarget = blocks[0].id;"
- , ""
- , " const findActiveTarget = () => {"
- , " const contentRect = content.getBoundingClientRect();"
- , " const topSnapThreshold = 40;"
- , " for (const block of blocks) {"
- , " const target = block.id;"
- , " const rect = block.getBoundingClientRect();"
- , " if (rect.top >= contentRect.top - 4 && rect.top <= contentRect.top + topSnapThreshold) {"
- , " return target;"
- , " }"
- , " }"
- , " const activationLine = contentRect.top + contentRect.height * 0.22;"
- , " let candidate = firstTarget;"
- , " for (const block of blocks) {"
- , " const target = block.id;"
- , " if (block.getBoundingClientRect().top <= activationLine) {"
- , " candidate = target;"
- , " continue;"
- , " }"
- , " break;"
- , " }"
- , " return candidate;"
- , " };"
- , ""
- , " const scheduleUpdate = () => {"
- , " if (rafId) return;"
- , " rafId = window.requestAnimationFrame(() => {"
- , " rafId = 0;"
- , " setActiveTarget(findActiveTarget());"
- , " });"
- , " };"
- , ""
- , " const suspendAutofollow = () => {"
- , " suspendUntil = performance.now() + 1500;"
- , " };"
- , ""
- , " const revealTarget = (target) => {"
- , " let parent = target.parentElement;"
- , " while (parent) {"
- , " if (parent.localName === 'details') {"
- , " parent.open = true;"
- , " }"
- , " parent = parent.parentElement;"
- , " }"
- , " };"
- , ""
- , " const scrollToTarget = (targetId) => {"
- , " const target = document.getElementById(targetId);"
- , " if (!target || !content.contains(target)) return false;"
- , " revealTarget(target);"
- , " target.scrollIntoView({ block: 'start', inline: 'nearest' });"
- , " setActiveTarget(targetId);"
- , " return true;"
- , " };"
- , ""
- , " content.addEventListener('scroll', scheduleUpdate, { passive: true });"
- , " window.addEventListener('resize', scheduleUpdate);"
- , " tocList.addEventListener('wheel', suspendAutofollow, { passive: true });"
- , " tocList.addEventListener('touchstart', suspendAutofollow, { passive: true });"
- , " toc.addEventListener('pointerdown', suspendAutofollow);"
- , " toc.addEventListener('focusin', suspendAutofollow);"
- , " if (filterInput) {"
- , " filterInput.addEventListener('input', applyFilter);"
- , " }"
- , " for (const details of content.querySelectorAll('details')) {"
- , " details.addEventListener('toggle', scheduleUpdate);"
- , " }"
- , ""
- , " toc.addEventListener('click', (event) => {"
- , " const link = event.target.closest('a[href^=\"#\"]');"
- , " if (!link || !tocList.contains(link)) return;"
- , " const targetId = decodeURIComponent(link.hash.slice(1));"
- , " if (!scrollToTarget(targetId)) return;"
- , " event.preventDefault();"
- , " suspendUntil = 0;"
- , " if (location.hash !== '#' + targetId) {"
- , " try {"
- , " history.pushState(null, '', '#' + targetId);"
- , " } catch (_error) {"
- , " location.hash = targetId;"
- , " }"
- , " }"
- , " });"
- , ""
- , " window.addEventListener('hashchange', () => {"
- , " if (location.hash.length <= 1) return;"
- , " const hashTarget = decodeURIComponent(location.hash.slice(1));"
- , " if (!scrollToTarget(hashTarget)) scheduleUpdate();"
- , " });"
- , ""
- , " if (location.hash.length > 1) {"
- , " const hashTarget = decodeURIComponent(location.hash.slice(1));"
- , " window.requestAnimationFrame(() => {"
- , " applyFilter();"
- , " if (!scrollToTarget(hashTarget)) scheduleUpdate();"
- , " });"
- , " return;"
- , " }"
- , ""
- , " applyFilter();"
- , " scheduleUpdate();"
- , "})();"
- ]
-
-referencePreviewScript :: Text
-referencePreviewScript = Text.unlines
- [ "(function () {"
- , " const content = document.querySelector('main');"
- , " const popup = document.getElementById('reference-preview-popup');"
- , " if (!content || !popup) return;"
- , " let activeTrigger = null;"
- , " let lastPointer = null;"
- , " let isPinned = false;"
- , " let hideTimer = 0;"
- , " const offset = 14;"
- , " const margin = 12;"
- , " const hideDelay = 180;"
- , ""
- , " const cloneHiddenPreview = (trigger) => {"
- , " const previewId = trigger.getAttribute('data-preview-id');"
- , " const template = previewId ? document.getElementById(previewId) : null;"
- , " if (!template) return null;"
- , " const clone = template.cloneNode(true);"
- , " clone.removeAttribute('id');"
- , " return clone;"
- , " };"
- , ""
- , " const buildCurrentPreview = (trigger) => {"
- , " const targetId = trigger.getAttribute('data-preview-target-id');"
- , " const target = targetId ? document.getElementById(targetId) : null;"
- , " if (!target || !content.contains(target)) return null;"
- , " const template = document.createElement('div');"
- , " template.className = 'reference-preview-template';"
- , " const heading = document.createElement('div');"
- , " heading.className = 'reference-preview-heading';"
- , " const kind = target.getAttribute('data-preview-kind') || 'Reference';"
- , " const label = target.getAttribute('data-preview-label') || targetId;"
- , " const title = target.getAttribute('data-preview-title');"
- , " heading.append(document.createTextNode(kind + ' '));"
- , " const code = document.createElement('code');"
- , " code.textContent = label;"
- , " heading.append(code);"
- , " if (title) {"
- , " heading.append(document.createTextNode(' (' + title + ')'));"
- , " }"
- , " template.append(heading);"
- , " const body = document.createElement('div');"
- , " body.className = 'reference-preview-body';"
- , " const statement = document.createElement('div');"
- , " statement.className = 'reference-preview-statement';"
- , " const head = Array.from(target.children).find((child) => child.localName === 'head-');"
- , " const nodes = Array.from(target.childNodes);"
- , " const start = head ? nodes.indexOf(head) + 1 : 0;"
- , " for (const node of nodes.slice(start)) {"
- , " statement.append(node.cloneNode(true));"
- , " }"
- , " if (!statement.childNodes.length) return null;"
- , " body.append(statement);"
- , " template.append(body);"
- , " return template;"
- , " };"
- , ""
- , " const buildMissingPreview = (item) => {"
- , " const label = item.getAttribute('data-reference-label') || '';"
- , " const template = document.createElement('div');"
- , " template.className = 'reference-preview-template';"
- , " const heading = document.createElement('div');"
- , " heading.className = 'reference-preview-heading';"
- , " heading.append(document.createTextNode('Reference '));"
- , " const code = document.createElement('code');"
- , " code.textContent = label;"
- , " heading.append(code);"
- , " template.append(heading);"
- , " const body = document.createElement('div');"
- , " body.className = 'reference-preview-body';"
- , " const statement = document.createElement('p');"
- , " statement.className = 'reference-preview-statement';"
- , " statement.textContent = 'Preview unavailable.';"
- , " body.append(statement);"
- , " template.append(body);"
- , " return template;"
- , " };"
- , ""
- , " const linkGroupHeading = (item, template) => {"
- , " const href = item.getAttribute('data-preview-link');"
- , " if (!href) return template;"
- , " const heading = template.querySelector('.reference-preview-heading');"
- , " const code = heading ? heading.querySelector('code') : null;"
- , " if (!heading || !code || code.closest('a')) return template;"
- , " const link = document.createElement('a');"
- , " link.href = href;"
- , " link.append(code.cloneNode(true));"
- , " code.replaceWith(link);"
- , " return template;"
- , " };"
- , ""
- , " const buildGroupPreview = (trigger) => {"
- , " if (!trigger.hasAttribute('data-preview-group')) return null;"
- , " const items = Array.from(trigger.querySelectorAll('.reference-preview-group-items > [data-reference-label]'));"
- , " if (!items.length) return null;"
- , " const template = document.createElement('div');"
- , " template.className = 'reference-preview-group-template';"
- , " for (const item of items) {"
- , " const preview = cloneHiddenPreview(item) || buildCurrentPreview(item) || buildMissingPreview(item);"
- , " template.append(linkGroupHeading(item, preview));"
- , " }"
- , " return template;"
- , " };"
- , ""
- , " const previewFor = (trigger) => buildGroupPreview(trigger) || cloneHiddenPreview(trigger) || buildCurrentPreview(trigger);"
- , ""
- , " const clamp = (value, min, max) => Math.min(Math.max(value, min), max);"
- , " const findTrigger = (target) => target instanceof Element ? target.closest('[data-preview-group], [data-preview-id], [data-preview-target-id]') : null;"
- , " const clearHideTimer = () => {"
- , " if (!hideTimer) return;"
- , " window.clearTimeout(hideTimer);"
- , " hideTimer = 0;"
- , " };"
- , ""
- , " const hidePreview = () => {"
- , " clearHideTimer();"
- , " activeTrigger = null;"
- , " lastPointer = null;"
- , " isPinned = false;"
- , " popup.classList.remove('is-visible');"
- , " popup.setAttribute('aria-hidden', 'true');"
- , " popup.replaceChildren();"
- , " };"
- , ""
- , " const scheduleHide = () => {"
- , " if (isPinned) return;"
- , " clearHideTimer();"
- , " hideTimer = window.setTimeout(() => {"
- , " hideTimer = 0;"
- , " if (!isPinned) hidePreview();"
- , " }, hideDelay);"
- , " };"
- , ""
- , " const placePreview = () => {"
- , " if (!activeTrigger) return;"
- , " const triggerRect = activeTrigger.getBoundingClientRect();"
- , " const popupRect = popup.getBoundingClientRect();"
- , " const fallbackWidth = Math.min(704, Math.max(0, window.innerWidth - margin * 2));"
- , " const popupWidth = popupRect.width || fallbackWidth;"
- , " const popupHeight = popupRect.height || 0;"
- , " const pointer = lastPointer;"
- , " const anchorX = pointer ? pointer.clientX : triggerRect.left;"
- , " const anchorY = pointer ? pointer.clientY : triggerRect.bottom;"
- , " let left = anchorX + (pointer ? offset : 0);"
- , " let top = anchorY + offset;"
- , " if (top + popupHeight + margin > window.innerHeight) {"
- , " const upperAnchor = pointer ? pointer.clientY : triggerRect.top;"
- , " top = Math.max(margin, upperAnchor - popupHeight - offset);"
- , " }"
- , " left = clamp(left, margin, Math.max(margin, window.innerWidth - popupWidth - margin));"
- , " popup.style.left = `${left}px`;"
- , " popup.style.top = `${top}px`;"
- , " };"
- , ""
- , " const showPreview = (trigger, pointerEvent, pinned = false) => {"
- , " const source = previewFor(trigger);"
- , " if (!source) {"
- , " hidePreview();"
- , " return;"
- , " }"
- , " const wasPinned = isPinned && trigger === activeTrigger;"
- , " clearHideTimer();"
- , " activeTrigger = trigger;"
- , " lastPointer = pointerEvent ? { clientX: pointerEvent.clientX, clientY: pointerEvent.clientY } : null;"
- , " isPinned = pinned || wasPinned;"
- , " popup.replaceChildren(source);"
- , " popup.scrollTop = 0;"
- , " popup.setAttribute('aria-hidden', 'false');"
- , " popup.classList.add('is-visible');"
- , " placePreview();"
- , " };"
- , ""
- , " content.addEventListener('pointerover', (event) => {"
- , " const trigger = findTrigger(event.target);"
- , " if (!trigger || !content.contains(trigger) || trigger === activeTrigger) return;"
- , " showPreview(trigger, event);"
- , " });"
- , ""
- , " content.addEventListener('pointermove', (event) => {"
- , " const trigger = findTrigger(event.target);"
- , " if (!trigger || trigger !== activeTrigger) return;"
- , " if (isPinned) return;"
- , " lastPointer = { clientX: event.clientX, clientY: event.clientY };"
- , " placePreview();"
- , " });"
- , ""
- , " content.addEventListener('pointerout', (event) => {"
- , " const trigger = findTrigger(event.target);"
- , " if (!trigger || trigger !== activeTrigger) return;"
- , " if (isPinned) return;"
- , " const related = event.relatedTarget;"
- , " if (related instanceof Node && trigger.contains(related)) return;"
- , " if (related instanceof Node && popup.contains(related)) return;"
- , " scheduleHide();"
- , " });"
- , ""
- , " content.addEventListener('focusin', (event) => {"
- , " const trigger = findTrigger(event.target);"
- , " if (!trigger || !content.contains(trigger)) return;"
- , " showPreview(trigger, null);"
- , " });"
- , ""
- , " content.addEventListener('focusout', (event) => {"
- , " const trigger = findTrigger(event.target);"
- , " if (trigger && trigger === activeTrigger && !isPinned) scheduleHide();"
- , " });"
- , ""
- , " content.addEventListener('click', (event) => {"
- , " const trigger = findTrigger(event.target);"
- , " if (!trigger || !trigger.hasAttribute('data-preview-group') || !content.contains(trigger)) return;"
- , " event.preventDefault();"
- , " showPreview(trigger, event, true);"
- , " });"
- , ""
- , " popup.addEventListener('pointerenter', clearHideTimer);"
- , " popup.addEventListener('pointerleave', scheduleHide);"
- , ""
- , " document.addEventListener('click', (event) => {"
- , " if (!isPinned) return;"
- , " const target = event.target;"
- , " if (target instanceof Node && popup.contains(target)) return;"
- , " if (activeTrigger && target instanceof Node && activeTrigger.contains(target)) return;"
- , " hidePreview();"
- , " });"
- , ""
- , " content.addEventListener('scroll', hidePreview, { passive: true });"
- , " window.addEventListener('resize', hidePreview);"
- , " window.addEventListener('hashchange', hidePreview);"
- , " document.addEventListener('keydown', (event) => {"
- , " if (event.key === 'Escape') {"
- , " hidePreview();"
- , " return;"
- , " }"
- , " if (event.key !== 'Enter' && event.key !== ' ') return;"
- , " const trigger = findTrigger(document.activeElement);"
- , " if (!trigger || !trigger.hasAttribute('data-preview-group')) return;"
- , " event.preventDefault();"
- , " showPreview(trigger, null, true);"
- , " });"
- , "})();"
- ]
-
-collectReferencedMarkersOfBlock :: Block -> Set Marker
-collectReferencedMarkersOfBlock =
- collectBlock
- where
- collectBlock :: Block -> Set Marker
- collectBlock = \case
- BlockProof _start proof _end ->
- collectProof proof
- _ ->
- mempty
-
- collectProof :: Proof -> Set Marker
- collectProof = \case
- Omitted _loc ->
- mempty
- Qed _loc justification ->
- collectJustification justification
- Contradiction _loc justification ->
- collectJustification justification
- ByCase _loc cases ->
- foldMap collectCase cases
- ByContradiction _loc proof ->
- collectProof proof
- BySetInduction _loc _term proof ->
- collectProof proof
- ByOrdInduction _loc proof ->
- collectProof proof
- Assume _loc _stmt proof ->
- collectProof proof
- FixSymbolic _loc _vars _bound proof ->
- collectProof proof
- FixSuchThat _loc _vars _stmt proof ->
- collectProof proof
- Calc _loc _maybeQuant calc proof ->
- collectCalc calc <> collectProof proof
- TakeVar _loc _vars _bound _stmt justification proof ->
- collectJustification justification <> collectProof proof
- TakeNoun _loc _np justification proof ->
- collectJustification justification <> collectProof proof
- Have _loc _maybeStmt _stmt justification proof ->
- collectJustification justification <> collectProof proof
- Suffices _loc _stmt justification proof ->
- collectJustification justification <> collectProof proof
- Subclaim _loc _stmt subproof proof ->
- collectProof subproof <> collectProof proof
- Define _loc _var _expr proof ->
- collectProof proof
- DefineFunction _loc _fun _arg _value _boundVar _boundExpr proof ->
- collectProof proof
- DefineFunctionLocal _loc _fun _arg _target _domVar _codVar _rules proof ->
- collectProof proof
-
- collectCase :: Case -> Set Marker
- collectCase Case{caseProof} =
- collectProof caseProof
-
- collectCalc :: Calc -> Set Marker
- collectCalc = \case
- Equation _expr steps ->
- foldMap (collectJustification . snd) steps
- Biconditionals _formula steps ->
- foldMap (collectJustification . snd) steps
-
- collectJustification :: Justification -> Set Marker
- collectJustification = \case
- JustificationRef markers ->
- Set.fromList (toList markers)
- JustificationSetExt ->
- mempty
- JustificationEmpty ->
- mempty
- JustificationLocal ->
- mempty
-
-collectMissingHints :: HintMap -> [Block] -> MissingHintMap
-collectMissingHints hints = foldMap collectBlock
- where
- noteMissingHint :: HintCategory -> Marker -> Int -> MissingHintMap
- noteMissingHint category marker arity =
- if Map.member (category, marker, arity) hints
- then mempty
- else MissingHintMap (Map.singleton category (Set.singleton marker))
-
- collectBlock :: Block -> MissingHintMap
- collectBlock = \case
- BlockAxiom _loc _title _marker axiom ->
- collectAxiom axiom
- BlockClaim _kind _loc _title _marker claim ->
- collectClaim claim
- BlockProof _start proof _end ->
- collectProof proof
- BlockDefn _loc _title _marker defn ->
- collectDefn defn
- BlockAbbr _loc _title _marker abbr ->
- collectAbbreviation abbr
- BlockData _loc _title _marker datatype ->
- collectDatatype datatype
- BlockInductive _loc _title _marker ind ->
- collectInductive ind
- BlockSig _loc _title _marker asms sig ->
- collectAsms asms
- <> collectSignature sig
- BlockStruct _loc _title _marker structDefn ->
- collectStructDefn structDefn
-
- collectAxiom :: Axiom -> MissingHintMap
- collectAxiom (Axiom asms stmt) =
- collectAsms asms <> collectStmt stmt
-
- collectClaim :: Claim -> MissingHintMap
- collectClaim (Claim asms stmt) =
- collectAsms asms <> collectStmt stmt
-
- collectDefn :: Defn -> MissingHintMap
- collectDefn = \case
- Defn asms defnHead stmt ->
- collectAsms asms
- <> collectDefnHead defnHead
- <> collectStmt stmt
- DefnFun asms _fun maybeTerm resultTerm ->
- collectAsms asms
- <> foldMap collectTerm maybeTerm
- <> collectTerm resultTerm
- DefnOp symb expr ->
- collectSymbolPattern symb
- <> collectExpr expr
-
- collectDefnHead :: DefnHead -> MissingHintMap
- collectDefnHead = \case
- DefnAdj maybeNp _var _adj ->
- foldMap collectNounPhraseMaybe maybeNp
- DefnVerb maybeNp _var _verb ->
- foldMap collectNounPhraseMaybe maybeNp
- DefnNoun _var noun ->
- collectVarNoun noun
- DefnSymbolicPredicate _predi marker vars ->
- noteMissingHint PredicateHint marker (length vars)
- <> foldMap (collectExpr . ExprVar) vars
- DefnRel _x rel params _y ->
- noteMissingHint RelationHint (relationSymbolMarker rel) (length params)
-
- collectAbbreviation :: Abbreviation -> MissingHintMap
- collectAbbreviation = \case
- AbbreviationAdj _var _adj stmt ->
- collectStmt stmt
- AbbreviationVerb _var _verb stmt ->
- collectStmt stmt
- AbbreviationNoun _var _noun stmt ->
- collectStmt stmt
- AbbreviationRel _x rel params _y stmt ->
- noteMissingHint RelationHint (relationSymbolMarker rel) (length params)
- <> collectStmt stmt
- AbbreviationFun _fun bodyTerm ->
- collectTerm bodyTerm
- AbbreviationEq symb expr ->
- collectSymbolPattern symb
- <> collectExpr expr
-
- collectDatatype :: Datatype -> MissingHintMap
- collectDatatype Datatype{..} =
- collectExpr datatypeHeadExpr
- <> foldMap collectDatatypeClause datatypeClauses
-
- collectDatatypeClause :: DatatypeClause -> MissingHintMap
- collectDatatypeClause DatatypeClause{..} =
- collectExpr datatypeClauseConstructorExpr
- <> collectExpr datatypeClauseTargetExpr
- <> foldMap (collectExpr . snd) datatypeClausePremises
-
- collectInductive :: Inductive -> MissingHintMap
- collectInductive Inductive{..} =
- collectSymbolPattern inductiveSymbolPattern
- <> collectExpr inductiveDomain
- <> foldMap collectIntroRule inductiveIntros
-
- collectIntroRule :: IntroRule -> MissingHintMap
- collectIntroRule IntroRule{..} =
- foldMap collectFormula introConditions
- <> collectFormula introResult
-
- collectSignature :: Signature -> MissingHintMap
- collectSignature = \case
- SignatureAdj _var adj ->
- collectVarAdj adj
- SignatureVerb _var verb ->
- collectVarVerb verb
- SignatureNoun _var noun ->
- collectVarNoun noun
- SignatureSymbolic symb np ->
- collectSymbolPattern symb
- <> collectNounPhraseMaybe np
-
- collectStructDefn :: StructDefn -> MissingHintMap
- collectStructDefn StructDefn{structAssumes} =
- foldMap (collectStmt . snd) structAssumes
-
- collectProof :: Proof -> MissingHintMap
- collectProof = \case
- Omitted _loc ->
- mempty
- Qed{} ->
- mempty
- Contradiction{} ->
- mempty
- ByCase _loc cases ->
- foldMap collectCase cases
- ByContradiction _loc proof ->
- collectProof proof
- BySetInduction _loc maybeTerm proof ->
- foldMap collectTerm maybeTerm
- <> collectProof proof
- ByOrdInduction _loc proof ->
- collectProof proof
- Assume _loc stmt proof ->
- collectStmt stmt
- <> collectProof proof
- FixSymbolic _loc _vars bound proof ->
- collectBound bound
- <> collectProof proof
- FixSuchThat _loc _vars stmt proof ->
- collectStmt stmt
- <> collectProof proof
- Calc _loc maybeQuant calc proof ->
- foldMap collectCalcQuantifier maybeQuant
- <> collectCalc calc
- <> collectProof proof
- TakeVar _loc _vars bound stmt _justification proof ->
- collectBound bound
- <> collectStmt stmt
- <> collectProof proof
- TakeNoun _loc np _justification proof ->
- collectNounPhraseList np
- <> collectProof proof
- Have _loc maybeStmt stmt _justification proof ->
- foldMap collectStmt maybeStmt
- <> collectStmt stmt
- <> collectProof proof
- Suffices _loc stmt _justification proof ->
- collectStmt stmt
- <> collectProof proof
- Subclaim _loc stmt subproof proof ->
- collectStmt stmt
- <> collectProof subproof
- <> collectProof proof
- Define _loc _var expr proof ->
- collectExpr expr
- <> collectProof proof
- DefineFunction _loc _fun _arg value _boundVar boundExpr proof ->
- collectExpr value
- <> collectExpr boundExpr
- <> collectProof proof
- DefineFunctionLocal _loc _fun _arg _target _domVar _codVar rules proof ->
- foldMap collectLocalFunctionRule rules
- <> collectProof proof
-
- collectLocalFunctionRule :: (Expr, Formula) -> MissingHintMap
- collectLocalFunctionRule (ruleTerm, formula) =
- collectExpr ruleTerm
- <> collectFormula formula
-
- collectCase :: Case -> MissingHintMap
- collectCase Case{caseOf, caseProof} =
- collectStmt caseOf
- <> collectProof caseProof
-
- collectCalcQuantifier :: CalcQuantifier -> MissingHintMap
- collectCalcQuantifier (CalcQuantifier _vars bound maybeStmt) =
- collectBound bound
- <> foldMap collectStmt maybeStmt
-
- collectCalc :: Calc -> MissingHintMap
- collectCalc = \case
- Equation expr steps ->
- collectExpr expr
- <> foldMap (collectExpr . fst) steps
- Biconditionals phi steps ->
- collectFormula phi
- <> foldMap (collectFormula . fst) steps
-
- collectStmt :: Stmt -> MissingHintMap
- collectStmt = \case
- StmtFormula phi ->
- collectFormula phi
- StmtVerbPhrase terms verbPhrase ->
- collectTerms terms
- <> collectVerbPhrase verbPhrase
- StmtNoun terms np ->
- collectTerms terms
- <> collectNounPhraseMaybe np
- StmtStruct stmtTerm _structPhrase ->
- collectTerm stmtTerm
- StmtNeg _loc stmt ->
- collectStmt stmt
- StmtExists _loc np ->
- collectNounPhraseList np
- StmtConnected _conn _loc stmt1 stmt2 ->
- collectStmt stmt1
- <> collectStmt stmt2
- StmtQuantPhrase _loc qp stmt ->
- collectQuantPhrase qp
- <> collectStmt stmt
- SymbolicQuantified _loc _quant _vars bound suchThat stmt ->
- collectBound bound
- <> foldMap collectStmt suchThat
- <> collectStmt stmt
-
- collectQuantPhrase :: QuantPhrase -> MissingHintMap
- collectQuantPhrase (QuantPhrase _quant np) =
- collectNounPhraseList np
-
- collectAsm :: Asm -> MissingHintMap
- collectAsm = \case
- AsmSuppose stmt ->
- collectStmt stmt
- AsmLetNoun _vars np ->
- collectNounPhraseMaybe np
- AsmLetIn _vars expr ->
- collectExpr expr
- AsmLetThe _var fun ->
- collectFun fun
- AsmLetEq _var expr ->
- collectExpr expr
- AsmLetStruct{} ->
- mempty
-
- collectTerm :: Term -> MissingHintMap
- collectTerm = \case
- TermExpr expr ->
- collectExpr expr
- TermFun fun ->
- collectFun fun
- TermIota _loc _var stmt ->
- collectStmt stmt
- TermQuantified _quant _loc np ->
- collectNounPhraseMaybe np
-
- collectNounPhraseMaybe :: NounPhrase Maybe -> MissingHintMap
- collectNounPhraseMaybe (NounPhrase ls noun _maybeName rs maybeSuchThat) =
- collectAdjLs ls
- <> collectNoun noun
- <> collectAdjRs rs
- <> foldMap collectStmt maybeSuchThat
-
- collectNounPhraseList :: NounPhrase [] -> MissingHintMap
- collectNounPhraseList (NounPhrase ls noun _names rs maybeSuchThat) =
- collectAdjLs ls
- <> collectNoun noun
- <> collectAdjRs rs
- <> foldMap collectStmt maybeSuchThat
-
- collectAdjL :: AdjLOf Term -> MissingHintMap
- collectAdjL (AdjL _loc _item args) =
- collectTerms args
-
- collectAdjR :: AdjROf Term -> MissingHintMap
- collectAdjR = \case
- AdjR _loc _item args ->
- collectTerms args
- AttrRThat verbPhrase ->
- collectVerbPhrase verbPhrase
-
- collectAdj :: AdjOf Term -> MissingHintMap
- collectAdj (Adj _loc _item args) =
- collectTerms args
-
- collectVarAdj :: AdjOf VarSymbol -> MissingHintMap
- collectVarAdj _adj =
- mempty
-
- collectVerb :: VerbOf Term -> MissingHintMap
- collectVerb (Verb _loc _item args) =
- collectTerms args
-
- collectVarVerb :: VerbOf VarSymbol -> MissingHintMap
- collectVarVerb _verb =
- mempty
-
- collectVerbPhrase :: VerbPhrase -> MissingHintMap
- collectVerbPhrase = \case
- VPVerb verb ->
- collectVerb verb
- VPAdj adjs ->
- foldMap collectAdj adjs
- VPVerbNot verb ->
- collectVerb verb
- VPAdjNot adjs ->
- foldMap collectAdj adjs
-
- collectNoun :: NounOf Term -> MissingHintMap
- collectNoun (Noun _loc _item args) =
- collectTerms args
-
- collectVarNoun :: NounOf VarSymbol -> MissingHintMap
- collectVarNoun _noun =
- mempty
-
- collectFun :: FunOf Term -> MissingHintMap
- collectFun Fun{funArgs} =
- collectTerms funArgs
-
- collectBound :: Bound -> MissingHintMap
- collectBound = \case
- Unbounded ->
- mempty
- Bounded _loc _sign rel expr ->
- collectRelation rel
- <> collectExpr expr
-
- collectFormula :: Formula -> MissingHintMap
- collectFormula = \case
- FormulaChain chain ->
- collectChain chain
- FormulaPredicate _loc _predi marker exprs ->
- noteMissingHint PredicateHint marker (length exprs)
- <> collectExprs exprs
- Connected _loc _conn phi psi ->
- collectFormula phi
- <> collectFormula psi
- FormulaNeg _loc phi ->
- collectFormula phi
- FormulaQuantified _loc _quant _vars bound phi ->
- collectBound bound
- <> collectFormula phi
- PropositionalConstant{} ->
- mempty
-
- collectChain :: Chain -> MissingHintMap
- collectChain = \case
- ChainBase lhs _sign rel rhs ->
- collectExprs lhs
- <> collectRelation rel
- <> collectExprs rhs
- ChainCons lhs _sign rel chain ->
- collectExprs lhs
- <> collectRelation rel
- <> collectChain chain
-
- collectRelation :: Relation -> MissingHintMap
- collectRelation = \case
- Relation _loc symbol relParams ->
- noteMissingHint RelationHint (relationSymbolMarker symbol) (length relParams)
- <> collectExprs relParams
- RelationExpr _loc expr ->
- collectExpr expr
-
- collectExpr :: Expr -> MissingHintMap
- collectExpr = \case
- ExprVar{} ->
- mempty
- ExprInteger{} ->
- mempty
- ExprOp _loc item args ->
- noteMissingHint OperatorHint (mixfixMarker item) (length args)
- <> collectExprs args
- ExprStructOp _loc symb maybeExpr ->
- noteMissingHint StructOpHint (structMarker symb) (length (maybeToList maybeExpr))
- <> foldMap collectExpr maybeExpr
- ExprFiniteSet _loc exprs ->
- collectExprs exprs
- ExprSep _loc _var boundExpr stmt ->
- collectExpr boundExpr
- <> collectStmt stmt
- ExprReplace _loc expr bounds maybeStmt ->
- collectExpr expr
- <> foldMap (collectExpr . snd) bounds
- <> foldMap collectStmt maybeStmt
- ExprReplacePred _loc _rangeVar _domVar domExpr stmt ->
- collectExpr domExpr
- <> collectStmt stmt
-
- collectSymbolPattern :: SymbolPattern -> MissingHintMap
- collectSymbolPattern (SymbolPattern symbol vars) =
- noteMissingHint OperatorHint (mixfixMarker symbol) (length vars)
-
- collectAsms :: [Asm] -> MissingHintMap
- collectAsms =
- foldMap collectAsm
-
- collectTerms :: Foldable t => t Term -> MissingHintMap
- collectTerms =
- foldMap collectTerm
-
- collectAdjLs :: [AdjLOf Term] -> MissingHintMap
- collectAdjLs =
- foldMap collectAdjL
-
- collectAdjRs :: [AdjROf Term] -> MissingHintMap
- collectAdjRs =
- foldMap collectAdjR
-
- collectExprs :: Foldable t => t Expr -> MissingHintMap
- collectExprs =
- foldMap collectExpr
-
-formatMissingHintWarning :: MissingHintMap -> Maybe Text
-formatMissingHintWarning missingHints
- | null parts = Nothing
- | otherwise = Just ("WARNING: missing render hints: " <> Text.intercalate "; " parts)
- where
- missingHintMap = unMissingHintMap missingHints
-
- parts =
- [ label <> "(" <> Text.intercalate ", " (markerText <$> Set.toAscList markers) <> ")"
- | (category, label) <- categoryLabels
- , Just markers <- [Map.lookup category missingHintMap]
- , not (Set.null markers)
- ]
-
- categoryLabels :: [(HintCategory, Text)]
- categoryLabels =
- [ (OperatorHint, "operators")
- , (RelationHint, "relations")
- , (PredicateHint, "predicates")
- , (StructOpHint, "structops")
- ]
-
-
-parseHints :: Text -> HintMap
-parseHints source = Map.fromList (parseLine <$> zip [1 :: Int ..] relevantLines)
- where
- relevantLines = [line | line <- Text.lines source, not (Text.all isSpace line)]
-
- parseLine :: (Int, Text) -> ((HintCategory, Marker, Int), RenderHint)
- parseLine (lineNo, line) = case Text.splitOn "\t" line of
- [categoryText, markerName, arityText, templateText] ->
- let category = parseCategory lineNo categoryText
- marker = Marker markerName
- arity = parseArity lineNo arityText
- template = parseTemplate lineNo templateText
- in ((category, marker, arity), RenderHint arity template)
- _ ->
- error ("Malformed render hint at line " <> show lineNo <> ": expected exactly 4 tab-separated columns")
-
-parseCategory :: Int -> Text -> HintCategory
-parseCategory lineNo = \case
- "operator" -> OperatorHint
- "relation" -> RelationHint
- "predicate" -> PredicateHint
- "structop" -> StructOpHint
- other -> error ("Unknown render hint category at line " <> show lineNo <> ": " <> Text.unpack other)
-
-parseArity :: Int -> Text -> Int
-parseArity lineNo text = case reads (Text.unpack text) of
- [(n, "")] -> n
- _ -> error ("Malformed render-hint arity at line " <> show lineNo <> ": " <> Text.unpack text)
-
-parseTemplate :: Int -> Text -> [TemplatePiece]
-parseTemplate lineNo template = reverse (flush mempty (go mempty [] template))
- where
- go :: Text -> [TemplatePiece] -> Text -> [TemplatePiece]
- go literal acc rest = case parseSlot rest of
- Just (slot, rest') ->
- go mempty (Slot slot : flush literal acc) rest'
- Nothing -> case Text.uncons rest of
- Nothing -> flush literal acc
- Just (c, rest') -> go (Text.snoc literal c) acc rest'
-
- flush :: Text -> [TemplatePiece] -> [TemplatePiece]
- flush literal acc
- | Text.null literal = acc
- | otherwise = Literal literal : acc
-
- parseSlot :: Text -> Maybe (Int, Text)
- parseSlot text = do
- text' <- Text.stripPrefix "<x" text
- (digit, rest) <- Text.uncons text'
- guard (isDigit digit)
- rest' <- Text.stripPrefix "/>" rest
- let slot = digitToInt digit
- guard (slot > 0 && slot <= 9)
- pure (slot, rest')
-
- _unusedLineNo = lineNo
-
-
-renderBlock :: HintMap -> ReferenceContext -> BlockRenderInfo -> Html ()
-renderBlock hints references (_index, block, blockId) = case block of
- BlockAxiom _loc title marker axiom ->
- renderCustomBlock blockId "axiom-" "Axiom" (Just marker) title (renderAxiom hints axiom)
- BlockClaim kind _loc title marker claim ->
- renderCustomBlock blockId (claimKindElement kind) (claimKindPrefix kind) (Just marker) title (renderClaim hints claim)
- BlockProof _start proof _end ->
- renderProofBlock hints references proof
- BlockDefn _loc title marker defn ->
- renderCustomBlock blockId "definition-" "Definition" (Just marker) title (renderDefn hints defn)
- BlockAbbr _loc title marker abbr ->
- renderCustomBlock blockId "abbreviation-" "Abbreviation" (Just marker) title (renderAbbreviation hints abbr)
- BlockData _loc title marker datatype ->
- renderCustomBlock blockId "datatype-" "Datatype" (Just marker) title (renderDatatype hints datatype)
- BlockInductive _loc title marker ind ->
- renderCustomBlock blockId "inductive-" "Inductive" (Just marker) title (renderInductive hints marker ind)
- BlockSig _loc title marker asms sig ->
- renderCustomBlock blockId "signature-" "Signature" (Just marker) title (renderSignatureBlock hints asms sig)
- BlockStruct _loc title marker structDefn ->
- renderCustomBlock blockId "struct-" "Structure" (Just marker) title (renderStructDefn hints structDefn)
-
-renderTocEntry :: (Int, Text, Block) -> Html ()
-renderTocEntry (index, blockId, block) =
- li_ do
- a_ [href_ (renderUrlFragment blockId)] do
- span_ (toHtml (blockPrefixText block))
- case formatMarker (blockMarkerOf block) of
- Nothing ->
- when (blockNeedsIndexLabel block) do
- code_ (toHtml (Text.pack (show index)))
- Just marker ->
- code_ (toHtml marker)
-
-includeInToc :: Block -> Bool
-includeInToc = \case
- BlockProof{} -> False
- _ -> True
-
-renderCustomBlock :: Text -> Text -> Text -> Maybe Marker -> Maybe BlockTitle -> Html () -> Html ()
-renderCustomBlock blockId name prefix mmarker mtitle body =
- term name (id_ blockId : previewTargetAttributes prefix mmarker mtitle) do
- renderBlockLead prefix mmarker mtitle True
- body
-
-previewTargetAttributes :: Text -> Maybe Marker -> Maybe BlockTitle -> [Attributes]
-previewTargetAttributes prefix mmarker mtitle =
- case formatMarker mmarker of
- Nothing ->
- []
- Just marker ->
- [ makeAttributes "data-preview-kind" prefix
- , makeAttributes "data-preview-label" marker
- ]
- <> case formatBlockTitle mtitle of
- Nothing ->
- []
- Just title ->
- [makeAttributes "data-preview-title" title]
-
-renderProofBlock :: HintMap -> ReferenceContext -> Proof -> Html ()
-renderProofBlock hints references proof
- | proofStepCount proof >= proofCollapseThreshold =
- term "proof-" do
- details_ do
- summary_ (renderBlockLead "Proof" Nothing Nothing False)
- renderProof hints references proof
- | otherwise =
- term "proof-" do
- renderBlockLead "Proof" Nothing Nothing True
- renderProof hints references proof
-
-blockAnchorId :: Int -> Block -> Text
-blockAnchorId index block =
- case formatMarker (blockMarkerOf block) of
- Just marker -> marker
- Nothing -> sanitizeIdFragment (Text.toLower (blockPrefixText block) <> "-" <> Text.pack (show index))
-
-sanitizeIdFragment :: Text -> Text
-sanitizeIdFragment =
- Text.dropWhile (== '-') . Text.map sanitize . Text.toLower
- where
- sanitize c
- | isAlphaNum c = c
- | c == '-' || c == '_' = c
- | otherwise = '-'
-
-blockPrefixText :: Block -> Text
-blockPrefixText = \case
- BlockAxiom{} -> "Axiom"
- BlockClaim kind _ _ _ _ -> claimKindPrefix kind
- BlockProof{} -> "Proof"
- BlockDefn{} -> "Definition"
- BlockAbbr{} -> "Abbreviation"
- BlockData{} -> "Datatype"
- BlockInductive{} -> "Inductive"
- BlockSig{} -> "Signature"
- BlockStruct{} -> "Structure"
-
-blockMarkerOf :: Block -> Maybe Marker
-blockMarkerOf = \case
- BlockAxiom _ _ marker _ -> Just marker
- BlockClaim _ _ _ marker _ -> Just marker
- BlockProof{} -> Nothing
- BlockDefn _ _ marker _ -> Just marker
- BlockAbbr _ _ marker _ -> Just marker
- BlockData _ _ marker _ -> Just marker
- BlockInductive _ _ marker _ -> Just marker
- BlockSig _ _ marker _ _ -> Just marker
- BlockStruct _ _ marker _ -> Just marker
-
-blockTitleOf :: Block -> Maybe BlockTitle
-blockTitleOf = \case
- BlockAxiom _ title _ _ -> title
- BlockClaim _ _ title _ _ -> title
- BlockProof{} -> Nothing
- BlockDefn _ title _ _ -> title
- BlockAbbr _ title _ _ -> title
- BlockData _ title _ _ -> title
- BlockInductive _ title _ _ -> title
- BlockSig _ title _ _ _ -> title
- BlockStruct _ title _ _ -> title
-
-blockNeedsIndexLabel :: Block -> Bool
-blockNeedsIndexLabel block = case (formatMarker (blockMarkerOf block), formatBlockTitle (blockTitleOf block)) of
- (Nothing, Nothing) -> True
- _ -> False
-
-renderBlockLead :: Text -> Maybe Marker -> Maybe BlockTitle -> Bool -> Html ()
-renderBlockLead prefix mmarker mtitle withTrailingSpace =
- term "head-" do
- toHtml prefix
- case formatMarker mmarker of
- Nothing -> skip
- Just marker ->
- term "id-" (toHtml marker)
- case formatBlockTitle mtitle of
- Nothing ->
- toHtml ("." <> suffix)
- Just title -> do
- toHtml (" (" :: Text)
- term "title-" (toHtml title)
- toHtml (")." <> suffix)
- where
- suffix :: Text
- suffix
- | withTrailingSpace = " "
- | otherwise = ""
-
-formatMarker :: Maybe Marker -> Maybe Text
-formatMarker = \case
- Nothing -> Nothing
- Just marker ->
- let text = Text.strip (markerText marker)
- in if Text.null text then Nothing else Just text
-
-formatBlockTitle :: Maybe BlockTitle -> Maybe Text
-formatBlockTitle =
- fmap capitalizeTitle . nonEmptyTitle
- where
- nonEmptyTitle = \case
- Nothing -> Nothing
- Just toks ->
- let text = Text.strip (Text.unwords (tokToText <$> toks))
- in if Text.null text then Nothing else Just text
-
-capitalizeTitle :: Text -> Text
-capitalizeTitle text = case Text.uncons text of
- Nothing -> text
- Just (c, rest) -> Text.cons (toUpper c) rest
-
-claimKindElement :: ClaimKind -> Text
-claimKindElement = \case
- Proposition -> "proposition-"
- Theorem -> "theorem-"
- Lemma -> "lemma-"
- Corollary -> "corollary-"
- PlainClaim -> "claim-"
-
-claimKindPrefix :: ClaimKind -> Text
-claimKindPrefix = \case
- Proposition -> "Proposition"
- Theorem -> "Theorem"
- Lemma -> "Lemma"
- Corollary -> "Corollary"
- PlainClaim -> "Claim"
-
-
-renderAxiom :: HintMap -> Axiom -> Html ()
-renderAxiom hints (Axiom asms stmt) =
- renderWithAssumptions hints asms stmt
-
-renderClaim :: HintMap -> Claim -> Html ()
-renderClaim hints (Claim asms stmt) =
- renderWithAssumptions hints asms stmt
-
-renderWithAssumptions :: HintMap -> [Asm] -> Stmt -> Html ()
-renderWithAssumptions hints asms stmt = do
- case asms of
- [] -> renderStmtInline hints stmt
- _ -> do
- toHtml ("Suppose " :: Text)
- renderAsmList hints asms
- toHtml (". Then " :: Text)
- renderStmtInline hints stmt
- toHtml ("." :: Text)
-
-renderDefn :: HintMap -> Defn -> Html ()
-renderDefn hints = \case
- Defn asms headStmt stmt ->
- do
- when (not (null asms)) do
- toHtml ("If " :: Text)
- renderAsmList hints asms
- toHtml (", then " :: Text)
- renderDefnHead hints headStmt
- toHtml (" iff " :: Text)
- renderStmtInline hints stmt
- toHtml ("." :: Text)
- DefnFun asms fun maybeSymbol resultTerm ->
- do
- when (not (null asms)) do
- toHtml ("If " :: Text)
- renderAsmList hints asms
- toHtml (", then " :: Text)
- renderFunInline renderVarInline fun
- case maybeSymbol of
- Nothing -> skip
- Just symbolicTerm -> do
- toHtml (", " :: Text)
- renderTermInline hints symbolicTerm
- toHtml (" is " :: Text)
- renderTermInline hints resultTerm
- toHtml ("." :: Text)
- DefnOp symb expr ->
- do
- inlineMath do
- renderSymbolPatternMath hints symb
- moText "="
- renderExprMathRow hints expr
- toHtml ("." :: Text)
-
-renderDefnHead :: HintMap -> DefnHead -> Html ()
-renderDefnHead hints = \case
- DefnAdj maybeNp var adj -> do
- renderTypedVar hints maybeNp var
- toHtml (" is " :: Text)
- renderAdjInline renderVarInline adj
- DefnVerb maybeNp var verb -> do
- renderTypedVar hints maybeNp var
- toHtml (" " :: Text)
- renderVerbInline False renderVarInline verb
- DefnNoun var noun -> do
- renderVarInline var
- toHtml (" is a " :: Text)
- renderNounInline False renderVarInline noun
- DefnSymbolicPredicate predi marker vars ->
- inlineMath
- ( renderHintedMathRow
- hints
- PredicateHint
- marker
- (ExprVar <$> toList vars)
- (renderPrefixPredicateFallback predi (renderVarMath <$> toList vars))
- )
- DefnRel x rel params y ->
- inlineMath (renderRelationApplication hints Positive [ExprVar x] (Relation Nowhere rel [ExprVar p | p <- params]) [ExprVar y])
-
-renderTypedVar :: HintMap -> Maybe (NounPhrase Maybe) -> VarSymbol -> Html ()
-renderTypedVar hints = \case
- Nothing -> renderVarInline
- Just np -> \var -> do
- renderNounPhraseMaybe hints np
- toHtml (" " :: Text)
- renderVarInline var
-
-
-renderAbbreviation :: HintMap -> Abbreviation -> Html ()
-renderAbbreviation hints = \case
- AbbreviationAdj var adj stmt ->
- do
- renderVarInline var
- toHtml (" is " :: Text)
- renderAdjInline renderVarInline adj
- toHtml (" stands for " :: Text)
- renderStmtInline hints stmt
- toHtml ("." :: Text)
- AbbreviationVerb var verb stmt ->
- do
- renderVarInline var
- toHtml (" " :: Text)
- renderVerbInline False renderVarInline verb
- toHtml (" stands for " :: Text)
- renderStmtInline hints stmt
- toHtml ("." :: Text)
- AbbreviationNoun var noun stmt ->
- do
- renderVarInline var
- toHtml (" is a " :: Text)
- renderNounInline False renderVarInline noun
- toHtml (" stands for " :: Text)
- renderStmtInline hints stmt
- toHtml ("." :: Text)
- AbbreviationRel x rel params y stmt ->
- do
- inlineMath (renderRelationApplication hints Positive [ExprVar x] (Relation Nowhere rel [ExprVar p | p <- params]) [ExprVar y])
- toHtml (" stands for " :: Text)
- renderStmtInline hints stmt
- toHtml ("." :: Text)
- AbbreviationFun fun bodyTerm ->
- do
- renderFunInline renderVarInline fun
- toHtml (" stands for " :: Text)
- renderTermInline hints bodyTerm
- toHtml ("." :: Text)
- AbbreviationEq symb expr ->
- do
- renderSymbolPatternInline hints symb
- toHtml (" stands for " :: Text)
- inlineMath (renderExprMathRow hints expr)
- toHtml ("." :: Text)
-
-renderDatatype :: HintMap -> Datatype -> Html ()
-renderDatatype hints Datatype{..} = do
- toHtml ("Datatype of " :: Text)
- inlineMath (renderExprMathRow hints datatypeHeadExpr)
- toHtml ("." :: Text)
- ul_ do
- traverse_ renderDatatypeClause (toList datatypeClauses)
- -- Derived facts require checked semantic context.
- where
- renderDatatypeClause :: DatatypeClause -> Html ()
- renderDatatypeClause DatatypeClause{..} = li_ do
- inlineMath do
- renderRelationApplication hints Positive [datatypeClauseConstructorExpr] (Relation Nowhere ElementSymbol []) [datatypeClauseTargetExpr]
- case datatypeClausePremises of
- [] ->
- toHtml ("." :: Text)
- premises -> do
- toHtml (" for " :: Text)
- joinHtml (toHtml (" and " :: Text)) (renderDatatypePremise <$> premises)
- toHtml ("." :: Text)
-
- renderDatatypePremise :: (VarSymbol, Expr) -> Html ()
- renderDatatypePremise (x, domain) =
- inlineMath (renderRelationApplication hints Positive [ExprVar x] (Relation Nowhere ElementSymbol []) [domain])
-
-exprVar :: Expr -> Maybe VarSymbol
-exprVar = \case
- ExprVar x ->
- Just x
- _ ->
- Nothing
-
-substituteExpr :: Map.Map VarSymbol Expr -> Expr -> Expr
-substituteExpr env = \case
- ExprVar x ->
- fromMaybe (ExprVar x) (Map.lookup x env)
- ExprInteger loc n ->
- ExprInteger loc n
- ExprOp loc symbol args ->
- ExprOp loc symbol (substituteExpr env <$> args)
- ExprStructOp loc symbol expr ->
- ExprStructOp loc symbol (substituteExpr env <$> expr)
- ExprFiniteSet loc exprs ->
- ExprFiniteSet loc (substituteExpr env <$> exprs)
- ExprSep loc x bound stmt ->
- ExprSep loc x (substituteExpr env bound) stmt
- ExprReplace loc expr bounds maybeStmt ->
- ExprReplace loc (substituteExpr env expr) ((\(x, bound) -> (x, substituteExpr env bound)) <$> bounds) maybeStmt
- ExprReplacePred loc x y expr stmt ->
- ExprReplacePred loc x y (substituteExpr env expr) stmt
-
-elementOfFormula :: Expr -> Expr -> Formula
-elementOfFormula left right =
- FormulaChain (ChainBase (left :| []) Positive (Relation Nowhere ElementSymbol []) (right :| []))
-
-semanticSubsetFormula :: Set VarSymbol -> Expr -> Expr -> Formula
-semanticSubsetFormula reserved left right =
- forallIfNeeded [witnessVar]
- (impliesFormula
- (elementOfFormula (ExprVar witnessVar) left)
- (elementOfFormula (ExprVar witnessVar) right))
- where
- witnessVar = freshDatatypeVar (reserved <> Set.fromList (exprVars left <> exprVars right)) "x"
-
-equalsFormula :: Expr -> Expr -> Formula
-equalsFormula left right =
- FormulaChain (ChainBase (left :| []) Positive (Relation Nowhere EqSymbol []) (right :| []))
-
-impliesFormula :: Formula -> Formula -> Formula
-impliesFormula left right =
- Connected Nowhere Implication left right
-
-formulaConjunction :: [Formula] -> Formula
-formulaConjunction = \case
- [] ->
- PropositionalConstant Nowhere IsTop
- phi : rest ->
- foldl' (\left right -> Connected Nowhere Conjunction left right) phi rest
-
-formulaDisjunction :: [Formula] -> Formula
-formulaDisjunction = \case
- [] ->
- PropositionalConstant Nowhere IsBottom
- phi : rest ->
- foldl' (\left right -> Connected Nowhere Disjunction left right) phi rest
-
-forallIfNeeded :: [VarSymbol] -> Formula -> Formula
-forallIfNeeded [] phi = phi
-forallIfNeeded vars phi =
- FormulaQuantified Nowhere Universally (NonEmpty.fromList vars) Unbounded phi
-
-existsIfNeeded :: [VarSymbol] -> Formula -> Formula
-existsIfNeeded [] phi = phi
-existsIfNeeded vars phi =
- FormulaQuantified Nowhere Existentially (NonEmpty.fromList vars) Unbounded phi
-
-impliesFrom :: [Formula] -> Formula -> Formula
-impliesFrom [] conclusion = conclusion
-impliesFrom premises conclusion = impliesFormula (formulaConjunction premises) conclusion
-
-freshDatatypeVar :: Set VarSymbol -> Text -> VarSymbol
-freshDatatypeVar used base =
- List.head
- [ NamedVar candidate
- | candidate <- base : [base <> Text.pack (show n) | n <- [(1 :: Int) ..]]
- , NamedVar candidate `Set.notMember` used
- ]
-
-renderInductive :: HintMap -> Marker -> Inductive -> Html ()
-renderInductive hints marker Inductive{..} = do
- toHtml ("Inductive definition of " :: Text)
- renderSymbolPatternInline hints inductiveSymbolPattern
- toHtml (" over " :: Text)
- inlineMath (renderExprMathRow hints inductiveDomain)
- toHtml ("." :: Text)
- ul_ do
- traverse_ renderIntro (toList inductiveIntros)
- case inductiveDerivedFacts marker Inductive{..} of
- [] ->
- skip
- derivedFacts ->
- details_ do
- summary_ (toHtml ("Derived facts" :: Text))
- ul_ [class_ "inductive-derived-facts"] do
- traverse_ (renderInductiveDerivedFact hints) derivedFacts
- where
- renderIntro :: IntroRule -> Html ()
- renderIntro IntroRule{..} = li_ do
- case introConditions of
- [] -> skip
- _ -> do
- toHtml ("If " :: Text)
- joinHtml (toHtml (" and " :: Text)) (inlineMath . renderFormulaMath hints <$> introConditions)
- toHtml (", then " :: Text)
- inlineMath (renderFormulaMath hints introResult)
- toHtml ("." :: Text)
-
-renderInductiveDerivedFact :: HintMap -> InductiveDerivedFact -> Html ()
-renderInductiveDerivedFact hints InductiveDerivedFact{inductiveDerivedFactMarker, inductiveDerivedFactFormula} =
- li_ (id_ (markerText inductiveDerivedFactMarker) : previewTargetAttributes "Inductive Fact" (Just inductiveDerivedFactMarker) Nothing) do
- term "head-" do
- code_ (toHtml (markerText inductiveDerivedFactMarker))
- toHtml (": " :: Text)
- inlineMath (renderFormulaMath hints inductiveDerivedFactFormula)
- toHtml ("." :: Text)
-
-data InductiveDerivedFact = InductiveDerivedFact
- { inductiveDerivedFactMarker :: Marker
- , inductiveDerivedFactFormula :: Formula
- }
-
-data InductiveRenderInfo = InductiveRenderInfo
- { inductiveFactBaseMarker :: Marker
- , inductiveRenderParams :: [VarSymbol]
- , inductiveRenderCarrierExpr :: Expr
- , inductiveRenderDomainExpr :: Expr
- , inductiveRenderClauses :: NonEmpty InductiveRenderClause
- }
-
-data InductiveRenderClause = InductiveRenderClause
- { inductiveRenderClauseVars :: [VarSymbol]
- , inductiveRenderClauseConditions :: [InductiveRenderCondition]
- , inductiveRenderClauseResultExpr :: Expr
- }
-
-data InductiveRenderCondition
- = InductiveRenderSideCondition Formula
- | InductiveRenderRecursiveCondition Expr Expr
-
-inductiveDerivedFacts :: Marker -> Inductive -> [InductiveDerivedFact]
-inductiveDerivedFacts marker inductive = case inductiveRenderInfo marker inductive of
- Nothing ->
- []
- Just info ->
- inductiveIntroFacts info
- <> [ InductiveDerivedFact (inductiveDomSubsetMarker info) (inductiveDomSubsetFormula info)
- , InductiveDerivedFact (inductiveCasesMarker info) (inductiveCasesFormula info)
- , InductiveDerivedFact (inductiveInductMarker info) (inductiveInductFormula info)
- ]
-
-inductiveRenderInfo :: Marker -> Inductive -> Maybe InductiveRenderInfo
-inductiveRenderInfo marker Inductive{inductiveSymbolPattern = SymbolPattern inductiveSymbol inductiveParams, inductiveDomain, inductiveIntros} = do
- inductiveRenderClauses <- traverse (inductiveRenderClause inductiveSymbol inductiveParams) inductiveIntros
- pure InductiveRenderInfo
- { inductiveFactBaseMarker = marker
- , inductiveRenderParams = inductiveParams
- , inductiveRenderCarrierExpr = ExprOp Nowhere inductiveSymbol (ExprVar <$> inductiveParams)
- , inductiveRenderDomainExpr = inductiveDomain
- , inductiveRenderClauses
- }
- where
- inductiveRenderClause :: MixfixItem -> [VarSymbol] -> IntroRule -> Maybe InductiveRenderClause
- inductiveRenderClause symbol params IntroRule{introConditions, introResult} = do
- inductiveRenderClauseResultExpr <- inductiveRenderResultExpr symbol params introResult
- inductiveRenderClauseConditions <- traverse (inductiveRenderCondition symbol params) introConditions
- let paramSet = Set.fromList params
- inductiveRenderClauseVars =
- List.filter (`Set.notMember` paramSet)
- (orderedRenderVars (concatMap formulaVars introConditions <> exprVars inductiveRenderClauseResultExpr))
- pure InductiveRenderClause
- { inductiveRenderClauseVars
- , inductiveRenderClauseConditions
- , inductiveRenderClauseResultExpr
- }
-
-inductiveRenderResultExpr :: MixfixItem -> [VarSymbol] -> Formula -> Maybe Expr
-inductiveRenderResultExpr symbol params = \case
- FormulaChain (ChainBase (resultExpr :| []) Positive (Relation _ ElementSymbol []) (carrierExpr :| []))
- | sameInductiveCarrierExpr symbol params carrierExpr ->
- Just resultExpr
- _ ->
- Nothing
-
-inductiveRenderCondition :: MixfixItem -> [VarSymbol] -> Formula -> Maybe InductiveRenderCondition
-inductiveRenderCondition symbol params phi
- | not (formulaMentionsFunction symbol phi) =
- Just (InductiveRenderSideCondition phi)
- | otherwise = case phi of
- FormulaChain (ChainBase (recursiveTerm :| []) Positive (Relation _ ElementSymbol []) (recursiveCarrier :| []))
- | not (exprMentionsFunction symbol recursiveTerm) ->
- InductiveRenderRecursiveCondition recursiveTerm <$> replaceCarrierExpr symbol params recursiveCarrier
- _ ->
- Nothing
- where
- replaceCarrierExpr :: MixfixItem -> [VarSymbol] -> Expr -> Maybe Expr
- replaceCarrierExpr target targetParams =
- replaceInductiveCarrierExpr target targetParams (ExprVar "__inductive")
-
-inductiveIntroFacts :: InductiveRenderInfo -> [InductiveDerivedFact]
-inductiveIntroFacts info =
- [ InductiveDerivedFact (inductiveIntroMarker info index) (inductiveIntroFormula info clause)
- | (index, clause) <- zip [(1 :: Int) ..] (NonEmpty.toList (inductiveRenderClauses info))
- ]
-
-inductiveIntroMarker :: InductiveRenderInfo -> Int -> Marker
-inductiveIntroMarker info index =
- Marker (markerText (inductiveFactBaseMarker info) <> "_intro_" <> Text.pack (show index))
-
-inductiveDomSubsetMarker :: InductiveRenderInfo -> Marker
-inductiveDomSubsetMarker info =
- Marker (markerText (inductiveFactBaseMarker info) <> "_dom_subset")
-
-inductiveCasesMarker :: InductiveRenderInfo -> Marker
-inductiveCasesMarker info =
- Marker (markerText (inductiveFactBaseMarker info) <> "_cases")
-
-inductiveInductMarker :: InductiveRenderInfo -> Marker
-inductiveInductMarker info =
- Marker (markerText (inductiveFactBaseMarker info) <> "_induct")
-
-inductiveIntroFormula :: InductiveRenderInfo -> InductiveRenderClause -> Formula
-inductiveIntroFormula info clause =
- forallIfNeeded (orderedRenderVars (inductiveRenderParams info <> inductiveRenderClauseVars clause))
- (impliesFrom premises conclusion)
- where
- premises = inductiveRenderConditionFormula info <$> inductiveRenderClauseConditions clause
- conclusion = elementOfFormula (inductiveRenderClauseResultExpr clause) (inductiveRenderCarrierExpr info)
-
-inductiveDomSubsetFormula :: InductiveRenderInfo -> Formula
-inductiveDomSubsetFormula info =
- forallIfNeeded (inductiveRenderParams info)
- (semanticSubsetFormula (inductiveUsedVars info) (inductiveRenderCarrierExpr info) (inductiveRenderDomainExpr info))
-
-inductiveCasesFormula :: InductiveRenderInfo -> Formula
-inductiveCasesFormula info =
- forallIfNeeded (orderedRenderVars (inductiveRenderParams info <> [witnessVar]))
- (impliesFormula (elementOfFormula (ExprVar witnessVar) (inductiveRenderCarrierExpr info)) (formulaDisjunction disjuncts))
- where
- witnessVar = freshDatatypeVar (inductiveUsedVars info) "x"
- disjuncts = inductiveCaseDisjunct witnessVar <$> NonEmpty.toList (inductiveRenderClauses info)
- inductiveCaseDisjunct x clause =
- existsIfNeeded (inductiveRenderClauseVars clause)
- (formulaConjunction (premises <> [equalsFormula (ExprVar x) (inductiveRenderClauseResultExpr clause)]))
- where
- premises = inductiveRenderConditionFormula info <$> inductiveRenderClauseConditions clause
-
-inductiveInductFormula :: InductiveRenderInfo -> Formula
-inductiveInductFormula info =
- forallIfNeeded (orderedRenderVars (inductiveRenderParams info <> [subsetVar]))
- (impliesFrom closures conclusion)
- where
- subsetVar = freshDatatypeVar (inductiveUsedVars info) "S"
- closures = inductiveInductionClosure subsetVar <$> NonEmpty.toList (inductiveRenderClauses info)
- conclusion =
- semanticSubsetFormula
- (Set.insert subsetVar (inductiveUsedVars info))
- (inductiveRenderCarrierExpr info)
- (ExprVar subsetVar)
-
-inductiveInductionClosure :: VarSymbol -> InductiveRenderClause -> Formula
-inductiveInductionClosure subsetVar clause =
- forallIfNeeded (inductiveRenderClauseVars clause) (impliesFrom premises conclusion)
- where
- premises = inductiveRenderConditionFormulaAt (ExprVar subsetVar) <$> inductiveRenderClauseConditions clause
- conclusion = elementOfFormula (inductiveRenderClauseResultExpr clause) (ExprVar subsetVar)
-
-inductiveRenderConditionFormula :: InductiveRenderInfo -> InductiveRenderCondition -> Formula
-inductiveRenderConditionFormula info =
- inductiveRenderConditionFormulaAt (inductiveRenderCarrierExpr info)
-
-inductiveRenderConditionFormulaAt :: Expr -> InductiveRenderCondition -> Formula
-inductiveRenderConditionFormulaAt replacement = \case
- InductiveRenderSideCondition phi ->
- phi
- InductiveRenderRecursiveCondition recursiveTerm recursiveCarrierTemplate ->
- elementOfFormula recursiveTerm (substituteExpr (Map.singleton "__inductive" replacement) recursiveCarrierTemplate)
-
-inductiveUsedVars :: InductiveRenderInfo -> Set VarSymbol
-inductiveUsedVars info =
- Set.fromList
- ( inductiveRenderParams info
- <> [ var
- | clause <- NonEmpty.toList (inductiveRenderClauses info)
- , var <- inductiveRenderClauseVars clause
- ]
- )
-
-sameInductiveCarrierExpr :: MixfixItem -> [VarSymbol] -> Expr -> Bool
-sameInductiveCarrierExpr symbol params = \case
- ExprOp _ symbol' args ->
- symbol == symbol'
- && length params == length args
- && and (zipWith (\param arg -> exprVar arg == Just param) params args)
- _ ->
- False
-
-replaceInductiveCarrierExpr :: MixfixItem -> [VarSymbol] -> Expr -> Expr -> Maybe Expr
-replaceInductiveCarrierExpr symbol params replacement = go
- where
- go = \case
- ExprVar x ->
- Just (ExprVar x)
- ExprInteger loc n ->
- Just (ExprInteger loc n)
- ExprOp loc symbol' args
- | sameInductiveCarrierExpr symbol params (ExprOp loc symbol' args) ->
- Just replacement
- | symbol == symbol' ->
- Nothing
- | otherwise ->
- ExprOp loc symbol' <$> traverse go args
- ExprStructOp loc structSymbol expr ->
- ExprStructOp loc structSymbol <$> traverse go expr
- ExprFiniteSet loc exprs ->
- ExprFiniteSet loc <$> traverse go exprs
- ExprSep{} ->
- Nothing
- ExprReplace{} ->
- Nothing
- ExprReplacePred{} ->
- Nothing
-
-formulaMentionsFunction :: MixfixItem -> Formula -> Bool
-formulaMentionsFunction symbol = \case
- FormulaChain chain ->
- chainMentionsFunction symbol chain
- FormulaPredicate _loc _predicate _marker exprs ->
- any (exprMentionsFunction symbol) exprs
- Connected _loc _conn left right ->
- formulaMentionsFunction symbol left || formulaMentionsFunction symbol right
- FormulaNeg _loc phi ->
- formulaMentionsFunction symbol phi
- FormulaQuantified _loc _quant _vars _bound phi ->
- formulaMentionsFunction symbol phi
- PropositionalConstant{} ->
- False
-
-chainMentionsFunction :: MixfixItem -> Chain -> Bool
-chainMentionsFunction symbol = \case
- ChainBase left _sign relation right ->
- any (exprMentionsFunction symbol) left || relationMentionsFunction symbol relation || any (exprMentionsFunction symbol) right
- ChainCons left _sign relation rest ->
- any (exprMentionsFunction symbol) left || relationMentionsFunction symbol relation || chainMentionsFunction symbol rest
-
-relationMentionsFunction :: MixfixItem -> Relation -> Bool
-relationMentionsFunction symbol = \case
- Relation _loc _relationSymbol exprs ->
- any (exprMentionsFunction symbol) exprs
- RelationExpr _loc expr ->
- exprMentionsFunction symbol expr
-
-exprMentionsFunction :: MixfixItem -> Expr -> Bool
-exprMentionsFunction symbol = \case
- ExprVar{} ->
- False
- ExprInteger{} ->
- False
- ExprOp _loc symbol' args ->
- symbol == symbol' || any (exprMentionsFunction symbol) args
- ExprStructOp _loc _structSymbol expr ->
- maybe False (exprMentionsFunction symbol) expr
- ExprFiniteSet _loc exprs ->
- any (exprMentionsFunction symbol) exprs
- ExprSep _loc _var bound _stmt ->
- exprMentionsFunction symbol bound
- ExprReplace _loc expr bounds _maybeStmt ->
- exprMentionsFunction symbol expr || any (exprMentionsFunction symbol . snd) bounds
- ExprReplacePred _loc _x _y expr _stmt ->
- exprMentionsFunction symbol expr
-
-formulaVars :: Formula -> [VarSymbol]
-formulaVars = \case
- FormulaChain chain ->
- chainVars chain
- FormulaPredicate _loc _predicate _marker exprs ->
- concatMap exprVars exprs
- Connected _loc _conn left right ->
- formulaVars left <> formulaVars right
- FormulaNeg _loc phi ->
- formulaVars phi
- FormulaQuantified _loc _quant vars _bound phi ->
- List.filter (`notElem` toList vars) (formulaVars phi)
- PropositionalConstant{} ->
- []
-
-chainVars :: Chain -> [VarSymbol]
-chainVars = \case
- ChainBase left _sign relation right ->
- concatMap exprVars (toList left) <> relationVars relation <> concatMap exprVars (toList right)
- ChainCons left _sign relation rest ->
- concatMap exprVars (toList left) <> relationVars relation <> chainVars rest
-
-relationVars :: Relation -> [VarSymbol]
-relationVars = \case
- Relation _loc _relationSymbol exprs ->
- concatMap exprVars exprs
- RelationExpr _loc expr ->
- exprVars expr
-
-exprVars :: Expr -> [VarSymbol]
-exprVars = \case
- ExprVar x ->
- [x]
- ExprInteger{} ->
- []
- ExprOp _loc _symbol args ->
- concatMap exprVars args
- ExprStructOp _loc _structSymbol expr ->
- maybe [] exprVars expr
- ExprFiniteSet _loc exprs ->
- concatMap exprVars exprs
- ExprSep _loc x bound _stmt ->
- List.filter (/= x) (exprVars bound)
- ExprReplace _loc expr bounds _maybeStmt ->
- let boundVars = fst <$> toList bounds
- free = exprVars expr <> concatMap (exprVars . snd) (toList bounds)
- in List.filter (`notElem` boundVars) free
- ExprReplacePred _loc x y expr _stmt ->
- List.filter (\var -> var /= x && var /= y) (exprVars expr)
-
-orderedRenderVars :: [VarSymbol] -> [VarSymbol]
-orderedRenderVars =
- reverse . snd . foldl' step (Set.empty, [])
- where
- step (seen, acc) x
- | x `Set.member` seen = (seen, acc)
- | otherwise = (Set.insert x seen, x : acc)
-
-renderSignatureBlock :: HintMap -> [Asm] -> Signature -> Html ()
-renderSignatureBlock hints asms sig = do
- case asms of
- [] -> skip
- _ -> do
- toHtml ("Assumptions: " :: Text)
- renderAsmList hints asms
- toHtml ("." :: Text)
- when (not (null asms)) do
- p_ do
- renderSignature hints sig
- toHtml ("." :: Text)
- when (null asms) do
- renderSignature hints sig
- toHtml ("." :: Text)
-
-renderSignature :: HintMap -> Signature -> Html ()
-renderSignature hints = \case
- SignatureAdj var adj -> do
- renderVarInline var
- toHtml (" can be " :: Text)
- renderAdjInline renderVarInline adj
- SignatureVerb var verb -> do
- renderVarInline var
- toHtml (" can " :: Text)
- renderVerbInline False renderVarInline verb
- SignatureNoun var noun -> do
- renderVarInline var
- toHtml (" is a " :: Text)
- renderNounInline False renderVarInline noun
- SignatureSymbolic symb np -> do
- renderSymbolPatternInline hints symb
- toHtml (" is a " :: Text)
- renderNounPhraseMaybe hints np
-
-renderStructDefn :: HintMap -> StructDefn -> Html ()
-renderStructDefn hints StructDefn{..} = do
- toHtml ("Structure phrase: " :: Text)
- renderStructPhraseInline structPhrase
- toHtml ("." :: Text)
- p_ do
- toHtml ("Label: " :: Text)
- renderVarInline structLabel
- toHtml ("." :: Text)
- when (not (null structParents)) do
- p_ do
- toHtml ("Parents: " :: Text)
- joinHtml (toHtml (", " :: Text)) (renderStructPhraseInline <$> structParents)
- toHtml ("." :: Text)
- when (not (null structFixes)) do
- p_ do
- toHtml ("Fixes: " :: Text)
- inlineMath (joinHtml (moText ",") (renderStructSymbolName <$> structFixes))
- toHtml ("." :: Text)
- when (not (null structAssumes)) do
- ul_ do
- for_ structAssumes \(marker, stmt) -> li_ do
- toHtml (markerText marker)
- toHtml (": " :: Text)
- renderStmtInline hints stmt
- toHtml ("." :: Text)
-
-
-buildPreviewMap
- :: HtmlRenderContext
- -> Set Marker
- -> AnchorMap
- -> HtmlRenderIndex
- -> Either HtmlRenderContextError PreviewMap
-buildPreviewMap
- context
- referencedMarkers
- anchors
- (HtmlRenderIndex targetIndex) =
- Map.fromList <$> traverse makePreviewEntry indexedTargets
- where
- targets =
- List.sortOn targetOrdinal
- [ indexed
- | marker <- Set.toList referencedMarkers
- , marker `Map.notMember` anchors
- , Just indexed@(IndexedReferenceTarget _ source _target) <-
- [Map.lookup marker targetIndex]
- , source /= htmlCurrentSource context
- ]
- targetOrdinal (IndexedReferenceTarget ordinal _source _target) =
- ordinal
- sourceTarget (IndexedReferenceTarget _ordinal source target) =
- (source, target)
- indexedTargets =
- zip [1 :: Int ..] (sourceTarget <$> targets)
-
- makePreviewEntry (index, (source, target)) = do
- previewSourceLabel <-
- htmlSourceLabel context source
- previewSourceHref <-
- htmlSourcePageHref context source
- previewReferenceHref <-
- htmlSourceFragmentHref
- context
- source
- (targetAnchorId target)
- let marker = targetMarker target
- previewMarker = marker
- previewKind = targetKind target
- previewTitle = targetTitle target
- previewId =
- "reference-preview-" <> Text.pack (show index)
- previewBody = targetBody target
- Right (marker, PreviewEntry{..})
-
-renderPreviewStore :: HintMap -> PreviewMap -> Html ()
-renderPreviewStore hints previews =
- div_ [class_ "reference-preview-store", makeAttributes "aria-hidden" "true"] do
- traverse_ (renderPreviewEntry hints) (Map.elems previews)
-
-renderPreviewEntry :: HintMap -> PreviewEntry -> Html ()
-renderPreviewEntry hints PreviewEntry{..} =
- div_ [id_ previewId, class_ "reference-preview-template"] do
- div_ [class_ "reference-preview-heading"] do
- toHtml previewKind
- toHtml (" " :: Text)
- code_ (toHtml (markerText previewMarker))
- case previewTitle of
- Nothing ->
- skip
- Just title -> do
- toHtml (" (" :: Text)
- toHtml title
- toHtml (")" :: Text)
- div_ [class_ "reference-preview-source"] do
- toHtml ("from " :: Text)
- a_ [href_ previewSourceHref] do
- code_ (toHtml previewSourceLabel)
- div_ [class_ "reference-preview-body"] do
- previewBody hints
-
-renderPreviewBlockBody :: HintMap -> Block -> Html ()
-renderPreviewBlockBody hints = \case
- BlockAxiom _loc _title _marker axiom ->
- previewStatement (renderAxiom hints axiom)
- BlockClaim _kind _loc _title _marker claim ->
- previewStatement (renderClaim hints claim)
- BlockDefn _loc _title _marker defn ->
- previewStatement (renderDefn hints defn)
- BlockAbbr _loc _title _marker abbr ->
- previewStatement (renderAbbreviation hints abbr)
- BlockData _loc _title _marker datatype ->
- renderDatatype hints datatype
- BlockInductive _loc _title marker ind ->
- renderInductive hints marker ind
- BlockSig _loc _title _marker asms sig ->
- renderSignatureBlock hints asms sig
- BlockStruct _loc _title _marker structDefn ->
- renderStructDefn hints structDefn
- BlockProof{} ->
- skip
-
-previewStatement :: Html () -> Html ()
-previewStatement =
- p_ [class_ "reference-preview-statement"]
-
-referenceTargetsOfBlockRenderInfo :: BlockRenderInfo -> [ReferenceTarget]
-referenceTargetsOfBlockRenderInfo (_index, block, blockId) =
- maybeToList blockTarget <> inductiveTargets
- where
- blockTarget = do
- marker <- blockMarkerOf block
- pure ReferenceTarget
- { targetMarker = marker
- , targetAnchorId = blockId
- , targetKind = blockPrefixText block
- , targetTitle = formatBlockTitle (blockTitleOf block)
- , targetBody = \hints -> renderPreviewBlockBody hints block
- }
-
- inductiveTargets = case block of
- BlockInductive _loc _title marker inductive ->
- [ ReferenceTarget
- { targetMarker = inductiveDerivedFactMarker
- , targetAnchorId = markerText inductiveDerivedFactMarker
- , targetKind = "Inductive Fact"
- , targetTitle = Nothing
- , targetBody = \hints -> previewStatement (inlineMath (renderFormulaMath hints inductiveDerivedFactFormula))
- }
- | InductiveDerivedFact{inductiveDerivedFactMarker, inductiveDerivedFactFormula} <- inductiveDerivedFacts marker inductive
- ]
- _ ->
- []
-
-
-renderProof :: HintMap -> ReferenceContext -> Proof -> Html ()
-renderProof hints references = \case
- Omitted _loc ->
- p_ "Omitted."
- Qed mloc justification ->
- renderProofTerminal mloc justification
- Contradiction _loc justification ->
- p_ do
- toHtml ("Contradiction" :: Text)
- renderJustificationSuffix references justification
- toHtml ("." :: Text)
- ByCase _loc cases -> do
- p_ "Proof by cases."
- term "proof-" (traverse_ (renderCase hints references) cases)
- ByContradiction _loc proof -> do
- p_ "Proof by contradiction."
- term "proof-" (renderProof hints references proof)
- BySetInduction _loc maybeTerm proof -> do
- p_ do
- toHtml ("Proof by set induction" :: Text)
- case maybeTerm of
- Nothing -> skip
- Just targetTerm -> do
- toHtml (" on " :: Text)
- renderTermInline hints targetTerm
- toHtml ("." :: Text)
- term "proof-" (renderProof hints references proof)
- ByOrdInduction _loc proof -> do
- p_ "Proof by ordinal induction."
- term "proof-" (renderProof hints references proof)
- Assume _loc stmt proof -> do
- p_ do
- toHtml ("Assume " :: Text)
- renderStmtInline hints stmt
- toHtml ("." :: Text)
- renderProofContinuation hints references proof
- FixSymbolic _loc vars bound proof -> do
- p_ do
- toHtml ("Fix " :: Text)
- renderVarListInline vars
- renderBoundInline hints vars bound
- toHtml ("." :: Text)
- renderProofContinuation hints references proof
- FixSuchThat _loc vars stmt proof -> do
- p_ do
- toHtml ("Fix " :: Text)
- renderVarListInline vars
- toHtml (" such that " :: Text)
- renderStmtInline hints stmt
- toHtml ("." :: Text)
- renderProofContinuation hints references proof
- Calc _loc maybeQuant calc proof -> do
- renderCalc hints references maybeQuant calc
- renderProofContinuation hints references proof
- TakeVar _loc vars bound stmt justification proof -> do
- p_ do
- toHtml ("Take " :: Text)
- renderVarListInline vars
- renderBoundInline hints vars bound
- toHtml (" such that " :: Text)
- renderStmtInline hints stmt
- renderJustificationSuffix references justification
- toHtml ("." :: Text)
- renderProofContinuation hints references proof
- TakeNoun _loc np justification proof -> do
- p_ do
- toHtml ("Take " :: Text)
- renderNounPhraseList hints np
- renderJustificationSuffix references justification
- toHtml ("." :: Text)
- renderProofContinuation hints references proof
- Have _loc maybeStmt stmt justification proof -> do
- p_ do
- case maybeStmt of
- Nothing
- | isImplicitProofEnd proof ->
- skip
- | otherwise ->
- toHtml ("We have " :: Text)
- Just premise -> do
- toHtml ("Since " :: Text)
- renderStmtInline hints premise
- toHtml (", we have " :: Text)
- renderStmtInline hints stmt
- renderJustificationSuffix references justification
- toHtml ("." :: Text)
- renderProofContinuation hints references proof
- Suffices _loc stmt justification proof -> do
- p_ do
- toHtml ("It suffices to show that " :: Text)
- renderStmtInline hints stmt
- renderJustificationSuffix references justification
- toHtml ("." :: Text)
- renderProofContinuation hints references proof
- Subclaim _loc stmt subproof proof -> do
- p_ do
- toHtml ("Show " :: Text)
- renderStmtInline hints stmt
- toHtml ("." :: Text)
- term "proof-" (renderProof hints references subproof)
- renderProofContinuation hints references proof
- Define _loc var expr proof -> do
- p_ do
- toHtml ("Let " :: Text)
- renderVarEqInline hints var expr
- toHtml ("." :: Text)
- renderProofContinuation hints references proof
- DefineFunction _loc fun arg value boundVar boundExpr proof -> do
- p_ do
- toHtml ("Let " :: Text)
- renderFunctionEqInline hints fun arg value
- toHtml (" for " :: Text)
- renderVarInline boundVar
- toHtml (" in " :: Text)
- inlineMath (renderExprMathRow hints boundExpr)
- toHtml ("." :: Text)
- renderProofContinuation hints references proof
- DefineFunctionLocal _loc fun arg _target domVar codVar rules proof -> do
- p_ do
- toHtml ("Let " :: Text)
- renderFunctionCallInline fun arg
- toHtml (" be locally defined from " :: Text)
- renderVarInline domVar
- toHtml (" to " :: Text)
- renderVarInline codVar
- toHtml ("." :: Text)
- ul_ do
- for_ (toList rules) \(ruleTerm, formula) -> li_ do
- inlineMath (renderExprMathRow hints ruleTerm)
- toHtml (" if " :: Text)
- inlineMath (renderFormulaMath hints formula)
- toHtml ("." :: Text)
- renderProofContinuation hints references proof
-
- where
- renderProofTerminal :: Maybe Location -> Justification -> Html ()
- renderProofTerminal mloc justification = case (mloc, justification) of
- (Nothing, JustificationEmpty) ->
- skip
- (Just _, JustificationEmpty) ->
- p_ "Trivial."
- _ ->
- p_ do
- toHtml ("Follows" :: Text)
- renderJustificationSuffix references justification
- toHtml ("." :: Text)
-
-renderProofContinuation :: HintMap -> ReferenceContext -> Proof -> Html ()
-renderProofContinuation hints references proof =
- unless (isImplicitProofEnd proof) (renderProof hints references proof)
-
-isImplicitProofEnd :: Proof -> Bool
-isImplicitProofEnd = \case
- Qed Nothing JustificationEmpty -> True
- _ -> False
-
-proofStepCount :: Proof -> Int
-proofStepCount = \case
- Omitted _loc -> 1
- Qed{} -> 1
- Contradiction{} -> 1
- ByCase _loc cases -> 1 + sum (caseStepCount <$> cases)
- ByContradiction _loc proof -> 1 + proofStepCount proof
- BySetInduction _loc _maybeTerm proof -> 1 + proofStepCount proof
- ByOrdInduction _loc proof -> 1 + proofStepCount proof
- Assume _loc _stmt proof -> 1 + proofStepCount proof
- FixSymbolic _loc _vars _bound proof -> 1 + proofStepCount proof
- FixSuchThat _loc _vars _stmt proof -> 1 + proofStepCount proof
- Calc _loc _maybeQuant calc proof -> 1 + calcStepCount calc + proofStepCount proof
- TakeVar _loc _vars _bound _stmt _justification proof -> 1 + proofStepCount proof
- TakeNoun _loc _np _justification proof -> 1 + proofStepCount proof
- Have _loc _maybeStmt _stmt _justification proof -> 1 + proofStepCount proof
- Suffices _loc _stmt _justification proof -> 1 + proofStepCount proof
- Subclaim _loc _stmt subproof proof -> 1 + proofStepCount subproof + proofStepCount proof
- Define _loc _var _expr proof -> 1 + proofStepCount proof
- DefineFunction _loc _fun _arg _value _boundVar _boundExpr proof -> 1 + proofStepCount proof
- DefineFunctionLocal _loc _fun _arg _target _domVar _codVar rules proof ->
- 1 + length rules + proofStepCount proof
-
-caseStepCount :: Case -> Int
-caseStepCount Case{caseProof} = 1 + proofStepCount caseProof
-
-calcStepCount :: Calc -> Int
-calcStepCount = \case
- Equation _ steps -> length steps
- Biconditionals _ steps -> length steps
-
-renderCase :: HintMap -> ReferenceContext -> Case -> Html ()
-renderCase hints references Case{..} =
- term "proof-" do
- p_ do
- toHtml ("Case " :: Text)
- renderStmtInline hints caseOf
- toHtml ("." :: Text)
- renderProof hints references caseProof
-
-renderCalc :: HintMap -> ReferenceContext -> Maybe CalcQuantifier -> Calc -> Html ()
-renderCalc hints references maybeQuant calc = do
- p_ do
- toHtml ("Calculation" :: Text)
- case maybeQuant of
- Nothing -> skip
- Just quant -> do
- toHtml (" for " :: Text)
- renderCalcQuantifierInline hints quant
- toHtml ("." :: Text)
- blockMath (renderCalcMath hints calc)
- let justifications = calcJustifications calc
- when (not (null justifications)) do
- ul_ do
- traverse_ renderStepJustification justifications
- where
- renderStepJustification :: (Int, Justification) -> Html ()
- renderStepJustification (_idx, JustificationEmpty) = skip
- renderStepJustification (idx, jst) = li_ do
- toHtml ("Step " <> Text.pack (show idx) <> ": " :: Text)
- renderJustification references jst
- toHtml ("." :: Text)
-
-renderCalcQuantifierInline :: HintMap -> CalcQuantifier -> Html ()
-renderCalcQuantifierInline hints (CalcQuantifier vars bound maybeStmt) = do
- renderVarListInline vars
- renderBoundInline hints vars bound
- case maybeStmt of
- Nothing -> skip
- Just stmt -> do
- toHtml (" such that " :: Text)
- renderStmtInline hints stmt
-
-renderCalcMath :: HintMap -> Calc -> Html ()
-renderCalcMath hints = \case
- Equation expr steps -> do
- renderExprMathRow hints expr
- for_ (toList steps) \(nextExpr, _jst) -> do
- moText "="
- renderExprMathRow hints nextExpr
- Biconditionals phi steps -> do
- renderFormulaMath hints phi
- for_ (toList steps) \(nextPhi, _jst) -> do
- moText "⇔"
- renderFormulaMath hints nextPhi
-
-calcJustifications :: Calc -> [(Int, Justification)]
-calcJustifications = \case
- Equation _ steps ->
- zip [1..] (snd <$> toList steps)
- Biconditionals _ steps ->
- zip [1..] (snd <$> toList steps)
-
-
-renderStmtInline :: HintMap -> Stmt -> Html ()
-renderStmtInline hints = \case
- StmtFormula phi ->
- inlineMath (renderFormulaMath hints phi)
- StmtVerbPhrase ts vp -> do
- renderTermList hints ts
- toHtml (" " :: Text)
- renderVerbPhraseInline hints (length ts > 1) vp
- StmtNoun ts np -> do
- renderTermList hints ts
- toHtml (if length ts > 1 then " are a " else " is a " :: Text)
- renderNounPhraseMaybe hints np
- StmtStruct t structPhrase -> do
- renderTermInline hints t
- toHtml (" is a " :: Text)
- renderStructPhraseInline structPhrase
- StmtNeg _loc stmt -> do
- toHtml ("it is not the case that " :: Text)
- renderStmtInline hints stmt
- StmtExists _loc np -> do
- toHtml ("there exists " :: Text)
- renderNounPhraseList hints np
- StmtConnected conn _loc stmt1 stmt2 -> do
- renderConnectedStmtInline hints conn stmt1 stmt2
- StmtQuantPhrase _loc qp stmt -> do
- renderQuantPhraseInline hints qp
- toHtml (" " :: Text)
- renderStmtInline hints stmt
- SymbolicQuantified _loc quant vars bound suchThat stmt -> do
- toHtml (quantifierWord quant)
- toHtml (" " :: Text)
- renderBoundSubjectInline hints vars bound
- renderQuantifiedTailInline hints quant suchThat stmt
-
-renderQuantPhraseInline :: HintMap -> QuantPhrase -> Html ()
-renderQuantPhraseInline hints (QuantPhrase quant np) = do
- toHtml (quantifierWord quant)
- toHtml (" " :: Text)
- renderNounPhraseList hints np
-
-quantifierWord :: Quantifier -> Text
-quantifierWord = \case
- Universally -> "for every"
- Existentially -> "there exists"
- Nonexistentially -> "there exists no"
-
-connectiveWord :: Connective -> Text
-connectiveWord = \case
- Conjunction -> "and"
- Disjunction -> "or"
- Implication -> "implies"
- Equivalence -> "iff"
- ExclusiveOr -> "xor"
- NegatedDisjunction -> "nor"
-
-renderConnectedStmtInline :: HintMap -> Connective -> Stmt -> Stmt -> Html ()
-renderConnectedStmtInline hints conn stmt1 stmt2 = case conn of
- ExclusiveOr -> do
- toHtml ("either " :: Text)
- renderStmtInline hints stmt1
- toHtml (" or " :: Text)
- renderStmtInline hints stmt2
- NegatedDisjunction -> do
- toHtml ("neither " :: Text)
- renderStmtInline hints stmt1
- toHtml (" nor " :: Text)
- renderStmtInline hints stmt2
- _ -> do
- renderStmtInline hints stmt1
- toHtml (" " :: Text)
- toHtml (connectiveWord conn)
- toHtml (" " :: Text)
- renderStmtInline hints stmt2
-
-renderQuantifiedTailInline :: HintMap -> Quantifier -> Maybe Stmt -> Stmt -> Html ()
-renderQuantifiedTailInline hints quant suchThat stmt =
- case quant of
- Universally -> do
- for_ suchThat \suchStmt -> do
- toHtml (" such that " :: Text)
- renderStmtInline hints suchStmt
- toHtml (" we have " :: Text)
- renderStmtInline hints stmt
- Existentially ->
- renderExistentialTailInline hints suchThat stmt
- Nonexistentially ->
- renderExistentialTailInline hints suchThat stmt
-
-renderExistentialTailInline :: HintMap -> Maybe Stmt -> Stmt -> Html ()
-renderExistentialTailInline hints suchThat stmt = do
- toHtml (" such that " :: Text)
- case suchThat of
- Nothing ->
- renderStmtInline hints stmt
- Just suchStmt -> do
- renderStmtInline hints suchStmt
- toHtml (" and " :: Text)
- renderStmtInline hints stmt
-
-
-renderAsmList :: HintMap -> [Asm] -> Html ()
-renderAsmList hints asms =
- joinHtml (toHtml ("; " :: Text)) (renderAsm hints <$> asms)
-
-renderAsm :: HintMap -> Asm -> Html ()
-renderAsm hints = \case
- AsmSuppose stmt ->
- renderStmtInline hints stmt
- AsmLetNoun vars np -> do
- renderVarListInline vars
- toHtml (" be " :: Text)
- renderNounPhraseMaybe hints np
- AsmLetIn vars expr -> do
- renderVarListInline vars
- toHtml (" be in " :: Text)
- inlineMath (renderExprMathRow hints expr)
- AsmLetThe var fun -> do
- renderVarInline var
- toHtml (" be " :: Text)
- renderFunInline renderTermInline' fun
- where renderTermInline' = renderTermInline hints
- AsmLetEq var expr -> do
- renderVarEqInline hints var expr
- AsmLetStruct var structPhrase -> do
- renderVarInline var
- toHtml (" be a " :: Text)
- renderStructPhraseInline structPhrase
-
-
-renderTermInline :: HintMap -> Term -> Html ()
-renderTermInline hints = \case
- TermExpr expr ->
- inlineMath (renderExprMathRow hints expr)
- TermFun fun -> do
- toHtml ("the " :: Text)
- renderFunInline (renderTermInline hints) fun
- TermIota _loc var stmt -> do
- toHtml ("the " :: Text)
- renderVarInline var
- toHtml (" such that " :: Text)
- renderStmtInline hints stmt
- TermQuantified quant _loc np -> do
- toHtml (termQuantifierWord quant)
- toHtml (" " :: Text)
- renderNounPhraseMaybe hints np
-
-termQuantifierWord :: Quantifier -> Text
-termQuantifierWord = \case
- Universally -> "every"
- Existentially -> "some"
- Nonexistentially -> "no"
-
-renderTermList :: HintMap -> NonEmpty Term -> Html ()
-renderTermList hints =
- joinHtml (toHtml (" and " :: Text)) . fmap (renderTermInline hints) . toList
-
-renderNounPhraseMaybe :: HintMap -> NounPhrase Maybe -> Html ()
-renderNounPhraseMaybe hints (NounPhrase ls noun maybeName rs maybeSuchThat) = do
- renderAdjListInline renderTermInline' ls
- renderNounInline False renderTermInline' noun
- case maybeName of
- Nothing -> skip
- Just name -> do
- toHtml (" " :: Text)
- renderVarInline name
- renderAdjRListInline hints rs
- case maybeSuchThat of
- Nothing -> skip
- Just stmt -> do
- toHtml (" such that " :: Text)
- renderStmtInline hints stmt
- where
- renderTermInline' = renderTermInline hints
-
-renderNounPhraseList :: HintMap -> NounPhrase [] -> Html ()
-renderNounPhraseList hints (NounPhrase ls noun names rs maybeSuchThat) = do
- renderAdjListInline renderTermInline' ls
- renderNounInline (length names > 1) renderTermInline' noun
- when (not (null names)) do
- toHtml (" " :: Text)
- renderVarListInline (NonEmpty.fromList names)
- renderAdjRListInline hints rs
- case maybeSuchThat of
- Nothing -> skip
- Just stmt -> do
- toHtml (" such that " :: Text)
- renderStmtInline hints stmt
- where
- renderTermInline' = renderTermInline hints
-
-renderAdjListInline :: (a -> Html ()) -> [AdjLOf a] -> Html ()
-renderAdjListInline renderArg adjs =
- unless (null adjs) do
- joinHtml (toHtml (" " :: Text)) (renderAdjLInline renderArg <$> adjs)
- toHtml (" " :: Text)
-
-renderAdjRListInline :: HintMap -> [AdjROf Term] -> Html ()
-renderAdjRListInline hints adjs =
- unless (null adjs) do
- toHtml (" " :: Text)
- joinHtml (toHtml (" and " :: Text)) (renderAdjRInline hints <$> adjs)
-
-renderAdjLInline :: (a -> Html ()) -> AdjLOf a -> Html ()
-renderAdjLInline renderArg (AdjL _loc item args) =
- renderLexicalItemInline renderArg item args
-
-renderAdjRInline :: HintMap -> AdjROf Term -> Html ()
-renderAdjRInline hints = \case
- AdjR _loc item args ->
- renderLexicalItemInline (renderTermInline hints) item args
- AttrRThat verbPhrase -> do
- toHtml ("that " :: Text)
- renderVerbPhraseInline hints False verbPhrase
-
-renderAdjInline :: (a -> Html ()) -> AdjOf a -> Html ()
-renderAdjInline renderArg (Adj _loc item args) =
- renderLexicalItemInline renderArg item args
-
-renderVerbInline :: Bool -> (a -> Html ()) -> VerbOf a -> Html ()
-renderVerbInline isPlural renderArg (Verb _loc item args) =
- renderLexicalItemSgPlInline isPlural renderArg item args
-
-renderVerbPhraseInline :: HintMap -> Bool -> VerbPhrase -> Html ()
-renderVerbPhraseInline hints isPlural = \case
- VPVerb verb ->
- renderVerbInline isPlural (renderTermInline hints) verb
- VPAdj adjs -> do
- toHtml (if isPlural then "are " else "is " :: Text)
- joinHtml (toHtml (" and " :: Text)) (renderAdjInline (renderTermInline hints) <$> toList adjs)
- VPVerbNot verb -> do
- toHtml (if isPlural then "do not " else "does not " :: Text)
- renderVerbInline True (renderTermInline hints) verb
- VPAdjNot adjs -> do
- toHtml (if isPlural then "are not " else "is not " :: Text)
- joinHtml (toHtml (" and " :: Text)) (renderAdjInline (renderTermInline hints) <$> toList adjs)
-
-renderNounInline :: Bool -> (a -> Html ()) -> NounOf a -> Html ()
-renderNounInline isPlural renderArg (Noun _loc item args) =
- renderLexicalItemSgPlInline isPlural renderArg item args
-
-renderFunInline :: (a -> Html ()) -> FunOf a -> Html ()
-renderFunInline renderArg Fun{phrase, funArgs} =
- renderLexicalItemSgPlInline False renderArg phrase funArgs
-
-renderStructPhraseInline :: StructPhrase -> Html ()
-renderStructPhraseInline item =
- renderLexicalItemSgPlInline False renderTermInlinePlaceholder item []
-
-renderTermInlinePlaceholder :: a -> Html ()
-renderTermInlinePlaceholder _ = toHtml ("?" :: Text)
-
-renderLexicalItemInline :: (a -> Html ()) -> LexicalItem -> [a] -> Html ()
-renderLexicalItemInline renderArg item args =
- renderPatternInline renderArg (lexicalItemPhrase item) args
-
-renderLexicalItemSgPlInline :: Bool -> (a -> Html ()) -> LexicalItemSgPl -> [a] -> Html ()
-renderLexicalItemSgPlInline isPlural renderArg item args =
- renderPatternInline renderArg phrase args
- where
- phrase = if isPlural then pl (lexicalItemSgPlPhrase item) else sg (lexicalItemSgPlPhrase item)
-
-renderPatternInline :: (a -> Html ()) -> [Maybe Token] -> [a] -> Html ()
-renderPatternInline renderArg patternParts args =
- joinHtml (toHtml (" " :: Text)) (go patternParts args)
- where
- go [] [] = []
- go [] (_ : _) = error "renderPatternInline: too many arguments"
- go (Nothing : rest) (arg : restArgs) = renderArg arg : go rest restArgs
- go (Nothing : _) [] = error "renderPatternInline: not enough arguments"
- go (Just tok : rest) restArgs = tokenTextHtml tok : go rest restArgs
-
-renderStmtMath :: HintMap -> Stmt -> Html ()
-renderStmtMath hints =
- renderStmtMathFragments . stmtMathFragments hints
-
-renderStmtMathFragments :: StmtMathFragments -> Html ()
-renderStmtMathFragments =
- go ""
- where
- go :: Text -> StmtMathFragments -> Html ()
- go pending = \case
- [] ->
- flush pending
- StmtMathProse text : rest ->
- go (pending <> text) rest
- StmtMathNode node : rest -> do
- flush pending
- node
- go "" rest
-
- flush :: Text -> Html ()
- flush text =
- let renderedText = preserveBoundarySpaces text
- in unless (Text.null renderedText) (mtextText renderedText)
-
- preserveBoundarySpaces :: Text -> Text
- preserveBoundarySpaces text =
- Text.replicate leadingCount nbsp
- <> middleText
- <> Text.replicate trailingCount nbsp
- where
- leadingCount = Text.length (Text.takeWhile (== ' ') text)
- textAfterLeading = Text.drop leadingCount text
- trailingCount = Text.length (Text.takeWhileEnd (== ' ') textAfterLeading)
- middleText = Text.dropEnd trailingCount textAfterLeading
- nbsp = Text.singleton '\160'
-
-stmtMathProse :: Text -> StmtMathFragments
-stmtMathProse text
- | Text.null text = []
- | otherwise = [StmtMathProse text]
-
-stmtMathNode :: Html () -> StmtMathFragments
-stmtMathNode html =
- [StmtMathNode html]
-
-joinStmtMathFragments :: StmtMathFragments -> [StmtMathFragments] -> StmtMathFragments
-joinStmtMathFragments _ [] = []
-joinStmtMathFragments separator (first : rest) =
- first <> foldMap (separator <>) rest
-
-stmtMathFragments :: HintMap -> Stmt -> StmtMathFragments
-stmtMathFragments hints = \case
- StmtFormula phi ->
- stmtMathNode (renderFormulaMath hints phi)
- StmtVerbPhrase ts vp -> do
- termListMathFragments hints ts
- <> stmtMathProse " "
- <> verbPhraseMathFragments hints (length ts > 1) vp
- StmtNoun ts np -> do
- termListMathFragments hints ts
- <> stmtMathProse (if length ts > 1 then " are a " else " is a ")
- <> nounPhraseMaybeMathFragments hints np
- StmtStruct t structPhrase -> do
- termMathFragments hints t
- <> stmtMathProse " is a "
- <> structPhraseMathFragments structPhrase
- StmtNeg _loc stmt -> do
- stmtMathProse "it is not the case that "
- <> stmtMathFragments hints stmt
- StmtExists _loc np -> do
- stmtMathProse "there exists "
- <> nounPhraseListMathFragments hints np
- StmtConnected conn _loc stmt1 stmt2 -> do
- connectedStmtMathFragments hints conn stmt1 stmt2
- StmtQuantPhrase _loc qp stmt -> do
- quantPhraseMathFragments hints qp
- <> stmtMathProse " "
- <> stmtMathFragments hints stmt
- SymbolicQuantified _loc quant vars bound suchThat stmt -> do
- stmtMathProse (quantifierWord quant <> " ")
- <> boundSubjectMathFragments hints vars bound
- <> quantifiedTailMathFragments hints quant suchThat stmt
-
-quantPhraseMathFragments :: HintMap -> QuantPhrase -> StmtMathFragments
-quantPhraseMathFragments hints (QuantPhrase quant np) =
- stmtMathProse (quantifierWord quant <> " ")
- <> nounPhraseListMathFragments hints np
-
-connectedStmtMathFragments :: HintMap -> Connective -> Stmt -> Stmt -> StmtMathFragments
-connectedStmtMathFragments hints conn stmt1 stmt2 = case conn of
- ExclusiveOr ->
- stmtMathProse "either "
- <> stmtMathFragments hints stmt1
- <> stmtMathProse " or "
- <> stmtMathFragments hints stmt2
- NegatedDisjunction ->
- stmtMathProse "neither "
- <> stmtMathFragments hints stmt1
- <> stmtMathProse " nor "
- <> stmtMathFragments hints stmt2
- _ ->
- stmtMathFragments hints stmt1
- <> stmtMathProse (" " <> connectiveWord conn <> " ")
- <> stmtMathFragments hints stmt2
-
-quantifiedTailMathFragments :: HintMap -> Quantifier -> Maybe Stmt -> Stmt -> StmtMathFragments
-quantifiedTailMathFragments hints quant suchThat stmt =
- case quant of
- Universally ->
- foldMap
- (\suchStmt -> stmtMathProse " such that " <> stmtMathFragments hints suchStmt)
- suchThat
- <> stmtMathProse " we have "
- <> stmtMathFragments hints stmt
- Existentially ->
- existentialTailMathFragments hints suchThat stmt
- Nonexistentially ->
- existentialTailMathFragments hints suchThat stmt
-
-existentialTailMathFragments :: HintMap -> Maybe Stmt -> Stmt -> StmtMathFragments
-existentialTailMathFragments hints suchThat stmt =
- stmtMathProse " such that "
- <> case suchThat of
- Nothing ->
- stmtMathFragments hints stmt
- Just suchStmt ->
- stmtMathFragments hints suchStmt
- <> stmtMathProse " and "
- <> stmtMathFragments hints stmt
-
-termMathFragments :: HintMap -> Term -> StmtMathFragments
-termMathFragments hints = \case
- TermExpr expr ->
- stmtMathNode (renderExprMathRow hints expr)
- TermFun fun -> do
- stmtMathProse "the "
- <> funMathFragments (termMathFragments hints) fun
- TermIota _loc var stmt -> do
- stmtMathProse "the "
- <> stmtMathNode (renderVarMath var)
- <> stmtMathProse " such that "
- <> stmtMathFragments hints stmt
- TermQuantified quant _loc np -> do
- stmtMathProse (termQuantifierWord quant <> " ")
- <> nounPhraseMaybeMathFragments hints np
-
-termListMathFragments :: HintMap -> NonEmpty Term -> StmtMathFragments
-termListMathFragments hints =
- joinStmtMathFragments (stmtMathProse " and ") . fmap (termMathFragments hints) . toList
-
-nounPhraseMaybeMathFragments :: HintMap -> NounPhrase Maybe -> StmtMathFragments
-nounPhraseMaybeMathFragments hints (NounPhrase ls noun maybeName rs maybeSuchThat) =
- adjListMathFragments renderTermMath' ls
- <> nounMathFragments False renderTermMath' noun
- <> foldMap (\name -> stmtMathProse " " <> stmtMathNode (renderVarMath name)) maybeName
- <> adjRListMathFragments hints rs
- <> foldMap (\stmt -> stmtMathProse " such that " <> stmtMathFragments hints stmt) maybeSuchThat
- where
- renderTermMath' = termMathFragments hints
-
-nounPhraseListMathFragments :: HintMap -> NounPhrase [] -> StmtMathFragments
-nounPhraseListMathFragments hints (NounPhrase ls noun names rs maybeSuchThat) =
- adjListMathFragments renderTermMath' ls
- <> nounMathFragments (length names > 1) renderTermMath' noun
- <> if null names
- then []
- else stmtMathProse " " <> stmtMathNode (renderVarListMath (NonEmpty.fromList names))
- <> adjRListMathFragments hints rs
- <> foldMap (\stmt -> stmtMathProse " such that " <> stmtMathFragments hints stmt) maybeSuchThat
- where
- renderTermMath' = termMathFragments hints
-
-adjListMathFragments :: (a -> StmtMathFragments) -> [AdjLOf a] -> StmtMathFragments
-adjListMathFragments renderArg adjs =
- if null adjs
- then []
- else joinStmtMathFragments (stmtMathProse " ") (renderAdjLMathFragments renderArg <$> adjs)
- <> stmtMathProse " "
-
-adjRListMathFragments :: HintMap -> [AdjROf Term] -> StmtMathFragments
-adjRListMathFragments hints adjs =
- if null adjs
- then []
- else stmtMathProse " "
- <> joinStmtMathFragments (stmtMathProse " and ") (renderAdjRMathFragments hints <$> adjs)
-
-renderAdjLMathFragments :: (a -> StmtMathFragments) -> AdjLOf a -> StmtMathFragments
-renderAdjLMathFragments renderArg (AdjL _loc item args) =
- lexicalItemStmtMathFragments renderArg item args
-
-renderAdjRMathFragments :: HintMap -> AdjROf Term -> StmtMathFragments
-renderAdjRMathFragments hints = \case
- AdjR _loc item args ->
- lexicalItemStmtMathFragments (termMathFragments hints) item args
- AttrRThat verbPhrase ->
- stmtMathProse "that "
- <> verbPhraseMathFragments hints False verbPhrase
-
-adjMathFragments :: (a -> StmtMathFragments) -> AdjOf a -> StmtMathFragments
-adjMathFragments renderArg (Adj _loc item args) =
- lexicalItemStmtMathFragments renderArg item args
-
-verbMathFragments :: Bool -> (a -> StmtMathFragments) -> VerbOf a -> StmtMathFragments
-verbMathFragments isPlural renderArg (Verb _loc item args) =
- lexicalItemSgPlStmtMathFragments isPlural renderArg item args
-
-verbPhraseMathFragments :: HintMap -> Bool -> VerbPhrase -> StmtMathFragments
-verbPhraseMathFragments hints isPlural = \case
- VPVerb verb ->
- verbMathFragments isPlural (termMathFragments hints) verb
- VPAdj adjs ->
- stmtMathProse (if isPlural then "are " else "is ")
- <> joinStmtMathFragments (stmtMathProse " and ") (adjMathFragments (termMathFragments hints) <$> toList adjs)
- VPVerbNot verb ->
- stmtMathProse (if isPlural then "do not " else "does not ")
- <> verbMathFragments True (termMathFragments hints) verb
- VPAdjNot adjs ->
- stmtMathProse (if isPlural then "are not " else "is not ")
- <> joinStmtMathFragments (stmtMathProse " and ") (adjMathFragments (termMathFragments hints) <$> toList adjs)
-
-nounMathFragments :: Bool -> (a -> StmtMathFragments) -> NounOf a -> StmtMathFragments
-nounMathFragments isPlural renderArg (Noun _loc item args) =
- lexicalItemSgPlStmtMathFragments isPlural renderArg item args
-
-funMathFragments :: (a -> StmtMathFragments) -> FunOf a -> StmtMathFragments
-funMathFragments renderArg Fun{phrase, funArgs} =
- lexicalItemSgPlStmtMathFragments False renderArg phrase funArgs
-
-structPhraseMathFragments :: StructPhrase -> StmtMathFragments
-structPhraseMathFragments item =
- lexicalItemSgPlStmtMathFragments False termMathPlaceholderFragments item []
-
-termMathPlaceholderFragments :: a -> StmtMathFragments
-termMathPlaceholderFragments _ =
- stmtMathProse "?"
-
-lexicalItemStmtMathFragments :: (a -> StmtMathFragments) -> LexicalItem -> [a] -> StmtMathFragments
-lexicalItemStmtMathFragments renderArg item args =
- patternStmtMathFragments renderArg (lexicalItemPhrase item) args
-
-lexicalItemSgPlStmtMathFragments :: Bool -> (a -> StmtMathFragments) -> LexicalItemSgPl -> [a] -> StmtMathFragments
-lexicalItemSgPlStmtMathFragments isPlural renderArg item args =
- patternStmtMathFragments renderArg phrase args
- where
- phrase = if isPlural then pl (lexicalItemSgPlPhrase item) else sg (lexicalItemSgPlPhrase item)
-
-patternStmtMathFragments :: (a -> StmtMathFragments) -> [Maybe Token] -> [a] -> StmtMathFragments
-patternStmtMathFragments renderArg patternParts args =
- joinStmtMathFragments (stmtMathProse " ") (go patternParts args)
- where
- go [] [] = []
- go [] (_ : _) = error "renderPatternStmtMath: too many arguments"
- go (Nothing : rest) (arg : restArgs) = renderArg arg : go rest restArgs
- go (Nothing : _) [] = error "renderPatternStmtMath: not enough arguments"
- go (Just tok : rest) restArgs = renderStmtToken tok : go rest restArgs
-
- renderStmtToken :: Token -> StmtMathFragments
- renderStmtToken = \case
- Word w -> stmtMathProse w
- tok -> stmtMathNode (renderMathToken tok)
-
-
-renderFormulaMath :: HintMap -> Formula -> Html ()
-renderFormulaMath hints = \case
- FormulaChain chain ->
- renderChainMathRow hints chain
- FormulaPredicate _loc predi marker exprs ->
- renderHintedMathRow hints PredicateHint marker (toList exprs) (renderPrefixPredicateFallback predi (renderExprMath hints <$> toList exprs))
- Connected _loc conn phi psi -> do
- renderFormulaMath hints phi
- moText (connectiveSymbol conn)
- renderFormulaMath hints psi
- FormulaNeg _loc phi -> do
- moText "¬"
- renderFormulaMath hints phi
- FormulaQuantified _loc quant vars bound phi -> do
- moText (quantifierSymbol quant)
- renderVarListMath vars
- renderBoundMath hints vars bound
- moText "."
- renderFormulaMath hints phi
- PropositionalConstant _loc pc ->
- moText (propositionalConstantSymbol pc)
-
-connectiveSymbol :: Connective -> Text
-connectiveSymbol = \case
- Conjunction -> "∧"
- Disjunction -> "∨"
- Implication -> "⇒"
- Equivalence -> "⇔"
- ExclusiveOr -> "⊕"
- NegatedDisjunction -> "↓"
-
-quantifierSymbol :: Quantifier -> Text
-quantifierSymbol = \case
- Universally -> "∀"
- Existentially -> "∃"
- Nonexistentially -> "∄"
-
-propositionalConstantSymbol :: PropositionalConstant -> Text
-propositionalConstantSymbol = \case
- IsBottom -> "⊥"
- IsTop -> "⊤"
-
-renderChainMathRow :: HintMap -> Chain -> Html ()
-renderChainMathRow hints chain =
- joinHtml (moText "∧") (renderLink <$> splatChain chain)
- where
- renderLink (lhs, sign, rel, rhs) =
- renderRelationApplication hints sign (toList lhs) rel (toList rhs)
-
- splatChain :: Chain -> [(NonEmpty Expr, Sign, Relation, NonEmpty Expr)]
- splatChain = \case
- ChainBase es sign rel es' ->
- [(es, sign, rel, es')]
- ChainCons es sign rel ch'@(ChainBase es' _ _ _) ->
- (es, sign, rel, es') : splatChain ch'
- ChainCons es sign rel ch'@(ChainCons es' _ _ _) ->
- (es, sign, rel, es') : splatChain ch'
-
-renderBoundMath :: HintMap -> NonEmpty VarSymbol -> Bound -> Html ()
-renderBoundMath hints vars = \case
- Unbounded -> skip
- Bounded _loc sign rel expr -> do
- moText ","
- renderRelationApplication hints sign (ExprVar <$> toList vars) rel [expr]
-
-renderRelationApplication :: HintMap -> Sign -> [Expr] -> Relation -> [Expr] -> Html ()
-renderRelationApplication hints sign lhs rel rhs = case (sign, rel) of
- (Negative, Relation _loc symbol [])
- | Just negated <- negatedRelationSymbol (relationSymbolToken symbol) -> do
- renderExprListMath hints lhs
- moText negated
- renderExprListMath hints rhs
- _ ->
- applySign sign (renderRelationCore hints lhs rel rhs)
-
-applySign :: Sign -> Html () -> Html ()
-applySign sign html = case sign of
- Positive -> html
- Negative -> do
- mo_ "¬"
- html
-
-renderRelationCore :: HintMap -> [Expr] -> Relation -> [Expr] -> Html ()
-renderRelationCore hints lhs rel rhs = case rel of
- Relation _loc symbol relParams -> do
- renderExprListMath hints lhs
- renderRelationSymbolCore hints symbol relParams
- renderExprListMath hints rhs
- RelationExpr _loc expr -> do
- renderExprListMath hints lhs
- renderExprMath hints expr
- renderExprListMath hints rhs
-
-renderRelationSymbolCore :: HintMap -> RelationSymbol -> [Expr] -> Html ()
-renderRelationSymbolCore hints symbol relParams =
- renderHintedMathRow
- hints
- RelationHint
- (relationSymbolMarker symbol)
- relParams
- (renderRelationFallback hints symbol relParams)
-
-renderRelationFallback :: HintMap -> RelationSymbol -> [Expr] -> Html ()
-renderRelationFallback hints symbol relParams =
- merror_ (renderRelationFallbackCore hints symbol relParams)
-
-renderRelationFallbackCore :: HintMap -> RelationSymbol -> [Expr] -> Html ()
-renderRelationFallbackCore hints symbol relParams
- | null relParams = renderRelationToken (relationSymbolToken symbol)
- | otherwise = msub_ do
- renderRelationToken (relationSymbolToken symbol)
- mrow_ (renderExprListMath hints relParams)
-
-renderRelationToken :: Token -> Html ()
-renderRelationToken = \case
- Command "in" -> moText "∈"
- Command "ni" -> moText "∋"
- Command "notin" -> moText "∉"
- Command "meets" -> moText "⋈"
- Command "notmeets" -> moText "⋈̸"
- Command "subset" -> moText "⊂"
- Command "subseteq" -> moText "⊆"
- Command "supset" -> moText "⊃"
- Command "supseteq" -> moText "⊇"
- Command "neq" -> moText "≠"
- tok -> renderMathToken tok
-
-negatedRelationSymbol :: Token -> Maybe Text
-negatedRelationSymbol = \case
- Command "in" -> Just "∉"
- Command "ni" -> Just "∌"
- Command "subset" -> Just "⊄"
- Command "subseteq" -> Just "⊈"
- Command "supset" -> Just "⊅"
- Command "supseteq" -> Just "⊉"
- Command "meets" -> Just "⋈̸"
- Symbol "=" -> Just "≠"
- Symbol "<" -> Just "≮"
- Symbol ">" -> Just "≯"
- Symbol "≤" -> Just "≰"
- Symbol "≥" -> Just "≱"
- _ -> Nothing
-
-
-renderExprMath :: HintMap -> Expr -> Html ()
-renderExprMath hints expr = case expr of
- ExprVar var ->
- renderVarMath var
- ExprInteger _loc n ->
- mnText (Text.pack (show n))
- ExprOp _loc item args ->
- renderHintedMath hints OperatorHint (mixfixMarker item) args (renderPatternFallback (mixfixPattern item) (renderExprMath hints <$> args))
- ExprStructOp _loc symb maybeExpr ->
- let marker = structMarker symb
- args = maybeToList maybeExpr
- in renderHintedMath hints StructOpHint marker args (renderStructFallback symb (renderExprMath hints <$> args))
- ExprFiniteSet{} ->
- mrow_ (renderExprMathRow hints expr)
- ExprSep{} ->
- mrow_ (renderExprMathRow hints expr)
- ExprReplace{} ->
- mrow_ (renderExprMathRow hints expr)
- ExprReplacePred{} ->
- mrow_ (renderExprMathRow hints expr)
-
-renderExprMathRow :: HintMap -> Expr -> Html ()
-renderExprMathRow hints = \case
- ExprVar var ->
- renderVarMath var
- ExprInteger _loc n ->
- mnText (Text.pack (show n))
- ExprOp _loc item args ->
- renderHintedMathRow hints OperatorHint (mixfixMarker item) args (renderPatternFallback (mixfixPattern item) (renderExprMath hints <$> args))
- ExprStructOp _loc symb maybeExpr ->
- let marker = structMarker symb
- args = maybeToList maybeExpr
- in renderHintedMathRow hints StructOpHint marker args (renderStructFallback symb (renderExprMath hints <$> args))
- ExprFiniteSet _loc exprs ->
- renderFiniteSetMath hints (toList exprs)
- ExprSep _loc var bound stmt -> do
- moText "{"
- renderVarMath var
- moText "∈"
- renderExprMathRow hints bound
- moText "|"
- renderStmtMath hints stmt
- moText "}"
- ExprReplace _loc expr bounds maybeStmt -> do
- moText "{"
- renderExprMathRow hints expr
- moText "|"
- renderReplaceBoundsMath hints (toList bounds)
- for_ maybeStmt \stmt -> do
- moText "|"
- renderStmtMath hints stmt
- moText "}"
- ExprReplacePred _loc rangeVar domVar domExpr stmt -> do
- moText "{"
- renderVarMath rangeVar
- moText "|"
- moText "∃"
- renderVarMath domVar
- moText "∈"
- renderExprMathRow hints domExpr
- moText "."
- renderStmtMath hints stmt
- moText "}"
-
-renderReplaceBoundsMath :: HintMap -> [(VarSymbol, Expr)] -> Html ()
-renderReplaceBoundsMath hints =
- joinHtml (moText ",") . fmap renderBound
- where
- renderBound (var, expr) = do
- renderVarMath var
- moText "∈"
- renderExprMathRow hints expr
-
-renderExprListMath :: HintMap -> [Expr] -> Html ()
-renderExprListMath hints =
- joinHtml (moText ",") . fmap (renderExprMath hints)
-
-renderFiniteSetMath :: HintMap -> [Expr] -> Html ()
-renderFiniteSetMath hints exprs =
- do
- moText "{"
- renderExprListMath hints exprs
- moText "}"
-
-renderHintedMath :: HintMap -> HintCategory -> Marker -> [Expr] -> Html () -> Html ()
-renderHintedMath hints category marker args fallback =
- case Map.lookup (category, marker, length args) hints of
- Nothing ->
- fallback
- Just RenderHint{..}
- | renderHintArity /= length args ->
- error ("Render hint arity mismatch for " <> show category <> " " <> show marker <> ": expected " <> show renderHintArity <> ", got " <> show (length args))
- | otherwise ->
- renderTemplateAsNode renderHintTemplate
- where
- renderedArgs = renderExprMath hints <$> args
-
- renderTemplateAsNode :: [TemplatePiece] -> Html ()
- renderTemplateAsNode = \case
- [piece] ->
- renderPiece piece
- pieces ->
- mrow_ (traverse_ renderPiece pieces)
-
- renderPiece :: TemplatePiece -> Html ()
- renderPiece = \case
- Literal text -> toHtmlRaw text
- Slot ix -> case nth (ix - 1) renderedArgs of
- Just html -> html
- Nothing -> error ("Render hint slot out of bounds for " <> show marker <> ": <x" <> show ix <> "/>")
-
-renderHintedMathRow :: HintMap -> HintCategory -> Marker -> [Expr] -> Html () -> Html ()
-renderHintedMathRow hints category marker args fallback =
- case Map.lookup (category, marker, length args) hints of
- Nothing ->
- fallback
- Just RenderHint{..}
- | renderHintArity /= length args ->
- error ("Render hint arity mismatch for " <> show category <> " " <> show marker <> ": expected " <> show renderHintArity <> ", got " <> show (length args))
- | otherwise ->
- traverse_ renderPiece renderHintTemplate
- where
- renderedArgs = renderExprMath hints <$> args
-
- renderPiece :: TemplatePiece -> Html ()
- renderPiece = \case
- Literal text -> toHtmlRaw text
- Slot ix -> case nth (ix - 1) renderedArgs of
- Just html -> html
- Nothing -> error ("Render hint slot out of bounds for " <> show marker <> ": <x" <> show ix <> "/>")
-
-renderPatternFallback :: Pattern -> [Html ()] -> Html ()
-renderPatternFallback patternParts renderedArgs =
- merror_ (renderPatternMath patternParts renderedArgs)
-
-renderPatternMath :: Pattern -> [Html ()] -> Html ()
-renderPatternMath patternParts renderedArgs =
- traverse_ id (go patternParts renderedArgs)
- where
- go End [] = []
- go End (_ : _) = error "renderPatternMath: too many arguments"
- go (HoleCons rest) (arg : args) = arg : go rest args
- go (HoleCons _) [] = error "renderPatternMath: not enough arguments"
- go (TokenCons tok rest) args = renderMathToken tok : go rest args
-
-renderPrefixPredicateFallback :: PrefixPredicate -> [Html ()] -> Html ()
-renderPrefixPredicateFallback (PrefixPredicate command _arity) renderedArgs =
- merror_ do
- miText command
- when (not (null renderedArgs)) do
- moText "("
- joinHtml (moText ",") renderedArgs
- moText ")"
-
-renderStructFallback :: StructSymbol -> [Html ()] -> Html ()
-renderStructFallback symb renderedArgs =
- merror_ do
- renderStructSymbolName symb
- when (not (null renderedArgs)) do
- moText "("
- joinHtml (moText ",") renderedArgs
- moText ")"
-
-renderStructSymbolName :: StructSymbol -> Html ()
-renderStructSymbolName (StructSymbol name) = miText name
-
-structMarker :: StructSymbol -> Marker
-structMarker (StructSymbol name) = Marker name
-
-renderMathToken :: Token -> Html ()
-renderMathToken = \case
- Word w -> miText w
- Variable v -> renderNamedVariableMath v
- Symbol s -> moText s
- Integer n -> mnText (Text.pack (show n))
- Command cmd -> miText cmd
- Label m -> mtextText ("label:" <> m)
- Ref ms -> mtextText ("ref:" <> Text.intercalate "," (toList ms))
- BeginEnv env -> mtextText ("begin:" <> env)
- EndEnv env -> mtextText ("end:" <> env)
- ParenL -> moText "("
- ParenR -> moText ")"
- BracketL -> moText "["
- BracketR -> moText "]"
- VisibleBraceL -> moText "{"
- VisibleBraceR -> moText "}"
- InvisibleBraceL -> moText "("
- InvisibleBraceR -> moText ")"
-
-
-inlineMath :: Html () -> Html ()
-inlineMath inner = math_ inner
-
-blockMath :: Html () -> Html ()
-blockMath inner = math_ [displayblock_] inner
-
-renderVarInline :: VarSymbol -> Html ()
-renderVarInline = inlineMath . renderVarMath
-
-renderVarMath :: VarSymbol -> Html ()
-renderVarMath = \case
- NamedVarAt _loc name ->
- renderNamedVariableMath name
- FreshVarAt _loc n ->
- miText ("_" <> Text.pack (show n))
-
-renderNamedVariableMath :: Text -> Html ()
-renderNamedVariableMath rawName =
- case displayVariable rawName of
- VariableDisplay baseText Nothing ->
- miText baseText
- VariableDisplay baseText (Just (VariableTicks tickCount)) ->
- msup_ do
- miText baseText
- renderPrimeSuperscript tickCount
- VariableDisplay baseText (Just (VariableSubscript subscriptText)) ->
- msub_ do
- miText baseText
- renderVariableSubscriptMath subscriptText
-
-renderPrimeSuperscript :: Int -> Html ()
-renderPrimeSuperscript tickCount
- | tickCount <= 1 =
- moText "′"
- | otherwise =
- mrow_ (foldMap (const (moText "′")) [1 .. tickCount])
-
-renderVariableSubscriptMath :: Text -> Html ()
-renderVariableSubscriptMath subscriptText
- | Text.all isDigit subscriptText =
- mnText subscriptText
- | otherwise =
- miText subscriptText
-
-renderVarEqInline :: HintMap -> VarSymbol -> Expr -> Html ()
-renderVarEqInline hints var expr =
- inlineMath do
- renderVarMath var
- moText "="
- renderExprMathRow hints expr
-
-renderFunctionCallInline :: VarSymbol -> VarSymbol -> Html ()
-renderFunctionCallInline fun arg =
- inlineMath (renderFunctionCallMath fun arg)
-
-renderFunctionEqInline :: HintMap -> VarSymbol -> VarSymbol -> Expr -> Html ()
-renderFunctionEqInline hints fun arg expr =
- inlineMath do
- renderFunctionCallMath fun arg
- moText "="
- renderExprMathRow hints expr
-
-renderFunctionCallMath :: VarSymbol -> VarSymbol -> Html ()
-renderFunctionCallMath fun arg = do
- renderVarMath fun
- moText "("
- renderVarMath arg
- moText ")"
-
-renderVarListInline :: NonEmpty VarSymbol -> Html ()
-renderVarListInline vars =
- inlineMath (renderVarListMath vars)
-
-renderVarListMath :: NonEmpty VarSymbol -> Html ()
-renderVarListMath vars =
- joinHtml (moText ",") (renderVarMath <$> toList vars)
-
-renderBoundInline :: HintMap -> NonEmpty VarSymbol -> Bound -> Html ()
-renderBoundInline hints vars = \case
- Unbounded -> skip
- bound -> do
- toHtml (" with " :: Text)
- inlineMath (renderBoundPhraseMath hints vars bound)
-
-renderBoundSubjectInline :: HintMap -> NonEmpty VarSymbol -> Bound -> Html ()
-renderBoundSubjectInline hints vars = \case
- Unbounded ->
- renderVarListInline vars
- bound ->
- inlineMath (renderBoundSubjectMath hints vars bound)
-
-renderBoundPhraseMath :: HintMap -> NonEmpty VarSymbol -> Bound -> Html ()
-renderBoundPhraseMath hints vars = \case
- Unbounded -> mrow_ skip
- Bounded _loc sign rel expr ->
- renderRelationApplication hints sign (ExprVar <$> toList vars) rel [expr]
-
-renderBoundSubjectMath :: HintMap -> NonEmpty VarSymbol -> Bound -> Html ()
-renderBoundSubjectMath hints vars = \case
- Unbounded ->
- renderVarListMath vars
- Bounded _loc sign rel expr ->
- renderRelationApplication hints sign (ExprVar <$> toList vars) rel [expr]
-
-boundSubjectMathFragments :: HintMap -> NonEmpty VarSymbol -> Bound -> StmtMathFragments
-boundSubjectMathFragments hints vars = \case
- Unbounded ->
- stmtMathNode (renderVarListMath vars)
- bound ->
- stmtMathNode (renderBoundSubjectMath hints vars bound)
-
-renderSymbolPatternInline :: HintMap -> SymbolPattern -> Html ()
-renderSymbolPatternInline hints =
- inlineMath . renderSymbolPatternMath hints
-
-renderSymbolPatternMath :: HintMap -> SymbolPattern -> Html ()
-renderSymbolPatternMath hints (SymbolPattern symbol vars) =
- renderHintedMathRow hints OperatorHint (mixfixMarker symbol) (ExprVar <$> vars) (renderPatternFallback (mixfixPattern symbol) (renderVarMath <$> vars))
-
-renderJustification :: ReferenceContext -> Justification -> Html ()
-renderJustification references = \case
- JustificationRef markers -> do
- toHtml ("by " :: Text)
- renderMarkerReferences references (toList markers)
- JustificationSetExt ->
- toHtml ("by set extensionality" :: Text)
- JustificationEmpty ->
- skip
- JustificationLocal ->
- toHtml ("by local assumptions" :: Text)
-
-renderJustificationSuffix :: ReferenceContext -> Justification -> Html ()
-renderJustificationSuffix _ JustificationEmpty = skip
-renderJustificationSuffix references justification = do
- toHtml (" " :: Text)
- renderJustification references justification
-
-renderMarkerReferences :: ReferenceContext -> [Marker] -> Html ()
-renderMarkerReferences references markers
- | length markers >= referenceGroupThreshold =
- renderMarkerReferenceGroup references markers
- | otherwise =
- joinHtml (toHtml (", " :: Text)) (renderMarkerReference references <$> markers)
-
-renderMarkerReferenceGroup :: ReferenceContext -> [Marker] -> Html ()
-renderMarkerReferenceGroup references markers =
- span_ groupAttributes do
- toHtml ("[...]" :: Text)
- span_ [class_ "reference-preview-group-items", makeAttributes "hidden" "hidden"] do
- traverse_ (renderMarkerReferenceGroupItem references) markers
- where
- referenceCountLabel = Text.pack (show (length markers)) <> " references"
-
- groupAttributes =
- [ class_ "ref-badge has-preview ref-badge-group"
- , makeAttributes "data-preview-group" "true"
- , makeAttributes "data-reference-label" referenceCountLabel
- , makeAttributes "aria-describedby" "reference-preview-popup"
- , makeAttributes "tabindex" "0"
- , makeAttributes "role" "button"
- , makeAttributes "aria-label" ("Show " <> referenceCountLabel)
- ]
-
-renderMarkerReferenceGroupItem :: ReferenceContext -> Marker -> Html ()
-renderMarkerReferenceGroupItem ReferenceContext{..} marker =
- span_ itemAttributes skip
- where
- label = markerText marker
- baseAttributes =
- [ class_ "reference-preview-group-item"
- , makeAttributes "data-reference-label" label
- ]
-
- itemAttributes =
- baseAttributes <> case Map.lookup marker referenceAnchors of
- Just anchor ->
- [ makeAttributes
- "data-preview-link"
- (renderUrlFragment anchor)
- , makeAttributes "data-preview-target-id" anchor
- ]
- Nothing ->
- case Map.lookup marker referencePreviews of
- Nothing ->
- []
- Just preview ->
- [ makeAttributes
- "data-preview-link"
- (previewReferenceHref preview)
- , makeAttributes "data-preview-id" (previewId preview)
- ]
-
-renderMarkerReference :: ReferenceContext -> Marker -> Html ()
-renderMarkerReference ReferenceContext{..} marker =
- case Map.lookup marker referenceAnchors of
- Just anchor ->
- a_
- ( href_ (renderUrlFragment anchor)
- : referenceAttributes
- (Just (currentPreviewAttributes anchor))
- )
- (toHtml label)
- Nothing ->
- case Map.lookup marker referencePreviews of
- Nothing ->
- span_ (referenceAttributes Nothing) (toHtml label)
- Just preview ->
- a_
- ( href_ (previewReferenceHref preview)
- : referenceAttributes (Just (importedPreviewAttributes preview))
- )
- (toHtml label)
- where
- label = markerText marker
-
- referenceAttributes preview =
- [ class_ (if hasPreview preview then "ref-badge has-preview" else "ref-badge")
- , makeAttributes "data-reference-label" label
- ]
- <> foldMap id preview
-
- hasPreview =
- \case
- Nothing -> False
- Just _ -> True
-
- currentPreviewAttributes anchor =
- [ makeAttributes "data-preview-target-id" anchor
- , makeAttributes "aria-describedby" "reference-preview-popup"
- ]
-
- importedPreviewAttributes entry =
- [ makeAttributes "data-preview-id" (previewId entry)
- , makeAttributes "aria-describedby" "reference-preview-popup"
- ]
-
-
-markerText :: Marker -> Text
-markerText (Marker text) = text
-
-tokenTextHtml :: Token -> Html ()
-tokenTextHtml = toHtml . tokToText
-
-joinHtml :: Html () -> [Html ()] -> Html ()
-joinHtml _ [] = mempty
-joinHtml separator (x : xs) = x <> foldMap (separator <>) xs
-
-miText :: Text -> Html ()
-miText = mi_ . toHtml
-
-moText :: Text -> Html ()
-moText = mo_ . toHtml
-
-mnText :: Text -> Html ()
-mnText = mn_ . toHtml
-
-mtextText :: Text -> Html ()
-mtextText = mtext_ . toHtml
diff --git a/source/Render/Html/Context.hs b/source/Render/Html/Context.hs
deleted file mode 100644
index 151edd5..0000000
--- a/source/Render/Html/Context.hs
+++ /dev/null
@@ -1,171 +0,0 @@
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE NamedFieldPuns #-}
-{-# LANGUAGE NoImplicitPrelude #-}
-
--- | Browser-facing routing authority for one rendered HTML page.
-module Render.Html.Context
- ( HtmlRenderEnvironment
- , htmlRenderEnvironment
- , HtmlRenderContext
- , HtmlRenderContextError(..)
- , htmlRenderContext
- , htmlRenderContextFromEnvironment
- , htmlCurrentSource
- , htmlCurrentPageUrl
- , htmlCurrentPageLabel
- , htmlRouteNamespaces
- , htmlSourceUrl
- , htmlSourceLabel
- , htmlSourcePageHref
- , htmlSourceFragmentHref
- , htmlSupportScriptHref
- ) where
-
-import Base
-import Felix.Source
-import Render.Html.Layout
-
-import Control.Exception (Exception)
-import Data.Map.Strict qualified as Map
-import Data.Text qualified as Text
-
-
-data HtmlRenderContextError
- = HtmlCurrentSourceNotRouted !ResolvedSource
- | HtmlReferencedSourceNotRouted !ResolvedSource
- deriving stock (Show, Eq)
-
-instance Exception HtmlRenderContextError
-
-data HtmlRenderContext = HtmlRenderContext
- { contextCurrentSource :: !ResolvedSource
- , contextCurrentPageUrl :: !UrlPath
- , contextEnvironment :: !HtmlRenderEnvironment
- }
- deriving stock (Show, Eq)
-
-data HtmlRenderEnvironment = HtmlRenderEnvironment
- { environmentSourceUrls :: !(Map ResolvedSource UrlPath)
- , environmentRouteNamespaces :: !(Map SourceMountId UrlPath)
- , environmentSupportScriptUrl :: !UrlPath
- }
- deriving stock (Show, Eq)
-
-htmlRenderEnvironment :: HtmlLayout -> HtmlRenderEnvironment
-htmlRenderEnvironment layout =
- HtmlRenderEnvironment
- { environmentSourceUrls =
- Map.fromList
- [ (source, routeUrlPath route)
- | (source, route) <- htmlPageRoutes layout
- ]
- , environmentRouteNamespaces =
- htmlMountUrlPrefixes layout
- , environmentSupportScriptUrl =
- routeUrlPath (htmlSupportScriptRoute layout)
- }
-
-htmlRenderContext
- :: HtmlLayout
- -> ResolvedSource
- -> Either HtmlRenderContextError HtmlRenderContext
-htmlRenderContext layout =
- htmlRenderContextFromEnvironment
- (htmlRenderEnvironment layout)
-
-htmlRenderContextFromEnvironment
- :: HtmlRenderEnvironment
- -> ResolvedSource
- -> Either HtmlRenderContextError HtmlRenderContext
-htmlRenderContextFromEnvironment environment currentSource = do
- currentPageUrl <-
- maybe
- (Left (HtmlCurrentSourceNotRouted currentSource))
- Right
- (Map.lookup
- currentSource
- (environmentSourceUrls environment))
- Right
- HtmlRenderContext
- { contextCurrentSource = currentSource
- , contextCurrentPageUrl = currentPageUrl
- , contextEnvironment = environment
- }
-
-htmlCurrentSource :: HtmlRenderContext -> ResolvedSource
-htmlCurrentSource =
- contextCurrentSource
-
-htmlCurrentPageUrl :: HtmlRenderContext -> UrlPath
-htmlCurrentPageUrl =
- contextCurrentPageUrl
-
-htmlCurrentPageLabel :: HtmlRenderContext -> Text
-htmlCurrentPageLabel context =
- resolvedSourceLabel (contextCurrentSource context)
-
-htmlRouteNamespaces
- :: HtmlRenderContext
- -> Map SourceMountId UrlPath
-htmlRouteNamespaces =
- environmentRouteNamespaces . contextEnvironment
-
-htmlSourceUrl
- :: HtmlRenderContext
- -> ResolvedSource
- -> Either HtmlRenderContextError UrlPath
-htmlSourceUrl HtmlRenderContext{contextEnvironment} source =
- maybe
- (Left (HtmlReferencedSourceNotRouted source))
- Right
- (Map.lookup source (environmentSourceUrls contextEnvironment))
-
-htmlSourceLabel
- :: HtmlRenderContext
- -> ResolvedSource
- -> Either HtmlRenderContextError Text
-htmlSourceLabel context source = do
- _url <- htmlSourceUrl context source
- Right (resolvedSourceLabel source)
-
-htmlSourcePageHref
- :: HtmlRenderContext
- -> ResolvedSource
- -> Either HtmlRenderContextError Text
-htmlSourcePageHref context source =
- renderRelativeUrlPath
- (contextCurrentPageUrl context)
- <$> htmlSourceUrl context source
-
-htmlSourceFragmentHref
- :: HtmlRenderContext
- -> ResolvedSource
- -> Text
- -> Either HtmlRenderContextError Text
-htmlSourceFragmentHref context source fragment = do
- target <- htmlSourceUrl context source
- let encodedFragment =
- renderUrlFragment fragment
- Right
- (if target == contextCurrentPageUrl context
- then encodedFragment
- else
- renderRelativeUrlPath
- (contextCurrentPageUrl context)
- target
- <> encodedFragment)
-
-htmlSupportScriptHref :: HtmlRenderContext -> Text
-htmlSupportScriptHref context =
- renderRelativeUrlPath
- (contextCurrentPageUrl context)
- (environmentSupportScriptUrl
- (contextEnvironment context))
-
-resolvedSourceLabel :: ResolvedSource -> Text
-resolvedSourceLabel source =
- sourceMountIdText (resolvedSourceMount source)
- <> ":"
- <> Text.pack
- (safeRelativePathFilePath
- (resolvedSourceRelativePath source))
diff --git a/source/Render/Html/Export.hs b/source/Render/Html/Export.hs
deleted file mode 100644
index 2c747d8..0000000
--- a/source/Render/Html/Export.hs
+++ /dev/null
@@ -1,277 +0,0 @@
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE NoImplicitPrelude #-}
-
--- | Prepare a complete HTML export from retained parsed presentation.
-module Render.Html.Export
- ( HtmlPresentation
- , htmlPresentationFromParsedWorkspace
- , HtmlExportError(..)
- , renderHtmlExportError
- , prepareHtmlExport
- , prepareHtmlExportWithLayout
- , prepareHtmlExportWithLayoutFromRendererRoots
- ) where
-
-import Base
-import Felix.Parse
-import Felix.Source
-import Render.Html qualified as Html
-import Render.Html.Context
-import Render.Html.Layout
-import Render.Html.Output
-import Syntax.Abstract (Block)
-
-import Control.Exception (Exception, IOException, displayException)
-import Control.Exception qualified as Exception
-import Data.Bifunctor (first)
-import Data.List.NonEmpty qualified as NonEmpty
-import Data.Text qualified as Text
-import Data.Text.Encoding qualified as TextEncoding
-import Data.Text.Encoding.Error (UnicodeException)
-import Data.Text.IO qualified as TextIO
-import System.Directory (doesFileExist)
-import System.FilePath.Posix ((</>))
-
-
--- | The strict, invocation-local subset of parsed presentation needed by the
--- renderer. Construction forces the complete module sequence and every page
--- shell, severing references to parser payloads, syntax interfaces, and
--- canonical cache payloads.
-data HtmlPresentation = HtmlPresentation
- !(NonEmpty HtmlSourcePresentation)
-
-data HtmlSourcePresentation = HtmlSourcePresentation
- !ResolvedSource
- ![Block]
-
-htmlPresentationFromParsedWorkspace
- :: ParsedSourceWorkspace
- -> HtmlPresentation
-htmlPresentationFromParsedWorkspace workspace =
- HtmlPresentation
- (strictMapNonEmpty project parsedModules)
- where
- parsedModules =
- parsedWorkspaceImportedBeforeImporter workspace
- project parsedModule =
- HtmlSourcePresentation
- (parsedModuleResolved parsedModule)
- (parsedModuleBlocks parsedModule)
-
-strictMapNonEmpty :: (a -> b) -> NonEmpty a -> NonEmpty b
-strictMapNonEmpty f (value :| values) =
- let !firstPage = f value
- !rest = strictMapList f values
- in firstPage :| rest
-
-strictMapList :: (a -> b) -> [a] -> [b]
-strictMapList _ [] =
- []
-strictMapList f (value : values) =
- let !next = f value
- !rest = strictMapList f values
- in next : rest
-
-data HtmlExportError
- = HtmlRendererDataNotFound !FilePath ![FilePath]
- | HtmlRendererDataLookupFailed !FilePath !Text
- | HtmlRendererDataReadFailed !FilePath !Text
- | HtmlExportLayoutError !HtmlLayoutError
- | HtmlExportContextError !HtmlRenderContextError
- deriving stock (Show)
-
-instance Exception HtmlExportError
-
-renderHtmlExportError :: HtmlExportError -> Text
-renderHtmlExportError = \case
- HtmlRendererDataNotFound requested searched ->
- "renderer data " <> quotePath requested <> " was not found; searched "
- <> Text.intercalate ", " (quotePath <$> searched)
- HtmlRendererDataLookupFailed path reason ->
- "could not locate renderer data " <> quotePath path <> ": " <> reason
- HtmlRendererDataReadFailed path reason ->
- "could not read renderer data " <> quotePath path <> ": " <> reason
- HtmlExportLayoutError failure ->
- renderHtmlLayoutError failure
- HtmlExportContextError failure ->
- case failure of
- HtmlCurrentSourceNotRouted source ->
- "current source has no HTML route: " <> sourceLabel source
- HtmlReferencedSourceNotRouted source ->
- "referenced source has no HTML route: " <> sourceLabel source
- where
- sourceLabel source =
- sourceMountIdText (resolvedSourceMount source)
- <> ":"
- <> Text.pack
- (safeRelativePathFilePath
- (resolvedSourceRelativePath source))
-
- quotePath = Text.pack . show
-
-prepareHtmlExport
- :: [(SourceMountId, [Text])]
- -> HtmlPresentation
- -> Text
- -> Either HtmlExportError [PreparedHtmlArtifact]
-prepareHtmlExport
- mountPrefixes
- (HtmlPresentation presentation)
- hints = do
- let sources =
- sourceOf <$> NonEmpty.toList presentation
- layout <-
- first
- HtmlExportLayoutError
- (layoutHtmlSources mountPrefixes sources)
- prepareRenderedExport hints layout presentation
- where
- sourceOf (HtmlSourcePresentation source _blocks) =
- source
-
-prepareHtmlExportWithLayout
- :: HtmlLayout
- -> HtmlPresentation
- -> Text
- -> Either HtmlExportError [PreparedHtmlArtifact]
-prepareHtmlExportWithLayout
- layout
- (HtmlPresentation presentation)
- hints =
- prepareRenderedExport hints layout presentation
-
-prepareHtmlExportWithLayoutFromRendererRoots
- :: [FilePath]
- -> HtmlLayout
- -> HtmlPresentation
- -> IO (Either HtmlExportError [PreparedHtmlArtifact])
-prepareHtmlExportWithLayoutFromRendererRoots roots layout presentation = do
- hintsResult <- findAndReadRendererFile roots "lexicon.tsv"
- pure do
- hints <- hintsResult
- prepareHtmlExportWithLayout layout presentation hints
-
--- Renderer data follows the established current-directory,
--- configured-library, and debug-directory lookup policy.
-findAndReadRendererFile
- :: [FilePath]
- -> FilePath
- -> IO (Either HtmlExportError Text)
-findAndReadRendererFile roots path =
- selectRendererData path ((</> path) <$> roots) >>= \case
- Left failure -> pure (Left failure)
- Right selectedPath -> do
- readResult <- tryRendererRead (TextIO.readFile selectedPath)
- pure case readResult of
- Left reason ->
- Left (HtmlRendererDataReadFailed selectedPath reason)
- Right contents -> Right contents
-
-selectRendererData
- :: FilePath
- -> [FilePath]
- -> IO (Either HtmlExportError FilePath)
-selectRendererData requested candidates = go candidates
- where
- go = \case
- [] -> pure (Left (HtmlRendererDataNotFound requested candidates))
- candidate : remaining ->
- tryRendererIO (doesFileExist candidate) >>= \case
- Left failure ->
- pure
- (Left
- (HtmlRendererDataLookupFailed
- candidate
- (Text.pack (displayException failure))))
- Right True -> pure (Right candidate)
- Right False -> go remaining
-
-tryRendererIO :: IO value -> IO (Either IOException value)
-tryRendererIO = Exception.try
-
-tryRendererRead :: IO value -> IO (Either Text value)
-tryRendererRead action =
- Exception.catch
- (Exception.catch (Right <$> action) renderIOException)
- renderUnicodeException
- where
- renderIOException :: IOException -> IO (Either Text value)
- renderIOException = pure . Left . Text.pack . displayException
-
- renderUnicodeException
- :: UnicodeException
- -> IO (Either Text value)
- renderUnicodeException = pure . Left . Text.pack . displayException
-
-prepareRenderedExport
- :: Text
- -> HtmlLayout
- -> NonEmpty HtmlSourcePresentation
- -> Either HtmlExportError [PreparedHtmlArtifact]
-prepareRenderedExport hints layout presentation = do
- let sourceBlocks =
- (\(HtmlSourcePresentation source blocks) ->
- (source, blocks))
- <$> presentation
- (unforcedRenderIndex, pages) =
- Html.buildRenderIndex sourceBlocks
- !renderIndex = unforcedRenderIndex
- renderEnvironment =
- htmlRenderEnvironment layout
- pageArtifacts <-
- traverse
- (prepareSourceArtifact
- renderEnvironment
- layout
- hints
- renderIndex)
- pages
- let supportRoute =
- htmlSupportScriptRoute layout
- supportArtifact =
- preparedHtmlArtifact
- (routeDestination supportRoute)
- (Right
- (TextEncoding.encodeUtf8
- Html.supportScriptAssetContents))
- -- Page artifacts retain imported-before-importer source order. The
- -- singleton support asset is published deterministically afterward.
- Right (NonEmpty.toList pageArtifacts <> [supportArtifact])
-
-prepareSourceArtifact
- :: HtmlRenderEnvironment
- -> HtmlLayout
- -> Text
- -> Html.HtmlRenderIndex
- -> Html.HtmlPagePresentation
- -> Either
- HtmlExportError
- PreparedHtmlArtifact
-prepareSourceArtifact renderEnvironment layout hints renderIndex page = do
- let source = Html.htmlPagePresentationSource page
- context <-
- first
- HtmlExportContextError
- (htmlRenderContextFromEnvironment
- renderEnvironment
- source)
- route <-
- maybe
- (Left
- (HtmlExportContextError
- (HtmlCurrentSourceNotRouted source)))
- Right
- (htmlPageRoute layout source)
- Right
- (preparedHtmlArtifact
- (routeDestination route)
- (first renderHtmlExportError
- (TextEncoding.encodeUtf8
- <$> first
- HtmlExportContextError
- (Html.renderDocument
- context
- hints
- renderIndex
- page))))
diff --git a/source/Render/Html/Layout.hs b/source/Render/Html/Layout.hs
deleted file mode 100644
index c26b6b5..0000000
--- a/source/Render/Html/Layout.hs
+++ /dev/null
@@ -1,602 +0,0 @@
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE NoImplicitPrelude #-}
-
--- | Pure browser and destination routing for a resolved source graph.
-module Render.Html.Layout
- ( UrlSegment
- , UrlSegmentError(..)
- , urlSegment
- , renderUrlSegment
- , UrlPath
- , urlPath
- , renderUrlPath
- , renderRelativeUrlPath
- , renderUrlFragment
- , HtmlRoute
- , routeDestination
- , routeUrlPath
- , HtmlRouteOwner(..)
- , HtmlUrlRouteCollision(..)
- , HtmlDestinationRouteCollision(..)
- , HtmlLayoutError(..)
- , renderHtmlLayoutError
- , HtmlLayout
- , htmlPageRoutes
- , htmlPageRoute
- , htmlSupportScriptRoute
- , htmlMountUrlPrefixes
- , layoutHtmlSources
- , layoutHtmlSourceGraph
- ) where
-
-import Base
-import Felix.Source
-import Felix.Source.Graph
-
-import Control.Exception (Exception)
-import Data.Bifunctor (first)
-import Data.ByteString qualified as ByteString
-import Data.Char (chr)
-import Data.List qualified as List
-import Data.Map.Strict qualified as Map
-import Data.Set qualified as Set
-import Data.Text qualified as Text
-import Data.Text.Encoding qualified as TextEncoding
-import Data.Word (Word8)
-import System.FilePath.Posix qualified as Posix
-
-
--- | One canonical percent-encoded URL path segment.
-newtype UrlSegment = UrlSegment Text
- deriving stock (Show, Eq, Ord)
-
-data UrlSegmentError
- = EmptyUrlSegment
- | DotUrlSegment !Text
- | UrlSegmentContainsSeparator !Text
- | UrlSegmentContainsNull !Text
- deriving stock (Show, Eq)
-
-urlSegment :: Text -> Either UrlSegmentError UrlSegment
-urlSegment decoded
- | Text.null decoded =
- Left EmptyUrlSegment
- | decoded == "." || decoded == ".." =
- Left (DotUrlSegment decoded)
- | "/" `Text.isInfixOf` decoded =
- Left (UrlSegmentContainsSeparator decoded)
- | "\0" `Text.isInfixOf` decoded =
- Left (UrlSegmentContainsNull decoded)
- | otherwise =
- Right (UrlSegment (percentEncodeUtf8 decoded))
-
-renderUrlSegment :: UrlSegment -> Text
-renderUrlSegment (UrlSegment encoded) =
- encoded
-
-
--- | A root-relative URL path. Its segments are already encoded.
-newtype UrlPath = UrlPath [UrlSegment]
- deriving stock (Show, Eq, Ord)
-
-urlPath :: [Text] -> Either UrlSegmentError UrlPath
-urlPath =
- fmap UrlPath . traverse urlSegment
-
-renderUrlPath :: UrlPath -> Text
-renderUrlPath (UrlPath segments) =
- "/" <> Text.intercalate "/" (renderUrlSegment <$> segments)
-
--- | Render a target path relative to the directory of a current page.
-renderRelativeUrlPath :: UrlPath -> UrlPath -> Text
-renderRelativeUrlPath
- (UrlPath currentPageSegments)
- (UrlPath targetSegments) =
- case relativeSegments of
- [] ->
- "."
- _ ->
- Text.intercalate "/" relativeSegments
- where
- currentDirectorySegments =
- case reverse currentPageSegments of
- [] ->
- []
- _page : directoryReversed ->
- reverse directoryReversed
- (remainingCurrent, remainingTarget) =
- dropCommonPrefix currentDirectorySegments targetSegments
- relativeSegments =
- replicate (length remainingCurrent) ".."
- <> (renderUrlSegment <$> remainingTarget)
-
--- | Render an exact source marker as an encoded URL fragment.
-renderUrlFragment :: Text -> Text
-renderUrlFragment marker =
- "#" <> percentEncodeUtf8 marker
-
-
-data HtmlRoute = HtmlRoute
- { routeDestination :: !SafeRelativePath
- , routeUrlPath :: !UrlPath
- }
- deriving stock (Show, Eq)
-
-data HtmlRouteOwner
- = HtmlPage !ResolvedSource
- | HtmlSupportScript
- deriving stock (Show, Eq, Ord)
-
-data HtmlUrlRouteCollision = HtmlUrlRouteCollision
- !UrlPath
- !(NonEmpty HtmlRouteOwner)
- deriving stock (Show, Eq)
-
-data HtmlDestinationRouteCollision = HtmlDestinationRouteCollision
- !SafeRelativePath
- !(NonEmpty HtmlRouteOwner)
- | NestedHtmlDestinationRouteCollision
- !SafeRelativePath
- !HtmlRouteOwner
- !SafeRelativePath
- !HtmlRouteOwner
- deriving stock (Show, Eq)
-
-data HtmlLayoutError
- = DuplicateHtmlMountId !SourceMountId
- | InvalidHtmlMountPrefixSegment
- !SourceMountId
- !Text
- !UrlSegmentError
- | DuplicateHtmlMountPrefix
- ![Text]
- !(NonEmpty SourceMountId)
- | MissingHtmlMountPrefix !SourceMountId
- | InvalidHtmlRouteSegment
- !HtmlRouteOwner
- !Text
- !UrlSegmentError
- | InvalidHtmlRouteDestination
- !HtmlRouteOwner
- !FilePath
- !RelativePathError
- | CollidingHtmlRoutes
- ![HtmlUrlRouteCollision]
- ![HtmlDestinationRouteCollision]
- deriving stock (Show, Eq)
-
-instance Exception HtmlLayoutError
-
-renderHtmlLayoutError :: HtmlLayoutError -> Text
-renderHtmlLayoutError = \case
- DuplicateHtmlMountId mount ->
- "HTML mount is configured more than once: "
- <> quoteText (sourceMountIdText mount)
- InvalidHtmlMountPrefixSegment mount segment _problem ->
- "HTML mount " <> quoteText (sourceMountIdText mount)
- <> " has invalid route segment " <> quoteText segment
- DuplicateHtmlMountPrefix prefix mounts ->
- "HTML route prefix " <> quoteText (Text.intercalate "/" prefix)
- <> " is shared by mounts "
- <> Text.intercalate ", "
- (quoteText . sourceMountIdText <$> toList mounts)
- MissingHtmlMountPrefix mount ->
- "no HTML route prefix is configured for mount "
- <> quoteText (sourceMountIdText mount)
- InvalidHtmlRouteSegment owner segment _problem ->
- renderOwner owner <> " has invalid route segment " <> quoteText segment
- InvalidHtmlRouteDestination owner path _problem ->
- renderOwner owner <> " has invalid HTML destination " <> quotePath path
- CollidingHtmlRoutes urlCollisions destinationCollisions ->
- "HTML routes collide: "
- <> Text.intercalate "; "
- ( (renderUrlCollision <$> urlCollisions)
- <> (renderDestinationCollision <$> destinationCollisions)
- )
- where
- renderUrlCollision (HtmlUrlRouteCollision path owners) =
- "URL " <> quoteText (renderUrlPath path)
- <> " is owned by "
- <> Text.intercalate ", " (renderOwner <$> toList owners)
-
- renderDestinationCollision
- (HtmlDestinationRouteCollision path owners) =
- "destination "
- <> quotePath (safeRelativePathFilePath path)
- <> " is owned by "
- <> Text.intercalate ", " (renderOwner <$> toList owners)
- renderDestinationCollision
- (NestedHtmlDestinationRouteCollision
- ancestor ancestorOwner descendant descendantOwner) =
- "destination " <> quotePath (safeRelativePathFilePath ancestor)
- <> " for " <> renderOwner ancestorOwner
- <> " is an ancestor of "
- <> quotePath (safeRelativePathFilePath descendant)
- <> " for " <> renderOwner descendantOwner
-
-renderOwner :: HtmlRouteOwner -> Text
-renderOwner = \case
- HtmlPage source ->
- "page "
- <> quoteText
- (sourceMountIdText (resolvedSourceMount source)
- <> ":"
- <> Text.pack
- (safeRelativePathFilePath
- (resolvedSourceRelativePath source)))
- HtmlSupportScript ->
- "support script"
-
-quotePath :: FilePath -> Text
-quotePath = Text.pack . show
-
-quoteText :: Text -> Text
-quoteText = Text.pack . show
-
-data HtmlLayout = HtmlLayout
- !(Map ResolvedSource HtmlRoute)
- !HtmlRoute
- !(Map SourceMountId UrlPath)
- deriving stock (Show, Eq)
-
-htmlPageRoutes :: HtmlLayout -> [(ResolvedSource, HtmlRoute)]
-htmlPageRoutes (HtmlLayout routes _supportScript _mountPrefixes) =
- Map.toAscList routes
-
-htmlPageRoute :: HtmlLayout -> ResolvedSource -> Maybe HtmlRoute
-htmlPageRoute (HtmlLayout routes _supportScript _mountPrefixes) source =
- Map.lookup source routes
-
-htmlSupportScriptRoute :: HtmlLayout -> HtmlRoute
-htmlSupportScriptRoute (HtmlLayout _routes supportScript _mountPrefixes) =
- supportScript
-
-htmlMountUrlPrefixes :: HtmlLayout -> Map SourceMountId UrlPath
-htmlMountUrlPrefixes (HtmlLayout _routes _supportScript mountPrefixes) =
- mountPrefixes
-
-
-data ValidatedMountPrefix = ValidatedMountPrefix
- ![Text]
- ![UrlSegment]
-
-layoutHtmlSourceGraph
- :: [(SourceMountId, [Text])]
- -> ResolvedSourceGraph
- -> Either HtmlLayoutError HtmlLayout
-layoutHtmlSourceGraph specifications graph =
- layoutHtmlSources
- specifications
- (sourceNodeResolved <$> sourceGraphNodes graph)
-
-layoutHtmlSources
- :: [(SourceMountId, [Text])]
- -> [ResolvedSource]
- -> Either HtmlLayoutError HtmlLayout
-layoutHtmlSources specifications inputSources = do
- prefixes <- validateMountPrefixes specifications
- let sources =
- List.sort
- inputSources
- usedMounts =
- Set.fromList (resolvedSourceMount <$> sources)
- missingMounts =
- usedMounts `Set.difference` Map.keysSet prefixes
- case Set.lookupMin missingMounts of
- Just missing ->
- Left (MissingHtmlMountPrefix missing)
- Nothing -> do
- pageEntries <-
- traverse
- (makePageRoute prefixes)
- sources
- encodedSupportScript <-
- encodeRouteSegments
- HtmlSupportScript
- supportScriptAssetComponents
- supportScript <-
- makeRoute
- HtmlSupportScript
- supportScriptAssetComponents
- encodedSupportScript
- let ownedRoutes =
- (HtmlSupportScript, supportScript)
- : [ (HtmlPage source, route)
- | (source, route) <- pageEntries
- ]
- urlCollisions =
- collectUrlCollisions ownedRoutes
- destinationCollisions =
- collectDestinationCollisions ownedRoutes
- <> collectNestedDestinationCollisions ownedRoutes
- if null urlCollisions && null destinationCollisions
- then
- Right
- (HtmlLayout
- (Map.fromList pageEntries)
- supportScript
- (Map.map
- (\(ValidatedMountPrefix _decoded encoded) ->
- UrlPath encoded)
- prefixes))
- else
- Left
- (CollidingHtmlRoutes
- urlCollisions
- destinationCollisions)
-
-validateMountPrefixes
- :: [(SourceMountId, [Text])]
- -> Either HtmlLayoutError (Map SourceMountId ValidatedMountPrefix)
-validateMountPrefixes specifications =
- case duplicateValues (fst <$> specifications) of
- duplicate : _ ->
- Left (DuplicateHtmlMountId duplicate)
- [] -> do
- validated <- traverse validatePrefix (List.sort specifications)
- case duplicatePrefixGroups validated of
- duplicate : _ ->
- Left duplicate
- [] ->
- Right
- (Map.fromList
- [ (mount, prefix)
- | (mount, _decoded, prefix) <- validated
- ])
- where
- validatePrefix (mount, decoded) = do
- encoded <- traverse
- (\segment ->
- first
- (InvalidHtmlMountPrefixSegment mount segment)
- (urlSegment segment))
- decoded
- Right
- ( mount
- , decoded
- , ValidatedMountPrefix decoded encoded
- )
-
-duplicatePrefixGroups
- :: [(SourceMountId, [Text], ValidatedMountPrefix)]
- -> [HtmlLayoutError]
-duplicatePrefixGroups validated =
- [ DuplicateHtmlMountPrefix prefix (firstMount :| otherMounts)
- | (prefix, mounts) <-
- Map.toAscList
- (Map.fromListWith (<>)
- [ (decoded, [mount])
- | (mount, decoded, _prefix) <- validated
- ])
- , firstMount : secondMount : remainingMounts <-
- [List.sort mounts]
- , let otherMounts = secondMount : remainingMounts
- ]
-
-duplicateValues :: Ord a => [a] -> [a]
-duplicateValues values =
- [ value
- | (value, multiplicity) <-
- Map.toAscList
- (Map.fromListWith (+)
- [(value, 1 :: Int) | value <- values])
- , multiplicity > 1
- ]
-
-makePageRoute
- :: Map SourceMountId ValidatedMountPrefix
- -> ResolvedSource
- -> Either HtmlLayoutError (ResolvedSource, HtmlRoute)
-makePageRoute prefixes source = do
- prefix <- case Map.lookup (resolvedSourceMount source) prefixes of
- Nothing ->
- Left
- (MissingHtmlMountPrefix
- (resolvedSourceMount source))
- Just found ->
- Right found
- let sourceComponents =
- Text.splitOn
- "/"
- (Text.pack
- (safeRelativePathFilePath
- (resolvedSourceRelativePath source)))
- destinationComponents =
- replaceFinalComponent
- (\component ->
- dropFinalExtension component <> ".html")
- sourceComponents
- urlComponents =
- replaceFinalComponent
- dropFinalExtension
- sourceComponents
- ValidatedMountPrefix decodedPrefix encodedPrefix =
- prefix
- owner = HtmlPage source
- encodedPageComponents <-
- encodeRouteSegments owner urlComponents
- route <-
- makeRoute
- owner
- (decodedPrefix <> destinationComponents)
- (encodedPrefix <> encodedPageComponents)
- Right (source, route)
-
-replaceFinalComponent :: (a -> a) -> [a] -> [a]
-replaceFinalComponent transform components =
- case reverse components of
- [] ->
- []
- final : precedingReversed ->
- reverse precedingReversed <> [transform final]
-
-dropFinalExtension :: Text -> Text
-dropFinalExtension component =
- case Text.breakOnEnd "." component of
- ("", _suffix) ->
- component
- (".", _suffix) ->
- component
- (prefix, _suffix) ->
- Text.dropEnd 1 prefix
-
-makeRoute
- :: HtmlRouteOwner
- -> [Text]
- -> [UrlSegment]
- -> Either HtmlLayoutError HtmlRoute
-makeRoute owner destinationComponents encodedUrlComponents = do
- let destinationSpelling =
- List.intercalate
- "/"
- (Text.unpack <$> destinationComponents)
- destination <-
- first
- (InvalidHtmlRouteDestination
- owner
- destinationSpelling)
- (safeRelativePath destinationSpelling)
- Right
- HtmlRoute
- { routeDestination = destination
- , routeUrlPath = UrlPath encodedUrlComponents
- }
-
-encodeRouteSegments
- :: HtmlRouteOwner
- -> [Text]
- -> Either HtmlLayoutError [UrlSegment]
-encodeRouteSegments owner =
- traverse
- (\decoded ->
- first
- (InvalidHtmlRouteSegment owner decoded)
- (urlSegment decoded))
-
-collectUrlCollisions
- :: [(HtmlRouteOwner, HtmlRoute)]
- -> [HtmlUrlRouteCollision]
-collectUrlCollisions ownedRoutes =
- [ HtmlUrlRouteCollision path owners
- | (path, collidingOwners) <-
- Map.toAscList
- (Map.fromListWith (<>)
- [ (routeUrlPath route, [owner])
- | (owner, route) <- ownedRoutes
- ])
- , owners <-
- collisionOwners collidingOwners
- ]
-
-collectDestinationCollisions
- :: [(HtmlRouteOwner, HtmlRoute)]
- -> [HtmlDestinationRouteCollision]
-collectDestinationCollisions ownedRoutes =
- [ HtmlDestinationRouteCollision destination owners
- | (destination, collidingOwners) <-
- Map.toAscList
- (Map.fromListWith (<>)
- [ (routeDestination route, [owner])
- | (owner, route) <- ownedRoutes
- ])
- , owners <-
- collisionOwners collidingOwners
- ]
-
-collectNestedDestinationCollisions
- :: [(HtmlRouteOwner, HtmlRoute)]
- -> [HtmlDestinationRouteCollision]
-collectNestedDestinationCollisions ownedRoutes =
- take 1
- [ NestedHtmlDestinationRouteCollision
- ancestor
- ancestorOwner
- descendant
- descendantOwner
- | ( (ancestorComponents, ancestor, ancestorOwner)
- , (descendantComponents, descendant, descendantOwner)
- ) <- zip destinations (drop 1 destinations)
- , strictComponentPrefix ancestorComponents descendantComponents
- ]
- where
- destinations =
- List.sort
- [ ( relativePathComponents destination
- , destination
- , owner
- )
- | (owner, route) <- ownedRoutes
- , let destination = routeDestination route
- ]
-
-relativePathComponents :: SafeRelativePath -> [FilePath]
-relativePathComponents =
- Posix.splitDirectories . safeRelativePathFilePath
-
-strictComponentPrefix :: [FilePath] -> [FilePath] -> Bool
-strictComponentPrefix possibleAncestor possibleDescendant =
- length possibleAncestor < length possibleDescendant
- && possibleAncestor `List.isPrefixOf` possibleDescendant
-
-collisionOwners :: [HtmlRouteOwner] -> [NonEmpty HtmlRouteOwner]
-collisionOwners owners =
- case List.sort owners of
- firstOwner : secondOwner : rest ->
- [firstOwner :| (secondOwner : rest)]
- _ ->
- []
-
-dropCommonPrefix :: Eq a => [a] -> [a] -> ([a], [a])
-dropCommonPrefix (left : lefts) (right : rights)
- | left == right =
- dropCommonPrefix lefts rights
-dropCommonPrefix left right =
- (left, right)
-
-
-supportScriptAssetComponents :: [Text]
-supportScriptAssetComponents =
- ["_static", "naproche-html.js"]
-
-percentEncodeUtf8 :: Text -> Text
-percentEncodeUtf8 =
- Text.pack
- . concatMap encodeByte
- . ByteString.unpack
- . TextEncoding.encodeUtf8
-
-encodeByte :: Word8 -> String
-encodeByte byte
- | isUnreservedAscii byte =
- [chr (fromIntegral byte)]
- | otherwise =
- [ '%'
- , hexadecimalDigit (byte `div` 16)
- , hexadecimalDigit (byte `mod` 16)
- ]
-
-isUnreservedAscii :: Word8 -> Bool
-isUnreservedAscii byte =
- isAsciiUpper byte
- || isAsciiLower byte
- || isAsciiDigit byte
- || byte `elem` fmap (fromIntegral . fromEnum) ("-._~" :: String)
-
-isAsciiUpper :: Word8 -> Bool
-isAsciiUpper byte =
- byte >= 65 && byte <= 90
-
-isAsciiLower :: Word8 -> Bool
-isAsciiLower byte =
- byte >= 97 && byte <= 122
-
-isAsciiDigit :: Word8 -> Bool
-isAsciiDigit byte =
- byte >= 48 && byte <= 57
-
-hexadecimalDigit :: Word8 -> Char
-hexadecimalDigit value
- | value < 10 =
- chr (fromIntegral value + fromEnum '0')
- | otherwise =
- chr (fromIntegral value - 10 + fromEnum 'A')
diff --git a/source/Render/Html/Output.hs b/source/Render/Html/Output.hs
deleted file mode 100644
index 685c83a..0000000
--- a/source/Render/Html/Output.hs
+++ /dev/null
@@ -1,471 +0,0 @@
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE NoImplicitPrelude #-}
-
--- | Confined filesystem authority for HTML output.
---
--- The output root is assumed to be user-owned and not concurrently changed by
--- a hostile actor between planning and writing. Existing parent symlinks are
--- accepted only when they resolve inside the canonical root. Final-target
--- symlinks are rejected without following them; regular generated files may be
--- replaced. This policy prevents stable-tree escapes, not TOCTOU attacks.
-module Render.Html.Output
- ( PreparedHtmlArtifact
- , preparedHtmlArtifact
- , preparedHtmlArtifactDestination
- , HtmlRoutePlan
- , htmlRoutePlanDestinations
- , planHtmlRoutes
- , HtmlOutputPlan
- , HtmlOutputError(..)
- , renderHtmlOutputError
- , planHtmlOutput
- , planHtmlOutputAgainst
- , HtmlPublicationError(..)
- , renderHtmlPublicationError
- , writeHtmlOutput
- ) where
-
-import Base
-import Felix.Output.Atomic (writeBytesAtomically)
-import Felix.Source
- ( SafeRelativePath
- , safeRelativePathFilePath
- )
-
-import Control.Exception (Exception, IOException, displayException)
-import Control.Exception qualified as Exception
-import Control.Monad (unless, when)
-import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE)
-import Data.ByteString (ByteString)
-import Data.List qualified as List
-import Data.Map.Strict qualified as Map
-import Data.Set qualified as Set
-import Data.Text qualified as Text
-import System.Directory qualified as Directory
-import System.FilePath.Posix qualified as Posix
-import System.Posix.Files qualified as PosixFiles
-
-
--- | One lazily rendered artifact. The destination is available for complete
--- preflight without demanding the strict bytes or a renderer failure.
-data PreparedHtmlArtifact = PreparedHtmlArtifact
- !SafeRelativePath
- (Either Text ByteString)
-
-preparedHtmlArtifact
- :: SafeRelativePath
- -> Either Text ByteString
- -> PreparedHtmlArtifact
-preparedHtmlArtifact =
- PreparedHtmlArtifact
-
-preparedHtmlArtifactDestination
- :: PreparedHtmlArtifact
- -> SafeRelativePath
-preparedHtmlArtifactDestination
- (PreparedHtmlArtifact destination _rendered) =
- destination
-
-
--- Constructors and absolute paths stay private to this module.
-newtype HtmlRoutePlan = HtmlRoutePlan
- [(SafeRelativePath, FilePath)]
-
-htmlRoutePlanDestinations
- :: HtmlRoutePlan
- -> [(SafeRelativePath, FilePath)]
-htmlRoutePlanDestinations (HtmlRoutePlan routes) =
- routes
-
-newtype HtmlOutputPlan = HtmlOutputPlan
- [PlannedHtmlArtifact]
-
-data PlannedHtmlArtifact = PlannedHtmlArtifact
- !SafeRelativePath
- !FilePath
- (Either Text ByteString)
-
-data HtmlOutputError
- = EmptyPreparedHtmlOutput
- | DuplicatePreparedHtmlDestination !SafeRelativePath
- | HtmlOutputRouteMismatch
- ![SafeRelativePath]
- ![SafeRelativePath]
- | EmptyHtmlOutputRoot
- | HtmlOutputPathInspectionFailed !FilePath !Text
- | HtmlOutputRootNotDirectory !FilePath
- | HtmlOutputParentNotDirectory !FilePath
- | HtmlOutputParentEscapesRoot !FilePath !FilePath
- | HtmlOutputTargetIsSymbolicLink !FilePath
- | HtmlOutputTargetNotRegularFile !FilePath
- deriving stock (Show, Eq)
-
-renderHtmlOutputError :: HtmlOutputError -> Text
-renderHtmlOutputError = \case
- EmptyPreparedHtmlOutput ->
- "HTML output contains no artifacts"
- DuplicatePreparedHtmlDestination relative ->
- "HTML output contains destination more than once: "
- <> quoteRelative relative
- HtmlOutputRouteMismatch planned prepared ->
- "prepared HTML destinations do not match the reserved routes; planned "
- <> renderRelatives planned <> ", prepared " <> renderRelatives prepared
- EmptyHtmlOutputRoot ->
- "HTML output root is empty"
- HtmlOutputPathInspectionFailed path reason ->
- "could not inspect HTML output path " <> quotePath path <> ": " <> reason
- HtmlOutputRootNotDirectory path ->
- "HTML output root is not a directory: " <> quotePath path
- HtmlOutputParentNotDirectory path ->
- "HTML output parent is not a directory: " <> quotePath path
- HtmlOutputParentEscapesRoot root parent ->
- "HTML output parent " <> quotePath parent
- <> " resolves outside root " <> quotePath root
- HtmlOutputTargetIsSymbolicLink path ->
- "HTML output target is a symbolic link: " <> quotePath path
- HtmlOutputTargetNotRegularFile path ->
- "HTML output target is not a regular file: " <> quotePath path
- where
- renderRelatives = Text.intercalate ", " . fmap quoteRelative
-
-quoteRelative :: SafeRelativePath -> Text
-quoteRelative = quotePath . safeRelativePathFilePath
-
-quotePath :: FilePath -> Text
-quotePath = Text.pack . show
-
-instance Exception HtmlOutputError
-
--- | Validate every destination without changing the filesystem.
-planHtmlOutput
- :: FilePath
- -> [PreparedHtmlArtifact]
- -> IO (Either HtmlOutputError HtmlOutputPlan)
-planHtmlOutput outputRoot artifacts = do
- routes <- planHtmlRoutes
- outputRoot
- (preparedHtmlArtifactDestination <$> artifacts)
- pure (routes >>= (`planHtmlOutputAgainst` artifacts))
-
-planHtmlRoutes
- :: FilePath
- -> [SafeRelativePath]
- -> IO (Either HtmlOutputError HtmlRoutePlan)
-planHtmlRoutes outputRoot destinations =
- runExceptT do
- when (null destinations)
- (throwE EmptyPreparedHtmlOutput)
- case duplicateDestinations destinations of
- duplicate : _ ->
- throwE
- (DuplicatePreparedHtmlDestination duplicate)
- [] ->
- pure ()
- when (null outputRoot) (throwE EmptyHtmlOutputRoot)
- absoluteRoot <-
- inspectPath
- outputRoot
- (Directory.makeAbsolute outputRoot)
- rootIsLink <- inspectSymbolicLink absoluteRoot
- rootExists <-
- inspectPath
- absoluteRoot
- (Directory.doesPathExist absoluteRoot)
- rootIsDirectory <-
- inspectPath
- absoluteRoot
- (Directory.doesDirectoryExist absoluteRoot)
- when
- ((rootIsLink || rootExists) && not rootIsDirectory)
- (throwE (HtmlOutputRootNotDirectory absoluteRoot))
- canonicalRoot <-
- inspectPath
- absoluteRoot
- (Directory.canonicalizePath absoluteRoot)
- planned <- for (List.sort destinations)
- \relative -> do
- let components =
- Posix.splitDirectories
- (safeRelativePathFilePath relative)
- destination =
- confinedDestination
- absoluteRoot
- components
- preflightDestination
- canonicalRoot
- absoluteRoot
- components
- destination
- pure
- ( relative
- , destination
- )
- pure (HtmlRoutePlan planned)
-
-planHtmlOutputAgainst
- :: HtmlRoutePlan
- -> [PreparedHtmlArtifact]
- -> Either HtmlOutputError HtmlOutputPlan
-planHtmlOutputAgainst
- (HtmlRoutePlan routes)
- artifacts
- | null artifacts =
- Left EmptyPreparedHtmlOutput
- | duplicate : _ <- duplicateDestinations preparedDestinations =
- Left (DuplicatePreparedHtmlDestination duplicate)
- | Set.fromList plannedDestinations
- /= Set.fromList preparedDestinations =
- Left
- (HtmlOutputRouteMismatch
- plannedDestinations
- preparedDestinations)
- | otherwise = HtmlOutputPlan <$> traverse attach artifacts
- where
- plannedDestinations = fst <$> routes
- preparedDestinations =
- preparedHtmlArtifactDestination <$> artifacts
- routeDestinations = Map.fromList routes
-
- attach (PreparedHtmlArtifact relative rendered) =
- case Map.lookup relative routeDestinations of
- Nothing ->
- Left
- (HtmlOutputRouteMismatch
- plannedDestinations
- preparedDestinations)
- Just destination ->
- Right
- (PlannedHtmlArtifact
- relative
- destination
- rendered)
-
-duplicateDestinations
- :: [SafeRelativePath]
- -> [SafeRelativePath]
-duplicateDestinations destinations =
- [ destination
- | (destination, multiplicity) <-
- Map.toAscList
- (Map.fromListWith (+)
- [ (destination, 1 :: Int)
- | destination <- destinations
- ])
- , multiplicity > 1
- ]
-
-
-data HtmlPublicationError = IncompleteHtmlPublication
- { committedHtmlDestinations :: ![SafeRelativePath]
- , failedHtmlDestination :: !SafeRelativePath
- , htmlPublicationFailure :: !Text
- }
- deriving stock (Show, Eq)
-
-instance Exception HtmlPublicationError
-
-renderHtmlPublicationError :: HtmlPublicationError -> [Text]
-renderHtmlPublicationError failure =
- [ "HTML publication failed at "
- <> quoteRelative (failedHtmlDestination failure)
- <> ": " <> htmlPublicationFailure failure
- ]
- <> case committedHtmlDestinations failure of
- [] -> []
- committed ->
- [ "HTML files published before the failure: "
- <> Text.intercalate ", "
- (quoteRelative <$> committed)
- ]
-
--- | Render, stage, and atomically replace each completely preflighted artifact
--- in the supplied source order. No later artifact is rendered or staged
--- before the preceding destination has been replaced.
-writeHtmlOutput
- :: HtmlOutputPlan
- -> IO (Either HtmlPublicationError ())
-writeHtmlOutput (HtmlOutputPlan planned) =
- publishAll [] planned
-
-publishAll
- :: [SafeRelativePath]
- -> [PlannedHtmlArtifact]
- -> IO (Either HtmlPublicationError ())
-publishAll _committed [] =
- pure (Right ())
-publishAll
- committedReversed
- (PlannedHtmlArtifact relative destination rendered : remaining) =
- case rendered of
- Left failure ->
- pure
- (Left
- (IncompleteHtmlPublication
- { committedHtmlDestinations =
- reverse committedReversed
- , failedHtmlDestination = relative
- , htmlPublicationFailure = failure
- }))
- Right bytes -> do
- result <-
- tryIOException
- (stageAndReplace destination bytes)
- case result of
- Left err ->
- pure
- (Left
- (publicationError
- (reverse committedReversed)
- relative
- err))
- Right () ->
- publishAll
- (relative : committedReversed)
- remaining
-
-stageAndReplace
- :: FilePath
- -> ByteString
- -> IO ()
-stageAndReplace destination bytes = do
- let directory = Posix.takeDirectory destination
- Directory.createDirectoryIfMissing True directory
- writeBytesAtomically destination bytes
-
-publicationError
- :: [SafeRelativePath]
- -> SafeRelativePath
- -> IOException
- -> HtmlPublicationError
-publicationError committed failed err =
- IncompleteHtmlPublication
- { committedHtmlDestinations = committed
- , failedHtmlDestination = failed
- , htmlPublicationFailure =
- Text.pack (displayException err)
- }
-
-tryIOException :: IO a -> IO (Either IOException a)
-tryIOException =
- Exception.try
-
-
-preflightDestination
- :: FilePath
- -> FilePath
- -> [FilePath]
- -> FilePath
- -> ExceptT HtmlOutputError IO ()
-preflightDestination canonicalRoot outputRoot components destination = do
- traverse_
- (preflightParent canonicalRoot)
- (destinationParents outputRoot components)
- preflightTarget destination
-
-destinationParents :: FilePath -> [FilePath] -> [FilePath]
-destinationParents root components =
- take
- (length components)
- (scanl (Posix.</>) root components)
-
-confinedDestination :: FilePath -> [FilePath] -> FilePath
-confinedDestination =
- foldl' (Posix.</>)
-
-preflightParent
- :: FilePath
- -> FilePath
- -> ExceptT HtmlOutputError IO ()
-preflightParent canonicalRoot parent = do
- parentIsLink <- inspectSymbolicLink parent
- parentExists <-
- inspectPath parent (Directory.doesPathExist parent)
- parentIsDirectory <-
- inspectPath parent (Directory.doesDirectoryExist parent)
- when (parentIsLink || parentExists) do
- unless
- parentIsDirectory
- (throwE (HtmlOutputParentNotDirectory parent))
- canonicalParent <-
- inspectPath
- parent
- (Directory.canonicalizePath parent)
- unless
- (isComponentwiseChild canonicalRoot canonicalParent)
- (throwE
- (HtmlOutputParentEscapesRoot
- parent
- canonicalParent))
-
-preflightTarget
- :: FilePath
- -> ExceptT HtmlOutputError IO ()
-preflightTarget target = do
- statusResult <-
- liftIO
- (tryIOError
- (PosixFiles.getSymbolicLinkStatus target))
- case statusResult of
- Left err
- | isDoesNotExistError err ->
- pure ()
- | otherwise ->
- throwE
- (HtmlOutputPathInspectionFailed
- target
- (Text.pack (displayException err)))
- Right status
- | PosixFiles.isSymbolicLink status ->
- throwE
- (HtmlOutputTargetIsSymbolicLink target)
- | PosixFiles.isRegularFile status ->
- pure ()
- | otherwise ->
- throwE
- (HtmlOutputTargetNotRegularFile target)
-
-isComponentwiseChild :: FilePath -> FilePath -> Bool
-isComponentwiseChild root child =
- canonicalComponents root
- `List.isPrefixOf`
- canonicalComponents child
-
-canonicalComponents :: FilePath -> [FilePath]
-canonicalComponents =
- Posix.splitDirectories
- . Posix.dropTrailingPathSeparator
-
-inspectSymbolicLink
- :: FilePath
- -> ExceptT HtmlOutputError IO Bool
-inspectSymbolicLink path = do
- result <-
- liftIO
- (tryIOError
- (Directory.pathIsSymbolicLink path))
- case result of
- Right isLink ->
- pure isLink
- Left err
- | isDoesNotExistError err ->
- pure False
- | otherwise ->
- throwE
- (HtmlOutputPathInspectionFailed
- path
- (Text.pack (displayException err)))
-
-inspectPath
- :: FilePath
- -> IO a
- -> ExceptT HtmlOutputError IO a
-inspectPath path action = do
- result <- liftIO (tryIOError action)
- case result of
- Right value ->
- pure value
- Left err ->
- throwE
- (HtmlOutputPathInspectionFailed
- path
- (Text.pack (displayException err)))