{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Felix.Render.Html ( HtmlRenderIndex , HtmlPagePresentation , buildRenderIndex , htmlPagePresentationSource , renderDocument , supportScriptAssetContents ) where import Felix.Syntax.Abstract import Base import Felix.Source (ResolvedSource) import Lucid hiding (Term, for_) import Lucid.Base (makeAttributes) import Lucid.Math import Felix.Render.Html.Context ( HtmlRenderContext , HtmlRenderContextError , htmlCurrentPageLabel , htmlCurrentSource , htmlSourceFragmentHref , htmlSourceLabel , htmlSourcePageHref , htmlSupportScriptHref ) import Felix.Render.Html.Layout (renderUrlFragment) import Felix.Report.Location (Location, pattern Nowhere) import Felix.Syntax.Token (VariableDisplay(..), VariableSuffix(..), displayVariable, tokToText) 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 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 "" 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 <> ": 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 <> ": 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