summaryrefslogtreecommitdiff
path: root/source/Felix
diff options
context:
space:
mode:
Diffstat (limited to 'source/Felix')
-rw-r--r--source/Felix/Cache/Codec.hs2
-rw-r--r--source/Felix/Checking/Authority.hs612
-rw-r--r--source/Felix/Checking/Backend/Problem.hs1301
-rw-r--r--source/Felix/Checking/Backend/Tptp.hs1281
-rw-r--r--source/Felix/Checking/Core.hs1604
-rw-r--r--source/Felix/Checking/Datatype.hs670
-rw-r--r--source/Felix/Checking/Declaration.hs6770
-rw-r--r--source/Felix/Checking/Exact.hs3936
-rw-r--r--source/Felix/Checking/Exact/Datatype.hs751
-rw-r--r--source/Felix/Checking/Exact/Global.hs116
-rw-r--r--source/Felix/Checking/Exact/Inductive.hs838
-rw-r--r--source/Felix/Checking/Exact/Proof.hs2639
-rw-r--r--source/Felix/Checking/Exact/Vocabulary.hs218
-rw-r--r--source/Felix/Checking/FinalPrelude.hs1266
-rw-r--r--source/Felix/Checking/Foundation.hs1037
-rw-r--r--source/Felix/Checking/Identity.hs1023
-rw-r--r--source/Felix/Checking/Kernel/Derivation.hs1264
-rw-r--r--source/Felix/Checking/Kernel/Proof.hs1443
-rw-r--r--source/Felix/Checking/Kernel/Semantics.hs436
-rw-r--r--source/Felix/Checking/Kernel/SetLfp.hs762
-rw-r--r--source/Felix/Checking/Materialization.hs288
-rw-r--r--source/Felix/Checking/Module.hs986
-rw-r--r--source/Felix/Checking/Semantic.hs1759
-rw-r--r--source/Felix/Checking/SetConstruction.hs1191
-rw-r--r--source/Felix/Checking/Typed/Inductive.hs4732
-rw-r--r--source/Felix/CommandLine.hs8
-rw-r--r--source/Felix/Math/Codec.hs2
-rw-r--r--source/Felix/Meaning.hs12
-rw-r--r--source/Felix/OutputPlan.hs2
-rw-r--r--source/Felix/Parse.hs16
-rw-r--r--source/Felix/Parsed/Identity.hs2
-rw-r--r--source/Felix/Parsed/Payload.hs10
-rw-r--r--source/Felix/Prelude.hs8
-rw-r--r--source/Felix/Provers.hs8
-rw-r--r--source/Felix/Render/Html.hs3964
-rw-r--r--source/Felix/Render/Html/Context.hs171
-rw-r--r--source/Felix/Render/Html/Export.hs277
-rw-r--r--source/Felix/Render/Html/Layout.hs602
-rw-r--r--source/Felix/Render/Html/Output.hs471
-rw-r--r--source/Felix/Report/Location.hs284
-rw-r--r--source/Felix/Source.hs2
-rw-r--r--source/Felix/Source/Graph.hs4
-rw-r--r--source/Felix/Store.hs12
-rw-r--r--source/Felix/Syntax/Abstract.hs863
-rw-r--r--source/Felix/Syntax/Adapt.hs928
-rw-r--r--source/Felix/Syntax/Concrete.hs1032
-rw-r--r--source/Felix/Syntax/Concrete/Keywords.hs228
-rw-r--r--source/Felix/Syntax/Interface.hs887
-rw-r--r--source/Felix/Syntax/Internal.hs830
-rw-r--r--source/Felix/Syntax/LexicalPhrase.hs95
-rw-r--r--source/Felix/Syntax/Lexicon.hs330
-rw-r--r--source/Felix/Syntax/Mixfix.hs139
-rw-r--r--source/Felix/Syntax/Pragma.hs250
-rw-r--r--source/Felix/Syntax/Token.hs633
-rw-r--r--source/Felix/Test/All.hs14
-rw-r--r--source/Felix/Test/Golden.hs86
-rw-r--r--source/Felix/Test/Unit.hs52
-rw-r--r--source/Felix/Test/Unit/Abstract.hs114
-rw-r--r--source/Felix/Test/Unit/Backend.hs752
-rw-r--r--source/Felix/Test/Unit/CommandLine.hs799
-rw-r--r--source/Felix/Test/Unit/Concrete.hs221
-rw-r--r--source/Felix/Test/Unit/Core.hs670
-rw-r--r--source/Felix/Test/Unit/Declaration.hs3596
-rw-r--r--source/Felix/Test/Unit/Foundation.hs284
-rw-r--r--source/Felix/Test/Unit/Html.hs225
-rw-r--r--source/Felix/Test/Unit/HtmlLayout.hs478
-rw-r--r--source/Felix/Test/Unit/HtmlOutput.hs560
-rw-r--r--source/Felix/Test/Unit/Identity.hs802
-rw-r--r--source/Felix/Test/Unit/Kernel.hs858
-rw-r--r--source/Felix/Test/Unit/Lexicon.hs333
-rw-r--r--source/Felix/Test/Unit/Materialization.hs357
-rw-r--r--source/Felix/Test/Unit/Meaning.hs1119
-rw-r--r--source/Felix/Test/Unit/Module.hs9759
-rw-r--r--source/Felix/Test/Unit/OutputPlan.hs236
-rw-r--r--source/Felix/Test/Unit/Provers.hs1159
-rw-r--r--source/Felix/Test/Unit/Semantic.hs437
-rw-r--r--source/Felix/Test/Unit/Source.hs2581
-rw-r--r--source/Felix/Test/Unit/Store.hs1675
-rw-r--r--source/Felix/Test/Unit/Token.hs285
-rw-r--r--source/Felix/Verification.hs18
-rw-r--r--source/Felix/Workspace.hs10
81 files changed, 76417 insertions, 58 deletions
diff --git a/source/Felix/Cache/Codec.hs b/source/Felix/Cache/Codec.hs
index b216576..34f2694 100644
--- a/source/Felix/Cache/Codec.hs
+++ b/source/Felix/Cache/Codec.hs
@@ -49,7 +49,7 @@ module Felix.Cache.Codec
) where
import Base hiding (Empty)
-import Checking.Core
+import Felix.Checking.Core
import Felix.Math.Codec
import Control.DeepSeq (NFData)
diff --git a/source/Felix/Checking/Authority.hs b/source/Felix/Checking/Authority.hs
new file mode 100644
index 0000000..58c123f
--- /dev/null
+++ b/source/Felix/Checking/Authority.hs
@@ -0,0 +1,612 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Compact public fact authority and exact contextual authorization.
+module Felix.Checking.Authority
+ ( EscapeKind(..)
+ , EscapeKinds
+ , emptyEscapeKinds
+ , singletonEscapeKind
+ , escapeKinds
+ , escapeKindsToList
+ , unionEscapeKinds
+ , AuthoritySafety
+ , cleanAuthoritySafety
+ , authoritySafety
+ , authoritySafetyEscapeKinds
+ , unionAuthoritySafety
+ , FactAuthority
+ , factAuthority
+ , factAuthorityTheorem
+ , factAuthoritySafety
+ , PreparedRequestDialect(..)
+ , PreparedRequestMode(..)
+ , PreparedRequestId
+ , preparedRequestId
+ , GuardedRuleSet
+ , guardedRuleSet
+ , guardedRuleTags
+ , KernelConstructionDescriptor(..)
+ , DatatypeCompilationDescriptor
+ , datatypeCompilationDescriptor
+ , CompilationDescriptor(..)
+ , DirectAuthorization(..)
+ , ValidationCertificate
+ , validationCertificate
+ , validationTarget
+ , validationDirectAuthorization
+ , ValidationCertificateError(..)
+ , CandidateSafety
+ , initialCandidateSafety
+ , candidateSafetyAuthority
+ , candidateFactAuthority
+ , accumulateFactSafety
+ , addCandidateEscape
+ , FactSafetyError(..)
+ , putEscapeKindsCache
+ , getEscapeKindsCache
+ , putAuthoritySafetyCache
+ , getAuthoritySafetyCache
+ , putFactAuthorityCache
+ , getFactAuthorityCache
+ , putPreparedRequestIdCache
+ , getPreparedRequestIdCache
+ , putDirectAuthorizationCache
+ , getDirectAuthorizationCache
+ , putValidationCertificateCache
+ , getValidationCertificateCache
+ ) where
+
+import Base
+import Felix.Checking.Foundation
+import Felix.Checking.Identity
+import Felix.Cache.Codec
+
+import Control.DeepSeq (NFData)
+import Control.Monad (unless, when)
+import Data.Bits ((.&.), (.|.), bit, complement)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as ByteString
+import Data.List (filter)
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Set qualified as Set
+import Data.Word (Word8)
+
+
+data EscapeKind
+ = SourceAxiom
+ | Omitted
+ deriving stock (Show, Eq, Ord, Enum, Bounded, Generic)
+ deriving anyclass (NFData)
+
+-- | Canonical bounded set. The bit positions are stable cache tags.
+newtype EscapeKinds =
+ EscapeKinds Word8
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (NFData)
+
+emptyEscapeKinds :: EscapeKinds
+emptyEscapeKinds =
+ EscapeKinds 0
+
+singletonEscapeKind :: EscapeKind -> EscapeKinds
+singletonEscapeKind =
+ EscapeKinds . escapeKindBit
+
+escapeKinds :: Foldable collection => collection EscapeKind -> EscapeKinds
+escapeKinds =
+ foldl'
+ (\acc kind ->
+ unionEscapeKinds acc (singletonEscapeKind kind))
+ emptyEscapeKinds
+
+escapeKindsToList :: EscapeKinds -> [EscapeKind]
+escapeKindsToList kinds =
+ filter
+ (\kind ->
+ let EscapeKinds bits = kinds
+ in bits .&. escapeKindBit kind /= 0)
+ [SourceAxiom, Omitted]
+
+unionEscapeKinds :: EscapeKinds -> EscapeKinds -> EscapeKinds
+unionEscapeKinds (EscapeKinds left) (EscapeKinds right) =
+ EscapeKinds (left .|. right)
+
+escapeKindBit :: EscapeKind -> Word8
+escapeKindBit = \case
+ SourceAxiom -> bit 0
+ Omitted -> bit 1
+
+allEscapeBits :: Word8
+allEscapeBits =
+ escapeKindBit SourceAxiom .|. escapeKindBit Omitted
+
+
+data AuthoritySafety
+ = Clean
+ | EscapeHatchBacked !EscapeKinds
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+cleanAuthoritySafety :: AuthoritySafety
+cleanAuthoritySafety =
+ Clean
+
+authoritySafety :: EscapeKinds -> AuthoritySafety
+authoritySafety kinds
+ | kinds == emptyEscapeKinds = Clean
+ | otherwise = EscapeHatchBacked kinds
+
+authoritySafetyEscapeKinds :: AuthoritySafety -> EscapeKinds
+authoritySafetyEscapeKinds = \case
+ Clean -> emptyEscapeKinds
+ EscapeHatchBacked kinds -> kinds
+
+unionAuthoritySafety
+ :: AuthoritySafety
+ -> AuthoritySafety
+ -> AuthoritySafety
+unionAuthoritySafety left right =
+ authoritySafety
+ (unionEscapeKinds
+ (authoritySafetyEscapeKinds left)
+ (authoritySafetyEscapeKinds right))
+
+
+data FactAuthority = FactAuthority
+ !TheoremRef
+ !AuthoritySafety
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+factAuthority :: TheoremRef -> AuthoritySafety -> FactAuthority
+factAuthority =
+ FactAuthority
+
+factAuthorityTheorem :: FactAuthority -> TheoremRef
+factAuthorityTheorem (FactAuthority reference _) =
+ reference
+
+factAuthoritySafety :: FactAuthority -> AuthoritySafety
+factAuthoritySafety (FactAuthority _ safety) =
+ safety
+
+
+data PreparedRequestDialect
+ = PreparedRequestFof
+ | PreparedRequestTh0
+ deriving stock (Show, Eq, Ord, Generic)
+
+data PreparedRequestMode
+ = PreparedRequestDirect
+ | PreparedRequestIndirect
+ deriving stock (Show, Eq, Ord, Generic)
+
+newtype PreparedRequestId =
+ PreparedRequestId CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+preparedRequestId
+ :: PreparedRequestDialect
+ -> PreparedRequestMode
+ -> ByteString
+ -> PreparedRequestId
+preparedRequestId dialect mode bytes =
+ PreparedRequestId
+ (hashCacheFields
+ "felix-prepared-request-v2"
+ [ ByteString.singleton (preparedRequestDialectTag dialect)
+ , ByteString.singleton (preparedRequestModeTag mode)
+ , bytes
+ ])
+
+preparedRequestDialectTag :: PreparedRequestDialect -> Word8
+preparedRequestDialectTag = \case
+ PreparedRequestFof -> 0x00
+ PreparedRequestTh0 -> 0x01
+
+preparedRequestModeTag :: PreparedRequestMode -> Word8
+preparedRequestModeTag = \case
+ PreparedRequestDirect -> 0x00
+ PreparedRequestIndirect -> 0x01
+
+
+-- | A nonempty canonical set of guarded kernel rules.
+newtype GuardedRuleSet =
+ GuardedRuleSet (Set KernelRuleTag)
+ deriving stock (Show, Eq, Ord, Generic)
+
+guardedRuleSet :: NonEmpty KernelRuleTag -> GuardedRuleSet
+guardedRuleSet =
+ GuardedRuleSet . Set.fromList . NonEmpty.toList
+
+guardedRuleTags :: GuardedRuleSet -> Set KernelRuleTag
+guardedRuleTags (GuardedRuleSet rules) =
+ rules
+
+data KernelConstructionDescriptor
+ = FoundationLeaf !FoundationAxiomTag
+ | GuardedFoundationRules !GuardedRuleSet
+ | CheckedDefinitionEquation !ObjectId
+ | CheckedSetConstructionExtensionality !ObjectId !CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+
+-- | Exact semantic members of one trusted datatype compilation.
+data DatatypeCompilationDescriptor =
+ DatatypeCompilationDescriptor
+ !ObjectId
+ !(NonEmpty ObjectId)
+ ![TheoremRef]
+ deriving stock (Show, Eq, Ord, Generic)
+
+datatypeCompilationDescriptor
+ :: ObjectId
+ -> NonEmpty ObjectId
+ -> [TheoremRef]
+ -> DatatypeCompilationDescriptor
+datatypeCompilationDescriptor =
+ DatatypeCompilationDescriptor
+
+data CompilationDescriptor
+ = DatatypeCompilation !DatatypeCompilationDescriptor
+ deriving stock (Show, Eq, Ord, Generic)
+
+data DirectAuthorization
+ = CheckedKernelConstruction !KernelConstructionDescriptor
+ | CheckedSourceProof ![PreparedRequestId]
+ | TrustedCompilation !CompilationDescriptor
+ | SourceAxiomAuthorization
+ | OmittedAuthorization
+ deriving stock (Show, Eq, Ord, Generic)
+
+
+data ValidationCertificate = ValidationCertificate
+ !FactAuthority
+ !DirectAuthorization
+ deriving stock (Show, Eq, Ord, Generic)
+
+data ValidationCertificateError
+ = SourceAxiomSafetyMismatch !AuthoritySafety
+ | OmittedSafetyMissing !AuthoritySafety
+ deriving stock (Show, Eq)
+
+validationCertificate
+ :: FactAuthority
+ -> DirectAuthorization
+ -> Either ValidationCertificateError ValidationCertificate
+validationCertificate target direct = do
+ case direct of
+ SourceAxiomAuthorization ->
+ unless
+ (factAuthoritySafety target
+ == authoritySafety
+ (singletonEscapeKind SourceAxiom))
+ (Left
+ (SourceAxiomSafetyMismatch
+ (factAuthoritySafety target)))
+ OmittedAuthorization ->
+ unless
+ (Omitted
+ `elem` escapeKindsToList
+ (authoritySafetyEscapeKinds
+ (factAuthoritySafety target)))
+ (Left
+ (OmittedSafetyMissing
+ (factAuthoritySafety target)))
+ CheckedKernelConstruction{} -> pure ()
+ CheckedSourceProof{} -> pure ()
+ TrustedCompilation{} -> pure ()
+ pure (ValidationCertificate target direct)
+
+validationTarget :: ValidationCertificate -> FactAuthority
+validationTarget (ValidationCertificate target _) =
+ target
+
+validationDirectAuthorization
+ :: ValidationCertificate
+ -> DirectAuthorization
+validationDirectAuthorization (ValidationCertificate _ direct) =
+ direct
+
+
+newtype CandidateSafety =
+ CandidateSafety AuthoritySafety
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (NFData)
+
+initialCandidateSafety :: CandidateSafety
+initialCandidateSafety =
+ CandidateSafety Clean
+
+candidateSafetyAuthority :: CandidateSafety -> AuthoritySafety
+candidateSafetyAuthority (CandidateSafety safety) =
+ safety
+
+-- | Freeze the final safety of one completed candidate into public authority.
+--
+-- The trusted completion boundary uses this projection for both the inert
+-- validation certificate and its runtime pending authorization.
+candidateFactAuthority
+ :: TheoremRef
+ -> CandidateSafety
+ -> FactAuthority
+candidateFactAuthority reference (CandidateSafety safety) =
+ FactAuthority reference safety
+
+data FactSafetyError
+ = FactSafetyTheoremMismatch !TheoremRef !TheoremRef
+ deriving stock (Show, Eq)
+
+-- | Check theorem agreement and accumulate already-authorized public safety.
+--
+-- This operation does not authorize a freely constructed 'FactAuthority'.
+-- Callers may use it only after checking opaque global, imported, or staged
+-- builder authorization at the boundary that owns that authority.
+accumulateFactSafety
+ :: TheoremRef
+ -> FactAuthority
+ -> CandidateSafety
+ -> Either FactSafetyError CandidateSafety
+accumulateFactSafety expected supplied (CandidateSafety current) = do
+ unless
+ (factAuthorityTheorem supplied == expected)
+ (Left
+ (FactSafetyTheoremMismatch
+ expected
+ (factAuthorityTheorem supplied)))
+ pure
+ (CandidateSafety
+ (unionAuthoritySafety
+ current
+ (factAuthoritySafety supplied)))
+
+addCandidateEscape
+ :: EscapeKind
+ -> CandidateSafety
+ -> CandidateSafety
+addCandidateEscape kind (CandidateSafety current) =
+ CandidateSafety
+ (unionAuthoritySafety
+ current
+ (authoritySafety (singletonEscapeKind kind)))
+
+
+putEscapeKindsCache :: EscapeKinds -> CachePut
+putEscapeKindsCache (EscapeKinds bits) =
+ putCacheTag bits
+
+getEscapeKindsCache :: CacheGet EscapeKinds
+getEscapeKindsCache = do
+ bits <- getCacheTag
+ unless
+ (bits .&. complement allEscapeBits == 0)
+ (fail "unknown cache escape-kind bit")
+ pure (EscapeKinds bits)
+
+putAuthoritySafetyCache :: AuthoritySafety -> CachePut
+putAuthoritySafetyCache = \case
+ Clean ->
+ putCacheTag 0x00
+ EscapeHatchBacked kinds -> do
+ putCacheTag 0x01
+ putEscapeKindsCache kinds
+
+getAuthoritySafetyCache :: CacheGet AuthoritySafety
+getAuthoritySafetyCache =
+ getCacheTag >>= \case
+ 0x00 ->
+ pure Clean
+ 0x01 -> do
+ kinds <- getEscapeKindsCache
+ when
+ (kinds == emptyEscapeKinds)
+ (fail "empty escape-hatch-backed safety")
+ pure (EscapeHatchBacked kinds)
+ tag ->
+ fail ("unknown cache authority-safety tag " <> show tag)
+
+putFactAuthorityCache :: FactAuthority -> CachePut
+putFactAuthorityCache (FactAuthority reference safety) = do
+ putTheoremRefCache reference
+ putAuthoritySafetyCache safety
+
+getFactAuthorityCache :: CacheGet FactAuthority
+getFactAuthorityCache =
+ FactAuthority
+ <$> getTheoremRefCache
+ <*> getAuthoritySafetyCache
+
+putPreparedRequestIdCache :: PreparedRequestId -> CachePut
+putPreparedRequestIdCache (PreparedRequestId digest) =
+ putCacheDigest digest
+
+getPreparedRequestIdCache :: CacheGet PreparedRequestId
+getPreparedRequestIdCache =
+ PreparedRequestId <$> getCacheDigest
+
+putDirectAuthorizationCache :: DirectAuthorization -> CachePut
+putDirectAuthorizationCache = \case
+ CheckedKernelConstruction descriptor -> do
+ putCacheTag 0x00
+ putKernelDescriptor descriptor
+ CheckedSourceProof requests -> do
+ putCacheTag 0x01
+ putCacheList putPreparedRequestIdCache requests
+ TrustedCompilation descriptor -> do
+ putCacheTag 0x02
+ putCompilationDescriptor descriptor
+ SourceAxiomAuthorization ->
+ putCacheTag 0x03
+ OmittedAuthorization ->
+ putCacheTag 0x04
+
+getDirectAuthorizationCache :: CacheGet DirectAuthorization
+getDirectAuthorizationCache =
+ getCacheTag >>= \case
+ 0x00 ->
+ CheckedKernelConstruction <$> getKernelDescriptor
+ 0x01 ->
+ CheckedSourceProof
+ <$> getCacheList getPreparedRequestIdCache
+ 0x02 ->
+ TrustedCompilation <$> getCompilationDescriptor
+ 0x03 ->
+ pure SourceAxiomAuthorization
+ 0x04 ->
+ pure OmittedAuthorization
+ tag ->
+ fail ("unknown cache direct-authorization tag " <> show tag)
+
+putValidationCertificateCache :: ValidationCertificate -> CachePut
+putValidationCertificateCache
+ (ValidationCertificate target direct) = do
+ putFactAuthorityCache target
+ putDirectAuthorizationCache direct
+
+getValidationCertificateCache :: CacheGet ValidationCertificate
+getValidationCertificateCache = do
+ target <- getFactAuthorityCache
+ direct <- getDirectAuthorizationCache
+ case validationCertificate target direct of
+ Left err ->
+ fail ("invalid cache validation certificate: " <> show err)
+ Right certificate ->
+ pure certificate
+
+
+putKernelDescriptor :: KernelConstructionDescriptor -> CachePut
+putKernelDescriptor = \case
+ FoundationLeaf tag -> do
+ putCacheTag 0x00
+ putFoundationTag tag
+ GuardedFoundationRules rules -> do
+ putCacheTag 0x01
+ putCacheList putRuleTag
+ (Set.toAscList (guardedRuleTags rules))
+ CheckedDefinitionEquation identity -> do
+ putCacheTag 0x02
+ putObjectIdCache identity
+ CheckedSetConstructionExtensionality identity construction -> do
+ putCacheTag 0x03
+ putObjectIdCache identity
+ putCacheDigest construction
+
+getKernelDescriptor :: CacheGet KernelConstructionDescriptor
+getKernelDescriptor =
+ getCacheTag >>= \case
+ 0x00 -> FoundationLeaf <$> getFoundationTag
+ 0x01 -> do
+ tags <- getCacheList getRuleTag
+ rules <-
+ maybe
+ (fail "cache guarded-rule set is empty")
+ pure
+ (NonEmpty.nonEmpty tags)
+ unless (strictlyIncreasing tags)
+ (fail "cache guarded-rule tags are not in canonical order")
+ pure (GuardedFoundationRules (guardedRuleSet rules))
+ 0x02 -> CheckedDefinitionEquation <$> getObjectIdCache
+ 0x03 ->
+ CheckedSetConstructionExtensionality
+ <$> getObjectIdCache
+ <*> getCacheDigest
+ tag ->
+ fail ("unknown cache kernel-construction tag " <> show tag)
+
+putCompilationDescriptor :: CompilationDescriptor -> CachePut
+putCompilationDescriptor
+ (DatatypeCompilation
+ (DatatypeCompilationDescriptor
+ carrier constructors facts)) = do
+ putCacheTag 0x00
+ putObjectIdCache carrier
+ putCacheList putObjectIdCache (NonEmpty.toList constructors)
+ putCacheList putTheoremRefCache facts
+
+getCompilationDescriptor :: CacheGet CompilationDescriptor
+getCompilationDescriptor =
+ getCacheTag >>= \case
+ 0x00 -> do
+ carrier <- getObjectIdCache
+ rawConstructors <-
+ getCacheList getObjectIdCache
+ constructors <-
+ maybe
+ (fail "cache datatype compilation has no constructors")
+ pure
+ (NonEmpty.nonEmpty rawConstructors)
+ facts <- getCacheList getTheoremRefCache
+ pure
+ (DatatypeCompilation
+ (DatatypeCompilationDescriptor
+ carrier constructors facts))
+ tag ->
+ fail ("unknown cache compilation tag " <> show tag)
+
+putFoundationTag :: FoundationAxiomTag -> CachePut
+putFoundationTag =
+ putCacheTag . \case
+ EmptyCharacteristic -> 0x00
+ PairSetCharacteristic -> 0x01
+ FamilyUnionCharacteristic -> 0x02
+ PowerSetCharacteristic -> 0x03
+ SeparationCharacteristic -> 0x04
+ ReplacementCharacteristic -> 0x05
+ SetChooseWitness -> 0x06
+ SetExtensionality -> 0x07
+ SetInduction -> 0x08
+ PropositionalExtensionality -> 0x09
+ DoubleNegationElim -> 0x0a
+ UnivOfContains -> 0x0b
+ UnivOfTransitive -> 0x0c
+ UnivOfFamilyUnionClosed -> 0x0d
+ UnivOfPowerSetClosed -> 0x0e
+ UnivOfReplacementClosed -> 0x0f
+ UnivOfMinimal -> 0x10
+
+getFoundationTag :: CacheGet FoundationAxiomTag
+getFoundationTag =
+ getCacheTag >>= \case
+ 0x00 -> pure EmptyCharacteristic
+ 0x01 -> pure PairSetCharacteristic
+ 0x02 -> pure FamilyUnionCharacteristic
+ 0x03 -> pure PowerSetCharacteristic
+ 0x04 -> pure SeparationCharacteristic
+ 0x05 -> pure ReplacementCharacteristic
+ 0x06 -> pure SetChooseWitness
+ 0x07 -> pure SetExtensionality
+ 0x08 -> pure SetInduction
+ 0x09 -> pure PropositionalExtensionality
+ 0x0a -> pure DoubleNegationElim
+ 0x0b -> pure UnivOfContains
+ 0x0c -> pure UnivOfTransitive
+ 0x0d -> pure UnivOfFamilyUnionClosed
+ 0x0e -> pure UnivOfPowerSetClosed
+ 0x0f -> pure UnivOfReplacementClosed
+ 0x10 -> pure UnivOfMinimal
+ tag ->
+ fail ("unknown cache foundation-axiom tag " <> show tag)
+
+putRuleTag :: KernelRuleTag -> CachePut
+putRuleTag =
+ putCacheTag . \case
+ SetLfpBound -> 0x00
+ SetLfpLeast -> 0x01
+ SetLfpFixed -> 0x02
+ SetLfpInduct -> 0x03
+
+getRuleTag :: CacheGet KernelRuleTag
+getRuleTag =
+ getCacheTag >>= \case
+ 0x00 -> pure SetLfpBound
+ 0x01 -> pure SetLfpLeast
+ 0x02 -> pure SetLfpFixed
+ 0x03 -> pure SetLfpInduct
+ tag ->
+ fail ("unknown cache kernel-rule tag " <> show tag)
+
+strictlyIncreasing :: Ord value => [value] -> Bool
+strictlyIncreasing values =
+ and (zipWith (<) values (drop 1 values))
diff --git a/source/Felix/Checking/Backend/Problem.hs b/source/Felix/Checking/Backend/Problem.hs
new file mode 100644
index 0000000..0390020
--- /dev/null
+++ b/source/Felix/Checking/Backend/Problem.hs
@@ -0,0 +1,1301 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Complete-problem FOF/TH0 classification and construction.
+module Felix.Checking.Backend.Problem
+ ( SupportedProposition
+ , supportedProposition
+ , projectSupportedProposition
+ , supportedPropositionSupport
+ , supportedPropositionTerm
+ , weakenClosedSupportedProposition
+ , SupportedPropositionError(..)
+ , SupportedPropositionProjectionError(..)
+ , CheckedFofProjection
+ , checkedFofProjectionProposition
+ , FofCapability(..)
+ , BackendFofExclusion(..)
+ , BackendClassificationError(..)
+ , classifySupportedProposition
+ , TypedBackendFact
+ , typedBackendFact
+ , typedBackendFactReference
+ , typedBackendFactProposition
+ , typedBackendFactCapability
+ , LocalPremiseOrdinal
+ , localPremiseOrdinal
+ , localPremiseOrdinalValue
+ , TypedLocalPremise
+ , typedLocalPremise
+ , typedLocalPremiseOrdinal
+ , typedLocalPremiseOrigin
+ , typedLocalPremiseProposition
+ , typedLocalPremiseCapability
+ , TypedFoundationAuxiliaryInput
+ , typedFoundationAuxiliaryInput
+ , TypedProblemAuxiliary
+ , typedProblemAuxiliaryOrdinal
+ , typedProblemAuxiliaryTag
+ , typedProblemAuxiliaryProposition
+ , typedProblemAuxiliaryCapability
+ , LocalPremisePolicy(..)
+ , HigherOrderJustificationPolicy(..)
+ , selectTypedLocalPremises
+ , TypedProblemRoute(..)
+ , TypedProblem
+ , planTypedProblem
+ , typedProblemRoute
+ , typedProblemClaim
+ , typedProblemGlobalPremises
+ , typedProblemLocalPremises
+ , typedProblemAuxiliaries
+ , typedProblemGlobalTypes
+ , typedProblemLocalTypes
+ , TypedProblemError(..)
+ ) where
+
+import Base
+import Felix.Checking.Core
+import Felix.Checking.Foundation
+
+import Control.Monad (foldM, unless)
+import Data.Bifunctor (first)
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Vector (Vector)
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+
+
+-- | A proposition under its exact nearest-first ambient-local support.
+data SupportedProposition local global =
+ SupportedProposition
+ !(Vector (local, CoreType))
+ !(ScopedCheckedCore global)
+ deriving stock (Eq)
+
+data SupportedPropositionError local
+ = SupportedPropositionIsNotProposition !CoreType
+ | SupportedPropositionContextMismatch
+ ![CoreType]
+ ![CoreType]
+ | DuplicateSupportedLocal !local
+ | UnusedSupportedLocal !local
+ deriving stock (Show, Eq)
+
+data SupportedPropositionProjectionError local
+ = SupportedProjectionContextMismatch
+ ![CoreType]
+ ![CoreType]
+ | SupportedProjectionDuplicateLocal !local
+ | SupportedProjectionIndexMissing !Natural
+ | SupportedProjectionCoreCheckFailed !CoreCheckError
+ | SupportedProjectionValidationFailed
+ !(SupportedPropositionError local)
+ deriving stock (Show, Eq)
+
+supportedProposition
+ :: Ord local
+ => Vector (local, CoreType)
+ -> ScopedCheckedCore global
+ -> Either
+ (SupportedPropositionError local)
+ (SupportedProposition local global)
+supportedProposition support statement = do
+ unless
+ (scopedCoreType statement == TyProp)
+ (Left
+ (SupportedPropositionIsNotProposition
+ (scopedCoreType statement)))
+ let expectedContext =
+ snd <$> Vector.toList support
+ actualContext =
+ scopedCoreContext statement
+ unless
+ (actualContext == expectedContext)
+ (Left
+ (SupportedPropositionContextMismatch
+ expectedContext
+ actualContext))
+ void
+ (foldM
+ (\seen (local, _coreType) ->
+ if local `Set.member` seen
+ then
+ Left
+ (DuplicateSupportedLocal local)
+ else
+ Right (Set.insert local seen))
+ Set.empty
+ support)
+ case
+ List.find
+ (\(ordinal, _entry) ->
+ fromIntegral ordinal
+ `Set.notMember`
+ ambientIndices
+ (scopedCoreTerm statement))
+ (Vector.toList
+ (Vector.indexed support)) of
+ Just (_ordinal, (local, _coreType)) ->
+ Left (UnusedSupportedLocal local)
+ Nothing ->
+ pure ()
+ pure
+ (SupportedProposition
+ support
+ statement)
+
+-- | Retain exactly the ambient locals used by a checked proposition and
+-- remap its indices to that dense nearest-first support.
+projectSupportedProposition
+ :: Ord local
+ => (global -> Maybe CoreType)
+ -> Vector (local, CoreType)
+ -> ScopedCheckedCore global
+ -> Either
+ (SupportedPropositionProjectionError local)
+ (SupportedProposition local global)
+projectSupportedProposition globalType available statement = do
+ let expectedContext = snd <$> Vector.toList available
+ actualContext = scopedCoreContext statement
+ unless (expectedContext == actualContext)
+ (Left
+ (SupportedProjectionContextMismatch
+ expectedContext
+ actualContext))
+ void
+ (foldM
+ (\seen (local, _coreType) ->
+ if local `Set.member` seen
+ then Left (SupportedProjectionDuplicateLocal local)
+ else Right (Set.insert local seen))
+ Set.empty
+ available)
+ let used =
+ Set.toAscList
+ (ambientIndices
+ (scopedCoreTerm statement))
+ selected <- traverse (lookupNatural available) used
+ let remapping =
+ Map.fromAscList
+ (zip used [0 ..])
+ remapped <- remapAmbientIndices remapping 0
+ (scopedCoreTerm statement)
+ checked <-
+ first SupportedProjectionCoreCheckFailed
+ (checkScopedCanonicalCore
+ globalType
+ (snd <$> selected)
+ remapped)
+ first SupportedProjectionValidationFailed
+ (supportedProposition
+ (Vector.fromList selected)
+ checked)
+ where
+ lookupNatural values index =
+ maybe
+ (Left (SupportedProjectionIndexMissing index))
+ Right
+ (go index (Vector.toList values))
+
+ go _index [] =
+ Nothing
+ go 0 (value : _rest) =
+ Just value
+ go index (_value : rest) =
+ go (index - 1) rest
+
+ remapAmbientIndices remapping depth = \case
+ CBound index
+ | index < depth ->
+ Right (CBound index)
+ | otherwise ->
+ maybe
+ (Left
+ (SupportedProjectionIndexMissing
+ (index - depth)))
+ (Right . CBound . (+ depth))
+ (Map.lookup (index - depth) remapping)
+ CGlobal global ->
+ Right (CGlobal global)
+ CIntrinsic intrinsic ->
+ Right (CIntrinsic intrinsic)
+ COpaqueInteger integer ->
+ Right (COpaqueInteger integer)
+ CApp function argument ->
+ CApp
+ <$> remapAmbientIndices remapping depth function
+ <*> remapAmbientIndices remapping depth argument
+ CLam binderType body ->
+ CLam binderType
+ <$> remapAmbientIndices remapping (depth + 1) body
+ CFalsum ->
+ Right CFalsum
+ CImp premise conclusion ->
+ CImp
+ <$> remapAmbientIndices remapping depth premise
+ <*> remapAmbientIndices remapping depth conclusion
+ CEq operandType left right ->
+ CEq operandType
+ <$> remapAmbientIndices remapping depth left
+ <*> remapAmbientIndices remapping depth right
+ CForall binderType body ->
+ CForall binderType
+ <$> remapAmbientIndices remapping (depth + 1) body
+
+ambientIndices
+ :: CanonicalTerm global
+ -> Set Natural
+ambientIndices =
+ go 0
+ where
+ go depth = \case
+ CBound index
+ | index < depth ->
+ mempty
+ | otherwise ->
+ Set.singleton (index - depth)
+ CGlobal{} ->
+ mempty
+ CIntrinsic{} ->
+ mempty
+ COpaqueInteger{} ->
+ mempty
+ CApp function argument ->
+ go depth function <> go depth argument
+ CLam _binderType body ->
+ go (depth + 1) body
+ CFalsum ->
+ mempty
+ CImp premise conclusion ->
+ go depth premise <> go depth conclusion
+ CEq _operandType left right ->
+ go depth left <> go depth right
+ CForall _binderType body ->
+ go (depth + 1) body
+
+supportedPropositionSupport
+ :: SupportedProposition local global
+ -> Vector (local, CoreType)
+supportedPropositionSupport
+ (SupportedProposition support _statement) =
+ support
+
+supportedPropositionTerm
+ :: SupportedProposition local global
+ -> CanonicalTerm global
+supportedPropositionTerm
+ (SupportedProposition _support statement) =
+ scopedCoreTerm statement
+
+weakenClosedSupportedProposition
+ :: SupportedProposition Void global
+ -> SupportedProposition local global
+weakenClosedSupportedProposition
+ (SupportedProposition support statement) =
+ SupportedProposition
+ (fmap
+ (\(local, coreType) ->
+ (absurd local, coreType))
+ support)
+ statement
+
+
+newtype CheckedFofProjection local global =
+ CheckedFofProjection
+ (SupportedProposition local global)
+ deriving stock (Eq)
+
+checkedFofProjectionProposition
+ :: CheckedFofProjection local global
+ -> SupportedProposition local global
+checkedFofProjectionProposition
+ (CheckedFofProjection proposition) =
+ proposition
+
+data FofCapability projection
+ = FofProjectable !projection
+ | RequiresTh0 !(NonEmpty BackendFofExclusion)
+ deriving stock (Eq)
+
+data BackendFofExclusion
+ = StructuralFofExclusion !FofExclusion
+ | HigherOrderGlobalType !CoreType
+ | HigherOrderAmbientLocal !CoreType
+ deriving stock (Show, Eq, Ord)
+
+data BackendClassificationError global
+ = UnknownBackendGlobal !global
+ | BackendFofProjectionInvariantFailed
+ deriving stock (Show, Eq)
+
+classifySupportedProposition
+ :: Ord global
+ => (global -> Maybe CoreType)
+ -> SupportedProposition local global
+ -> Either
+ (BackendClassificationError global)
+ (FofCapability
+ (CheckedFofProjection local global))
+classifySupportedProposition globalType proposition = do
+ globalExclusions <-
+ foldM
+ collectGlobal
+ Set.empty
+ (Set.toAscList
+ (canonicalGlobals
+ (supportedPropositionTerm proposition)))
+ let structuralExclusions =
+ case classifyScopedStructure proposition of
+ FoundationFofProjectable ->
+ Set.empty
+ FoundationRequiresTh0 structural ->
+ Set.fromList
+ (StructuralFofExclusion
+ <$> toList structural)
+ localExclusions =
+ Set.fromList
+ [ HigherOrderAmbientLocal coreType
+ | (_local, coreType) <-
+ Vector.toList
+ (supportedPropositionSupport
+ proposition)
+ , coreType /= TySet
+ ]
+ exclusions =
+ structuralExclusions
+ <> globalExclusions
+ <> localExclusions
+ case Set.toAscList exclusions of
+ [] ->
+ if isFirstOrderProposition
+ globalType
+ proposition
+ then
+ Right
+ (FofProjectable
+ (CheckedFofProjection
+ proposition))
+ else
+ Left BackendFofProjectionInvariantFailed
+ firstExclusion : remainingExclusions ->
+ Right
+ (RequiresTh0
+ (firstExclusion
+ :| remainingExclusions))
+ where
+ collectGlobal exclusions global =
+ case globalType global of
+ Nothing ->
+ Left (UnknownBackendGlobal global)
+ Just coreType ->
+ Right
+ (if isFirstOrderGlobalType coreType
+ then exclusions
+ else
+ Set.insert
+ (HigherOrderGlobalType
+ coreType)
+ exclusions)
+
+classifyScopedStructure
+ :: SupportedProposition local global
+ -> FoundationBackendClass
+classifyScopedStructure =
+ classifyCanonicalFofStructure
+ . supportedPropositionTerm
+
+isFirstOrderGlobalType :: CoreType -> Bool
+isFirstOrderGlobalType =
+ go
+ where
+ go = \case
+ TySet ->
+ True
+ TyProp ->
+ True
+ TyArrow TySet result ->
+ go result
+ TyArrow _argument _result ->
+ False
+
+-- The structural and type exclusions make this projection total. This final
+-- walk catches an accidentally unsaturated first-order head.
+isFirstOrderProposition
+ :: (global -> Maybe CoreType)
+ -> SupportedProposition local global
+ -> Bool
+isFirstOrderProposition globalType proposition =
+ isFormula initialContext
+ (supportedPropositionTerm proposition)
+ where
+ initialContext =
+ snd
+ <$> Vector.toList
+ (supportedPropositionSupport
+ proposition)
+
+ isFormula context = \case
+ CFalsum ->
+ True
+ CImp premise conclusion ->
+ isFormula context premise
+ && isFormula context conclusion
+ CEq TySet left right ->
+ isTerm context left
+ && isTerm context right
+ CEq TyProp left right ->
+ isFormula context left
+ && isFormula context right
+ CForall TySet body ->
+ isFormula (TySet : context) body
+ application ->
+ case applicationHead application of
+ (CGlobal global, arguments) ->
+ maybe
+ False
+ (\coreType ->
+ applicationResult
+ coreType
+ arguments
+ == Just TyProp
+ && all
+ (isTerm context)
+ arguments)
+ (globalType global)
+ (CIntrinsic intrinsic, arguments) ->
+ applicationResult
+ (coreIntrinsicType intrinsic)
+ arguments
+ == Just TyProp
+ && all
+ (isTerm context)
+ arguments
+ _ ->
+ False
+
+ isTerm context = \case
+ CBound index ->
+ contextAt index context
+ == Just TySet
+ CGlobal global ->
+ globalType global == Just TySet
+ CIntrinsic intrinsic ->
+ coreIntrinsicType intrinsic == TySet
+ COpaqueInteger{} ->
+ True
+ application ->
+ case applicationHead application of
+ (CGlobal global, arguments) ->
+ maybe
+ False
+ (\coreType ->
+ applicationResult
+ coreType
+ arguments
+ == Just TySet
+ && all
+ (isTerm context)
+ arguments)
+ (globalType global)
+ (CIntrinsic intrinsic, arguments) ->
+ applicationResult
+ (coreIntrinsicType intrinsic)
+ arguments
+ == Just TySet
+ && all
+ (isTerm context)
+ arguments
+ _ ->
+ False
+
+applicationHead
+ :: CanonicalTerm global
+ -> (CanonicalTerm global, [CanonicalTerm global])
+applicationHead =
+ go []
+ where
+ go arguments = \case
+ CApp function argument ->
+ go (argument : arguments) function
+ headTerm ->
+ (headTerm, arguments)
+
+applicationResult
+ :: CoreType
+ -> [CanonicalTerm global]
+ -> Maybe CoreType
+applicationResult =
+ foldM
+ (\coreType _argument ->
+ case coreType of
+ TyArrow TySet result ->
+ Just result
+ _ ->
+ Nothing)
+
+contextAt :: Natural -> [value] -> Maybe value
+contextAt _index [] =
+ Nothing
+contextAt 0 (value : _remaining) =
+ Just value
+contextAt index (_value : remaining) =
+ contextAt (index - 1) remaining
+
+canonicalGlobals
+ :: Ord global
+ => CanonicalTerm global
+ -> Set global
+canonicalGlobals = \case
+ CBound{} ->
+ mempty
+ CGlobal global ->
+ Set.singleton global
+ CIntrinsic{} ->
+ mempty
+ COpaqueInteger{} ->
+ mempty
+ CApp function argument ->
+ canonicalGlobals function
+ <> canonicalGlobals argument
+ CLam _binderType body ->
+ canonicalGlobals body
+ CFalsum ->
+ mempty
+ CImp premise conclusion ->
+ canonicalGlobals premise
+ <> canonicalGlobals conclusion
+ CEq _operandType left right ->
+ canonicalGlobals left
+ <> canonicalGlobals right
+ CForall _binderType body ->
+ canonicalGlobals body
+
+
+data TypedBackendFact ref global =
+ TypedBackendFact
+ !ref
+ !(SupportedProposition Void global)
+ !(FofCapability
+ (CheckedFofProjection Void global))
+ deriving stock (Eq)
+
+typedBackendFact
+ :: ref
+ -> SupportedProposition Void global
+ -> FofCapability
+ (CheckedFofProjection Void global)
+ -> TypedBackendFact ref global
+typedBackendFact =
+ TypedBackendFact
+
+typedBackendFactReference
+ :: TypedBackendFact ref global
+ -> ref
+typedBackendFactReference
+ (TypedBackendFact
+ reference
+ _proposition
+ _capability) =
+ reference
+
+typedBackendFactProposition
+ :: TypedBackendFact ref global
+ -> SupportedProposition Void global
+typedBackendFactProposition
+ (TypedBackendFact
+ _reference
+ proposition
+ _capability) =
+ proposition
+
+typedBackendFactCapability
+ :: TypedBackendFact ref global
+ -> FofCapability
+ (CheckedFofProjection Void global)
+typedBackendFactCapability
+ (TypedBackendFact
+ _reference
+ _proposition
+ capability) =
+ capability
+
+
+newtype LocalPremiseOrdinal =
+ LocalPremiseOrdinal Natural
+ deriving stock (Show, Eq, Ord)
+
+localPremiseOrdinal :: Natural -> LocalPremiseOrdinal
+localPremiseOrdinal =
+ LocalPremiseOrdinal
+
+localPremiseOrdinalValue
+ :: LocalPremiseOrdinal
+ -> Natural
+localPremiseOrdinalValue
+ (LocalPremiseOrdinal ordinal) =
+ ordinal
+
+data TypedLocalPremise local origin global =
+ TypedLocalPremise
+ !LocalPremiseOrdinal
+ !origin
+ !(SupportedProposition local global)
+ !(FofCapability
+ (CheckedFofProjection local global))
+ deriving stock (Eq)
+
+typedLocalPremise
+ :: Ord global
+ => (global -> Maybe CoreType)
+ -> LocalPremiseOrdinal
+ -> origin
+ -> SupportedProposition local global
+ -> Either
+ (BackendClassificationError global)
+ (TypedLocalPremise local origin global)
+typedLocalPremise globalType ordinal premiseOrigin proposition =
+ TypedLocalPremise
+ ordinal
+ premiseOrigin
+ proposition
+ <$> classifySupportedProposition
+ globalType
+ proposition
+
+typedLocalPremiseOrdinal
+ :: TypedLocalPremise local origin global
+ -> LocalPremiseOrdinal
+typedLocalPremiseOrdinal
+ (TypedLocalPremise
+ ordinal
+ _origin
+ _proposition
+ _capability) =
+ ordinal
+
+typedLocalPremiseOrigin
+ :: TypedLocalPremise local origin global
+ -> origin
+typedLocalPremiseOrigin
+ (TypedLocalPremise
+ _ordinal
+ premiseOrigin
+ _proposition
+ _capability) =
+ premiseOrigin
+
+typedLocalPremiseProposition
+ :: TypedLocalPremise local origin global
+ -> SupportedProposition local global
+typedLocalPremiseProposition
+ (TypedLocalPremise
+ _ordinal
+ _origin
+ proposition
+ _capability) =
+ proposition
+
+typedLocalPremiseCapability
+ :: TypedLocalPremise local origin global
+ -> FofCapability
+ (CheckedFofProjection local global)
+typedLocalPremiseCapability
+ (TypedLocalPremise
+ _ordinal
+ _origin
+ _proposition
+ capability) =
+ capability
+
+
+data TypedFoundationAuxiliaryInput global =
+ TypedFoundationAuxiliaryInput
+ !FoundationAxiomTag
+ !(SupportedProposition Void global)
+ !(FofCapability
+ (CheckedFofProjection Void global))
+
+typedFoundationAuxiliaryInput
+ :: CheckedFoundation
+ -> FoundationAxiomTag
+ -> TypedFoundationAuxiliaryInput global
+typedFoundationAuxiliaryInput foundation tag =
+ TypedFoundationAuxiliaryInput
+ tag
+ proposition
+ capability
+ where
+ proposition =
+ SupportedProposition
+ Vector.empty
+ (embedClosedCore
+ []
+ (mapFrozenGlobals
+ absurd
+ (foundationAxiomFrozen
+ foundation
+ tag)))
+ capability =
+ case foundationAxiomBackendClass
+ foundation
+ tag of
+ FoundationFofProjectable ->
+ FofProjectable
+ (CheckedFofProjection
+ proposition)
+ FoundationRequiresTh0 exclusions ->
+ RequiresTh0
+ (StructuralFofExclusion
+ <$> exclusions)
+
+data TypedProblemAuxiliary global =
+ TypedProblemAuxiliary
+ !Natural
+ !FoundationAxiomTag
+ !(SupportedProposition Void global)
+ !(FofCapability
+ (CheckedFofProjection Void global))
+ deriving stock (Eq)
+
+typedProblemAuxiliaryOrdinal
+ :: TypedProblemAuxiliary global
+ -> Natural
+typedProblemAuxiliaryOrdinal
+ (TypedProblemAuxiliary
+ ordinal
+ _tag
+ _proposition
+ _capability) =
+ ordinal
+
+typedProblemAuxiliaryTag
+ :: TypedProblemAuxiliary global
+ -> FoundationAxiomTag
+typedProblemAuxiliaryTag
+ (TypedProblemAuxiliary
+ _ordinal
+ tag
+ _proposition
+ _capability) =
+ tag
+
+typedProblemAuxiliaryProposition
+ :: TypedProblemAuxiliary global
+ -> SupportedProposition Void global
+typedProblemAuxiliaryProposition
+ (TypedProblemAuxiliary
+ _ordinal
+ _tag
+ proposition
+ _capability) =
+ proposition
+
+typedProblemAuxiliaryCapability
+ :: TypedProblemAuxiliary global
+ -> FofCapability
+ (CheckedFofProjection Void global)
+typedProblemAuxiliaryCapability
+ (TypedProblemAuxiliary
+ _ordinal
+ _tag
+ _proposition
+ capability) =
+ capability
+
+
+-- | Source justification policy for premise selection. Higher-order routing
+-- is validated separately after the complete selected problem is known.
+data LocalPremisePolicy
+ = FirstOrderLocals
+ | CompleteLocals
+ deriving stock (Show, Eq)
+
+-- | Whether selected higher-order components must be justified by one of the
+-- two approved inline construction forms. Premise selection has already
+-- happened when this policy is applied.
+data HigherOrderJustificationPolicy
+ = ImplicitConstructionJustification
+ | ExplicitHigherOrderJustification
+ deriving stock (Show, Eq)
+
+selectTypedLocalPremises
+ :: LocalPremisePolicy
+ -> [TypedLocalPremise local origin global]
+ -> Vector (TypedLocalPremise local origin global)
+selectTypedLocalPremises selection availableLocals =
+ Vector.fromList
+ (List.sortOn
+ typedLocalPremiseOrdinal
+ (case selection of
+ FirstOrderLocals ->
+ List.filter
+ (isFofCapability
+ . typedLocalPremiseCapability)
+ availableLocals
+ CompleteLocals ->
+ availableLocals))
+
+data ImplicitHigherOrderConstruction
+ = ImplicitSeparation
+ | ImplicitFunctionalReplacement
+ deriving stock (Show, Eq, Ord)
+
+data TypedProblemRoute
+ = RouteFof
+ | RouteTh0
+ deriving stock (Show, Eq)
+
+data TypedProblem ref local origin global =
+ TypedProblem
+ !TypedProblemRoute
+ !(SupportedProposition local global)
+ !(Vector (TypedBackendFact ref global))
+ !(Vector (TypedLocalPremise local origin global))
+ !(Vector (TypedProblemAuxiliary global))
+ !(Map global CoreType)
+ !(Map local CoreType)
+ deriving stock (Eq)
+
+data TypedProblemError local global
+ = TypedProblemClaimClassificationFailed
+ !(BackendClassificationError global)
+ | TypedProblemExplicitHigherOrderJustificationRequired
+ !(NonEmpty BackendFofExclusion)
+ | TypedProblemDuplicateLocalPremiseOrdinal
+ !LocalPremiseOrdinal
+ | TypedProblemLocalTypeMismatch
+ !local
+ !CoreType
+ !CoreType
+ deriving stock (Show, Eq)
+
+planTypedProblem
+ :: (Ord local, Ord global)
+ => (global -> Maybe CoreType)
+ -> Vector (TypedBackendFact ref global)
+ -> SupportedProposition local global
+ -> [TypedLocalPremise local origin global]
+ -> [TypedFoundationAuxiliaryInput global]
+ -> LocalPremisePolicy
+ -> HigherOrderJustificationPolicy
+ -> Either
+ (TypedProblemError local global)
+ (TypedProblem ref local origin global)
+planTypedProblem
+ globalType
+ selectedFacts
+ claim
+ availableLocals
+ auxiliaries
+ localPolicy
+ higherOrderPolicy = do
+ validateLocalPremiseOrdinals
+ availableLocals
+ claimCapability <-
+ first
+ TypedProblemClaimClassificationFailed
+ (classifySupportedProposition
+ globalType
+ claim)
+ let selectedLocals =
+ selectTypedLocalPremises
+ localPolicy
+ availableLocals
+ let preparedAuxiliaries =
+ zipWith
+ prepareAuxiliary
+ [0..]
+ auxiliaries
+ case higherOrderPolicy of
+ ImplicitConstructionJustification ->
+ validateImplicitHigherOrderAdmission
+ claim
+ claimCapability
+ selectedFacts
+ selectedLocals
+ preparedAuxiliaries
+ ExplicitHigherOrderJustification ->
+ pure ()
+ let selectedFofCapabilities =
+ isFofCapability claimCapability
+ : (isFofCapability
+ . typedBackendFactCapability
+ <$> Vector.toList selectedFacts)
+ <> (isFofCapability
+ . typedLocalPremiseCapability
+ <$> Vector.toList selectedLocals)
+ <> (isFofCapability
+ . typedProblemAuxiliaryCapability
+ <$> preparedAuxiliaries)
+ route =
+ if and selectedFofCapabilities
+ then RouteFof
+ else RouteTh0
+ globalTypes <-
+ collectProblemGlobals
+ globalType
+ claim
+ selectedFacts
+ selectedLocals
+ preparedAuxiliaries
+ localTypes <-
+ collectProblemLocals
+ claim
+ selectedLocals
+ pure
+ (TypedProblem
+ route
+ claim
+ selectedFacts
+ selectedLocals
+ (Vector.fromList preparedAuxiliaries)
+ globalTypes
+ localTypes)
+ where
+ prepareAuxiliary
+ ordinal
+ (TypedFoundationAuxiliaryInput
+ tag
+ proposition
+ capability) =
+ TypedProblemAuxiliary
+ ordinal
+ tag
+ proposition
+ capability
+
+-- | Implicit automation admits higher-order routing only for a checked
+-- proposition that itself contains one of the two approved set constructions.
+-- This classification selects no premise and grants no authority.
+implicitConstructionAdmission
+ :: SupportedProposition local global
+ -> FofCapability projection
+ -> Maybe (Set ImplicitHigherOrderConstruction)
+implicitConstructionAdmission proposition capability =
+ case capability of
+ FofProjectable{} ->
+ Nothing
+ RequiresTh0 exclusions
+ | Set.null constructions ->
+ Nothing
+ | all (admittedExclusion constructions) exclusions ->
+ Just constructions
+ | otherwise ->
+ Nothing
+ where
+ dependencies =
+ foundationAxiomDependencies
+ (supportedPropositionTerm proposition)
+ constructions =
+ Set.fromList
+ ( [ ImplicitSeparation
+ | SeparationCharacteristic `Set.member` dependencies
+ ]
+ <> [ ImplicitFunctionalReplacement
+ | ReplacementCharacteristic `Set.member` dependencies
+ ]
+ )
+
+ admittedExclusion allowed = \case
+ StructuralFofExclusion HigherOrderLambda ->
+ True
+ StructuralFofExclusion (HigherOrderIntrinsic Sep) ->
+ ImplicitSeparation `Set.member` allowed
+ StructuralFofExclusion (HigherOrderIntrinsic Repl) ->
+ ImplicitFunctionalReplacement `Set.member` allowed
+ -- The checked proposition is the deliberate granularity: its typed
+ -- global occurrences neither select another fact nor grant authority.
+ HigherOrderGlobalType{} ->
+ True
+ StructuralFofExclusion{} ->
+ False
+ HigherOrderAmbientLocal{} ->
+ False
+
+validateImplicitHigherOrderAdmission
+ :: SupportedProposition local global
+ -> FofCapability claimProjection
+ -> Vector (TypedBackendFact ref global)
+ -> Vector (TypedLocalPremise local origin global)
+ -> [TypedProblemAuxiliary global]
+ -> Either (TypedProblemError local global) ()
+validateImplicitHigherOrderAdmission
+ claim claimCapability selectedFacts selectedLocals auxiliaries = do
+ claimConstructions <-
+ admittedPropositionConstructions claim claimCapability
+ traverse_ requireFirstOrderGlobal selectedFacts
+ localConstructions <-
+ foldM
+ (\admitted premise ->
+ (admitted <>)
+ <$> admittedPropositionConstructions
+ (typedLocalPremiseProposition premise)
+ (typedLocalPremiseCapability premise))
+ Set.empty
+ (Vector.toList selectedLocals)
+ let admitted = claimConstructions <> localConstructions
+ traverse_ (requireAdmittedAuxiliary admitted) auxiliaries
+ where
+ admittedPropositionConstructions proposition = \case
+ FofProjectable{} ->
+ Right Set.empty
+ RequiresTh0 exclusions ->
+ maybe
+ (Left
+ (TypedProblemExplicitHigherOrderJustificationRequired
+ exclusions))
+ Right
+ (implicitConstructionAdmission
+ proposition
+ (RequiresTh0 exclusions))
+
+ requireFirstOrderGlobal fact =
+ case typedBackendFactCapability fact of
+ FofProjectable{} ->
+ Right ()
+ RequiresTh0 exclusions ->
+ Left
+ (TypedProblemExplicitHigherOrderJustificationRequired
+ exclusions)
+
+ requireAdmittedAuxiliary admitted auxiliary =
+ case typedProblemAuxiliaryCapability auxiliary of
+ FofProjectable{} ->
+ Right ()
+ RequiresTh0 exclusions
+ | auxiliaryAdmitted admitted
+ (typedProblemAuxiliaryTag auxiliary) ->
+ Right ()
+ | otherwise ->
+ Left
+ (TypedProblemExplicitHigherOrderJustificationRequired
+ exclusions)
+
+ auxiliaryAdmitted admitted = \case
+ SeparationCharacteristic ->
+ ImplicitSeparation `Set.member` admitted
+ ReplacementCharacteristic ->
+ ImplicitFunctionalReplacement `Set.member` admitted
+ _ ->
+ False
+
+validateLocalPremiseOrdinals
+ :: [TypedLocalPremise local origin global]
+ -> Either
+ (TypedProblemError local global)
+ ()
+validateLocalPremiseOrdinals =
+ void
+ . foldM
+ (\seen premise ->
+ let ordinal =
+ typedLocalPremiseOrdinal premise
+ in
+ if ordinal `Set.member` seen
+ then
+ Left
+ (TypedProblemDuplicateLocalPremiseOrdinal
+ ordinal)
+ else
+ Right
+ (Set.insert
+ ordinal
+ seen))
+ Set.empty
+
+isFofCapability :: FofCapability projection -> Bool
+isFofCapability = \case
+ FofProjectable{} ->
+ True
+ RequiresTh0{} ->
+ False
+
+collectProblemGlobals
+ :: Ord global
+ => (global -> Maybe CoreType)
+ -> SupportedProposition local global
+ -> Vector (TypedBackendFact ref global)
+ -> Vector (TypedLocalPremise local origin global)
+ -> [TypedProblemAuxiliary global]
+ -> Either
+ (TypedProblemError local global)
+ (Map global CoreType)
+collectProblemGlobals
+ globalType
+ claim
+ facts
+ locals
+ auxiliaries =
+ Map.fromAscList
+ <$> traverse
+ resolveGlobal
+ (Set.toAscList globals)
+ where
+ globals =
+ canonicalGlobals
+ (supportedPropositionTerm claim)
+ <> foldMap
+ (canonicalGlobals
+ . supportedPropositionTerm
+ . typedBackendFactProposition)
+ facts
+ <> foldMap
+ (canonicalGlobals
+ . supportedPropositionTerm
+ . typedLocalPremiseProposition)
+ locals
+ <> foldMap
+ (canonicalGlobals
+ . supportedPropositionTerm
+ . typedProblemAuxiliaryProposition)
+ auxiliaries
+
+ resolveGlobal global =
+ case globalType global of
+ Nothing ->
+ Left
+ (TypedProblemClaimClassificationFailed
+ (UnknownBackendGlobal global))
+ Just coreType ->
+ Right (global, coreType)
+
+collectProblemLocals
+ :: Ord local
+ => SupportedProposition local global
+ -> Vector (TypedLocalPremise local origin global)
+ -> Either
+ (TypedProblemError local global)
+ (Map local CoreType)
+collectProblemLocals claim locals =
+ foldM
+ insertSupport
+ Map.empty
+ supports
+ where
+ supports =
+ Vector.toList
+ (supportedPropositionSupport claim)
+ <> concatMap
+ (Vector.toList
+ . supportedPropositionSupport
+ . typedLocalPremiseProposition)
+ (Vector.toList locals)
+
+ insertSupport current (local, coreType) =
+ case Map.lookup local current of
+ Nothing ->
+ Right
+ (Map.insert
+ local
+ coreType
+ current)
+ Just previousType
+ | previousType == coreType ->
+ Right current
+ | otherwise ->
+ Left
+ (TypedProblemLocalTypeMismatch
+ local
+ previousType
+ coreType)
+
+typedProblemRoute
+ :: TypedProblem ref local origin global
+ -> TypedProblemRoute
+typedProblemRoute
+ (TypedProblem
+ route
+ _claim
+ _facts
+ _locals
+ _auxiliaries
+ _globals
+ _localTypes) =
+ route
+
+typedProblemClaim
+ :: TypedProblem ref local origin global
+ -> SupportedProposition local global
+typedProblemClaim
+ (TypedProblem
+ _route
+ claim
+ _facts
+ _locals
+ _auxiliaries
+ _globals
+ _localTypes) =
+ claim
+
+typedProblemGlobalPremises
+ :: TypedProblem ref local origin global
+ -> Vector (TypedBackendFact ref global)
+typedProblemGlobalPremises
+ (TypedProblem
+ _route
+ _claim
+ facts
+ _locals
+ _auxiliaries
+ _globals
+ _localTypes) =
+ facts
+
+typedProblemLocalPremises
+ :: TypedProblem ref local origin global
+ -> Vector (TypedLocalPremise local origin global)
+typedProblemLocalPremises
+ (TypedProblem
+ _route
+ _claim
+ _facts
+ locals
+ _auxiliaries
+ _globals
+ _localTypes) =
+ locals
+
+typedProblemAuxiliaries
+ :: TypedProblem ref local origin global
+ -> Vector (TypedProblemAuxiliary global)
+typedProblemAuxiliaries
+ (TypedProblem
+ _route
+ _claim
+ _facts
+ _locals
+ auxiliaries
+ _globals
+ _localTypes) =
+ auxiliaries
+
+typedProblemGlobalTypes
+ :: TypedProblem ref local origin global
+ -> Map global CoreType
+typedProblemGlobalTypes
+ (TypedProblem
+ _route
+ _claim
+ _facts
+ _locals
+ _auxiliaries
+ globals
+ _localTypes) =
+ globals
+
+typedProblemLocalTypes
+ :: TypedProblem ref local origin global
+ -> Map local CoreType
+typedProblemLocalTypes
+ (TypedProblem
+ _route
+ _claim
+ _facts
+ _locals
+ _auxiliaries
+ _globals
+ localTypes) =
+ localTypes
diff --git a/source/Felix/Checking/Backend/Tptp.hs b/source/Felix/Checking/Backend/Tptp.hs
new file mode 100644
index 0000000..a5c0546
--- /dev/null
+++ b/source/Felix/Checking/Backend/Tptp.hs
@@ -0,0 +1,1281 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Deterministic task-wide TPTP preparation for checked typed problems.
+module Felix.Checking.Backend.Tptp
+ ( TypedFormulaOccurrence(..)
+ , TypedTptpNameOrigin(..)
+ , PreparedTypedTptpProblem
+ , prepareTypedTptpProblem
+ , preparedTypedTptpRoute
+ , preparedTypedTptpText
+ , preparedTypedTptpTextNewline
+ , preparedTypedTptpConjectureText
+ , preparedTypedTptpNameOrigins
+ , TypedTptpPreparationError(..)
+ ) where
+
+import Base hiding (Empty)
+import Felix.Checking.Backend.Problem
+import Felix.Checking.Core
+import Tptp.UnsortedFirstOrder qualified as Tptp
+
+import Control.Monad (foldM)
+import Control.Monad.State.Strict (StateT)
+import Control.Monad.State.Strict qualified as State
+import Control.Monad.Trans.Class (lift)
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+import TextBuilder
+
+
+data TypedFormulaOccurrence ref
+ = TypedGlobalPremiseOccurrence !ref
+ | TypedLocalPremiseOccurrence !LocalPremiseOrdinal
+ | TypedAuxiliaryOccurrence !Natural
+ | TypedConjectureOccurrence
+ deriving stock (Show, Eq, Ord)
+
+data TypedTptpNameOrigin ref local global
+ = TypedGlobalNameOrigin !global
+ | TypedLocalNameOrigin !local
+ | TypedIntrinsicNameOrigin !CoreIntrinsicTag
+ | TypedIntegerNameOrigin !Integer
+ | TypedBinderNameOrigin !Natural
+ | TypedFormulaNameOrigin !(TypedFormulaOccurrence ref)
+ deriving stock (Show, Eq, Ord)
+
+data PreparedTypedTptpProblem ref local global =
+ PreparedTypedTptpProblem
+ !TypedProblemRoute
+ !Text
+ !Text
+ !(Map
+ Text
+ (TypedTptpNameOrigin ref local global))
+ deriving stock (Eq)
+
+preparedTypedTptpRoute
+ :: PreparedTypedTptpProblem ref local global
+ -> TypedProblemRoute
+preparedTypedTptpRoute
+ (PreparedTypedTptpProblem
+ route
+ _text
+ _conjecture
+ _origins) =
+ route
+
+preparedTypedTptpText
+ :: PreparedTypedTptpProblem ref local global
+ -> Text
+preparedTypedTptpText
+ (PreparedTypedTptpProblem
+ _route
+ problemText
+ _conjecture
+ _origins) =
+ problemText
+
+preparedTypedTptpTextNewline
+ :: PreparedTypedTptpProblem ref local global
+ -> Text
+preparedTypedTptpTextNewline =
+ (`Text.snoc` '\n')
+ . preparedTypedTptpText
+
+preparedTypedTptpConjectureText
+ :: PreparedTypedTptpProblem ref local global
+ -> Text
+preparedTypedTptpConjectureText
+ (PreparedTypedTptpProblem
+ _route
+ _text
+ conjecture
+ _origins) =
+ conjecture
+
+preparedTypedTptpNameOrigins
+ :: PreparedTypedTptpProblem ref local global
+ -> Map
+ Text
+ (TypedTptpNameOrigin ref local global)
+preparedTypedTptpNameOrigins
+ (PreparedTypedTptpProblem
+ _route
+ _text
+ _conjecture
+ origins) =
+ origins
+
+data TypedTptpPreparationError local global
+ = InvalidGeneratedTypedTptpName !Text
+ | DuplicateGeneratedTypedTptpName !Text
+ | TypedTptpUnknownGlobal !global
+ | TypedTptpUnknownLocal !local
+ | TypedTptpUnboundIndex !Natural
+ | TypedTptpFofProjectionMismatch
+ deriving stock (Show, Eq)
+
+
+data NameEnvironment ref local global =
+ NameEnvironment
+ !(Map global Tptp.AtomicWord)
+ !(Map global CoreType)
+ !(Map local Tptp.AtomicWord)
+ !(Map CoreIntrinsicTag Tptp.AtomicWord)
+ !(Map Integer Tptp.AtomicWord)
+ !(Map
+ Text
+ (TypedTptpNameOrigin ref local global))
+
+data RenderState ref local global =
+ RenderState
+ !Natural
+ !Natural
+ !(Map
+ Text
+ (TypedTptpNameOrigin ref local global))
+
+type Render ref local global =
+ StateT
+ (RenderState ref local global)
+ (Either
+ (TypedTptpPreparationError local global))
+
+data RenderedFormula ref = RenderedFormula
+ !Tptp.AtomicWord
+ !(TypedFormulaOccurrence ref)
+ !TextBuilder
+
+data BoundTarget
+ = BoundFofVariable !Tptp.Variable
+ | BoundTh0Variable !Tptp.Variable
+ | AmbientConstant !Tptp.AtomicWord
+
+prepareTypedTptpProblem
+ :: (Ord local, Ord global)
+ => TypedProblem ref local origin global
+ -> Either
+ (TypedTptpPreparationError local global)
+ (PreparedTypedTptpProblem ref local global)
+prepareTypedTptpProblem problem = do
+ names <-
+ allocateNames problem
+ let initialState =
+ RenderState
+ 0
+ 0
+ (nameEnvironmentOrigins names)
+ (rendered, finalState) <-
+ State.runStateT
+ (renderProblem names problem)
+ initialState
+ let (problemBuilder, conjectureBuilder) =
+ rendered
+ RenderState _nextBinder _nextHypothesis origins =
+ finalState
+ pure
+ (PreparedTypedTptpProblem
+ (typedProblemRoute problem)
+ (TextBuilder.toText problemBuilder)
+ (TextBuilder.toText conjectureBuilder)
+ origins)
+
+allocateNames
+ :: (Ord local, Ord global)
+ => TypedProblem ref local origin global
+ -> Either
+ (TypedTptpPreparationError local global)
+ (NameEnvironment ref local global)
+allocateNames problem = do
+ globalAllocations <-
+ allocateCategory
+ "tg_g"
+ TypedGlobalNameOrigin
+ (Map.keys
+ (typedProblemGlobalTypes
+ problem))
+ localAllocations <-
+ allocateCategory
+ "tg_l"
+ TypedLocalNameOrigin
+ (Map.keys
+ (typedProblemLocalTypes
+ problem))
+ intrinsicAllocations <-
+ allocateCategory
+ "tg_i"
+ TypedIntrinsicNameOrigin
+ (Set.toAscList
+ (problemIntrinsics problem))
+ integerAllocations <-
+ allocateCategory
+ "tg_n"
+ TypedIntegerNameOrigin
+ (Set.toAscList
+ (problemIntegers problem))
+ origins <-
+ foldM
+ (\current (target, nameOrigin) ->
+ insertOrigin
+ (Tptp.atomicWordText target)
+ nameOrigin
+ current)
+ Map.empty
+ ( [ (target, nameOrigin)
+ | (_global, target, nameOrigin) <-
+ globalAllocations
+ ]
+ <> [ (target, nameOrigin)
+ | (_local, target, nameOrigin) <-
+ localAllocations
+ ]
+ <> [ (target, nameOrigin)
+ | (_intrinsic, target, nameOrigin) <-
+ intrinsicAllocations
+ ]
+ <> [ (target, nameOrigin)
+ | (_integer, target, nameOrigin) <-
+ integerAllocations
+ ]
+ )
+ pure
+ (NameEnvironment
+ (Map.fromList
+ [ (global, target)
+ | (global, target, _nameOrigin) <-
+ globalAllocations
+ ])
+ (typedProblemGlobalTypes problem)
+ (Map.fromList
+ [ (local, target)
+ | (local, target, _nameOrigin) <-
+ localAllocations
+ ])
+ (Map.fromList
+ [ (intrinsic, target)
+ | (intrinsic, target, _nameOrigin) <-
+ intrinsicAllocations
+ ])
+ (Map.fromList
+ [ (integer, target)
+ | (integer, target, _nameOrigin) <-
+ integerAllocations
+ ])
+ origins)
+ where
+ allocateCategory prefix makeOrigin semantics =
+ traverse
+ (\(ordinal, semantic) -> do
+ target <-
+ generatedAtomicWord
+ (prefix
+ <> Text.pack
+ (show ordinal))
+ pure
+ ( semantic
+ , target
+ , makeOrigin semantic
+ ))
+ (zip [0 :: Int ..] semantics)
+
+nameEnvironmentOrigins
+ :: NameEnvironment ref local global
+ -> Map
+ Text
+ (TypedTptpNameOrigin ref local global)
+nameEnvironmentOrigins
+ (NameEnvironment
+ _globals
+ _globalTypes
+ _locals
+ _intrinsics
+ _integers
+ origins) =
+ origins
+
+generatedAtomicWord
+ :: Text
+ -> Either
+ (TypedTptpPreparationError local global)
+ Tptp.AtomicWord
+generatedAtomicWord target =
+ maybe
+ (Left
+ (InvalidGeneratedTypedTptpName
+ target))
+ Right
+ (Tptp.atomicWord target)
+
+generatedVariable
+ :: Text
+ -> Either
+ (TypedTptpPreparationError local global)
+ Tptp.Variable
+generatedVariable target =
+ maybe
+ (Left
+ (InvalidGeneratedTypedTptpName
+ target))
+ Right
+ (Tptp.variable target)
+
+insertOrigin
+ :: Text
+ -> TypedTptpNameOrigin ref local global
+ -> Map
+ Text
+ (TypedTptpNameOrigin ref local global)
+ -> Either
+ (TypedTptpPreparationError local global)
+ (Map
+ Text
+ (TypedTptpNameOrigin ref local global))
+insertOrigin target nameOrigin origins =
+ if Map.member target origins
+ then
+ Left
+ (DuplicateGeneratedTypedTptpName
+ target)
+ else
+ Right
+ (Map.insert
+ target
+ nameOrigin
+ origins)
+
+renderProblem
+ :: (Ord local, Ord global)
+ => NameEnvironment ref local global
+ -> TypedProblem ref local origin global
+ -> Render
+ ref
+ local
+ global
+ (TextBuilder, TextBuilder)
+renderProblem names problem = do
+ hypotheses <-
+ renderHypotheses names problem
+ conjecture <-
+ renderConjecture names problem
+ declarations <-
+ case typedProblemRoute problem of
+ RouteFof ->
+ pure []
+ RouteTh0 ->
+ renderTh0Declarations
+ names
+ problem
+ let formulaBuilders =
+ renderFormulaLine
+ (typedProblemRoute problem)
+ "axiom"
+ <$> hypotheses
+ conjectureBuilder =
+ renderFormulaLine
+ (typedProblemRoute problem)
+ "conjecture"
+ conjecture
+ complete =
+ intercalate
+ (char '\n')
+ (declarations
+ <> formulaBuilders
+ <> [conjectureBuilder])
+ pure
+ ( complete
+ , conjectureBuilder
+ )
+
+renderHypotheses
+ :: (Ord local, Ord global)
+ => NameEnvironment ref local global
+ -> TypedProblem ref local origin global
+ -> Render
+ ref
+ local
+ global
+ [RenderedFormula ref]
+renderHypotheses names problem = do
+ globalFormulas <-
+ traverse
+ (\fact ->
+ renderOccurrence
+ names
+ problem
+ (TypedGlobalPremiseOccurrence
+ (typedBackendFactReference
+ fact))
+ (weakenClosedSupportedProposition
+ (typedBackendFactProposition
+ fact)))
+ (Vector.toList
+ (typedProblemGlobalPremises
+ problem))
+ localFormulas <-
+ traverse
+ (\premise ->
+ renderOccurrence
+ names
+ problem
+ (TypedLocalPremiseOccurrence
+ (typedLocalPremiseOrdinal
+ premise))
+ (typedLocalPremiseProposition
+ premise))
+ (Vector.toList
+ (typedProblemLocalPremises
+ problem))
+ auxiliaryFormulas <-
+ traverse
+ (\auxiliary ->
+ renderOccurrence
+ names
+ problem
+ (TypedAuxiliaryOccurrence
+ (typedProblemAuxiliaryOrdinal
+ auxiliary))
+ (weakenClosedSupportedProposition
+ (typedProblemAuxiliaryProposition
+ auxiliary)))
+ (Vector.toList
+ (typedProblemAuxiliaries
+ problem))
+ pure
+ (globalFormulas
+ <> localFormulas
+ <> auxiliaryFormulas)
+
+renderConjecture
+ :: (Ord local, Ord global)
+ => NameEnvironment ref local global
+ -> TypedProblem ref local origin global
+ -> Render
+ ref
+ local
+ global
+ (RenderedFormula ref)
+renderConjecture names problem =
+ renderOccurrence
+ names
+ problem
+ TypedConjectureOccurrence
+ (typedProblemClaim problem)
+
+renderOccurrence
+ :: (Ord local, Ord global)
+ => NameEnvironment ref local global
+ -> TypedProblem ref local origin global
+ -> TypedFormulaOccurrence ref
+ -> SupportedProposition local global
+ -> Render
+ ref
+ local
+ global
+ (RenderedFormula ref)
+renderOccurrence names problem occurrence proposition = do
+ target <-
+ case occurrence of
+ TypedConjectureOccurrence ->
+ liftEither
+ (generatedAtomicWord "tg_q0")
+ _ -> do
+ ordinal <-
+ nextHypothesisOrdinal
+ liftEither
+ (generatedAtomicWord
+ ("tg_h"
+ <> Text.pack
+ (show ordinal)))
+ registerOrigin
+ (Tptp.atomicWordText target)
+ (TypedFormulaNameOrigin occurrence)
+ bounds <-
+ initialBounds
+ names
+ proposition
+ formula <-
+ case typedProblemRoute problem of
+ RouteFof ->
+ renderFofFormula
+ names
+ bounds
+ (supportedPropositionTerm
+ proposition)
+ RouteTh0 ->
+ renderTh0Term
+ names
+ bounds
+ (supportedPropositionTerm
+ proposition)
+ pure
+ (RenderedFormula
+ target
+ occurrence
+ formula)
+
+-- Formula and binder ordinals use separate dense namespaces.
+nextHypothesisOrdinal
+ :: Render ref local global Natural
+nextHypothesisOrdinal = do
+ RenderState nextBinder nextHypothesis origins <-
+ State.get
+ State.put
+ (RenderState
+ nextBinder
+ (nextHypothesis + 1)
+ origins)
+ pure nextHypothesis
+
+registerOrigin
+ :: Text
+ -> TypedTptpNameOrigin ref local global
+ -> Render ref local global ()
+registerOrigin target nameOrigin = do
+ RenderState nextBinder nextHypothesis origins <-
+ State.get
+ origins' <-
+ liftEither
+ (insertOrigin
+ target
+ nameOrigin
+ origins)
+ State.put
+ (RenderState
+ nextBinder
+ nextHypothesis
+ origins')
+
+freshBinder
+ :: Render ref local global Tptp.Variable
+freshBinder = do
+ RenderState nextBinder nextHypothesis origins <-
+ State.get
+ let target =
+ "V" <> Text.pack (show nextBinder)
+ variable <-
+ liftEither
+ (generatedVariable target)
+ origins' <-
+ liftEither
+ (insertOrigin
+ target
+ (TypedBinderNameOrigin
+ nextBinder)
+ origins)
+ State.put
+ (RenderState
+ (nextBinder + 1)
+ nextHypothesis
+ origins')
+ pure variable
+
+liftEither
+ :: Either
+ (TypedTptpPreparationError local global)
+ value
+ -> Render ref local global value
+liftEither =
+ lift
+
+initialBounds
+ :: Ord local
+ => NameEnvironment ref local global
+ -> SupportedProposition local global
+ -> Render ref local global [BoundTarget]
+initialBounds
+ (NameEnvironment
+ _globals
+ _globalTypes
+ localNames
+ _intrinsics
+ _integers
+ _origins)
+ proposition =
+ traverse
+ (\(local, _coreType) ->
+ maybe
+ (lift
+ (Left
+ (TypedTptpUnknownLocal
+ local)))
+ (pure . AmbientConstant)
+ (Map.lookup
+ local
+ localNames))
+ (Vector.toList
+ (supportedPropositionSupport
+ proposition))
+
+renderFormulaLine
+ :: TypedProblemRoute
+ -> TextBuilder
+ -> RenderedFormula ref
+ -> TextBuilder
+renderFormulaLine route role
+ (RenderedFormula target _occurrence formula) =
+ dialect
+ <> char '('
+ <> Tptp.buildAtomicWord target
+ <> char ','
+ <> role
+ <> char ','
+ <> formula
+ <> text ")."
+ where
+ dialect =
+ case route of
+ RouteFof ->
+ text "fof"
+ RouteTh0 ->
+ text "thf"
+
+
+renderFofFormula
+ :: (Ord global)
+ => NameEnvironment ref local global
+ -> [BoundTarget]
+ -> CanonicalTerm global
+ -> Render ref local global TextBuilder
+renderFofFormula names bounds = \case
+ CFalsum ->
+ pure (text "$false")
+ CImp premise conclusion -> do
+ premise' <-
+ renderFofFormula names bounds premise
+ conclusion' <-
+ renderFofFormula names bounds conclusion
+ pure
+ (parenthesize
+ (premise'
+ <> text "=>"
+ <> conclusion'))
+ CEq TySet left right -> do
+ left' <-
+ renderFofTerm names bounds left
+ right' <-
+ renderFofTerm names bounds right
+ pure
+ (parenthesize
+ (left'
+ <> char '='
+ <> right'))
+ CEq TyProp left right -> do
+ left' <-
+ renderFofFormula names bounds left
+ right' <-
+ renderFofFormula names bounds right
+ pure
+ (parenthesize
+ (left'
+ <> text "<=>"
+ <> right'))
+ CForall TySet body -> do
+ variable <-
+ freshBinder
+ body' <-
+ renderFofFormula
+ names
+ (BoundFofVariable variable
+ : bounds)
+ body
+ pure
+ (parenthesize
+ (text "!["
+ <> Tptp.buildVariable variable
+ <> text "]:"
+ <> body'))
+ application ->
+ renderFofApplication
+ names
+ bounds
+ TyProp
+ application
+
+renderFofTerm
+ :: Ord global
+ => NameEnvironment ref local global
+ -> [BoundTarget]
+ -> CanonicalTerm global
+ -> Render ref local global TextBuilder
+renderFofTerm names bounds = \case
+ CBound index ->
+ renderBound index bounds
+ CGlobal global ->
+ Tptp.buildAtomicWord
+ <$> lookupGlobal names global
+ CIntrinsic intrinsic ->
+ Tptp.buildAtomicWord
+ <$> lookupIntrinsic names intrinsic
+ COpaqueInteger integer ->
+ Tptp.buildAtomicWord
+ <$> lookupInteger names integer
+ application ->
+ renderFofApplication
+ names
+ bounds
+ TySet
+ application
+
+renderFofApplication
+ :: Ord global
+ => NameEnvironment ref local global
+ -> [BoundTarget]
+ -> CoreType
+ -> CanonicalTerm global
+ -> Render ref local global TextBuilder
+renderFofApplication names bounds expected application =
+ case applicationHead application of
+ (CGlobal global, arguments) -> do
+ coreType <-
+ maybe
+ (lift
+ (Left
+ (TypedTptpUnknownGlobal
+ global)))
+ pure
+ (Map.lookup
+ global
+ (nameEnvironmentGlobalTypes
+ names))
+ renderHead
+ coreType
+ (lookupGlobal names global)
+ arguments
+ (CIntrinsic intrinsic, arguments) ->
+ renderHead
+ (coreIntrinsicType intrinsic)
+ (lookupIntrinsic names intrinsic)
+ arguments
+ _ ->
+ lift
+ (Left
+ TypedTptpFofProjectionMismatch)
+ where
+ renderHead coreType targetAction arguments = do
+ unlessFofApplication
+ expected
+ coreType
+ arguments
+ target <-
+ targetAction
+ arguments' <-
+ traverse
+ (renderFofTerm names bounds)
+ arguments
+ pure
+ (applyAtomicWord
+ target
+ arguments')
+
+-- Global types are retained in the problem, but names need only the allocated
+-- symbols. FOF saturation was already checked by the projection witness.
+nameEnvironmentGlobalTypes
+ :: NameEnvironment ref local global
+ -> Map global CoreType
+nameEnvironmentGlobalTypes
+ (NameEnvironment
+ _globals
+ globalTypes
+ _locals
+ _intrinsics
+ _integers
+ _origins) =
+ globalTypes
+
+unlessFofApplication
+ :: CoreType
+ -> CoreType
+ -> [CanonicalTerm global]
+ -> Render ref local global ()
+unlessFofApplication expected coreType arguments =
+ case consume coreType arguments of
+ Just result
+ | result == expected ->
+ pure ()
+ _ ->
+ lift
+ (Left
+ TypedTptpFofProjectionMismatch)
+ where
+ consume current = \case
+ [] ->
+ Just current
+ _argument : remaining ->
+ case current of
+ TyArrow TySet result ->
+ consume result remaining
+ _ ->
+ Nothing
+
+applyAtomicWord
+ :: Tptp.AtomicWord
+ -> [TextBuilder]
+ -> TextBuilder
+applyAtomicWord target = \case
+ [] ->
+ Tptp.buildAtomicWord target
+ arguments ->
+ Tptp.buildAtomicWord target
+ <> Tptp.buildTuple arguments
+
+renderBound
+ :: Natural
+ -> [BoundTarget]
+ -> Render ref local global TextBuilder
+renderBound index bounds =
+ case contextAt index bounds of
+ Nothing ->
+ lift
+ (Left
+ (TypedTptpUnboundIndex
+ index))
+ Just (BoundFofVariable variable) ->
+ pure
+ (Tptp.buildVariable
+ variable)
+ Just (BoundTh0Variable variable) ->
+ pure
+ (Tptp.buildVariable
+ variable)
+ Just (AmbientConstant target) ->
+ pure
+ (Tptp.buildAtomicWord
+ target)
+
+
+renderTh0Term
+ :: Ord global
+ => NameEnvironment ref local global
+ -> [BoundTarget]
+ -> CanonicalTerm global
+ -> Render ref local global TextBuilder
+renderTh0Term names bounds = \case
+ CBound index ->
+ renderBound index bounds
+ CGlobal global ->
+ Tptp.buildAtomicWord
+ <$> lookupGlobal names global
+ CIntrinsic intrinsic ->
+ Tptp.buildAtomicWord
+ <$> lookupIntrinsic names intrinsic
+ COpaqueInteger integer ->
+ Tptp.buildAtomicWord
+ <$> lookupInteger names integer
+ CApp function argument -> do
+ function' <-
+ renderTh0Term names bounds function
+ argument' <-
+ renderTh0Term names bounds argument
+ pure
+ (parenthesize
+ (function'
+ <> char '@'
+ <> argument'))
+ CLam binderType body -> do
+ variable <-
+ freshBinder
+ body' <-
+ renderTh0Term
+ names
+ (BoundTh0Variable variable
+ : bounds)
+ body
+ pure
+ (parenthesize
+ (text "^ ["
+ <> Tptp.buildVariable variable
+ <> char ':'
+ <> renderCoreType binderType
+ <> text "] : "
+ <> body'))
+ CFalsum ->
+ pure (text "$false")
+ CImp premise conclusion -> do
+ premise' <-
+ renderTh0Term names bounds premise
+ conclusion' <-
+ renderTh0Term names bounds conclusion
+ pure
+ (parenthesize
+ (premise'
+ <> text "=>"
+ <> conclusion'))
+ CEq operandType left right -> do
+ left' <-
+ renderTh0Term names bounds left
+ right' <-
+ renderTh0Term names bounds right
+ pure
+ (parenthesize
+ (left'
+ <> (case operandType of
+ TyProp -> text "<=>"
+ _ -> char '=')
+ <> right'))
+ CForall binderType body -> do
+ variable <-
+ freshBinder
+ body' <-
+ renderTh0Term
+ names
+ (BoundTh0Variable variable
+ : bounds)
+ body
+ pure
+ (parenthesize
+ (text "! ["
+ <> Tptp.buildVariable variable
+ <> char ':'
+ <> renderCoreType binderType
+ <> text "] : "
+ <> body'))
+
+renderTh0Declarations
+ :: (Ord local, Ord global)
+ => NameEnvironment ref local global
+ -> TypedProblem ref local origin global
+ -> Render ref local global [TextBuilder]
+renderTh0Declarations names problem = do
+ globalDeclarations <-
+ traverse
+ (\(ordinal, (global, coreType)) -> do
+ target <-
+ lookupGlobal names global
+ label <-
+ liftEither
+ (generatedAtomicWord
+ ("tg_g_type_"
+ <> Text.pack
+ (show ordinal)))
+ pure
+ (typeDeclaration
+ label
+ target
+ (renderCoreType coreType)))
+ (zip [0 :: Int ..]
+ (Map.toAscList
+ (typedProblemGlobalTypes
+ problem)))
+ localDeclarations <-
+ traverse
+ (\(ordinal, (local, coreType)) -> do
+ target <-
+ lookupLocal names local
+ label <-
+ liftEither
+ (generatedAtomicWord
+ ("tg_l_type_"
+ <> Text.pack
+ (show ordinal)))
+ pure
+ (typeDeclaration
+ label
+ target
+ (renderCoreType coreType)))
+ (zip [0 :: Int ..]
+ (Map.toAscList
+ (typedProblemLocalTypes
+ problem)))
+ intrinsicDeclarations <-
+ traverse
+ (\(ordinal, intrinsic) -> do
+ target <-
+ lookupIntrinsic names intrinsic
+ label <-
+ liftEither
+ (generatedAtomicWord
+ ("tg_i_type_"
+ <> Text.pack
+ (show ordinal)))
+ pure
+ (typeDeclaration
+ label
+ target
+ (renderCoreType
+ (coreIntrinsicType
+ intrinsic))))
+ (zip [0 :: Int ..]
+ (Set.toAscList
+ (problemIntrinsics problem)))
+ integerDeclarations <-
+ traverse
+ (\(ordinal, integer) -> do
+ target <-
+ lookupInteger names integer
+ label <-
+ liftEither
+ (generatedAtomicWord
+ ("tg_n_type_"
+ <> Text.pack
+ (show ordinal)))
+ pure
+ (typeDeclaration
+ label
+ target
+ (renderCoreType TySet)))
+ (zip [0 :: Int ..]
+ (Set.toAscList
+ (problemIntegers problem)))
+ pure
+ (globalDeclarations
+ <> localDeclarations
+ <> intrinsicDeclarations
+ <> integerDeclarations)
+
+typeDeclaration
+ :: Tptp.AtomicWord
+ -> Tptp.AtomicWord
+ -> TextBuilder
+ -> TextBuilder
+typeDeclaration label target coreType =
+ text "thf("
+ <> Tptp.buildAtomicWord label
+ <> text ",type,("
+ <> Tptp.buildAtomicWord target
+ <> char ':'
+ <> coreType
+ <> text "))."
+
+renderCoreType :: CoreType -> TextBuilder
+renderCoreType = \case
+ TyProp ->
+ text "$o"
+ TySet ->
+ text "$i"
+ TyArrow argument result ->
+ parenthesize
+ (renderCoreType argument
+ <> char '>'
+ <> renderCoreType result)
+
+parenthesize :: TextBuilder -> TextBuilder
+parenthesize builder =
+ char '(' <> builder <> char ')'
+
+
+lookupGlobal
+ :: Ord global
+ => NameEnvironment ref local global
+ -> global
+ -> Render ref local global Tptp.AtomicWord
+lookupGlobal names global =
+ maybe
+ (lift
+ (Left
+ (TypedTptpUnknownGlobal
+ global)))
+ pure
+ (lookupGlobalPure names global)
+
+lookupGlobalPure
+ :: Ord global
+ => NameEnvironment ref local global
+ -> global
+ -> Maybe Tptp.AtomicWord
+lookupGlobalPure
+ (NameEnvironment
+ globals
+ _globalTypes
+ _locals
+ _intrinsics
+ _integers
+ _origins) =
+ (`Map.lookup` globals)
+
+lookupLocalPure
+ :: Ord local
+ => NameEnvironment ref local global
+ -> local
+ -> Maybe Tptp.AtomicWord
+lookupLocalPure
+ (NameEnvironment
+ _globals
+ _globalTypes
+ locals
+ _intrinsics
+ _integers
+ _origins) =
+ (`Map.lookup` locals)
+
+lookupLocal
+ :: Ord local
+ => NameEnvironment ref local global
+ -> local
+ -> Render ref local global Tptp.AtomicWord
+lookupLocal names local =
+ maybe
+ (lift
+ (Left
+ (TypedTptpUnknownLocal
+ local)))
+ pure
+ (lookupLocalPure names local)
+
+lookupIntrinsic
+ :: NameEnvironment ref local global
+ -> CoreIntrinsicTag
+ -> Render ref local global Tptp.AtomicWord
+lookupIntrinsic names intrinsic =
+ maybe
+ (lift
+ (Left
+ TypedTptpFofProjectionMismatch))
+ pure
+ (lookupIntrinsicPure names intrinsic)
+
+lookupIntrinsicPure
+ :: NameEnvironment ref local global
+ -> CoreIntrinsicTag
+ -> Maybe Tptp.AtomicWord
+lookupIntrinsicPure
+ (NameEnvironment
+ _globals
+ _globalTypes
+ _locals
+ intrinsics
+ _integers
+ _origins) =
+ (`Map.lookup` intrinsics)
+
+lookupInteger
+ :: NameEnvironment ref local global
+ -> Integer
+ -> Render ref local global Tptp.AtomicWord
+lookupInteger names integer =
+ maybe
+ (lift
+ (Left
+ TypedTptpFofProjectionMismatch))
+ pure
+ (lookupIntegerPure names integer)
+
+lookupIntegerPure
+ :: NameEnvironment ref local global
+ -> Integer
+ -> Maybe Tptp.AtomicWord
+lookupIntegerPure
+ (NameEnvironment
+ _globals
+ _globalTypes
+ _locals
+ _intrinsics
+ integers
+ _origins) =
+ (`Map.lookup` integers)
+
+problemIntrinsics
+ :: TypedProblem ref local origin global
+ -> Set CoreIntrinsicTag
+problemIntrinsics =
+ foldMap canonicalIntrinsics
+ . problemTerms
+
+problemIntegers
+ :: TypedProblem ref local origin global
+ -> Set Integer
+problemIntegers =
+ foldMap canonicalIntegers
+ . problemTerms
+
+problemTerms
+ :: TypedProblem ref local origin global
+ -> [CanonicalTerm global]
+problemTerms problem =
+ supportedPropositionTerm
+ (typedProblemClaim problem)
+ : (supportedPropositionTerm
+ . typedBackendFactProposition
+ <$> Vector.toList
+ (typedProblemGlobalPremises
+ problem))
+ <> (supportedPropositionTerm
+ . typedLocalPremiseProposition
+ <$> Vector.toList
+ (typedProblemLocalPremises
+ problem))
+ <> (supportedPropositionTerm
+ . typedProblemAuxiliaryProposition
+ <$> Vector.toList
+ (typedProblemAuxiliaries
+ problem))
+
+canonicalIntrinsics
+ :: CanonicalTerm global
+ -> Set CoreIntrinsicTag
+canonicalIntrinsics = \case
+ CBound{} ->
+ mempty
+ CGlobal{} ->
+ mempty
+ CIntrinsic intrinsic ->
+ Set.singleton intrinsic
+ COpaqueInteger{} ->
+ mempty
+ CApp function argument ->
+ canonicalIntrinsics function
+ <> canonicalIntrinsics argument
+ CLam _binderType body ->
+ canonicalIntrinsics body
+ CFalsum ->
+ mempty
+ CImp premise conclusion ->
+ canonicalIntrinsics premise
+ <> canonicalIntrinsics conclusion
+ CEq _operandType left right ->
+ canonicalIntrinsics left
+ <> canonicalIntrinsics right
+ CForall _binderType body ->
+ canonicalIntrinsics body
+
+canonicalIntegers
+ :: CanonicalTerm global
+ -> Set Integer
+canonicalIntegers = \case
+ CBound{} ->
+ mempty
+ CGlobal{} ->
+ mempty
+ CIntrinsic{} ->
+ mempty
+ COpaqueInteger integer ->
+ Set.singleton integer
+ CApp function argument ->
+ canonicalIntegers function
+ <> canonicalIntegers argument
+ CLam _binderType body ->
+ canonicalIntegers body
+ CFalsum ->
+ mempty
+ CImp premise conclusion ->
+ canonicalIntegers premise
+ <> canonicalIntegers conclusion
+ CEq _operandType left right ->
+ canonicalIntegers left
+ <> canonicalIntegers right
+ CForall _binderType body ->
+ canonicalIntegers body
+
+applicationHead
+ :: CanonicalTerm global
+ -> (CanonicalTerm global, [CanonicalTerm global])
+applicationHead =
+ go []
+ where
+ go arguments = \case
+ CApp function argument ->
+ go (argument : arguments) function
+ headTerm ->
+ (headTerm, arguments)
+
+contextAt :: Natural -> [value] -> Maybe value
+contextAt _index [] =
+ Nothing
+contextAt 0 (value : _remaining) =
+ Just value
+contextAt index (_value : remaining) =
+ contextAt (index - 1) remaining
diff --git a/source/Felix/Checking/Core.hs b/source/Felix/Checking/Core.hs
new file mode 100644
index 0000000..13ec218
--- /dev/null
+++ b/source/Felix/Checking/Core.hs
@@ -0,0 +1,1604 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Checked monomorphic HOL syntax and its nameless in-memory form.
+--
+-- Scoped syntax is an operational construction language. Only a checked,
+-- frozen value is semantic input to later kernel and backend boundaries.
+module Felix.Checking.Core
+ ( CoreType(..)
+ , CoreIntrinsicTag(..)
+ , coreIntrinsicType
+ , CoreSyntax
+ , coreLocal
+ , coreGlobal
+ , coreIntrinsic
+ , coreOpaqueInteger
+ , coreApply
+ , coreLambda
+ , coreFalsum
+ , coreImplication
+ , coreEquality
+ , coreForall
+ , CheckedCore
+ , checkedCoreType
+ , checkCore
+ , checkClosedCore
+ , ClosedCheckedProposition
+ , checkedPropositionCore
+ , checkClosedProposition
+ , CoreCheckError(..)
+ , CanonicalTerm(..)
+ , canonicalSetInsert
+ , FrozenCheckedCore
+ , frozenCoreType
+ , frozenCoreTerm
+ , thawFrozenCore
+ , frozenCoreGlobals
+ , mapFrozenGlobals
+ , ScopedCheckedCore
+ , scopedCoreContext
+ , scopedCoreType
+ , scopedCoreTerm
+ , mapScopedGlobals
+ , checkScopedCanonicalCore
+ , embedClosedCore
+ , weakenCheckedScopedCore
+ , weakenScopedCore
+ , scopedSetDefinition
+ , scopedCharacteristicDefinition
+ , scopedReplacementGraph
+ , implyScopedCore
+ , equalScopedCore
+ , conjoinScopedCore
+ , disjoinScopedCore
+ , negateScopedCore
+ , falsumScopedCore
+ , splitScopedSetEquality
+ , scopedSetInductionInstance
+ , closeScopedForall
+ , closeScopedExists
+ , openScopedForall
+ , openScopedImplication
+ , openScopedAssumption
+ , closeScopedCore
+ , betaNormalizeCanonical
+ , instantiateCanonical
+ , shiftCanonical
+ , mapCanonicalGlobals
+ , canonicalTermGlobals
+ , checkCanonicalCore
+ , freezeClosed
+ , FreezeError(..)
+ , referenceFreezeClosed
+ ) where
+
+import Base hiding (Empty)
+
+import Bound
+import Control.DeepSeq (NFData)
+import Control.Monad (ap, unless)
+import Data.Set qualified as Set
+import Numeric.Natural (Natural)
+
+
+-- | The complete monomorphic type grammar of the checked core.
+data CoreType
+ = TyProp
+ | TySet
+ | TyArrow !CoreType !CoreType
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+
+-- | The complete set-forming primitive inventory.
+data CoreIntrinsicTag
+ = Member
+ | Empty
+ | PairSet
+ | FamilyUnion
+ | PowerSet
+ | Sep
+ | Repl
+ | SetChoose
+ | UnivOf
+ -- | The bounded set-valued least fixed point. It denotes the elements of
+ -- its bound that belong to every bounded pre-fixed point of its operator.
+ | ISetLfp
+ deriving stock (Show, Eq, Ord, Enum, Bounded, Generic)
+ deriving anyclass (NFData)
+
+coreIntrinsicType :: CoreIntrinsicTag -> CoreType
+coreIntrinsicType = \case
+ Member ->
+ TySet `TyArrow` (TySet `TyArrow` TyProp)
+ Empty ->
+ TySet
+ PairSet ->
+ TySet `TyArrow` (TySet `TyArrow` TySet)
+ FamilyUnion ->
+ TySet `TyArrow` TySet
+ PowerSet ->
+ TySet `TyArrow` TySet
+ Sep ->
+ TySet
+ `TyArrow`
+ ((TySet `TyArrow` TyProp) `TyArrow` TySet)
+ Repl ->
+ TySet
+ `TyArrow`
+ ((TySet `TyArrow` TySet) `TyArrow` TySet)
+ SetChoose ->
+ (TySet `TyArrow` TyProp) `TyArrow` TySet
+ UnivOf ->
+ TySet `TyArrow` TySet
+ ISetLfp ->
+ TySet
+ `TyArrow`
+ ((TySet `TyArrow` TySet) `TyArrow` TySet)
+
+
+-- | Operational scoped syntax. Its constructors remain private because this
+-- value is neither a typing certificate nor an authority-bearing term.
+data CoreSyntax global local
+ = CoreLocal local
+ | CoreGlobal global
+ | CoreIntrinsic CoreIntrinsicTag
+ | CoreOpaqueInteger !Integer
+ | CoreApply
+ !(CoreSyntax global local)
+ !(CoreSyntax global local)
+ | CoreLambda
+ !CoreType
+ !(Scope () (CoreSyntax global) local)
+ | CoreFalsum
+ | CoreImplication
+ !(CoreSyntax global local)
+ !(CoreSyntax global local)
+ | CoreEquality
+ !CoreType
+ !(CoreSyntax global local)
+ !(CoreSyntax global local)
+ | CoreForall
+ !CoreType
+ !(Scope () (CoreSyntax global) local)
+ deriving stock (Functor, Foldable, Traversable)
+
+instance Applicative (CoreSyntax global) where
+ pure = CoreLocal
+ (<*>) = ap
+
+instance Monad (CoreSyntax global) where
+ CoreLocal local >>= replace =
+ replace local
+ CoreGlobal global >>= _replace =
+ CoreGlobal global
+ CoreIntrinsic intrinsic >>= _replace =
+ CoreIntrinsic intrinsic
+ CoreOpaqueInteger integer >>= _replace =
+ CoreOpaqueInteger integer
+ CoreApply function argument >>= replace =
+ CoreApply
+ (function >>= replace)
+ (argument >>= replace)
+ CoreLambda binderType body >>= replace =
+ CoreLambda binderType (body >>>= replace)
+ CoreFalsum >>= _replace =
+ CoreFalsum
+ CoreImplication premise conclusion >>= replace =
+ CoreImplication
+ (premise >>= replace)
+ (conclusion >>= replace)
+ CoreEquality operandType left right >>= replace =
+ CoreEquality
+ operandType
+ (left >>= replace)
+ (right >>= replace)
+ CoreForall binderType body >>= replace =
+ CoreForall binderType (body >>>= replace)
+
+coreLocal :: local -> CoreSyntax global local
+coreLocal = CoreLocal
+
+coreGlobal :: global -> CoreSyntax global local
+coreGlobal = CoreGlobal
+
+coreIntrinsic :: CoreIntrinsicTag -> CoreSyntax global local
+coreIntrinsic = CoreIntrinsic
+
+coreOpaqueInteger :: Integer -> CoreSyntax global local
+coreOpaqueInteger = CoreOpaqueInteger
+
+coreApply
+ :: CoreSyntax global local
+ -> CoreSyntax global local
+ -> CoreSyntax global local
+coreApply = CoreApply
+
+coreLambda
+ :: Eq local
+ => CoreType
+ -> local
+ -> CoreSyntax global local
+ -> CoreSyntax global local
+coreLambda binderType local body =
+ CoreLambda binderType (abstract1 local body)
+
+coreFalsum :: CoreSyntax global local
+coreFalsum = CoreFalsum
+
+coreImplication
+ :: CoreSyntax global local
+ -> CoreSyntax global local
+ -> CoreSyntax global local
+coreImplication = CoreImplication
+
+coreEquality
+ :: CoreType
+ -> CoreSyntax global local
+ -> CoreSyntax global local
+ -> CoreSyntax global local
+coreEquality = CoreEquality
+
+coreForall
+ :: Eq local
+ => CoreType
+ -> local
+ -> CoreSyntax global local
+ -> CoreSyntax global local
+coreForall binderType local body =
+ CoreForall binderType (abstract1 local body)
+
+
+data CoreCheckError
+ = UnknownCoreGlobal
+ | UnboundCoreLocal
+ | UnboundCoreIndex !Natural
+ | AppliedNonFunction !CoreType
+ | ApplicationArgumentTypeMismatch
+ !CoreType
+ !CoreType
+ | ImplicationOperandTypeMismatch
+ !CoreType
+ | EqualityOperandTypeMismatch
+ !CoreType
+ !CoreType
+ | QuantifierBodyTypeMismatch
+ !CoreType
+ | ExpectedCoreType
+ !CoreType
+ !CoreType
+ deriving stock (Show, Eq)
+
+
+-- | A scoped term whose complete tree has been type checked.
+data CheckedCore global local = CheckedCore
+ !CoreType
+ !(CoreSyntax global local)
+
+checkedCoreType :: CheckedCore global local -> CoreType
+checkedCoreType (CheckedCore coreType _syntax) =
+ coreType
+
+checkCore
+ :: (global -> Maybe CoreType)
+ -> (local -> Maybe CoreType)
+ -> CoreSyntax global local
+ -> Either CoreCheckError (CheckedCore global local)
+checkCore globalType localType syntax = do
+ coreType <-
+ inferCore globalType localType syntax
+ pure (CheckedCore coreType syntax)
+
+checkClosedCore
+ :: (global -> Maybe CoreType)
+ -> CoreSyntax global local
+ -> Either CoreCheckError (CheckedCore global Void)
+checkClosedCore globalType syntax = do
+ closedSyntax <-
+ maybe
+ (Left UnboundCoreLocal)
+ Right
+ (traverse (const Nothing) syntax)
+ checkCore globalType absurd closedSyntax
+
+newtype ClosedCheckedProposition global =
+ ClosedCheckedProposition (CheckedCore global Void)
+
+checkedPropositionCore
+ :: ClosedCheckedProposition global
+ -> CheckedCore global Void
+checkedPropositionCore
+ (ClosedCheckedProposition proposition) =
+ proposition
+
+checkClosedProposition
+ :: (global -> Maybe CoreType)
+ -> CoreSyntax global local
+ -> Either CoreCheckError (ClosedCheckedProposition global)
+checkClosedProposition globalType syntax = do
+ checked <-
+ checkClosedCore globalType syntax
+ unless
+ (checkedCoreType checked == TyProp)
+ (Left
+ (ExpectedCoreType
+ TyProp
+ (checkedCoreType checked)))
+ pure (ClosedCheckedProposition checked)
+
+inferCore
+ :: forall global local
+ . (global -> Maybe CoreType)
+ -> (local -> Maybe CoreType)
+ -> CoreSyntax global local
+ -> Either CoreCheckError CoreType
+inferCore globalType localType =
+ infer
+ (maybe
+ (Left UnboundCoreLocal)
+ Right
+ . localType)
+ where
+ infer
+ :: forall local'
+ . (local' -> Either CoreCheckError CoreType)
+ -> CoreSyntax global local'
+ -> Either CoreCheckError CoreType
+ infer resolveLocal = \case
+ CoreLocal local ->
+ resolveLocal local
+ CoreGlobal global ->
+ maybe
+ (Left UnknownCoreGlobal)
+ Right
+ (globalType global)
+ CoreIntrinsic intrinsic ->
+ Right (coreIntrinsicType intrinsic)
+ CoreOpaqueInteger{} ->
+ Right TySet
+ CoreApply function argument -> do
+ functionType <-
+ infer resolveLocal function
+ argumentType <-
+ infer resolveLocal argument
+ case functionType of
+ TyArrow expectedArgument resultType
+ | expectedArgument == argumentType ->
+ Right resultType
+ | otherwise ->
+ Left
+ (ApplicationArgumentTypeMismatch
+ expectedArgument
+ argumentType)
+ other ->
+ Left (AppliedNonFunction other)
+ CoreLambda binderType body -> do
+ bodyType <-
+ infer
+ (boundLocalType binderType resolveLocal)
+ (unscope body)
+ Right (binderType `TyArrow` bodyType)
+ CoreFalsum ->
+ Right TyProp
+ CoreImplication premise conclusion -> do
+ premiseType <-
+ infer resolveLocal premise
+ unless
+ (premiseType == TyProp)
+ (Left
+ (ImplicationOperandTypeMismatch
+ premiseType))
+ conclusionType <-
+ infer resolveLocal conclusion
+ unless
+ (conclusionType == TyProp)
+ (Left
+ (ImplicationOperandTypeMismatch
+ conclusionType))
+ Right TyProp
+ CoreEquality operandType left right -> do
+ leftType <-
+ infer resolveLocal left
+ unless
+ (leftType == operandType)
+ (Left
+ (EqualityOperandTypeMismatch
+ operandType
+ leftType))
+ rightType <-
+ infer resolveLocal right
+ unless
+ (rightType == operandType)
+ (Left
+ (EqualityOperandTypeMismatch
+ operandType
+ rightType))
+ Right TyProp
+ CoreForall binderType body -> do
+ bodyType <-
+ infer
+ (boundLocalType binderType resolveLocal)
+ (unscope body)
+ unless
+ (bodyType == TyProp)
+ (Left
+ (QuantifierBodyTypeMismatch bodyType))
+ Right TyProp
+
+ boundLocalType
+ :: forall local'
+ . CoreType
+ -> (local' -> Either CoreCheckError CoreType)
+ -> Var () (CoreSyntax global local')
+ -> Either CoreCheckError CoreType
+ boundLocalType binderType outerType = \case
+ B () ->
+ Right binderType
+ F outerSyntax ->
+ infer outerType outerSyntax
+
+
+-- | Felix-owned explicit-index syntax. Index zero denotes the nearest
+-- enclosing binder.
+data CanonicalTerm global
+ = CBound !Natural
+ | CGlobal !global
+ | CIntrinsic !CoreIntrinsicTag
+ | COpaqueInteger !Integer
+ | CApp
+ !(CanonicalTerm global)
+ !(CanonicalTerm global)
+ | CLam
+ !CoreType
+ !(CanonicalTerm global)
+ | CFalsum
+ | CImp
+ !(CanonicalTerm global)
+ !(CanonicalTerm global)
+ | CEq
+ !CoreType
+ !(CanonicalTerm global)
+ !(CanonicalTerm global)
+ | CForall
+ !CoreType
+ !(CanonicalTerm global)
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+-- | The fixed checked-core interpretation of set insertion.
+--
+-- Finite-set notation uses this intrinsic HOTG adjunction directly. The
+-- ordinary source-owned @cons@ function is not consulted during lowering.
+canonicalSetInsert
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+canonicalSetInsert element set =
+ CApp
+ (CIntrinsic FamilyUnion)
+ (CApp
+ (CApp
+ (CIntrinsic PairSet)
+ (CApp
+ (CApp
+ (CIntrinsic PairSet)
+ element)
+ element))
+ set)
+
+data FrozenCheckedCore global = FrozenCheckedCore
+ !CoreType
+ !(CanonicalTerm global)
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+frozenCoreType :: FrozenCheckedCore global -> CoreType
+frozenCoreType (FrozenCheckedCore coreType _term) =
+ coreType
+
+frozenCoreTerm :: FrozenCheckedCore global -> CanonicalTerm global
+frozenCoreTerm (FrozenCheckedCore _coreType term) =
+ term
+
+mapFrozenGlobals
+ :: (global -> global')
+ -> FrozenCheckedCore global
+ -> FrozenCheckedCore global'
+mapFrozenGlobals transform
+ (FrozenCheckedCore coreType term) =
+ FrozenCheckedCore
+ coreType
+ (mapCanonicalGlobals transform term)
+
+-- | Recover an operational closed term from a checked frozen value. Any caller
+-- that extends or substitutes it must check the resulting term again.
+thawFrozenCore
+ :: FrozenCheckedCore global
+ -> CoreSyntax global Void
+thawFrozenCore (FrozenCheckedCore _coreType term) =
+ case traverse (const Nothing) (go [] term) of
+ Just closedSyntax ->
+ closedSyntax
+ Nothing ->
+ impossible
+ "a frozen core term became open while being thawed"
+ where
+ go
+ :: [Natural]
+ -> CanonicalTerm global
+ -> CoreSyntax global Natural
+ go binders = \case
+ CBound index ->
+ case lookupBinder index binders of
+ Just local ->
+ coreLocal local
+ Nothing ->
+ impossible
+ "a frozen core term contains an unbound index"
+ CGlobal global ->
+ coreGlobal global
+ CIntrinsic intrinsic ->
+ coreIntrinsic intrinsic
+ COpaqueInteger integer ->
+ coreOpaqueInteger integer
+ CApp function argument ->
+ coreApply
+ (go binders function)
+ (go binders argument)
+ CLam binderType body ->
+ let local =
+ fromIntegral (length binders)
+ in coreLambda
+ binderType
+ local
+ (go (local : binders) body)
+ CFalsum ->
+ coreFalsum
+ CImp premise conclusion ->
+ coreImplication
+ (go binders premise)
+ (go binders conclusion)
+ CEq operandType left right ->
+ coreEquality
+ operandType
+ (go binders left)
+ (go binders right)
+ CForall binderType body ->
+ let local =
+ fromIntegral (length binders)
+ in coreForall
+ binderType
+ local
+ (go (local : binders) body)
+
+ lookupBinder
+ :: Natural
+ -> [Natural]
+ -> Maybe Natural
+ lookupBinder _index [] =
+ Nothing
+ lookupBinder 0 (local : _rest) =
+ Just local
+ lookupBinder index (_local : rest) =
+ lookupBinder (index - 1) rest
+
+frozenCoreGlobals
+ :: Ord global
+ => FrozenCheckedCore global
+ -> Set.Set global
+frozenCoreGlobals =
+ canonicalTermGlobals . frozenCoreTerm
+
+canonicalTermGlobals
+ :: Ord global
+ => CanonicalTerm global
+ -> Set.Set global
+canonicalTermGlobals = \case
+ CBound{} ->
+ mempty
+ CGlobal global ->
+ Set.singleton global
+ CIntrinsic{} ->
+ mempty
+ COpaqueInteger{} ->
+ mempty
+ CApp function argument ->
+ canonicalTermGlobals function
+ <> canonicalTermGlobals argument
+ CLam _binderType body ->
+ canonicalTermGlobals body
+ CFalsum ->
+ mempty
+ CImp premise conclusion ->
+ canonicalTermGlobals premise
+ <> canonicalTermGlobals conclusion
+ CEq _operandType left right ->
+ canonicalTermGlobals left
+ <> canonicalTermGlobals right
+ CForall _binderType body ->
+ canonicalTermGlobals body
+
+-- | A checked canonical term relative to the listed nearest-first binders.
+-- This is the construction boundary used by kernel replay; it carries no fact
+-- authority.
+data ScopedCheckedCore global = ScopedCheckedCore
+ ![CoreType]
+ !CoreType
+ !(CanonicalTerm global)
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+scopedCoreContext
+ :: ScopedCheckedCore global
+ -> [CoreType]
+scopedCoreContext
+ (ScopedCheckedCore context _coreType _term) =
+ context
+
+scopedCoreType
+ :: ScopedCheckedCore global
+ -> CoreType
+scopedCoreType
+ (ScopedCheckedCore _context coreType _term) =
+ coreType
+
+scopedCoreTerm
+ :: ScopedCheckedCore global
+ -> CanonicalTerm global
+scopedCoreTerm
+ (ScopedCheckedCore _context _coreType term) =
+ term
+
+mapScopedGlobals
+ :: (left -> right)
+ -> ScopedCheckedCore left
+ -> ScopedCheckedCore right
+mapScopedGlobals transform
+ (ScopedCheckedCore context coreType term) =
+ ScopedCheckedCore
+ context
+ coreType
+ (mapCanonicalGlobals transform term)
+
+checkScopedCanonicalCore
+ :: (global -> Maybe CoreType)
+ -> [CoreType]
+ -> CanonicalTerm global
+ -> Either CoreCheckError (ScopedCheckedCore global)
+checkScopedCanonicalCore globalType context term =
+ ScopedCheckedCore context
+ <$> inferCanonicalCore globalType context term
+ <*> pure term
+
+-- | Regard a closed term under a larger lexical context. Closed canonical
+-- terms contain no indices, so this does not shift the term.
+embedClosedCore
+ :: [CoreType]
+ -> FrozenCheckedCore global
+ -> ScopedCheckedCore global
+embedClosedCore context
+ (FrozenCheckedCore coreType term) =
+ ScopedCheckedCore context coreType term
+
+-- | Add one nearest binder to an already checked lexical context.
+weakenCheckedScopedCore
+ :: CoreType
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+weakenCheckedScopedCore binderType scoped =
+ ScopedCheckedCore
+ (binderType : scopedCoreContext scoped)
+ (scopedCoreType scoped)
+ (shiftCanonical 1 0 (scopedCoreTerm scoped))
+
+-- | Add one nearest binder to a checked lexical context.
+weakenScopedCore
+ :: (global -> Maybe CoreType)
+ -> CoreType
+ -> ScopedCheckedCore global
+ -> Either CoreCheckError (ScopedCheckedCore global)
+weakenScopedCore globalType binderType scoped =
+ checkScopedCanonicalCore
+ globalType
+ (binderType : scopedCoreContext scoped)
+ (shiftCanonical 1 0
+ (scopedCoreTerm scoped))
+
+-- | Introduce a fresh set-valued local definition. Separation specializes
+-- the checked foundation characteristic so its local premise remains
+-- first-order.
+scopedSetDefinition
+ :: Eq global
+ => FrozenCheckedCore Void
+ -> ScopedCheckedCore global
+ -> Maybe (ScopedCheckedCore global)
+scopedSetDefinition
+ characteristic
+ expression@(ScopedCheckedCore context TySet term) =
+ case term of
+ CApp
+ (CApp (CIntrinsic Sep) bound)
+ predicate@(CLam TySet _body) ->
+ scopedCharacteristicDefinition
+ characteristic
+ expression
+ ( ScopedCheckedCore context TySet bound
+ :| [ ScopedCheckedCore
+ context
+ (TySet `TyArrow` TyProp)
+ predicate
+ ]
+ )
+ _ ->
+ Just
+ (ScopedCheckedCore
+ (TySet : context)
+ TyProp
+ (CEq
+ TySet
+ (CBound 0)
+ (shiftCanonical 1 0 term)))
+scopedSetDefinition _characteristic _expression =
+ Nothing
+
+-- | Specialize a checked characteristic and abstract its set-valued target
+-- into one fresh nearest binder. Checked substitution and beta reduction
+-- preserve the foundation row's proposition type.
+scopedCharacteristicDefinition
+ :: Eq global
+ => FrozenCheckedCore Void
+ -> ScopedCheckedCore global
+ -> NonEmpty (ScopedCheckedCore global)
+ -> Maybe (ScopedCheckedCore global)
+scopedCharacteristicDefinition
+ (FrozenCheckedCore TyProp frozen)
+ (ScopedCheckedCore context TySet target)
+ arguments
+ | all ((== context) . scopedCoreContext) arguments = do
+ specialized <-
+ specialize
+ (mapCanonicalGlobals absurd frozen)
+ (toList arguments)
+ let normalized = betaNormalizeCanonical specialized
+ (found, abstracted) = abstractTarget 0 normalized
+ guard found
+ pure
+ (ScopedCheckedCore
+ (TySet : context)
+ TyProp
+ abstracted)
+ where
+ specialize term [] =
+ Just term
+ specialize (CForall binderType body)
+ (ScopedCheckedCore _ argumentType argument : rest)
+ | binderType == argumentType =
+ specialize
+ (instantiateCanonical argument body)
+ rest
+ specialize _term _arguments =
+ Nothing
+
+ abstractTarget depth term
+ | term == shiftCanonical depth 0 target =
+ (True, CBound (fromIntegral depth))
+ | otherwise =
+ case term of
+ CBound index
+ | index < fromIntegral depth ->
+ (False, CBound index)
+ | otherwise ->
+ (False, CBound (index + 1))
+ CGlobal global ->
+ (False, CGlobal global)
+ CIntrinsic intrinsic ->
+ (False, CIntrinsic intrinsic)
+ COpaqueInteger integer ->
+ (False, COpaqueInteger integer)
+ CApp function argument ->
+ combine CApp
+ (abstractTarget depth function)
+ (abstractTarget depth argument)
+ CLam binderType body ->
+ let (found, abstracted) =
+ abstractTarget (depth + 1) body
+ in (found, CLam binderType abstracted)
+ CFalsum ->
+ (False, CFalsum)
+ CImp premise conclusion ->
+ combine CImp
+ (abstractTarget depth premise)
+ (abstractTarget depth conclusion)
+ CEq operandType left right ->
+ combine (CEq operandType)
+ (abstractTarget depth left)
+ (abstractTarget depth right)
+ CForall binderType body ->
+ let (found, abstracted) =
+ abstractTarget (depth + 1) body
+ in (found, CForall binderType abstracted)
+
+ combine constructor (leftFound, left) (rightFound, right) =
+ (leftFound || rightFound, constructor left right)
+scopedCharacteristicDefinition _characteristic _target _arguments =
+ Nothing
+
+-- | Build the replacement graph of one checked set-valued local function.
+-- The ordered-pair constructor is an ordinary checked source object.
+scopedReplacementGraph
+ :: ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Maybe
+ ( ScopedCheckedCore global
+ , ScopedCheckedCore global
+ , ScopedCheckedCore global
+ )
+scopedReplacementGraph
+ (ScopedCheckedCore context pairType pair)
+ domain@(ScopedCheckedCore domainContext TySet domainTerm)
+ (ScopedCheckedCore valueContext TySet value)
+ | pairType == TySet `TyArrow` (TySet `TyArrow` TySet)
+ , domainContext == context
+ , valueContext == TySet : context =
+ let pairValue =
+ CApp
+ (CApp
+ (shiftCanonical 1 0 pair)
+ (CBound 0))
+ value
+ function = CLam TySet pairValue
+ graph =
+ CApp
+ (CApp (CIntrinsic Repl) domainTerm)
+ function
+ in Just
+ ( ScopedCheckedCore context TySet graph
+ , domain
+ , ScopedCheckedCore
+ context
+ (TySet `TyArrow` TySet)
+ function
+ )
+scopedReplacementGraph _pair _domain _value =
+ Nothing
+
+betaNormalizeCanonical
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+betaNormalizeCanonical = \case
+ CApp function argument ->
+ case betaNormalizeCanonical function of
+ CLam _binderType body ->
+ betaNormalizeCanonical
+ (instantiateCanonical
+ (betaNormalizeCanonical argument)
+ body)
+ normalizedFunction ->
+ CApp
+ normalizedFunction
+ (betaNormalizeCanonical argument)
+ CLam binderType body ->
+ CLam binderType (betaNormalizeCanonical body)
+ CImp premise conclusion ->
+ CImp
+ (betaNormalizeCanonical premise)
+ (betaNormalizeCanonical conclusion)
+ CEq operandType left right ->
+ CEq operandType
+ (betaNormalizeCanonical left)
+ (betaNormalizeCanonical right)
+ CForall binderType body ->
+ CForall binderType (betaNormalizeCanonical body)
+ term -> term
+
+-- | Combine two checked propositions under the same lexical context.
+implyScopedCore
+ :: ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Maybe (ScopedCheckedCore global)
+implyScopedCore
+ (ScopedCheckedCore premiseContext TyProp premise)
+ (ScopedCheckedCore conclusionContext TyProp conclusion)
+ | premiseContext == conclusionContext =
+ Just
+ (ScopedCheckedCore
+ premiseContext
+ TyProp
+ (CImp premise conclusion))
+implyScopedCore _premise _conclusion =
+ Nothing
+
+-- | Form an equality between checked operands under the same lexical
+-- context. This preserves the checked-core invariant without requiring a
+-- caller to recover global types merely to combine already checked terms.
+equalScopedCore
+ :: ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Maybe (ScopedCheckedCore global)
+equalScopedCore
+ (ScopedCheckedCore leftContext leftType left)
+ (ScopedCheckedCore rightContext rightType right)
+ | leftContext == rightContext
+ , leftType == rightType =
+ Just
+ (ScopedCheckedCore
+ leftContext
+ TyProp
+ (CEq leftType left right))
+equalScopedCore _left _right =
+ Nothing
+
+-- | Conjoin two checked propositions under the same lexical context. Truth
+-- is normalized away so callers can build an optional source guard without
+-- retaining an inert conjunct.
+conjoinScopedCore
+ :: Eq global
+ => ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Maybe (ScopedCheckedCore global)
+conjoinScopedCore
+ left@(ScopedCheckedCore leftContext TyProp leftTerm)
+ right@(ScopedCheckedCore rightContext TyProp rightTerm)
+ | leftContext == rightContext
+ , leftTerm == truth = Just right
+ | leftContext == rightContext
+ , rightTerm == truth = Just left
+ | leftContext == rightContext =
+ Just
+ (ScopedCheckedCore
+ leftContext
+ TyProp
+ (CImp
+ (CImp leftTerm (CImp rightTerm CFalsum))
+ CFalsum))
+ where
+ truth = CImp CFalsum CFalsum
+conjoinScopedCore _left _right =
+ Nothing
+
+-- | Disjoin two checked propositions under the same lexical context using
+-- the fixed classical encoding owned by the checked core.
+disjoinScopedCore
+ :: ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Maybe (ScopedCheckedCore global)
+disjoinScopedCore
+ (ScopedCheckedCore leftContext TyProp left)
+ (ScopedCheckedCore rightContext TyProp right)
+ | leftContext == rightContext =
+ Just
+ (ScopedCheckedCore
+ leftContext
+ TyProp
+ (CImp (CImp left CFalsum) right))
+disjoinScopedCore _left _right =
+ Nothing
+
+-- | Negate a checked proposition without changing its lexical context.
+negateScopedCore
+ :: ScopedCheckedCore global
+ -> Maybe (ScopedCheckedCore global)
+negateScopedCore (ScopedCheckedCore context TyProp proposition) =
+ Just
+ (ScopedCheckedCore
+ context
+ TyProp
+ (CImp proposition CFalsum))
+negateScopedCore _proposition =
+ Nothing
+
+-- | Checked falsum at an already established lexical context.
+falsumScopedCore :: [CoreType] -> ScopedCheckedCore global
+falsumScopedCore context =
+ ScopedCheckedCore context TyProp CFalsum
+
+-- | Split a checked set equality into its two extensionality directions.
+splitScopedSetEquality
+ :: ScopedCheckedCore global
+ -> Maybe
+ ( ScopedCheckedCore global
+ , ScopedCheckedCore global
+ )
+splitScopedSetEquality
+ (ScopedCheckedCore context TyProp (CEq TySet left right)) =
+ Just (subset left right, subset right left)
+ where
+ subset source target =
+ ScopedCheckedCore
+ context
+ TyProp
+ (CForall
+ TySet
+ (CImp
+ (memberOf (shiftCanonical 1 0 source))
+ (memberOf (shiftCanonical 1 0 target))))
+
+ memberOf set =
+ CApp
+ (CApp
+ (CIntrinsic Member)
+ (CBound 0))
+ set
+splitScopedSetEquality _proposition =
+ Nothing
+
+-- | Derive the exact predicate, member-wise hypothesis, induction step, and
+-- binder-level result for one set-valued ambient binder. The selected binder
+-- is replaced by the newly introduced set variable; every other ambient
+-- binder remains a parameter.
+scopedSetInductionInstance
+ :: Natural
+ -> ScopedCheckedCore global
+ -> Maybe
+ ( ScopedCheckedCore global
+ , ScopedCheckedCore global
+ , ScopedCheckedCore global
+ , ScopedCheckedCore global
+ )
+scopedSetInductionInstance selected
+ (ScopedCheckedCore context TyProp property)
+ | binderTypeAt selected context == Just TySet =
+ Just (predicate, hypothesis, step, result)
+ where
+ abstractedProperty = abstractSelected 0 property
+ predicate =
+ ScopedCheckedCore
+ context
+ (TySet `TyArrow` TyProp)
+ (CLam TySet abstractedProperty)
+ hypothesis =
+ ScopedCheckedCore
+ context
+ TyProp
+ (CForall
+ TySet
+ (CImp
+ (CApp
+ (CApp
+ (CIntrinsic Member)
+ (CBound 0))
+ (CBound (selected + 1)))
+ abstractedProperty))
+ step =
+ ScopedCheckedCore
+ context
+ TyProp
+ (CForall
+ TySet
+ (CImp
+ (abstractSelected
+ 0
+ (scopedCoreTerm hypothesis))
+ abstractedProperty))
+ result =
+ ScopedCheckedCore
+ context
+ TyProp
+ (CForall TySet abstractedProperty)
+
+ abstractSelected depth = \case
+ CBound index
+ | index == depth + selected ->
+ CBound depth
+ | index >= depth ->
+ CBound (index + 1)
+ | otherwise ->
+ CBound index
+ CGlobal global ->
+ CGlobal global
+ CIntrinsic intrinsic ->
+ CIntrinsic intrinsic
+ COpaqueInteger integer ->
+ COpaqueInteger integer
+ CApp function argument ->
+ CApp
+ (abstractSelected depth function)
+ (abstractSelected depth argument)
+ CLam binderType body ->
+ CLam binderType
+ (abstractSelected (depth + 1) body)
+ CFalsum ->
+ CFalsum
+ CImp premise conclusion ->
+ CImp
+ (abstractSelected depth premise)
+ (abstractSelected depth conclusion)
+ CEq operandType left right ->
+ CEq operandType
+ (abstractSelected depth left)
+ (abstractSelected depth right)
+ CForall binderType body ->
+ CForall binderType
+ (abstractSelected (depth + 1) body)
+scopedSetInductionInstance _selected _property =
+ Nothing
+
+-- | Close the nearest checked binder as one leading universal.
+closeScopedForall
+ :: ScopedCheckedCore global
+ -> Maybe (ScopedCheckedCore global)
+closeScopedForall
+ (ScopedCheckedCore (binderType : context) TyProp body) =
+ Just
+ (ScopedCheckedCore
+ context
+ TyProp
+ (CForall binderType body))
+closeScopedForall _scoped =
+ Nothing
+
+-- | Close the nearest checked binder as one leading existential.
+closeScopedExists
+ :: ScopedCheckedCore global
+ -> Maybe (ScopedCheckedCore global)
+closeScopedExists
+ (ScopedCheckedCore (binderType : context) TyProp body) =
+ Just
+ (ScopedCheckedCore
+ context
+ TyProp
+ (CImp
+ (CForall binderType (CImp body CFalsum))
+ CFalsum))
+closeScopedExists _scoped =
+ Nothing
+
+-- | Open one checked leading universal without rechecking its body.
+openScopedForall
+ :: ScopedCheckedCore global
+ -> Maybe (CoreType, ScopedCheckedCore global)
+openScopedForall
+ (ScopedCheckedCore context TyProp
+ (CForall binderType body)) =
+ Just
+ ( binderType
+ , ScopedCheckedCore
+ (binderType : context)
+ TyProp
+ body
+ )
+openScopedForall _scoped =
+ Nothing
+
+-- | Split one checked implication under its unchanged ambient context.
+openScopedImplication
+ :: ScopedCheckedCore global
+ -> Maybe
+ ( ScopedCheckedCore global
+ , ScopedCheckedCore global
+ )
+openScopedImplication
+ (ScopedCheckedCore context TyProp
+ (CImp premise conclusion)) =
+ Just
+ ( ScopedCheckedCore context TyProp premise
+ , ScopedCheckedCore context TyProp conclusion
+ )
+openScopedImplication _scoped =
+ Nothing
+
+-- | Open a checked proof assumption against the current goal. Besides a
+-- direct implication antecedent, the source language historically permits
+-- either immediate side of one binary conjunction antecedent to be assumed
+-- first. The other side remains the next implication antecedent. This is a
+-- deliberately shallow structural rule: it neither flattens conjunctions nor
+-- treats disjunction as an eliminable assumption.
+openScopedAssumption
+ :: Eq global
+ => ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Maybe
+ ( ScopedCheckedCore global
+ , ScopedCheckedCore global
+ )
+openScopedAssumption supplied goal = do
+ (antecedent, conclusion) <- openScopedImplication goal
+ if supplied == antecedent
+ then pure (antecedent, conclusion)
+ else do
+ (left, right) <- splitScopedConjunction antecedent
+ if supplied == left
+ then do
+ remaining <- implyScopedCore right conclusion
+ pure (left, remaining)
+ else if supplied == right
+ then do
+ remaining <- implyScopedCore left conclusion
+ pure (right, remaining)
+ else Nothing
+
+splitScopedConjunction
+ :: ScopedCheckedCore global
+ -> Maybe
+ ( ScopedCheckedCore global
+ , ScopedCheckedCore global
+ )
+splitScopedConjunction
+ (ScopedCheckedCore context TyProp
+ (CImp (CImp left (CImp right CFalsum)) CFalsum)) =
+ Just
+ ( ScopedCheckedCore context TyProp left
+ , ScopedCheckedCore context TyProp right
+ )
+splitScopedConjunction _scoped =
+ Nothing
+
+closeScopedCore
+ :: ScopedCheckedCore global
+ -> Maybe (FrozenCheckedCore global)
+closeScopedCore
+ (ScopedCheckedCore [] coreType term) =
+ Just (FrozenCheckedCore coreType term)
+closeScopedCore ScopedCheckedCore{} =
+ Nothing
+
+-- | Substitute an outer-context term for index zero and remove that binder.
+instantiateCanonical
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+instantiateCanonical argument =
+ instantiateAt 0
+ where
+ instantiateAt depth = \case
+ CBound index
+ | index == depth ->
+ shiftCanonical depth 0 argument
+ | index > depth ->
+ CBound (index - 1)
+ | otherwise ->
+ CBound index
+ CGlobal global ->
+ CGlobal global
+ CIntrinsic intrinsic ->
+ CIntrinsic intrinsic
+ COpaqueInteger integer ->
+ COpaqueInteger integer
+ CApp function operand ->
+ CApp
+ (instantiateAt depth function)
+ (instantiateAt depth operand)
+ CLam binderType body ->
+ CLam binderType
+ (instantiateAt (depth + 1) body)
+ CFalsum ->
+ CFalsum
+ CImp premise conclusion ->
+ CImp
+ (instantiateAt depth premise)
+ (instantiateAt depth conclusion)
+ CEq operandType left right ->
+ CEq operandType
+ (instantiateAt depth left)
+ (instantiateAt depth right)
+ CForall binderType body ->
+ CForall binderType
+ (instantiateAt (depth + 1) body)
+
+mapCanonicalGlobals
+ :: (global -> global')
+ -> CanonicalTerm global
+ -> CanonicalTerm global'
+mapCanonicalGlobals transform = \case
+ CBound index ->
+ CBound index
+ CGlobal global ->
+ CGlobal (transform global)
+ CIntrinsic intrinsic ->
+ CIntrinsic intrinsic
+ COpaqueInteger integer ->
+ COpaqueInteger integer
+ CApp function argument ->
+ CApp
+ (mapCanonicalGlobals transform function)
+ (mapCanonicalGlobals transform argument)
+ CLam binderType body ->
+ CLam binderType
+ (mapCanonicalGlobals transform body)
+ CFalsum ->
+ CFalsum
+ CImp premise conclusion ->
+ CImp
+ (mapCanonicalGlobals transform premise)
+ (mapCanonicalGlobals transform conclusion)
+ CEq operandType left right ->
+ CEq operandType
+ (mapCanonicalGlobals transform left)
+ (mapCanonicalGlobals transform right)
+ CForall binderType body ->
+ CForall binderType
+ (mapCanonicalGlobals transform body)
+
+-- | Recheck a nameless term without exposing the checked wrapper constructor.
+checkCanonicalCore
+ :: (global -> Maybe CoreType)
+ -> CanonicalTerm global
+ -> Either CoreCheckError (FrozenCheckedCore global)
+checkCanonicalCore globalType term =
+ FrozenCheckedCore
+ <$> inferCanonicalCore globalType [] term
+ <*> pure term
+
+inferCanonicalCore
+ :: (global -> Maybe CoreType)
+ -> [CoreType]
+ -> CanonicalTerm global
+ -> Either CoreCheckError CoreType
+inferCanonicalCore globalType binders = \case
+ CBound index ->
+ maybe
+ (Left (UnboundCoreIndex index))
+ Right
+ (binderTypeAt index binders)
+ CGlobal global ->
+ maybe
+ (Left UnknownCoreGlobal)
+ Right
+ (globalType global)
+ CIntrinsic intrinsic ->
+ Right (coreIntrinsicType intrinsic)
+ COpaqueInteger{} ->
+ Right TySet
+ CApp function argument -> do
+ functionType <-
+ inferCanonicalCore globalType binders function
+ argumentType <-
+ inferCanonicalCore globalType binders argument
+ case functionType of
+ TyArrow expectedArgument resultType
+ | expectedArgument == argumentType ->
+ Right resultType
+ | otherwise ->
+ Left
+ (ApplicationArgumentTypeMismatch
+ expectedArgument
+ argumentType)
+ other ->
+ Left (AppliedNonFunction other)
+ CLam binderType body -> do
+ bodyType <-
+ inferCanonicalCore
+ globalType
+ (binderType : binders)
+ body
+ Right (binderType `TyArrow` bodyType)
+ CFalsum ->
+ Right TyProp
+ CImp premise conclusion -> do
+ premiseType <-
+ inferCanonicalCore globalType binders premise
+ unless
+ (premiseType == TyProp)
+ (Left
+ (ImplicationOperandTypeMismatch
+ premiseType))
+ conclusionType <-
+ inferCanonicalCore globalType binders conclusion
+ unless
+ (conclusionType == TyProp)
+ (Left
+ (ImplicationOperandTypeMismatch
+ conclusionType))
+ Right TyProp
+ CEq operandType left right -> do
+ leftType <-
+ inferCanonicalCore globalType binders left
+ unless
+ (leftType == operandType)
+ (Left
+ (EqualityOperandTypeMismatch
+ operandType
+ leftType))
+ rightType <-
+ inferCanonicalCore globalType binders right
+ unless
+ (rightType == operandType)
+ (Left
+ (EqualityOperandTypeMismatch
+ operandType
+ rightType))
+ Right TyProp
+ CForall binderType body -> do
+ bodyType <-
+ inferCanonicalCore
+ globalType
+ (binderType : binders)
+ body
+ unless
+ (bodyType == TyProp)
+ (Left
+ (QuantifierBodyTypeMismatch bodyType))
+ Right TyProp
+
+binderTypeAt :: Natural -> [CoreType] -> Maybe CoreType
+binderTypeAt _index [] =
+ Nothing
+binderTypeAt 0 (binderType : _rest) =
+ Just binderType
+binderTypeAt index (_binderType : rest) =
+ binderTypeAt (index - 1) rest
+
+shiftCanonical
+ :: Natural
+ -> Natural
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+shiftCanonical amount cutoff = \case
+ CBound index
+ | index >= cutoff ->
+ CBound (index + amount)
+ | otherwise ->
+ CBound index
+ CGlobal global ->
+ CGlobal global
+ CIntrinsic intrinsic ->
+ CIntrinsic intrinsic
+ COpaqueInteger integer ->
+ COpaqueInteger integer
+ CApp function argument ->
+ CApp
+ (shiftCanonical amount cutoff function)
+ (shiftCanonical amount cutoff argument)
+ CLam binderType body ->
+ CLam binderType
+ (shiftCanonical amount (cutoff + 1) body)
+ CFalsum ->
+ CFalsum
+ CImp premise conclusion ->
+ CImp
+ (shiftCanonical amount cutoff premise)
+ (shiftCanonical amount cutoff conclusion)
+ CEq operandType left right ->
+ CEq operandType
+ (shiftCanonical amount cutoff left)
+ (shiftCanonical amount cutoff right)
+ CForall binderType body ->
+ CForall binderType
+ (shiftCanonical amount (cutoff + 1) body)
+
+data FreezeError
+ = FreeLocalInClosedCore
+ deriving stock (Show, Eq)
+
+-- | Freeze a checked closed term in one traversal of the operational syntax.
+freezeClosed
+ :: CheckedCore global Void
+ -> Either FreezeError (FrozenCheckedCore global)
+freezeClosed (CheckedCore coreType syntax) =
+ FrozenCheckedCore coreType
+ <$> optimizedFreeze 0 rootResolver syntax
+ where
+ rootResolver _depth =
+ absurd
+
+-- | Bounded executable oracle for tests. Production code uses 'freezeClosed'.
+referenceFreezeClosed
+ :: CheckedCore global Void
+ -> Either FreezeError (FrozenCheckedCore global)
+referenceFreezeClosed (CheckedCore coreType syntax) =
+ FrozenCheckedCore coreType
+ <$> referenceFreeze 0 rootResolver syntax
+ where
+ rootResolver _depth =
+ absurd
+
+type VariableResolver local global =
+ Natural
+ -> local
+ -> Either FreezeError (CanonicalTerm global)
+
+optimizedFreeze
+ :: Natural
+ -> VariableResolver local global
+ -> CoreSyntax global local
+ -> Either FreezeError (CanonicalTerm global)
+optimizedFreeze depth resolve = \case
+ CoreLocal local ->
+ resolve depth local
+ CoreGlobal global ->
+ Right (CGlobal global)
+ CoreIntrinsic intrinsic ->
+ Right (CIntrinsic intrinsic)
+ CoreOpaqueInteger integer ->
+ Right (COpaqueInteger integer)
+ CoreApply function argument ->
+ CApp
+ <$> optimizedFreeze depth resolve function
+ <*> optimizedFreeze depth resolve argument
+ CoreLambda binderType body ->
+ CLam binderType
+ <$> optimizedFreeze
+ (depth + 1)
+ (resolveGeneralized depth resolve)
+ (unscope body)
+ CoreFalsum ->
+ Right CFalsum
+ CoreImplication premise conclusion ->
+ CImp
+ <$> optimizedFreeze depth resolve premise
+ <*> optimizedFreeze depth resolve conclusion
+ CoreEquality operandType left right ->
+ CEq operandType
+ <$> optimizedFreeze depth resolve left
+ <*> optimizedFreeze depth resolve right
+ CoreForall binderType body ->
+ CForall binderType
+ <$> optimizedFreeze
+ (depth + 1)
+ (resolveGeneralized depth resolve)
+ (unscope body)
+ where
+ resolveGeneralized
+ :: Natural
+ -> VariableResolver local global
+ -> VariableResolver
+ (Var () (CoreSyntax global local))
+ global
+ resolveGeneralized binderLevel outerResolve currentDepth = \case
+ B () ->
+ Right
+ (CBound
+ (currentDepth - binderLevel - 1))
+ F outerSyntax ->
+ optimizedFreeze
+ currentDepth
+ outerResolve
+ outerSyntax
+
+referenceFreeze
+ :: Natural
+ -> VariableResolver local global
+ -> CoreSyntax global local
+ -> Either FreezeError (CanonicalTerm global)
+referenceFreeze depth resolve = \case
+ CoreLocal local ->
+ resolve depth local
+ CoreGlobal global ->
+ Right (CGlobal global)
+ CoreIntrinsic intrinsic ->
+ Right (CIntrinsic intrinsic)
+ CoreOpaqueInteger integer ->
+ Right (COpaqueInteger integer)
+ CoreApply function argument ->
+ CApp
+ <$> referenceFreeze depth resolve function
+ <*> referenceFreeze depth resolve argument
+ CoreLambda binderType body ->
+ CLam binderType
+ <$> referenceFreeze
+ (depth + 1)
+ (resolveNormalized depth resolve)
+ (fromScope body)
+ CoreFalsum ->
+ Right CFalsum
+ CoreImplication premise conclusion ->
+ CImp
+ <$> referenceFreeze depth resolve premise
+ <*> referenceFreeze depth resolve conclusion
+ CoreEquality operandType left right ->
+ CEq operandType
+ <$> referenceFreeze depth resolve left
+ <*> referenceFreeze depth resolve right
+ CoreForall binderType body ->
+ CForall binderType
+ <$> referenceFreeze
+ (depth + 1)
+ (resolveNormalized depth resolve)
+ (fromScope body)
+ where
+ resolveNormalized
+ :: Natural
+ -> VariableResolver local global
+ -> VariableResolver (Var () local) global
+ resolveNormalized binderLevel outerResolve currentDepth = \case
+ B () ->
+ Right
+ (CBound
+ (currentDepth - binderLevel - 1))
+ F outerLocal ->
+ outerResolve currentDepth outerLocal
diff --git a/source/Felix/Checking/Datatype.hs b/source/Felix/Checking/Datatype.hs
new file mode 100644
index 0000000..66e64db
--- /dev/null
+++ b/source/Felix/Checking/Datatype.hs
@@ -0,0 +1,670 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE NamedFieldPuns #-}
+
+module Felix.Checking.Datatype
+ ( CheckedDatatype
+ , DatatypeValidationError
+ , datatypeValidationErrorLocation
+ , renderDatatypeValidationError
+ , prepareCheckedDatatype
+ , checkedDatatypeHeadSymbol
+ , checkedDatatypeConstructorSymbols
+ , CheckedDatatypeClauseView(..)
+ , CheckedDatatypePremiseView(..)
+ , checkedDatatypeClauseViews
+ , checkedDatatypeGeneratedFacts
+ ) where
+
+import Base
+import Felix.Report.Location
+import Felix.Syntax.Internal
+import Felix.Syntax.Lexicon
+
+import Data.List qualified as List
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+
+
+-- | A datatype whose premise domains have been canonicalized and validated.
+data CheckedDatatype = CheckedDatatype
+ { checkedDatatypeHead :: !FunctionSymbol
+ , checkedDatatypeClauses :: !(NonEmpty CheckedDatatypeClause)
+ }
+
+data CheckedDatatypeClause = CheckedDatatypeClause
+ { checkedDatatypeClauseConstructor :: !FunctionSymbol
+ , checkedDatatypeClauseConstructorArgs :: ![VarSymbol]
+ , checkedDatatypeClausePremises :: ![CheckedDatatypePremise]
+ }
+
+-- Direct premises retain their canonical term for generated-fact locations.
+data CheckedDatatypePremise
+ = RecursiveDatatypePremise !VarSymbol !Expr
+ | NonRecursiveDatatypePremise !VarSymbol !Expr
+
+data DatatypeValidationError
+ = InvalidDatatype !Text
+ | InvalidDatatypePremise !Location !Text
+ deriving (Show, Eq)
+
+datatypeValidationErrorLocation
+ :: DatatypeValidationError
+ -> Maybe Location
+datatypeValidationErrorLocation = \case
+ InvalidDatatype _message -> Nothing
+ InvalidDatatypePremise location _message -> Just location
+
+renderDatatypeValidationError :: DatatypeValidationError -> Text
+renderDatatypeValidationError = \case
+ InvalidDatatype message -> message
+ InvalidDatatypePremise _location message -> message
+
+data CanonicalDatatypeClause = CanonicalDatatypeClause
+ { canonicalDatatypeClauseConstructor :: !SymbolPattern
+ , canonicalDatatypeClausePremises :: ![(VarSymbol, Location, Expr)]
+ }
+
+-- | Read-only normalized clause data for exact typed lowering.
+data CheckedDatatypeClauseView = CheckedDatatypeClauseView
+ { checkedDatatypeClauseViewConstructor :: !FunctionSymbol
+ , checkedDatatypeClauseViewArguments :: ![VarSymbol]
+ , checkedDatatypeClauseViewPremises
+ :: ![CheckedDatatypePremiseView]
+ } deriving (Show, Eq)
+
+data CheckedDatatypePremiseView
+ = CheckedRecursiveDatatypePremise !VarSymbol !Expr
+ | CheckedNonRecursiveDatatypePremise !VarSymbol !Expr
+ deriving (Show, Eq)
+
+-- | Canonicalize every premise domain once, then validate the declaration.
+prepareCheckedDatatype
+ :: Monad m
+ => (Expr -> m Expr)
+ -> Datatype
+ -> m (Either DatatypeValidationError CheckedDatatype)
+prepareCheckedDatatype canonicalizeDomain Datatype{datatypeHead, datatypeClauses} = do
+ canonicalClauses <- traverse canonicalizeClause datatypeClauses
+ pure (validateDatatype datatypeHead canonicalClauses)
+ where
+ canonicalizeClause DatatypeClause
+ { datatypeClauseConstructor
+ , datatypeClausePremises
+ } = do
+ premises <-
+ traverse
+ (\(var, domain) ->
+ (\canonicalDomain ->
+ (var, exprLocation domain, canonicalDomain))
+ <$> canonicalizeDomain domain)
+ datatypeClausePremises
+ pure
+ CanonicalDatatypeClause
+ { canonicalDatatypeClauseConstructor =
+ datatypeClauseConstructor
+ , canonicalDatatypeClausePremises = premises
+ }
+
+validateDatatype
+ :: SymbolPattern
+ -> NonEmpty CanonicalDatatypeClause
+ -> Either DatatypeValidationError CheckedDatatype
+validateDatatype
+ (SymbolPattern datatypeSymbol datatypeArgs)
+ datatypeClauses = do
+ require
+ (null datatypeArgs)
+ "datatype head must be nullary"
+ let constructorSymbols =
+ [ constructorSymbol
+ | CanonicalDatatypeClause
+ { canonicalDatatypeClauseConstructor =
+ SymbolPattern constructorSymbol _
+ } <- NonEmpty.toList datatypeClauses
+ ]
+ duplicateConstructors =
+ duplicateDatatypeConstructorSymbols constructorSymbols
+ require
+ (Set.null duplicateConstructors)
+ ( "datatype constructor patterns must be distinct: "
+ <> formatDatatypeConstructors duplicateConstructors
+ )
+ checkedDatatypeClauses <-
+ traverse (validateDatatypeClause datatypeSymbol) datatypeClauses
+ pure
+ CheckedDatatype
+ { checkedDatatypeHead = datatypeSymbol
+ , checkedDatatypeClauses
+ }
+
+validateDatatypeClause
+ :: FunctionSymbol
+ -> CanonicalDatatypeClause
+ -> Either DatatypeValidationError CheckedDatatypeClause
+validateDatatypeClause
+ datatypeSymbol
+ CanonicalDatatypeClause
+ { canonicalDatatypeClauseConstructor =
+ SymbolPattern constructorSymbol constructorArgs
+ , canonicalDatatypeClausePremises
+ } = do
+ require
+ (constructorSymbol /= ApplySymbol)
+ "datatype constructor cannot be function application"
+ require
+ (constructorSymbol /= datatypeSymbol)
+ "datatype constructor cannot reuse the datatype symbol"
+ let duplicateConstructorArgs = duplicateVars constructorArgs
+ require
+ (Set.null duplicateConstructorArgs)
+ ( "datatype constructor arguments must be linear: "
+ <> formatVars duplicateConstructorArgs
+ )
+ let premiseVars =
+ (\(variable, _location, _domain) -> variable)
+ <$> canonicalDatatypeClausePremises
+ duplicatePremiseVars = duplicateVars premiseVars
+ require
+ (Set.null duplicatePremiseVars)
+ ( "datatype premise variables must be linear: "
+ <> formatVars duplicatePremiseVars
+ )
+ let missingPremises =
+ Set.fromList constructorArgs
+ `Set.difference` Set.fromList premiseVars
+ require
+ (Set.null missingPremises)
+ ( "datatype constructor argument(s) missing premise: "
+ <> formatVars missingPremises
+ )
+ checkedDatatypeClausePremises <-
+ traverse
+ (validateDatatypePremise datatypeSymbol)
+ canonicalDatatypeClausePremises
+ pure
+ CheckedDatatypeClause
+ { checkedDatatypeClauseConstructor = constructorSymbol
+ , checkedDatatypeClauseConstructorArgs = constructorArgs
+ , checkedDatatypeClausePremises =
+ checkedDatatypeClausePremises
+ }
+
+validateDatatypePremise
+ :: FunctionSymbol
+ -> (VarSymbol, Location, Expr)
+ -> Either DatatypeValidationError CheckedDatatypePremise
+validateDatatypePremise datatypeSymbol (var, location, domain) = do
+ let domainFreeVars = freeVars domain
+ requirePremise location
+ (Set.null domainFreeVars)
+ ( "datatype premise domains must be closed terms: "
+ <> formatVars domainFreeVars
+ )
+ if equivalent domain datatypeCarrier
+ then
+ Right (RecursiveDatatypePremise var domain)
+ else do
+ requirePremise location
+ ( SymbolMixfix datatypeSymbol
+ `Set.notMember` mentionedSymbols domain
+ )
+ "datatype recursive premise must be direct"
+ Right (NonRecursiveDatatypePremise var domain)
+ where
+ datatypeCarrier =
+ TermSymbol Nowhere (SymbolMixfix datatypeSymbol) []
+
+require :: Bool -> Text -> Either DatatypeValidationError ()
+require condition message
+ | condition =
+ Right ()
+ | otherwise =
+ Left (InvalidDatatype message)
+
+requirePremise
+ :: Location
+ -> Bool
+ -> Text
+ -> Either DatatypeValidationError ()
+requirePremise location condition message
+ | condition =
+ Right ()
+ | otherwise =
+ Left (InvalidDatatypePremise location message)
+
+checkedDatatypeHeadSymbol :: CheckedDatatype -> Symbol
+checkedDatatypeHeadSymbol =
+ SymbolMixfix . checkedDatatypeHead
+
+checkedDatatypeConstructorSymbols
+ :: CheckedDatatype
+ -> NonEmpty Symbol
+checkedDatatypeConstructorSymbols =
+ fmap
+ (SymbolMixfix . checkedDatatypeClauseConstructor)
+ . checkedDatatypeClauses
+
+checkedDatatypeClauseViews
+ :: CheckedDatatype
+ -> NonEmpty CheckedDatatypeClauseView
+checkedDatatypeClauseViews =
+ fmap clauseView . checkedDatatypeClauses
+ where
+ clauseView clause =
+ CheckedDatatypeClauseView
+ { checkedDatatypeClauseViewConstructor =
+ checkedDatatypeClauseConstructor clause
+ , checkedDatatypeClauseViewArguments =
+ checkedDatatypeClauseConstructorArgs clause
+ , checkedDatatypeClauseViewPremises =
+ premiseView
+ <$> checkedDatatypeClausePremises clause
+ }
+
+ premiseView = \case
+ RecursiveDatatypePremise variable domain ->
+ CheckedRecursiveDatatypePremise variable domain
+ NonRecursiveDatatypePremise variable domain ->
+ CheckedNonRecursiveDatatypePremise variable domain
+
+checkedDatatypeGeneratedFacts
+ :: CheckedDatatype
+ -> NonEmpty (Marker, Formula)
+checkedDatatypeGeneratedFacts =
+ datatypeFacts
+
+datatypeFacts :: CheckedDatatype -> NonEmpty (Marker, Formula)
+datatypeFacts datatype =
+ appendList
+ (datatypeIntroFacts datatype)
+ ( datatypeDistinctFacts datatype
+ <> datatypeInjectiveFacts datatype
+ <> [ ( datatypeCasesMarker datatype
+ , datatypeCasesFormula datatype
+ )
+ , ( datatypeInductMarker datatype
+ , datatypeInductFormula datatype
+ )
+ ]
+ )
+ where
+ appendList (first :| rest) trailing =
+ first :| (rest <> trailing)
+
+datatypeIntroFacts :: CheckedDatatype -> NonEmpty (Marker, Formula)
+datatypeIntroFacts datatype =
+ fmap
+ (\clause ->
+ ( datatypeIntroMarker datatype clause
+ , datatypeIntroFormula datatype clause
+ ))
+ (checkedDatatypeClauses datatype)
+
+datatypeDistinctFacts :: CheckedDatatype -> [(Marker, Formula)]
+datatypeDistinctFacts datatype =
+ [ ( datatypeDistinctMarker datatype leftClause rightClause
+ , datatypeDistinctFormula leftClause rightClause
+ )
+ | (leftClause, rightClause) <-
+ unorderedPairs
+ (NonEmpty.toList (checkedDatatypeClauses datatype))
+ ]
+
+datatypeInjectiveFacts :: CheckedDatatype -> [(Marker, Formula)]
+datatypeInjectiveFacts datatype =
+ [ ( datatypeInjectiveMarker datatype clause
+ , datatypeInjectiveFormula clause
+ )
+ | clause <- NonEmpty.toList (checkedDatatypeClauses datatype)
+ , not (null (checkedDatatypeClauseConstructorArgs clause))
+ ]
+
+datatypeIntroMarker
+ :: CheckedDatatype
+ -> CheckedDatatypeClause
+ -> Marker
+datatypeIntroMarker datatype clause =
+ Marker
+ ( datatypeFactBase datatype
+ <> "_"
+ <> datatypeClauseSymbolText clause
+ <> "_intro"
+ )
+
+datatypeDistinctMarker
+ :: CheckedDatatype
+ -> CheckedDatatypeClause
+ -> CheckedDatatypeClause
+ -> Marker
+datatypeDistinctMarker datatype leftClause rightClause =
+ Marker
+ ( datatypeFactBase datatype
+ <> "_"
+ <> datatypeClauseSymbolText leftClause
+ <> "_"
+ <> datatypeClauseSymbolText rightClause
+ <> "_distinct"
+ )
+
+datatypeInjectiveMarker
+ :: CheckedDatatype
+ -> CheckedDatatypeClause
+ -> Marker
+datatypeInjectiveMarker datatype clause =
+ Marker
+ ( datatypeFactBase datatype
+ <> "_"
+ <> datatypeClauseSymbolText clause
+ <> "_injective"
+ )
+
+datatypeCasesMarker :: CheckedDatatype -> Marker
+datatypeCasesMarker datatype =
+ Marker (datatypeFactBase datatype <> "_cases")
+
+datatypeInductMarker :: CheckedDatatype -> Marker
+datatypeInductMarker datatype =
+ Marker (datatypeFactBase datatype <> "_induct")
+
+datatypeFactBase :: CheckedDatatype -> Text
+datatypeFactBase =
+ functionSymbolText . checkedDatatypeHead
+
+datatypeIntroFormula
+ :: CheckedDatatype
+ -> CheckedDatatypeClause
+ -> Formula
+datatypeIntroFormula datatype clause =
+ forallIfNeeded premiseVars (impliesFrom premises conclusion)
+ where
+ premiseVars = datatypeClausePremiseVars clause
+ premises = datatypeClausePremiseFormulas clause
+ conclusion = datatypeClauseResultFormula datatype clause
+
+datatypeDistinctFormula
+ :: CheckedDatatypeClause
+ -> CheckedDatatypeClause
+ -> Formula
+datatypeDistinctFormula leftClause rightClause =
+ forallIfNeeded
+ (leftVars <> rightVars)
+ (NotEquals Nowhere leftTerm rightTerm)
+ where
+ leftVars = checkedDatatypeClauseConstructorArgs leftClause
+ rightVars =
+ renameDatatypeVars
+ (Set.fromList leftVars)
+ (checkedDatatypeClauseConstructorArgs rightClause)
+ leftTerm = datatypeClauseTerm leftClause (TermVar <$> leftVars)
+ rightTerm = datatypeClauseTerm rightClause (TermVar <$> rightVars)
+
+datatypeInjectiveFormula :: CheckedDatatypeClause -> Formula
+datatypeInjectiveFormula clause =
+ forallIfNeeded
+ (leftVars <> rightVars)
+ ( Equals Nowhere leftTerm rightTerm
+ `Implies` makeConjunction equalities
+ )
+ where
+ leftVars = checkedDatatypeClauseConstructorArgs clause
+ rightVars = renameDatatypeVars (Set.fromList leftVars) leftVars
+ leftTerm = datatypeClauseTerm clause (TermVar <$> leftVars)
+ rightTerm = datatypeClauseTerm clause (TermVar <$> rightVars)
+ equalities =
+ zipWith
+ (\leftVar rightVar ->
+ Equals Nowhere (TermVar leftVar) (TermVar rightVar))
+ leftVars
+ rightVars
+
+datatypeCasesFormula :: CheckedDatatype -> Formula
+datatypeCasesFormula datatype =
+ makeForall
+ [witnessVar]
+ ( isElementOf (TermVar witnessVar) datatypeCarrier
+ `Implies` makeDisjunction disjuncts
+ )
+ where
+ usedVars = datatypeUsedVars datatype
+ witnessVar = freshDatatypeVar usedVars "x"
+ datatypeCarrier = datatypeCarrierTerm datatype
+ disjuncts =
+ datatypeCaseDisjunct witnessVar
+ <$> NonEmpty.toList (checkedDatatypeClauses datatype)
+ datatypeCaseDisjunct var clause =
+ existsIfNeeded
+ premiseVars
+ ( makeConjunction
+ ( premises
+ <> [ Equals
+ Nowhere
+ (TermVar var)
+ constructorTerm
+ ]
+ )
+ )
+ where
+ premiseVars = datatypeClausePremiseVars clause
+ premises = datatypeClausePremiseFormulas clause
+ constructorTerm =
+ datatypeClauseTerm
+ clause
+ (TermVar <$> checkedDatatypeClauseConstructorArgs clause)
+
+datatypeInductFormula :: CheckedDatatype -> Formula
+datatypeInductFormula datatype =
+ makeForall
+ [subsetVar]
+ (impliesFrom closureAssumptions conclusion)
+ where
+ usedVars = datatypeUsedVars datatype
+ subsetVar = freshDatatypeVar usedVars "S"
+ witnessVar = freshDatatypeVar (Set.insert subsetVar usedVars) "x"
+ datatypeCarrier = datatypeCarrierTerm datatype
+ conclusion =
+ makeForall
+ [witnessVar]
+ ( isElementOf (TermVar witnessVar) datatypeCarrier
+ `Implies` isElementOf
+ (TermVar witnessVar)
+ (TermVar subsetVar)
+ )
+ closureAssumptions =
+ datatypeInductionClosure subsetVar
+ <$> NonEmpty.toList (checkedDatatypeClauses datatype)
+
+datatypeInductionClosure
+ :: VarSymbol
+ -> CheckedDatatypeClause
+ -> Formula
+datatypeInductionClosure subsetVar clause =
+ forallIfNeeded
+ premiseVars
+ (impliesFrom inductionPremises conclusion)
+ where
+ premiseVars = datatypeClausePremiseVars clause
+ inductionPremises =
+ datatypeInductionPremise subsetVar
+ <$> checkedDatatypeClausePremises clause
+ conclusion =
+ isElementOf
+ ( datatypeClauseTerm
+ clause
+ (TermVar <$> checkedDatatypeClauseConstructorArgs clause)
+ )
+ (TermVar subsetVar)
+
+datatypeInductionPremise
+ :: VarSymbol
+ -> CheckedDatatypePremise
+ -> Formula
+datatypeInductionPremise subsetVar = \case
+ RecursiveDatatypePremise var _canonicalDomain ->
+ isElementOf (TermVar var) (TermVar subsetVar)
+ NonRecursiveDatatypePremise var domain ->
+ isElementOf (TermVar var) domain
+
+datatypeCarrierTerm :: CheckedDatatype -> Expr
+datatypeCarrierTerm datatype =
+ TermSymbol
+ Nowhere
+ (SymbolMixfix (checkedDatatypeHead datatype))
+ []
+
+datatypeClauseResultFormula
+ :: CheckedDatatype
+ -> CheckedDatatypeClause
+ -> Formula
+datatypeClauseResultFormula datatype clause =
+ isElementOf
+ ( datatypeClauseTerm
+ clause
+ (TermVar <$> checkedDatatypeClauseConstructorArgs clause)
+ )
+ (datatypeCarrierTerm datatype)
+
+datatypeClauseTerm :: CheckedDatatypeClause -> [Expr] -> Expr
+datatypeClauseTerm clause args =
+ TermSymbol
+ Nowhere
+ (SymbolMixfix (checkedDatatypeClauseConstructor clause))
+ args
+
+datatypeClausePremiseVars :: CheckedDatatypeClause -> [VarSymbol]
+datatypeClausePremiseVars =
+ fmap datatypePremiseVar . checkedDatatypeClausePremises
+
+datatypeClausePremiseFormulas
+ :: CheckedDatatypeClause
+ -> [Formula]
+datatypeClausePremiseFormulas =
+ fmap datatypePremiseFormula
+ . checkedDatatypeClausePremises
+
+datatypePremiseFormula
+ :: CheckedDatatypePremise
+ -> Formula
+datatypePremiseFormula = \case
+ RecursiveDatatypePremise var canonicalDomain ->
+ isElementOf (TermVar var) canonicalDomain
+ NonRecursiveDatatypePremise var domain ->
+ isElementOf (TermVar var) domain
+
+datatypePremiseVar :: CheckedDatatypePremise -> VarSymbol
+datatypePremiseVar = \case
+ RecursiveDatatypePremise var _canonicalDomain ->
+ var
+ NonRecursiveDatatypePremise var _domain ->
+ var
+
+datatypeClauseSymbolText :: CheckedDatatypeClause -> Text
+datatypeClauseSymbolText =
+ functionSymbolText . checkedDatatypeClauseConstructor
+
+functionSymbolText :: FunctionSymbol -> Text
+functionSymbolText symbol =
+ case mixfixMarker symbol of
+ Marker name ->
+ name
+
+datatypeUsedVars :: CheckedDatatype -> Set VarSymbol
+datatypeUsedVars datatype =
+ Set.fromList
+ [ var
+ | clause <- NonEmpty.toList (checkedDatatypeClauses datatype)
+ , var <- datatypeClausePremiseVars clause
+ ]
+
+duplicateDatatypeConstructorSymbols
+ :: [FunctionSymbol]
+ -> Set FunctionSymbol
+duplicateDatatypeConstructorSymbols =
+ snd . foldl' step (Set.empty, Set.empty)
+ where
+ step (seen, duplicates) symbol
+ | symbol `Set.member` seen =
+ (seen, Set.insert symbol duplicates)
+ | otherwise =
+ (Set.insert symbol seen, duplicates)
+
+duplicateVars :: [VarSymbol] -> Set VarSymbol
+duplicateVars =
+ snd . foldl' step (Set.empty, Set.empty)
+ where
+ step (seen, duplicates) var
+ | var `Set.member` seen =
+ (seen, Set.insert var duplicates)
+ | otherwise =
+ (Set.insert var seen, duplicates)
+
+formatDatatypeConstructors :: Set FunctionSymbol -> Text
+formatDatatypeConstructors symbols =
+ Text.intercalate
+ ", "
+ (functionSymbolText <$> Set.toList symbols)
+
+formatVars :: Set VarSymbol -> Text
+formatVars vars =
+ Text.intercalate ", " (formatVar <$> Set.toList vars)
+
+formatVar :: VarSymbol -> Text
+formatVar = \case
+ NamedVar name ->
+ name
+ FreshVar index ->
+ "_" <> Text.pack (show index)
+
+renameDatatypeVars :: Set VarSymbol -> [VarSymbol] -> [VarSymbol]
+renameDatatypeVars _used [] =
+ []
+renameDatatypeVars used (var:vars) =
+ let renamed = freshDatatypeLikeVar used var
+ in renamed : renameDatatypeVars (Set.insert renamed used) vars
+
+freshDatatypeLikeVar :: Set VarSymbol -> VarSymbol -> VarSymbol
+freshDatatypeLikeVar used = \case
+ NamedVar name ->
+ freshDatatypeVar used (name <> "_rhs")
+ FreshVar index ->
+ freshDatatypeVar
+ used
+ ("_" <> Text.pack (show index) <> "_rhs")
+
+freshDatatypeVar :: Set VarSymbol -> Text -> VarSymbol
+freshDatatypeVar used base =
+ List.head
+ [ NamedVar candidate
+ | candidate <-
+ base
+ : [ base <> Text.pack (show index)
+ | index <- [(1 :: Int) ..]
+ ]
+ , NamedVar candidate `Set.notMember` used
+ ]
+
+forallIfNeeded :: [VarSymbol] -> Formula -> Formula
+forallIfNeeded [] formula =
+ formula
+forallIfNeeded vars formula =
+ makeForall vars formula
+
+existsIfNeeded :: [VarSymbol] -> Formula -> Formula
+existsIfNeeded [] formula =
+ formula
+existsIfNeeded vars formula =
+ makeExists vars formula
+
+impliesFrom :: [Formula] -> Formula -> Formula
+impliesFrom [] conclusion =
+ conclusion
+impliesFrom premises conclusion =
+ makeConjunction premises `Implies` conclusion
+
+unorderedPairs :: [a] -> [(a, a)]
+unorderedPairs = \case
+ [] ->
+ []
+ value:rest ->
+ [(value, other) | other <- rest]
+ <> unorderedPairs rest
diff --git a/source/Felix/Checking/Declaration.hs b/source/Felix/Checking/Declaration.hs
new file mode 100644
index 0000000..15050c7
--- /dev/null
+++ b/source/Felix/Checking/Declaration.hs
@@ -0,0 +1,6770 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE RankNTypes #-}
+
+-- | Builder-confined authorization and atomic typed declaration append.
+module Felix.Checking.Declaration
+ ( ModuleDriver
+ , DriverFailure(..)
+ , DriverResult(..)
+ , ImportedModuleEvidence
+ , ImportedAliasOrigin(..)
+ , freshImportedModuleEvidence
+ , validateImportedModuleEvidence
+ , importSealedModule
+ , importSealedModuleDriver
+ , nextDeclarationSlotDriver
+ , currentTheoryDriver
+ , currentFoundationAxiomDriver
+ , resolveVisibleFactAliasDriver
+ , resolveVisibleFactTargetsDriver
+ , resolveVisibleGlobalDriver
+ , resolveVisibleGlobalContentDriver
+ , ResolvedStructure
+ , resolvedStructureDescriptor
+ , resolvedStructurePredicate
+ , resolvedStructureOperation
+ , resolvedStructureOperations
+ , resolveVisibleStructureDriver
+ , resolveVisibleStructureOperationObjectsDriver
+ , objectAvailableDriver
+ , objectTypeDriver
+ , LoweringDriver
+ , runProspectiveLoweringDriver
+ , nextDeclarationSlotLowering
+ , currentTheoryLowering
+ , currentFoundationLowering
+ , currentFoundationAxiomLowering
+ , resolveVisibleFactAliasLowering
+ , resolveVisibleFactTargetsLowering
+ , resolveVisibleGlobalLowering
+ , resolveVisibleGlobalContentLowering
+ , resolveVisibleStructureLowering
+ , resolveVisibleStructureOperationObjectsLowering
+ , objectAvailableLowering
+ , objectTypeLowering
+ , runModuleDriver
+ , ValidationLookup
+ , validationLookup
+ , ValidationRun(..)
+ , failModuleDriver
+ , failDeclarationDriver
+ , VampireResolver
+ , VampireSubmission(..)
+ , vampireBatchResolver
+ , vampireResolver
+ , vampireSubmissionResolver
+ , Declaration
+ , failDeclaration
+ , addDeclarationObject
+ , addDeclarationProposition
+ , resolveVisibleGlobal
+ , stageSemanticGlobalBinding
+ , stageSemanticStructureDescriptor
+ , CandidateSpec
+ , candidateSpec
+ , CandidatePlanningSpec
+ , CheckedCandidate
+ , checkedCandidate
+ , checkedDefinitionEquationPlanning
+ , checkedSourceAxiomPlanning
+ , checkedDatatypePlanning
+ , checkedKernelPlanning
+ , checkedKernelPlanningWithStaged
+ , checkedStagedKernelPlanning
+ , checkedSourceProofPlanning
+ , checkedOmittedPlanning
+ , CheckedPlannedVampireRequest
+ , checkedPlannedVampireRequest
+ , plannedEarlierCandidate
+ , CheckedDeclaration
+ , checkedProofDeclaration
+ , checkedCompiledDeclaration
+ , admitCheckedDeclaration
+ , PlannedDeclaration
+ , planCheckedDeclaration
+ , admitPlannedCheckedDeclaration
+ , plannedDeclarationPreviousPrefix
+ , plannedDeclarationNextPrefix
+ , plannedDeclarationDelta
+ , PlanningIntegrityError(..)
+ , prepareCandidateSpecDriver
+ , prepareFrozenCandidateSpecDriver
+ , prepareDefinitionEquationSpecDriver
+ , preparePointwiseDefinitionEquationSpecDriver
+ , prepareStagedCandidateVampireDriver
+ , prepareCandidateSpecLowering
+ , prepareFrozenCandidateSpecLowering
+ , prepareDefinitionEquationSpecLowering
+ , prepareDefinitionEquationSpecWithEligibilityLowering
+ , prepareNamedSetConstructionSpecLowering
+ , prepareRelationalSetConstructionSpecLowering
+ , preparePointwiseDefinitionEquationSpecLowering
+ , prepareStagedCandidateVampireLowering
+ , ReservedCandidate
+ , reserveCandidate
+ , reserveCandidateBatch
+ , reservePropositionCandidate
+ , reserveFrozenPropositionCandidateBatch
+ , reserveDefinitionEquationCandidate
+ , reservePointwiseDefinitionEquationCandidate
+ , reserveDefinitionEquationCandidateBatch
+ , reservedCandidateSlot
+ , reservedCandidateStage
+ , CandidateProof
+ , locateProofObligation
+ , recordOmittedUse
+ , VampirePremiseSelection(..)
+ , VampireObligationPreparationError(..)
+ , ScopedVampirePremise
+ , scopedVampirePremise
+ , PreparedVampireObligation
+ , prepareScopedVampireObligationDriver
+ , prepareScopedContradictionObligationDriver
+ , prepareScopedVampireObligationLowering
+ , prepareScopedContradictionObligationLowering
+ , useAuthorizedFact
+ , useStagedCandidate
+ , LocalClaim
+ , proveLocalKernelClaim
+ , useLocalClaim
+ , authorizeKernelProofCandidate
+ , authorizeKernelConstructionCandidate
+ , authorizeDefinitionEquationCandidate
+ , authorizeNamedSetConstructionCandidate
+ , authorizeRelationalSetConstructionCandidate
+ , acceptVampireObligation
+ , acceptPreparedVampireObligation
+ , acceptCurrentCandidateVampire
+ , prepareCurrentCandidateVampire
+ , authorizeVampireCandidate
+ , authorizeVampireCandidateBatch
+ , authorizeSourceAxiomCandidate
+ , authorizeOmittedCandidate
+ , authorizeDatatypeCompilationCandidates
+ , authorizeCompiledDeclaration
+ , commitProofDeclaration
+ , commitCompiledDeclaration
+ , CommittedDeclarationBatch
+ , committedBatchOwner
+ , committedBatchSlot
+ , committedBatchPreviousPrefix
+ , committedBatchNextPrefix
+ , committedBatchDelta
+ , committedBatchObjects
+ , committedBatchPropositions
+ , committedBatchProofValidations
+ , committedBatchDeclarationValidation
+ , PendingModulePrefix
+ , emptyPendingModulePrefix
+ , pendingModulePrefixBatches
+ , pendingModulePrefixCurrent
+ , ValidationIntegrityError(..)
+ , renderValidationIntegrityError
+ , DeclarationError(..)
+ , declarationErrorLocation
+ , renderDeclarationError
+ , DriverOpenError(..)
+ , renderDriverOpenError
+ ) where
+
+import Base
+import Felix.Cache.Codec (encodeCache)
+import Felix.Checking.Authority
+import Felix.Checking.Backend.Problem qualified as Backend
+import Felix.Checking.Backend.Tptp qualified as Tptp
+import Felix.Checking.Core
+import Felix.Checking.Foundation
+import Felix.Checking.Identity
+import Felix.Checking.Kernel.Derivation
+import Felix.Checking.Materialization qualified as Materialization
+import Felix.Checking.Semantic
+import Felix.Checking.SetConstruction
+import Felix.Module
+import Felix.Provers qualified as Provers
+import Felix.Report.Location
+import Felix.Syntax.Abstract (StructSymbol)
+
+import Control.Exception qualified as Exception
+import Control.DeepSeq (deepseq)
+import Control.Monad.Except (ExceptT)
+import Control.Monad.Except qualified as Except
+import Control.Monad (foldM, unless, when)
+import Control.Monad.State.Strict (StateT)
+import Control.Monad.State.Strict qualified as State
+import Data.Bifunctor (first)
+import Data.ByteString qualified as ByteString
+import Data.List qualified as List
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Map.Strict qualified as Map
+import Data.Maybe (catMaybes, mapMaybe)
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+import Data.Unique (Unique, newUnique)
+import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)
+import Data.Vector (Vector)
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+
+
+newtype BuilderIdentity = BuilderIdentity Unique
+ deriving stock (Eq)
+
+newtype DeclarationInvocation = DeclarationInvocation Natural
+ deriving stock (Eq)
+
+newtype CandidateStage = CandidateStage Natural
+ deriving stock (Show, Eq, Ord)
+
+data ValidationLookup = ValidationLookup
+ !(ProofValidationKey -> IO (Maybe ProofValidationRecord))
+ !(DeclarationValidationKey
+ -> IO (Maybe DeclarationValidationRecord))
+
+validationLookup
+ :: (ProofValidationKey -> IO (Maybe ProofValidationRecord))
+ -> (DeclarationValidationKey
+ -> IO (Maybe DeclarationValidationRecord))
+ -> ValidationLookup
+validationLookup =
+ ValidationLookup
+
+data ValidationRun
+ = FreshValidation
+ | WarmValidation !ValidationLookup
+
+data ValidationIntegrityError
+ = CachedValidationIntegrityError
+ !Materialization.MaterializationError
+ deriving stock (Show, Eq)
+
+instance Exception.Exception ValidationIntegrityError
+
+renderValidationIntegrityError :: ValidationIntegrityError -> Text
+renderValidationIntegrityError = \case
+ CachedValidationIntegrityError failure ->
+ "cached validation does not match its exact contextual input: "
+ <> Text.pack (show failure)
+
+
+data BuilderFactAuthorization = BuilderFactAuthorization
+ !BuilderIdentity
+ !FactSlot
+ !FactAuthority
+
+data PendingFactAuthorization = PendingFactAuthorization
+ !BuilderIdentity
+ !PrefixContextId
+ !DeclarationInvocation
+ !FactSlot
+ !FactAuthority
+ !CandidateStage
+
+-- | Checked fact semantics paired with caller-selected evidence. Semantic
+-- lookup and request preparation deliberately ignore the evidence parameter;
+-- only the admitted builder instantiation may consume capabilities.
+data FactEntry evidence = FactEntry
+ !CheckedPropositionContent
+ !SemanticFactOccurrence
+ !evidence
+
+-- | The shared checked semantic state. The evidence parameter makes the
+-- distinction between admitted authority and future inert planning data
+-- visible without duplicating lookup, collision, closure, or delta logic.
+data BuilderState evidence = BuilderState
+ { logicalBuilderIdentity :: !BuilderIdentity
+ , logicalBuilderFoundation :: !CheckedFoundation
+ , logicalBuilderTheory :: !TheoryId
+ , logicalBuilderOwner :: !ModuleName
+ , logicalBuilderDirectSemanticInputs :: ![SemanticInterfaceId]
+ , logicalBuilderPrefix :: !PrefixContextId
+ , logicalBuilderObjectClosure :: !CheckedObjectClosure
+ , logicalBuilderFacts
+ :: !(Map
+ SemanticFactOccurrenceFingerprint
+ (FactEntry evidence))
+ , logicalBuilderAliases
+ :: !(Map SemanticName ImportedAliasBinding)
+ , logicalBuilderGlobals
+ :: !(Map SemanticGlobalKey SemanticGlobalTarget)
+ , logicalBuilderStructures
+ :: !(Map SemanticStructurePhrase ResolvedStructure)
+ , logicalBuilderImportedInterfaces :: !(Set SemanticInterfaceId)
+ , logicalBuilderDeltas :: ![DeclarationInterfaceDelta]
+ , logicalBuilderNextDeclaration :: !Natural
+ , logicalBuilderNextFact :: !Natural
+ , logicalBuilderNextInvocation :: !Natural
+ }
+
+-- | The only production builder in this milestone. Capability-consuming
+-- operations below accept this admitted instantiation, never a polymorphic
+-- 'BuilderState'.
+type LogicalBuilder = BuilderState BuilderFactAuthorization
+
+-- | Public semantic contract and strict declaration-stage provenance for a
+-- prospective fact. It deliberately contains no builder identity, prefix,
+-- declaration invocation, pending authorization, or minting capability.
+data PlanningProvenance = PlanningProvenance
+ !DeclarationSlot
+ !CandidateStage
+ !FactSlot
+ deriving stock (Eq)
+
+data PlanningEvidence = PlanningEvidence
+ !FactAuthority
+ !(Maybe PlanningProvenance)
+
+data ResolvedStructureOperation = ResolvedStructureOperation
+ !ObjectId
+ !SemanticStructurePhrase
+ deriving stock (Show, Eq)
+
+data ResolvedStructure = ResolvedStructure
+ !SemanticStructureDescriptor
+ !(Set SemanticStructurePhrase)
+ !(Map StructSymbol ResolvedStructureOperation)
+ deriving stock (Show, Eq)
+
+resolvedStructureDescriptor
+ :: ResolvedStructure
+ -> SemanticStructureDescriptor
+resolvedStructureDescriptor (ResolvedStructure descriptor _ _) =
+ descriptor
+
+resolvedStructurePredicate :: ResolvedStructure -> Maybe ObjectId
+resolvedStructurePredicate =
+ semanticStructureDescriptorPredicate . resolvedStructureDescriptor
+
+resolvedStructureOperation
+ :: StructSymbol
+ -> ResolvedStructure
+ -> Maybe ObjectId
+resolvedStructureOperation symbol (ResolvedStructure _ _ operations) =
+ operationObject <$> Map.lookup symbol operations
+ where
+ operationObject (ResolvedStructureOperation object _origin) = object
+
+resolvedStructureOperations
+ :: ResolvedStructure
+ -> Map StructSymbol ObjectId
+resolvedStructureOperations (ResolvedStructure _ _ operations) =
+ operationObject <$> operations
+ where
+ operationObject (ResolvedStructureOperation object _origin) = object
+
+structureOperationBindings
+ :: Map SemanticStructurePhrase ResolvedStructure
+ -> Set (StructSymbol, ObjectId)
+structureOperationBindings structures =
+ Set.fromList
+ [ (symbol, object)
+ | structure <- Map.elems structures
+ , (symbol, object) <-
+ Map.toAscList (resolvedStructureOperations structure)
+ ]
+
+
+data CommittedDeclarationBatch = CommittedDeclarationBatch
+ !ModuleName
+ !DeclarationSlot
+ !PrefixContextId
+ !PrefixContextId
+ !DeclarationInterfaceDelta
+ ![AssertedObject]
+ ![CheckedPropositionContent]
+ ![ProofValidationRecord]
+ !(Maybe DeclarationValidationRecord)
+
+committedBatchOwner :: CommittedDeclarationBatch -> ModuleName
+committedBatchOwner
+ (CommittedDeclarationBatch owner _ _ _ _ _ _ _ _) =
+ owner
+
+committedBatchSlot
+ :: CommittedDeclarationBatch
+ -> DeclarationSlot
+committedBatchSlot
+ (CommittedDeclarationBatch _ slot _ _ _ _ _ _ _) =
+ slot
+
+committedBatchPreviousPrefix
+ :: CommittedDeclarationBatch
+ -> PrefixContextId
+committedBatchPreviousPrefix
+ (CommittedDeclarationBatch _ _ previous _ _ _ _ _ _) =
+ previous
+
+committedBatchNextPrefix
+ :: CommittedDeclarationBatch
+ -> PrefixContextId
+committedBatchNextPrefix
+ (CommittedDeclarationBatch _ _ _ next _ _ _ _ _) =
+ next
+
+committedBatchDelta
+ :: CommittedDeclarationBatch
+ -> DeclarationInterfaceDelta
+committedBatchDelta
+ (CommittedDeclarationBatch _ _ _ _ delta _ _ _ _) =
+ delta
+
+committedBatchObjects
+ :: CommittedDeclarationBatch
+ -> [AssertedObject]
+committedBatchObjects
+ (CommittedDeclarationBatch _ _ _ _ _ objects _ _ _) =
+ objects
+
+committedBatchPropositions
+ :: CommittedDeclarationBatch
+ -> [CheckedPropositionContent]
+committedBatchPropositions
+ (CommittedDeclarationBatch _ _ _ _ _ _ propositions _ _) =
+ propositions
+
+committedBatchProofValidations
+ :: CommittedDeclarationBatch
+ -> [ProofValidationRecord]
+committedBatchProofValidations
+ (CommittedDeclarationBatch _ _ _ _ _ _ _ validations _) =
+ validations
+
+committedBatchDeclarationValidation
+ :: CommittedDeclarationBatch
+ -> Maybe DeclarationValidationRecord
+committedBatchDeclarationValidation
+ (CommittedDeclarationBatch _ _ _ _ _ _ _ _ validation) =
+ validation
+
+
+data PendingModulePrefix = PendingModulePrefix
+ !PrefixContextId
+ ![CommittedDeclarationBatch]
+
+emptyPendingModulePrefix
+ :: PrefixContextId
+ -> PendingModulePrefix
+emptyPendingModulePrefix prefix =
+ PendingModulePrefix prefix []
+
+instance Show PendingModulePrefix where
+ show prefix =
+ "PendingModulePrefix {pendingModulePrefixCurrent = "
+ <> show (pendingModulePrefixCurrent prefix)
+ <> ", pendingModulePrefixBatchCount = "
+ <> show (length (pendingModulePrefixBatches prefix))
+ <> "}"
+
+pendingModulePrefixBatches
+ :: PendingModulePrefix
+ -> [CommittedDeclarationBatch]
+pendingModulePrefixBatches
+ (PendingModulePrefix _ batchesReversed) =
+ reverse batchesReversed
+
+pendingModulePrefixCurrent
+ :: PendingModulePrefix
+ -> PrefixContextId
+pendingModulePrefixCurrent
+ (PendingModulePrefix prefix _batches) =
+ prefix
+
+appendPendingBatch
+ :: CommittedDeclarationBatch
+ -> PendingModulePrefix
+ -> PendingModulePrefix
+appendPendingBatch batch (PendingModulePrefix _ batches) =
+ forceCommittedBatch batch `seq`
+ PendingModulePrefix
+ (committedBatchNextPrefix batch)
+ (batch : batches)
+
+forceCommittedBatch :: CommittedDeclarationBatch -> ()
+forceCommittedBatch
+ (CommittedDeclarationBatch
+ owner slot previous next delta objects propositions
+ proofValidations declarationValidation) =
+ owner `seq`
+ slot `seq`
+ previous `seq`
+ next `seq`
+ ByteString.length
+ (encodeCache (putDeclarationInterfaceDeltaCache delta)) `seq`
+ objects `deepseq`
+ propositions `deepseq`
+ forceProofValidations proofValidations `seq`
+ forceDeclarationValidation declarationValidation
+ where
+ forceProofValidations =
+ foldl'
+ (\() record ->
+ proofValidationRecordKey record `seq`
+ ByteString.length
+ (encodeCache
+ (putValidationCertificateCache
+ (proofValidationRecordCertificate
+ record))) `seq` ())
+ ()
+
+ forceDeclarationValidation = \case
+ Nothing -> ()
+ Just record ->
+ declarationValidationRecordKey record `seq`
+ foldl'
+ (\() certificate ->
+ ByteString.length
+ (encodeCache
+ (putValidationCertificateCache
+ certificate)) `seq` ())
+ ()
+ (declarationValidationRecordCertificates record)
+
+
+data VampireResolverMode
+ = SynchronousVampireResolution
+ (forall local origin.
+ NonEmpty
+ (Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId)
+ -> IO
+ (NonEmpty
+ (Either
+ Provers.ProverProcessError
+ Provers.ProverAnswer)))
+ | AsynchronousVampireSubmission
+ (NonEmpty VampireSubmission
+ -> IO (NonEmpty Provers.VampireHandle))
+
+data VampireResolver = VampireResolver !VampireResolverMode
+
+data VampireSubmission = VampireSubmission
+ !Location
+ !Provers.PreparedVerificationRequest
+
+vampireBatchResolver
+ :: (forall local origin.
+ NonEmpty
+ (Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId)
+ -> IO
+ (NonEmpty
+ (Either
+ Provers.ProverProcessError
+ Provers.ProverAnswer)))
+ -> VampireResolver
+vampireBatchResolver resolve =
+ VampireResolver (SynchronousVampireResolution resolve)
+
+-- | Production submission-only resolver. Retained-plan admission consumes
+-- the submitted handles through its private synchronous replay resolver.
+vampireSubmissionResolver
+ :: (NonEmpty VampireSubmission
+ -> IO (NonEmpty Provers.VampireHandle))
+ -> VampireResolver
+vampireSubmissionResolver submit =
+ VampireResolver (AsynchronousVampireSubmission submit)
+
+vampireResolver
+ :: (forall local origin.
+ Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId
+ -> IO
+ (Either
+ Provers.ProverProcessError
+ Provers.ProverAnswer))
+ -> VampireResolver
+vampireResolver resolve =
+ vampireBatchResolver (traverse resolve)
+
+resolveOneVampire
+ :: VampireResolver
+ -> Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId
+ -> ExceptT DeclarationError IO
+ (Either
+ Provers.ProverProcessError
+ Provers.ProverAnswer)
+resolveOneVampire resolver prepared = do
+ results@(result :| _) <-
+ liftIO (resolveSynchronousVampireBatch resolver (prepared :| []))
+ void
+ (Except.liftEither
+ (validateVampireResolverResultCount 1 results))
+ pure result
+
+resolveSynchronousVampireBatch
+ :: VampireResolver
+ -> NonEmpty
+ (Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId)
+ -> IO
+ (NonEmpty
+ (Either
+ Provers.ProverProcessError
+ Provers.ProverAnswer))
+resolveSynchronousVampireBatch
+ (VampireResolver mode) tasks =
+ case mode of
+ SynchronousVampireResolution resolve -> resolve tasks
+ AsynchronousVampireSubmission{} ->
+ throwIO
+ (PlanningIntegrityError
+ "asynchronous Vampire resolver reached synchronous admission")
+
+validateVampireResolverResultCount
+ :: Int
+ -> NonEmpty value
+ -> Either DeclarationError [value]
+validateVampireResolverResultCount expected results
+ | expected == actual =
+ Right (NonEmpty.toList results)
+ | otherwise =
+ Left (VampireResolverBatchSizeMismatch expected actual)
+ where
+ actual = NonEmpty.length results
+
+data DriverState = DriverState
+ !VampireResolver
+ !LogicalBuilder
+ !PendingModulePrefix
+ !ValidationRun
+
+-- | Authority-free semantic state used while lowering checked declarations.
+-- Existing admitted facts are projected to their public contracts; no
+-- builder-local authorization capability crosses this boundary.
+newtype ProspectiveBuilder = ProspectiveBuilder
+ (BuilderState PlanningEvidence)
+
+data LoweringState = LoweringState
+ !VampireResolver
+ !ProspectiveBuilder
+ !ValidationRun
+
+newtype LoweringDriver value = LoweringDriver
+ { runLoweringStep :: StateT LoweringState IO value
+ }
+ deriving newtype (Functor, Applicative, Monad)
+
+newtype ModuleDriver failure value = ModuleDriver
+ { runDriverStep
+ :: ExceptT
+ (DriverFailure failure)
+ (StateT DriverState IO)
+ value
+ }
+ deriving newtype (Functor, Applicative, Monad)
+
+-- | Run one complete authority-free planning action from the current admitted
+-- input projection. The prospective state advances throughout the action but
+-- never mutates the authoritative module builder.
+runProspectiveLoweringDriver
+ :: LoweringDriver value
+ -> ModuleDriver failure value
+runProspectiveLoweringDriver action = ModuleDriver do
+ DriverState resolver builder _prefix validation <- State.get
+ State.lift
+ (liftIO
+ (State.evalStateT
+ (runLoweringStep action)
+ (LoweringState resolver
+ (projectLogicalBuilder builder)
+ validation)))
+
+projectLogicalBuilder :: LogicalBuilder -> ProspectiveBuilder
+projectLogicalBuilder builder =
+ ProspectiveBuilder
+ builder
+ { logicalBuilderFacts =
+ fmap projectFact (logicalBuilderFacts builder)
+ }
+ where
+ projectFact (FactEntry proposition occurrence _authorization) =
+ FactEntry proposition occurrence
+ (PlanningEvidence
+ (semanticFactAuthority occurrence)
+ Nothing)
+
+data DriverFailure failure
+ = DriverDeclarationFailed !DeclarationError
+ | DriverActionFailed !failure
+ deriving stock (Show, Eq)
+
+data DriverResult failure value
+ = DriverSucceeded
+ !value
+ !SemanticInterface
+ !PendingModulePrefix
+ !CheckedObjectClosure
+ | DriverFailed
+ !(DriverFailure failure)
+ !PendingModulePrefix
+ | DriverSealFailed
+ !SemanticInterfaceError
+ !PendingModulePrefix
+
+data ImportedAliasOrigin = ImportedAliasOrigin
+ !DeclarationSlot
+ !SemanticFactOccurrenceFingerprint
+ deriving stock (Show, Eq)
+
+data ImportedAliasBinding = ImportedAliasBinding
+ !SemanticFactOccurrenceFingerprint
+ !ImportedAliasOrigin
+
+-- | Opaque evidence produced from a successfully sealed producer. Direct
+-- parents retain the semantic DAG; importing folds it deterministically and
+-- mints fresh authority for the consuming builder.
+data ImportedModuleEvidence = ImportedModuleEvidence
+ !SemanticInterface
+ ![ImportedModuleEvidence]
+ !(Map
+ SemanticFactOccurrenceFingerprint
+ (SemanticFactOccurrence, CheckedPropositionContent))
+ ![AssertedObject]
+
+freshImportedModuleEvidence
+ :: [ImportedModuleEvidence]
+ -> SemanticInterface
+ -> PendingModulePrefix
+ -> ImportedModuleEvidence
+freshImportedModuleEvidence parents interface prefix =
+ ImportedModuleEvidence
+ interface
+ parents
+ (Map.fromList
+ [ ( semanticFactFingerprint occurrence
+ , (occurrence, requireProposition occurrence)
+ )
+ | occurrence <- occurrences
+ ])
+ objects
+ where
+ batches = pendingModulePrefixBatches prefix
+ objects = concatMap committedBatchObjects batches
+ propositions = concatMap committedBatchPropositions batches
+ propositionMap =
+ Map.fromList
+ [ (checkedPropositionId proposition, proposition)
+ | proposition <- propositions
+ ]
+ occurrences =
+ concatMap
+ declarationDeltaFacts
+ (semanticInterfaceDeclarations interface)
+
+ requireProposition occurrence =
+ case Map.lookup
+ (semanticFactProposition occurrence)
+ propositionMap of
+ Just proposition ->
+ proposition
+ Nothing ->
+ impossible
+ ("sealed fact occurrence references absent proposition "
+ <> show (semanticFactProposition occurrence))
+
+-- | Construct import evidence after a trusted producer or validated store
+-- installation has supplied the complete canonical proposition payloads.
+validateImportedModuleEvidence
+ :: TheoryId
+ -> [ImportedModuleEvidence]
+ -> SemanticInterface
+ -> [AssertedObject]
+ -> [CheckedPropositionContent]
+ -> Either DeclarationError ImportedModuleEvidence
+validateImportedModuleEvidence
+ theory parents interface objects propositions = do
+ _ <- first ImportedEvidenceInterfaceFailed
+ (validateSemanticInterface
+ (semanticInterfaceOwner interface)
+ (semanticInterfaceDirectInputs interface)
+ (semanticInterfaceDeclarations interface)
+ (semanticInterfaceAssertedId interface))
+ unless
+ (semanticInterfaceDirectInputs interface == parentIds)
+ (Left
+ (ImportedEvidenceDirectMismatch
+ (semanticInterfaceDirectInputs interface)
+ parentIds))
+ let propositionMap =
+ Map.fromList
+ [ (checkedPropositionId proposition, proposition)
+ | proposition <- propositions
+ ]
+ objectMap =
+ Map.fromList
+ [ (assertedObjectId object, object)
+ | object <- objects
+ ]
+ localObjectIds =
+ concatMap
+ declarationDeltaObjects
+ (semanticInterfaceDeclarations interface)
+ parentObjectMap =
+ foldl'
+ (Map.unionWith const)
+ Map.empty
+ (evidenceObjectMap <$> parents)
+ localObjects <- traverse
+ (\identity ->
+ maybe
+ (Left (ImportedEvidenceObjectMissing identity))
+ Right
+ (Map.lookup identity objectMap))
+ localObjectIds
+ closure <- first DeclarationObjectValidationFailed
+ (validateObjectClosure
+ theory
+ (Map.elems parentObjectMap <> localObjects))
+ let occurrences =
+ concatMap
+ declarationDeltaFacts
+ (semanticInterfaceDeclarations interface)
+ pairs <- traverse
+ (\occurrence -> do
+ proposition <- maybe
+ (Left
+ (ImportedEvidencePropositionMissing
+ (semanticFactProposition occurrence)))
+ Right
+ (Map.lookup
+ (semanticFactProposition occurrence)
+ propositionMap)
+ _ <- first DeclarationPropositionValidationFailed
+ (validateAssertedPropositionContent
+ closure
+ (checkedPropositionId proposition)
+ (frozenCoreTerm
+ (checkedPropositionTerm proposition)))
+ _ <- first ImportedFactMaterializationFailed
+ (Materialization.checkImportedOccurrence
+ theory
+ (semanticFactFingerprint occurrence)
+ occurrence
+ proposition
+ (semanticFactAuthority occurrence))
+ pure
+ ( semanticFactFingerprint occurrence
+ , (occurrence, proposition)
+ ))
+ occurrences
+ let evidence =
+ ImportedModuleEvidence
+ interface
+ parents
+ (Map.fromList pairs)
+ localObjects
+ _ <- validateEvidenceInventory theory closure evidence
+ pure evidence
+ where
+ parentIds =
+ semanticInterfaceAssertedId . evidenceInterface <$> parents
+
+validateEvidenceInventory
+ :: TheoryId
+ -> CheckedObjectClosure
+ -> ImportedModuleEvidence
+ -> Either DeclarationError ()
+validateEvidenceInventory theory closure evidence =
+ do
+ void (foldEvidence Set.empty Map.empty Map.empty evidence)
+ (_seen, structures) <-
+ foldStructures Set.empty Map.empty evidence
+ void (foldGlobals structures Set.empty Map.empty evidence)
+ where
+ foldEvidence seen facts aliases current
+ | identity `Set.member` seen =
+ Right (seen, facts, aliases)
+ | otherwise = do
+ unless
+ (semanticInterfaceDirectInputs interface == parentIds)
+ (Left
+ (ImportedEvidenceDirectMismatch
+ (semanticInterfaceDirectInputs interface)
+ parentIds))
+ (parentsSeen, parentFacts, parentAliases) <-
+ foldM
+ (\(seen', facts', aliases') parent ->
+ foldEvidence seen' facts' aliases' parent)
+ (seen, facts, aliases)
+ parents
+ localFacts <- foldM insertFact parentFacts (Map.elems entries)
+ localAliases <- foldM
+ (insertAlias localFacts)
+ parentAliases
+ [ ( alias
+ , ImportedAliasOrigin
+ (declarationDeltaSlot delta)
+ (semanticAliasTarget alias)
+ )
+ | delta <- semanticInterfaceDeclarations interface
+ , alias <- declarationDeltaAliases delta
+ ]
+ pure
+ ( Set.insert identity parentsSeen
+ , localFacts
+ , localAliases
+ )
+ where
+ ImportedModuleEvidence interface parents entries _objects = current
+ identity = semanticInterfaceAssertedId interface
+ parentIds =
+ semanticInterfaceAssertedId . evidenceInterface <$> parents
+
+ insertFact facts pair@(occurrence, proposition) = do
+ let fingerprint = semanticFactFingerprint occurrence
+ _ <- first ImportedFactMaterializationFailed
+ (Materialization.checkImportedOccurrence
+ theory
+ fingerprint
+ occurrence
+ proposition
+ (semanticFactAuthority occurrence))
+ case Map.lookup fingerprint facts of
+ Nothing ->
+ Right (Map.insert fingerprint pair facts)
+ Just (existingOccurrence, existingProposition)
+ | existingOccurrence == occurrence
+ && checkedPropositionId existingProposition
+ == checkedPropositionId proposition ->
+ Right facts
+ | otherwise ->
+ Left (ImportedFactCollision fingerprint)
+
+ insertAlias facts aliases (alias, origin) = do
+ let name = semanticAliasName alias
+ target = semanticAliasTarget alias
+ unless
+ (Map.member target facts)
+ (Left (ImportedAliasTargetMissing target))
+ case Map.lookup name aliases of
+ Nothing ->
+ Right
+ (Map.insert
+ name
+ (ImportedAliasBinding target origin)
+ aliases)
+ Just (ImportedAliasBinding existingTarget existingOrigin)
+ | existingTarget == target ->
+ Right aliases
+ | otherwise ->
+ Left
+ (ImportedAliasCollision
+ name existingOrigin origin)
+
+ foldGlobals structures seen globals current
+ | identity `Set.member` seen =
+ Right (seen, globals)
+ | otherwise = do
+ (parentsSeen, parentGlobals) <-
+ foldM
+ (\(seen', globals') parent ->
+ foldGlobals structures seen' globals' parent)
+ (seen, globals)
+ parents
+ globals' <-
+ foldM
+ (insertGlobal
+ (structureOperationBindings structures))
+ parentGlobals
+ [ binding
+ | delta <- semanticInterfaceDeclarations interface
+ , binding <- semanticEnvironmentBindings
+ (declarationDeltaEnvironment delta)
+ ]
+ pure (Set.insert identity parentsSeen, globals')
+ where
+ ImportedModuleEvidence interface parents _entries _objects = current
+ identity = semanticInterfaceAssertedId interface
+
+ insertGlobal operationBindings globals binding = do
+ let key = semanticGlobalBindingKey binding
+ target = semanticGlobalBindingTarget binding
+ _ <-
+ first
+ (ImportedGlobalTargetInvalid key target)
+ (validateSemanticGlobalBindingTarget
+ operationBindings
+ closure binding)
+ case Map.lookup key globals of
+ Nothing -> Right (Map.insert key target globals)
+ Just existing
+ | existing == target -> Right globals
+ | otherwise ->
+ Left (ImportedGlobalCollision key existing target)
+
+ foldStructures seen structures current
+ | identity `Set.member` seen =
+ Right (seen, structures)
+ | otherwise = do
+ (parentsSeen, parentStructures) <-
+ foldM
+ (\(seen', structures') parent ->
+ foldStructures seen' structures' parent)
+ (seen, structures)
+ parents
+ structures' <-
+ foldM
+ (insertSemanticStructure closure)
+ parentStructures
+ [ descriptor
+ | delta <- semanticInterfaceDeclarations interface
+ , descriptor <- semanticEnvironmentStructures
+ (declarationDeltaEnvironment delta)
+ ]
+ pure (Set.insert identity parentsSeen, structures')
+ where
+ ImportedModuleEvidence interface parents _entries _objects = current
+ identity = semanticInterfaceAssertedId interface
+
+evidenceInterface :: ImportedModuleEvidence -> SemanticInterface
+evidenceInterface
+ (ImportedModuleEvidence interface _parents _entries _objects) =
+ interface
+
+evidenceObjectMap
+ :: ImportedModuleEvidence
+ -> Map ObjectId AssertedObject
+evidenceObjectMap
+ (ImportedModuleEvidence _interface parents _entries objects) =
+ foldl'
+ (Map.unionWith const)
+ (Map.fromList
+ [ (assertedObjectId object, object)
+ | object <- objects
+ ])
+ (evidenceObjectMap <$> parents)
+
+data DriverOpenError
+ = DriverInitialPrefixError !PrefixContextError
+ | DriverInitialObjectError !ObjectValidationError
+ deriving stock (Show, Eq)
+
+renderDriverOpenError :: DriverOpenError -> Text
+renderDriverOpenError = \case
+ DriverInitialPrefixError{} ->
+ "the initial semantic prefix is inconsistent"
+ DriverInitialObjectError{} ->
+ "the initial object closure is inconsistent"
+
+runModuleDriver
+ :: CheckedFoundation
+ -> ModuleName
+ -> [SemanticInterfaceId]
+ -> VampireResolver
+ -> ValidationRun
+ -> ModuleDriver failure value
+ -> IO
+ (Either
+ DriverOpenError
+ (DriverResult failure value))
+runModuleDriver
+ foundation owner direct resolver validationRun action = do
+ unique <- BuilderIdentity <$> newUnique
+ let theory = theoryId foundation
+ case (,) <$>
+ first DriverInitialPrefixError
+ (initialPrefixContextId theory owner direct)
+ <*>
+ first DriverInitialObjectError
+ (validateObjectClosure theory []) of
+ Left err ->
+ pure (Left err)
+ Right (prefix, closure) -> do
+ let builder =
+ BuilderState
+ { logicalBuilderIdentity = unique
+ , logicalBuilderFoundation = foundation
+ , logicalBuilderTheory = theory
+ , logicalBuilderOwner = owner
+ , logicalBuilderDirectSemanticInputs = direct
+ , logicalBuilderPrefix = prefix
+ , logicalBuilderObjectClosure = closure
+ , logicalBuilderFacts = Map.empty
+ , logicalBuilderAliases = Map.empty
+ , logicalBuilderGlobals = Map.empty
+ , logicalBuilderStructures = Map.empty
+ , logicalBuilderImportedInterfaces = Set.empty
+ , logicalBuilderDeltas = []
+ , logicalBuilderNextDeclaration = 0
+ , logicalBuilderNextFact = 0
+ , logicalBuilderNextInvocation = 0
+ }
+ initialState =
+ DriverState
+ resolver
+ builder
+ (PendingModulePrefix prefix [])
+ validationRun
+ (result, DriverState _ finalBuilder finalPrefix _) <-
+ State.runStateT
+ (Except.runExceptT
+ (runDriverStep action))
+ initialState
+ case result of
+ Left err ->
+ pure (Right (DriverFailed err finalPrefix))
+ Right value -> do
+ case semanticInterface
+ owner
+ direct
+ (reverse
+ (logicalBuilderDeltas
+ finalBuilder)) of
+ Left err ->
+ pure
+ (Right
+ (DriverSealFailed
+ err
+ finalPrefix))
+ Right interface ->
+ pure
+ (Right
+ (DriverSucceeded
+ value
+ interface
+ finalPrefix
+ (logicalBuilderObjectClosure
+ finalBuilder)))
+
+failModuleDriver :: failure -> ModuleDriver failure value
+failModuleDriver err =
+ ModuleDriver
+ (Except.throwError
+ (DriverActionFailed err))
+
+failDeclarationDriver :: DeclarationError -> ModuleDriver failure value
+failDeclarationDriver err =
+ ModuleDriver (Except.throwError (DriverDeclarationFailed err))
+
+nextDeclarationSlotDriver
+ :: ModuleDriver failure DeclarationSlot
+nextDeclarationSlotDriver = ModuleDriver do
+ DriverState _resolver builder _prefix _validation <- State.get
+ pure
+ (declarationSlot
+ (logicalBuilderOwner builder)
+ (localDeclarationOrdinal
+ (logicalBuilderNextDeclaration builder)))
+
+currentTheoryDriver :: ModuleDriver failure TheoryId
+currentTheoryDriver =
+ ModuleDriver
+ (State.gets
+ (\(DriverState _resolver builder _prefix _validation) ->
+ logicalBuilderTheory builder))
+
+currentFoundationAxiomDriver
+ :: FoundationAxiomTag
+ -> ModuleDriver failure (FrozenCheckedCore Void)
+currentFoundationAxiomDriver tag =
+ ModuleDriver
+ (State.gets
+ (\(DriverState _resolver builder _prefix _validation) ->
+ foundationAxiomFrozen
+ (logicalBuilderFoundation builder)
+ tag))
+
+resolveVisibleFactAliasDriver
+ :: SemanticName
+ -> ModuleDriver failure
+ (Maybe SemanticFactOccurrenceFingerprint)
+resolveVisibleFactAliasDriver alias = ModuleDriver do
+ DriverState _resolver builder _prefix _validation <- State.get
+ pure do
+ ImportedAliasBinding fingerprint _origin <-
+ Map.lookup alias (logicalBuilderAliases builder)
+ pure fingerprint
+
+resolveVisibleFactTargetsDriver
+ :: FrozenCheckedCore ObjectId
+ -> ModuleDriver failure
+ [SemanticFactOccurrenceFingerprint]
+resolveVisibleFactTargetsDriver target = ModuleDriver do
+ DriverState _resolver builder _prefix _validation <- State.get
+ pure
+ [ fingerprint
+ | (fingerprint, FactEntry proposition _occurrence _authorization) <-
+ Map.toAscList (logicalBuilderFacts builder)
+ , checkedPropositionTerm proposition == target
+ ]
+
+resolveVisibleGlobalDriver
+ :: SemanticGlobalKey
+ -> ModuleDriver failure (Maybe (SemanticGlobalTarget, CoreType))
+resolveVisibleGlobalDriver key = ModuleDriver do
+ DriverState _resolver builder _prefix _validation <- State.get
+ pure
+ ( (\(target, _content, _dependencies) ->
+ ( target
+ , fromMaybe
+ (impossible "visible semantic key has no source type")
+ (semanticGlobalKeyType key)
+ ))
+ <$> resolveVisibleGlobalContent builder key
+ )
+
+resolveVisibleGlobalContentDriver
+ :: SemanticGlobalKey
+ -> ModuleDriver failure
+ (Maybe
+ ( SemanticGlobalTarget
+ , ObjectContent
+ , Map ObjectId CoreType
+ ))
+resolveVisibleGlobalContentDriver key = ModuleDriver do
+ DriverState _resolver builder _prefix _validation <- State.get
+ pure (resolveVisibleGlobalContent builder key)
+
+resolveVisibleStructureDriver
+ :: SemanticStructurePhrase
+ -> ModuleDriver failure (Maybe ResolvedStructure)
+resolveVisibleStructureDriver structurePhrase = ModuleDriver do
+ DriverState _resolver builder _prefix _validation <- State.get
+ pure (Map.lookup structurePhrase (logicalBuilderStructures builder))
+
+resolveVisibleStructureOperationObjectsDriver
+ :: StructSymbol
+ -> ModuleDriver failure [ObjectId]
+resolveVisibleStructureOperationObjectsDriver symbol = ModuleDriver do
+ DriverState _resolver builder _prefix _validation <- State.get
+ pure
+ ( Set.toAscList
+ (Set.fromList
+ (mapMaybe
+ (resolvedStructureOperation symbol)
+ (Map.elems (logicalBuilderStructures builder))))
+ )
+
+resolveVisibleGlobalContent
+ :: BuilderState evidence
+ -> SemanticGlobalKey
+ -> Maybe
+ ( SemanticGlobalTarget
+ , ObjectContent
+ , Map ObjectId CoreType
+ )
+resolveVisibleGlobalContent builder key = do
+ target <- Map.lookup key (logicalBuilderGlobals builder)
+ content <-
+ lookupCheckedObjectContent
+ (semanticGlobalTargetObject target)
+ closure
+ let dependencies =
+ Map.fromList
+ [ (identity, requireDependency identity)
+ | identity <- case content of
+ TransparentObjectContent _theory _coreType body ->
+ Set.toAscList (canonicalTermGlobals body)
+ _ -> []
+ ]
+ pure
+ ( target
+ , content
+ , dependencies
+ )
+ where
+ closure = logicalBuilderObjectClosure builder
+
+ requireDependency identity =
+ case lookupCheckedObjectType identity closure of
+ Just coreType -> coreType
+ Nothing ->
+ impossible "checked object dependency is absent"
+
+objectAvailableDriver
+ :: ObjectId
+ -> ModuleDriver failure Bool
+objectAvailableDriver identity =
+ ModuleDriver
+ (State.gets
+ (\(DriverState _resolver builder _prefix _validation) ->
+ isJust
+ (lookupCheckedObjectType
+ identity
+ (logicalBuilderObjectClosure builder))))
+
+objectTypeDriver
+ :: ObjectId
+ -> ModuleDriver failure (Maybe CoreType)
+objectTypeDriver identity =
+ ModuleDriver
+ (State.gets
+ (\(DriverState _resolver builder _prefix _validation) ->
+ lookupCheckedObjectType
+ identity
+ (logicalBuilderObjectClosure builder)))
+
+nextDeclarationSlotLowering :: LoweringDriver DeclarationSlot
+nextDeclarationSlotLowering =
+ withLoweringBuilder \builder ->
+ declarationSlot
+ (logicalBuilderOwner builder)
+ (localDeclarationOrdinal
+ (logicalBuilderNextDeclaration builder))
+
+currentTheoryLowering :: LoweringDriver TheoryId
+currentTheoryLowering =
+ withLoweringBuilder logicalBuilderTheory
+
+currentFoundationLowering :: LoweringDriver CheckedFoundation
+currentFoundationLowering =
+ withLoweringBuilder logicalBuilderFoundation
+
+currentFoundationAxiomLowering
+ :: FoundationAxiomTag
+ -> LoweringDriver (FrozenCheckedCore Void)
+currentFoundationAxiomLowering tag =
+ withLoweringBuilder
+ (\builder ->
+ foundationAxiomFrozen
+ (logicalBuilderFoundation builder)
+ tag)
+
+resolveVisibleFactAliasLowering
+ :: SemanticName
+ -> LoweringDriver (Maybe SemanticFactOccurrenceFingerprint)
+resolveVisibleFactAliasLowering alias =
+ withLoweringBuilder \builder -> do
+ ImportedAliasBinding fingerprint _origin <-
+ Map.lookup alias (logicalBuilderAliases builder)
+ pure fingerprint
+
+resolveVisibleFactTargetsLowering
+ :: FrozenCheckedCore ObjectId
+ -> LoweringDriver [SemanticFactOccurrenceFingerprint]
+resolveVisibleFactTargetsLowering target =
+ withLoweringBuilder \builder ->
+ [ fingerprint
+ | (fingerprint, FactEntry proposition _occurrence _evidence) <-
+ Map.toAscList (logicalBuilderFacts builder)
+ , checkedPropositionTerm proposition == target
+ ]
+
+resolveVisibleGlobalLowering
+ :: SemanticGlobalKey
+ -> LoweringDriver (Maybe (SemanticGlobalTarget, CoreType))
+resolveVisibleGlobalLowering key =
+ withLoweringBuilder \builder ->
+ ( (\(target, _content, _dependencies) ->
+ ( target
+ , fromMaybe
+ (impossible "visible semantic key has no source type")
+ (semanticGlobalKeyType key)
+ ))
+ <$> resolveVisibleGlobalContent builder key
+ )
+
+resolveVisibleGlobalContentLowering
+ :: SemanticGlobalKey
+ -> LoweringDriver
+ (Maybe
+ ( SemanticGlobalTarget
+ , ObjectContent
+ , Map ObjectId CoreType
+ ))
+resolveVisibleGlobalContentLowering key =
+ withLoweringBuilder (\builder -> resolveVisibleGlobalContent builder key)
+
+resolveVisibleStructureLowering
+ :: SemanticStructurePhrase
+ -> LoweringDriver (Maybe ResolvedStructure)
+resolveVisibleStructureLowering phrase =
+ withLoweringBuilder (Map.lookup phrase . logicalBuilderStructures)
+
+resolveVisibleStructureOperationObjectsLowering
+ :: StructSymbol
+ -> LoweringDriver [ObjectId]
+resolveVisibleStructureOperationObjectsLowering symbol =
+ withLoweringBuilder \builder ->
+ Set.toAscList
+ (Set.fromList
+ (mapMaybe
+ (resolvedStructureOperation symbol)
+ (Map.elems (logicalBuilderStructures builder))))
+
+objectAvailableLowering :: ObjectId -> LoweringDriver Bool
+objectAvailableLowering identity =
+ withLoweringBuilder
+ (isJust
+ . lookupCheckedObjectType identity
+ . logicalBuilderObjectClosure)
+
+objectTypeLowering :: ObjectId -> LoweringDriver (Maybe CoreType)
+objectTypeLowering identity =
+ withLoweringBuilder
+ (lookupCheckedObjectType identity
+ . logicalBuilderObjectClosure)
+
+withLoweringBuilder
+ :: (BuilderState PlanningEvidence -> value)
+ -> LoweringDriver value
+withLoweringBuilder action = LoweringDriver do
+ LoweringState _resolver (ProspectiveBuilder builder) _validation <- State.get
+ pure (action builder)
+
+
+data CandidateSpec = CandidateSpec
+ !CheckedPropositionContent
+ !FactSearchEligibility
+ ![SemanticName]
+
+candidateSpec
+ :: CheckedPropositionContent
+ -> FactSearchEligibility
+ -> [SemanticName]
+ -> CandidateSpec
+candidateSpec =
+ CandidateSpec
+
+-- | One strictly earlier candidate in the same checked declaration. The
+-- position is structural and becomes a concrete fact slot only while the
+-- declaration is planned.
+data PlannedCandidatePosition = PlannedCandidatePosition
+ !Natural
+ !Natural
+ deriving stock (Show, Eq, Ord)
+
+data CandidatePlanningDirect
+ = PlanningDefinitionEquation !ObjectId
+ | PlanningSourceAxiom
+ | PlanningTrustedDatatype !DatatypeCompilationDescriptor
+ | PlanningKernelConstruction !KernelConstructionDescriptor
+ | PlanningCheckedSourceProof
+ | PlanningOmitted
+ deriving stock (Show, Eq)
+
+-- | Erased exact request data needed by scheduling and public-contract
+-- planning. The family body retains the typed obligation used by admission;
+-- this projection carries only its one canonical request and exact global
+-- premise fingerprints.
+data CheckedPlannedVampireRequest = CheckedPlannedVampireRequest
+ !Location
+ !Provers.PreparedVerificationRequest
+ ![SemanticFactOccurrenceFingerprint]
+
+data CandidatePlanningSpec = CandidatePlanningSpec
+ !CandidatePlanningDirect
+ ![SemanticFactOccurrenceFingerprint]
+ ![PlannedCandidatePosition]
+ ![CheckedPlannedVampireRequest]
+
+-- | One authority-free checked candidate and its exact planning contract.
+-- The two parts are constructed together by the declaration-family lowerer
+-- and retain no builder capability or publication path.
+data CheckedCandidate = CheckedCandidate
+ !CandidateSpec
+ !CandidatePlanningSpec
+
+checkedCandidate
+ :: CandidateSpec
+ -> CandidatePlanningSpec
+ -> CheckedCandidate
+checkedCandidate = CheckedCandidate
+
+checkedDefinitionEquationPlanning :: ObjectId -> CandidatePlanningSpec
+checkedDefinitionEquationPlanning identity =
+ CandidatePlanningSpec
+ (PlanningDefinitionEquation identity) [] [] []
+
+checkedSourceAxiomPlanning :: CandidatePlanningSpec
+checkedSourceAxiomPlanning =
+ CandidatePlanningSpec PlanningSourceAxiom [] [] []
+
+checkedDatatypePlanning
+ :: DatatypeCompilationDescriptor
+ -> CandidatePlanningSpec
+checkedDatatypePlanning descriptor =
+ CandidatePlanningSpec (PlanningTrustedDatatype descriptor) [] [] []
+
+checkedKernelPlanning
+ :: KernelConstructionDescriptor
+ -> [SemanticFactOccurrenceFingerprint]
+ -> CandidatePlanningSpec
+checkedKernelPlanning descriptor facts =
+ CandidatePlanningSpec
+ (PlanningKernelConstruction descriptor)
+ facts
+ []
+ []
+
+checkedKernelPlanningWithStaged
+ :: KernelConstructionDescriptor
+ -> [SemanticFactOccurrenceFingerprint]
+ -> [PlannedCandidatePosition]
+ -> CandidatePlanningSpec
+checkedKernelPlanningWithStaged descriptor facts staged =
+ CandidatePlanningSpec
+ (PlanningKernelConstruction descriptor)
+ facts
+ staged
+ []
+
+checkedStagedKernelPlanning
+ :: KernelConstructionDescriptor
+ -> [PlannedCandidatePosition]
+ -> CandidatePlanningSpec
+checkedStagedKernelPlanning descriptor staged =
+ CandidatePlanningSpec
+ (PlanningKernelConstruction descriptor)
+ []
+ staged
+ []
+
+checkedSourceProofPlanning
+ :: [CheckedPlannedVampireRequest]
+ -> [PlannedCandidatePosition]
+ -> CandidatePlanningSpec
+checkedSourceProofPlanning requests staged =
+ CandidatePlanningSpec
+ PlanningCheckedSourceProof [] staged requests
+
+checkedOmittedPlanning
+ :: [CheckedPlannedVampireRequest]
+ -> [PlannedCandidatePosition]
+ -> CandidatePlanningSpec
+checkedOmittedPlanning requests staged =
+ CandidatePlanningSpec PlanningOmitted [] staged requests
+
+checkedPlannedVampireRequest
+ :: Location
+ -> PreparedVampireObligation local origin
+ -> CheckedPlannedVampireRequest
+checkedPlannedVampireRequest location
+ (PreparedVampireObligation expected task) =
+ CheckedPlannedVampireRequest
+ location
+ (Provers.preparedTypedProverRequest task)
+ ( Backend.typedBackendFactReference
+ <$> Vector.toList
+ (Backend.typedProblemGlobalPremises expected)
+ )
+
+plannedEarlierCandidate
+ :: Natural
+ -> Natural
+ -> PlannedCandidatePosition
+plannedEarlierCandidate =
+ PlannedCandidatePosition
+
+data CheckedDeclarationMode
+ = CheckedProofMode !ProofSyntaxId
+ | CheckedCompiledMode !DeclarationSyntaxId
+
+-- | One completely lowered, authority-free declaration transaction. The
+-- family body is a closed checked authorization recipe owned by its exact
+-- lowerer; structural effects and stable source-ordered candidate stages are
+-- common. This value is invocation-local and never serialized.
+data CheckedDeclaration body = CheckedDeclaration
+ !CheckedDeclarationMode
+ ![AssertedObject]
+ ![CheckedPropositionContent]
+ ![SemanticGlobalBinding]
+ ![SemanticStructureDescriptor]
+ ![NonEmpty CheckedCandidate]
+ !body
+
+checkedProofDeclaration
+ :: ProofSyntaxId
+ -> [AssertedObject]
+ -> [CheckedPropositionContent]
+ -> [SemanticGlobalBinding]
+ -> [SemanticStructureDescriptor]
+ -> [NonEmpty CheckedCandidate]
+ -> body
+ -> CheckedDeclaration body
+checkedProofDeclaration
+ syntax objects propositions globals structures stages =
+ CheckedDeclaration
+ (CheckedProofMode syntax) objects propositions globals structures
+ stages
+
+checkedCompiledDeclaration
+ :: DeclarationSyntaxId
+ -> [AssertedObject]
+ -> [CheckedPropositionContent]
+ -> [SemanticGlobalBinding]
+ -> [SemanticStructureDescriptor]
+ -> [NonEmpty CheckedCandidate]
+ -> body
+ -> CheckedDeclaration body
+checkedCompiledDeclaration
+ syntax objects propositions globals structures stages =
+ CheckedDeclaration
+ (CheckedCompiledMode syntax) objects propositions globals structures
+ stages
+
+prepareCandidateSpecDriver
+ :: [AssertedObject]
+ -> ScopedCheckedCore ObjectId
+ -> FactSearchEligibility
+ -> [SemanticName]
+ -> ModuleDriver failure (Either DeclarationError CandidateSpec)
+prepareCandidateSpecDriver objects scoped eligibility aliases =
+ prepareCandidateSpecWithDriverClosure objects
+ (\closure ->
+ prepareScopedCandidateSpec
+ closure scoped eligibility aliases)
+
+prepareFrozenCandidateSpecDriver
+ :: [AssertedObject]
+ -> FrozenCheckedCore ObjectId
+ -> FactSearchEligibility
+ -> [SemanticName]
+ -> ModuleDriver failure (Either DeclarationError CandidateSpec)
+prepareFrozenCandidateSpecDriver objects frozen eligibility aliases =
+ prepareCandidateSpecWithDriverClosure objects
+ (\closure ->
+ prepareFrozenCandidateSpec
+ closure frozen eligibility aliases)
+
+prepareDefinitionEquationSpecDriver
+ :: [AssertedObject]
+ -> ObjectId
+ -> SemanticName
+ -> ModuleDriver failure (Either DeclarationError CandidateSpec)
+prepareDefinitionEquationSpecDriver objects identity alias =
+ prepareCandidateSpecWithDriverClosure objects
+ (\closure ->
+ prepareDefinitionEquationSpec closure identity alias)
+
+preparePointwiseDefinitionEquationSpecDriver
+ :: [AssertedObject]
+ -> ObjectId
+ -> SemanticName
+ -> ModuleDriver failure (Either DeclarationError CandidateSpec)
+preparePointwiseDefinitionEquationSpecDriver objects identity alias =
+ prepareCandidateSpecWithDriverClosure objects
+ (\closure ->
+ preparePointwiseDefinitionEquationSpec closure identity alias)
+
+prepareCandidateSpecLowering
+ :: [AssertedObject]
+ -> ScopedCheckedCore ObjectId
+ -> FactSearchEligibility
+ -> [SemanticName]
+ -> LoweringDriver (Either DeclarationError CandidateSpec)
+prepareCandidateSpecLowering objects scoped eligibility aliases =
+ prepareCandidateSpecWithLoweringClosure objects
+ (\closure ->
+ prepareScopedCandidateSpec
+ closure scoped eligibility aliases)
+
+prepareFrozenCandidateSpecLowering
+ :: [AssertedObject]
+ -> FrozenCheckedCore ObjectId
+ -> FactSearchEligibility
+ -> [SemanticName]
+ -> LoweringDriver (Either DeclarationError CandidateSpec)
+prepareFrozenCandidateSpecLowering objects frozen eligibility aliases =
+ prepareCandidateSpecWithLoweringClosure objects
+ (\closure ->
+ prepareFrozenCandidateSpec
+ closure frozen eligibility aliases)
+
+prepareDefinitionEquationSpecLowering
+ :: [AssertedObject]
+ -> ObjectId
+ -> SemanticName
+ -> LoweringDriver (Either DeclarationError CandidateSpec)
+prepareDefinitionEquationSpecLowering objects identity alias =
+ prepareDefinitionEquationSpecWithEligibilityLowering
+ objects identity SearchEligible alias
+
+prepareDefinitionEquationSpecWithEligibilityLowering
+ :: [AssertedObject]
+ -> ObjectId
+ -> FactSearchEligibility
+ -> SemanticName
+ -> LoweringDriver (Either DeclarationError CandidateSpec)
+prepareDefinitionEquationSpecWithEligibilityLowering
+ objects identity eligibility alias =
+ prepareCandidateSpecWithLoweringClosure objects
+ (\closure ->
+ prepareDefinitionEquationSpecWithEligibility
+ closure identity eligibility alias)
+
+-- | Prepare the unaliased first-order view of one checked, named set
+-- construction. The returned descriptor binds direct authorization to the
+-- transparent object and the complete checked source decomposition.
+prepareNamedSetConstructionSpecLowering
+ :: [AssertedObject]
+ -> ObjectId
+ -> NamedSetConstruction ObjectId
+ -> LoweringDriver
+ (Either
+ DeclarationError
+ (CandidateSpec, KernelConstructionDescriptor))
+prepareNamedSetConstructionSpecLowering objects identity construction =
+ withLoweringBuilder \builder -> do
+ closure <-
+ first DeclarationObjectValidationFailed
+ (extendObjectClosure
+ (logicalBuilderObjectClosure builder)
+ objects)
+ (proposition, descriptor) <-
+ prepareNamedSetConstruction
+ (logicalBuilderFoundation builder)
+ closure identity construction
+ spec <-
+ prepareFrozenCandidateSpec
+ closure proposition SearchEligible []
+ pure (spec, descriptor)
+
+prepareRelationalSetConstructionSpecLowering
+ :: [AssertedObject]
+ -> ObjectId
+ -> CheckedRelationalSetConstruction ObjectId
+ -> FrozenCheckedCore ObjectId
+ -> LoweringDriver
+ (Either
+ DeclarationError
+ (CandidateSpec, KernelConstructionDescriptor))
+prepareRelationalSetConstructionSpecLowering
+ objects identity construction functionality =
+ withLoweringBuilder \builder -> do
+ closure <-
+ first DeclarationObjectValidationFailed
+ (extendObjectClosure
+ (logicalBuilderObjectClosure builder)
+ objects)
+ (proposition, descriptor) <-
+ prepareRelationalSetConstruction
+ (logicalBuilderFoundation builder)
+ closure identity construction functionality
+ spec <-
+ prepareFrozenCandidateSpec
+ closure proposition SearchEligible []
+ pure (spec, descriptor)
+
+preparePointwiseDefinitionEquationSpecLowering
+ :: [AssertedObject]
+ -> ObjectId
+ -> SemanticName
+ -> LoweringDriver (Either DeclarationError CandidateSpec)
+preparePointwiseDefinitionEquationSpecLowering objects identity alias =
+ prepareCandidateSpecWithLoweringClosure objects
+ (\closure ->
+ preparePointwiseDefinitionEquationSpec closure identity alias)
+
+-- | Prepare one closed Vampire obligation whose only premise is a
+-- conditionally available fact from a strictly earlier candidate stage of
+-- the same declaration. The temporary checked stage contains public fact
+-- semantics and structural provenance only; admission must still consume the
+-- corresponding real reserved candidate before accepting this request.
+prepareStagedCandidateVampireDriver
+ :: Location
+ -> [AssertedObject]
+ -> CandidateSpec
+ -> CandidateSpec
+ -> ModuleDriver failure
+ (Either
+ DeclarationError
+ (PreparedVampireObligation Void ()))
+prepareStagedCandidateVampireDriver
+ location objects premiseSpec targetSpec =
+ ModuleDriver do
+ DriverState _resolver builder _prefix _validation <- State.get
+ State.lift
+ (liftIO
+ (prepareStagedCandidateVampireWith
+ builder location objects premiseSpec targetSpec))
+
+prepareStagedCandidateVampireLowering
+ :: Location
+ -> [AssertedObject]
+ -> CandidateSpec
+ -> CandidateSpec
+ -> LoweringDriver
+ (Either
+ DeclarationError
+ (PreparedVampireObligation Void ()))
+prepareStagedCandidateVampireLowering
+ location objects premiseSpec targetSpec =
+ LoweringDriver do
+ LoweringState _resolver (ProspectiveBuilder builder) _validation <-
+ State.get
+ liftIO
+ (prepareStagedCandidateVampireWith
+ builder location objects premiseSpec targetSpec)
+
+prepareStagedCandidateVampireWith
+ :: BuilderState evidence
+ -> Location
+ -> [AssertedObject]
+ -> CandidateSpec
+ -> CandidateSpec
+ -> IO
+ (Either
+ DeclarationError
+ (PreparedVampireObligation Void ()))
+prepareStagedCandidateVampireWith
+ builder location objects premiseSpec targetSpec = do
+ let staged = do
+ closure <-
+ first DeclarationObjectValidationFailed
+ (extendObjectClosure
+ (logicalBuilderObjectClosure builder)
+ objects)
+ premise <-
+ prospectiveStagePremise
+ builder closure premiseSpec targetSpec
+ pure (closure, premise)
+ case staged of
+ Left failure -> pure (Left failure)
+ Right (closure, premise) -> do
+ prepared <-
+ forceVampirePreparation
+ (prepareClosedCandidateVampire
+ builder closure
+ (candidateSpecProposition targetSpec)
+ [premise])
+ pure
+ (first
+ (ProofObligationFailedAt location
+ . CurrentCandidateVampirePreparationFailed)
+ prepared)
+
+candidateSpecProposition :: CandidateSpec -> CheckedPropositionContent
+candidateSpecProposition (CandidateSpec proposition _eligibility _aliases) =
+ proposition
+
+prospectiveStagePremise
+ :: BuilderState evidence
+ -> CheckedObjectClosure
+ -> CandidateSpec
+ -> CandidateSpec
+ -> Either DeclarationError CheckedPropositionContent
+prospectiveStagePremise builder closure premiseSpec targetSpec = do
+ let owner = logicalBuilderOwner builder
+ declaration =
+ declarationSlot owner
+ (localDeclarationOrdinal
+ (logicalBuilderNextDeclaration builder))
+ premiseSlot =
+ factSlot owner
+ (localFactOrdinal (logicalBuilderNextFact builder))
+ targetSlot =
+ factSlot owner
+ (localFactOrdinal (logicalBuilderNextFact builder + 1))
+ premiseStage = CandidateStage 0
+ targetStage = CandidateStage 1
+ proposition = candidateSpecProposition premiseSpec
+ authority =
+ factAuthority
+ (theoremRef
+ (logicalBuilderTheory builder)
+ (checkedPropositionId proposition))
+ cleanAuthoritySafety
+ CandidateSpec _ eligibility _aliases = premiseSpec
+ occurrence =
+ semanticFactOccurrence premiseSlot authority eligibility
+ stage =
+ CheckedDeclarationStage declaration closure
+ (Map.singleton premiseSlot
+ (FactEntry proposition occurrence
+ (PlanningEvidence
+ authority
+ (Just
+ (PlanningProvenance
+ declaration premiseStage premiseSlot)))))
+ unless
+ (propositionMatchesClosure closure
+ (candidateSpecProposition targetSpec))
+ (Left (PlanningFactContractMismatch targetSlot))
+ useProspectiveStageFact
+ stage targetStage targetSlot premiseStage premiseSlot
+
+useProspectiveStageFact
+ :: CheckedDeclarationStage
+ -> CandidateStage
+ -> FactSlot
+ -> CandidateStage
+ -> FactSlot
+ -> Either DeclarationError CheckedPropositionContent
+useProspectiveStageFact
+ (CheckedDeclarationStage ownSlot closure entries)
+ currentStage currentSlot expectedStage premiseSlot = do
+ FactEntry proposition occurrence
+ (PlanningEvidence authority provenance) <-
+ maybe
+ (Left (PlanningFactContractMismatch premiseSlot))
+ Right
+ (Map.lookup premiseSlot entries)
+ unless
+ ( provenance
+ == Just
+ (PlanningProvenance
+ ownSlot expectedStage premiseSlot)
+ && expectedStage < currentStage
+ && semanticFactSlot occurrence == premiseSlot
+ && semanticFactAuthority occurrence == authority
+ && propositionMatchesClosure closure proposition
+ )
+ (Left (PlanningFactContractMismatch currentSlot))
+ pure proposition
+
+prepareCandidateSpecWithDriverClosure
+ :: [AssertedObject]
+ -> (CheckedObjectClosure -> Either DeclarationError CandidateSpec)
+ -> ModuleDriver failure (Either DeclarationError CandidateSpec)
+prepareCandidateSpecWithDriverClosure objects prepare = ModuleDriver do
+ DriverState _resolver builder _prefix _validation <- State.get
+ pure do
+ closure <-
+ first DeclarationObjectValidationFailed
+ (extendObjectClosure
+ (logicalBuilderObjectClosure builder)
+ objects)
+ prepare closure
+
+prepareCandidateSpecWithLoweringClosure
+ :: [AssertedObject]
+ -> (CheckedObjectClosure -> Either DeclarationError value)
+ -> LoweringDriver (Either DeclarationError value)
+prepareCandidateSpecWithLoweringClosure objects prepare =
+ withLoweringBuilder \builder -> do
+ closure <-
+ first DeclarationObjectValidationFailed
+ (extendObjectClosure
+ (logicalBuilderObjectClosure builder)
+ objects)
+ prepare closure
+
+data ReservedCandidate = ReservedCandidate
+ !BuilderIdentity
+ !PrefixContextId
+ !DeclarationInvocation
+ !CandidateStage
+ !FactSlot
+ !CandidateSpec
+
+reservedCandidateSlot :: ReservedCandidate -> FactSlot
+reservedCandidateSlot
+ (ReservedCandidate _ _ _ _ slot _) =
+ slot
+
+reservedCandidateStage :: ReservedCandidate -> Natural
+reservedCandidateStage
+ (ReservedCandidate _ _ _ (CandidateStage stage) _ _) =
+ stage
+
+candidateCheckedProposition
+ :: ReservedCandidate
+ -> CheckedPropositionContent
+candidateCheckedProposition
+ (ReservedCandidate _ _ _ _ _ (CandidateSpec proposition _ _)) =
+ proposition
+
+candidateTheoremReference
+ :: BuilderState evidence
+ -> ReservedCandidate
+ -> TheoremRef
+candidateTheoremReference builder candidate =
+ theoremRef
+ (logicalBuilderTheory builder)
+ (checkedPropositionId
+ (candidateCheckedProposition candidate))
+
+
+data PendingCandidate = PendingCandidate
+ !ReservedCandidate
+ !ValidationCertificate
+ !PendingFactAuthorization
+
+data DeclarationValidationSelection
+ = DeclarationValidationUnselected
+ | FreshDeclarationValidation
+ | CachedDeclarationValidation
+ !DeclarationSyntaxId
+ ![ObjectId]
+ ![TheoremId]
+ !DeclarationValidationRecord
+ !(Map FactSlot Natural)
+
+data DeclarationState = DeclarationState
+ { declarationVampireResolver :: !VampireResolver
+ , declarationValidationRun :: !ValidationRun
+ , declarationValidationMode :: !ValidationMode
+ , declarationValidationSelection
+ :: !DeclarationValidationSelection
+ , declarationBuilder :: !LogicalBuilder
+ , declarationOwnSlot :: !DeclarationSlot
+ , declarationInvocation :: !DeclarationInvocation
+ , declarationObjectsReversed :: ![AssertedObject]
+ , declarationObjectClosure :: !(Maybe CheckedObjectClosure)
+ , declarationPropositionsReversed
+ :: ![CheckedPropositionContent]
+ , declarationGlobalBindingsReversed
+ :: ![SemanticGlobalBinding]
+ , declarationStructureDescriptorsReversed
+ :: ![SemanticStructureDescriptor]
+ , declarationReservations
+ :: !(Map FactSlot ReservedCandidate)
+ , declarationPending :: !(Map FactSlot PendingCandidate)
+ , declarationNextFact :: !Natural
+ , declarationNextStage :: !Natural
+ , declarationAuthorizationFrontier :: !Natural
+ }
+
+-- | Authority-free view of checked declaration-local semantics. This is the
+-- narrow staging substrate for later lowerers: it carries the exact object
+-- closure and conditional public fact contracts, but no builder identity,
+-- prefix, invocation, pending authorization, admitted alias, or published
+-- occurrence.
+data CheckedDeclarationStage = CheckedDeclarationStage
+ !DeclarationSlot
+ !CheckedObjectClosure
+ !(Map FactSlot (FactEntry PlanningEvidence))
+
+newtype Declaration value = Declaration
+ { runDeclaration
+ :: StateT
+ DeclarationState
+ (ExceptT DeclarationError IO)
+ value
+ }
+ deriving newtype (Functor, Applicative, Monad)
+
+failDeclaration :: DeclarationError -> Declaration value
+failDeclaration =
+ Declaration . State.lift . Except.throwError
+
+addDeclarationObject :: AssertedObject -> Declaration ()
+addDeclarationObject asserted = Declaration do
+ state <- State.get
+ case declarationValidationSelection state of
+ DeclarationValidationUnselected -> pure ()
+ _ ->
+ State.lift
+ (Except.throwError
+ DeclarationShapeChangedAfterValidationLookup)
+ when
+ (isJust (declarationObjectClosure state))
+ (State.lift
+ (Except.throwError
+ DeclarationObjectAddedAfterAuthorization))
+ State.put
+ state
+ { declarationObjectsReversed =
+ asserted : declarationObjectsReversed state
+ }
+
+addDeclarationProposition
+ :: CheckedPropositionContent
+ -> Declaration ()
+addDeclarationProposition proposition =
+ Declaration
+ (State.modify' \state ->
+ state
+ { declarationPropositionsReversed =
+ proposition
+ : declarationPropositionsReversed state
+ })
+
+resolveVisibleGlobal
+ :: SemanticGlobalKey
+ -> Declaration (Maybe (SemanticGlobalTarget, CoreType))
+resolveVisibleGlobal key = Declaration do
+ state <- State.get
+ let builder = declarationBuilder state
+ pure do
+ target <- Map.lookup key (logicalBuilderGlobals builder)
+ coreType <- semanticGlobalKeyType key
+ pure (target, coreType)
+
+stageSemanticGlobalBinding
+ :: SemanticGlobalKey
+ -> SemanticGlobalTarget
+ -> Declaration ()
+stageSemanticGlobalBinding key target = Declaration do
+ state <- State.get
+ case declarationValidationSelection state of
+ DeclarationValidationUnselected -> pure ()
+ _ ->
+ State.lift
+ (Except.throwError
+ DeclarationShapeChangedAfterValidationLookup)
+ when
+ (any
+ ((== key) . semanticGlobalBindingKey)
+ (declarationGlobalBindingsReversed state))
+ (State.lift
+ (Except.throwError
+ (DeclarationGlobalAlreadyStaged key)))
+ State.put
+ state
+ { declarationGlobalBindingsReversed =
+ semanticGlobalBinding key target
+ : declarationGlobalBindingsReversed state
+ }
+
+stageSemanticStructureDescriptor
+ :: SemanticStructureDescriptor
+ -> Declaration ()
+stageSemanticStructureDescriptor descriptor = Declaration do
+ state <- State.get
+ case declarationValidationSelection state of
+ DeclarationValidationUnselected -> pure ()
+ _ ->
+ State.lift
+ (Except.throwError
+ DeclarationShapeChangedAfterValidationLookup)
+ let structurePhrase = semanticStructureDescriptorPhrase descriptor
+ when
+ (any
+ ((== structurePhrase) . semanticStructureDescriptorPhrase)
+ (declarationStructureDescriptorsReversed state))
+ (State.lift
+ (Except.throwError
+ (DeclarationStructureAlreadyStaged structurePhrase)))
+ State.put
+ state
+ { declarationStructureDescriptorsReversed =
+ descriptor
+ : declarationStructureDescriptorsReversed state
+ }
+
+prepareScopedCandidateSpec
+ :: CheckedObjectClosure
+ -> ScopedCheckedCore ObjectId
+ -> FactSearchEligibility
+ -> [SemanticName]
+ -> Either DeclarationError CandidateSpec
+prepareScopedCandidateSpec closure scoped eligibility aliases = do
+ frozen <-
+ maybe (Left CandidatePropositionNotClosed) Right
+ (closeScopedCore scoped)
+ prepareFrozenCandidateSpec closure frozen eligibility aliases
+
+prepareFrozenCandidateSpec
+ :: CheckedObjectClosure
+ -> FrozenCheckedCore ObjectId
+ -> FactSearchEligibility
+ -> [SemanticName]
+ -> Either DeclarationError CandidateSpec
+prepareFrozenCandidateSpec closure frozen eligibility aliases = do
+ unless
+ (frozenCoreType frozen == TyProp)
+ (Left
+ (CandidatePropositionNotProposition
+ (frozenCoreType frozen)))
+ proposition <-
+ first DeclarationPropositionValidationFailed
+ (validatePropositionContent closure (frozenCoreTerm frozen))
+ pure (candidateSpec proposition eligibility aliases)
+
+prepareDefinitionEquationSpec
+ :: CheckedObjectClosure
+ -> ObjectId
+ -> SemanticName
+ -> Either DeclarationError CandidateSpec
+prepareDefinitionEquationSpec closure identity alias = do
+ prepareDefinitionEquationSpecWithEligibility
+ closure identity SearchEligible alias
+
+prepareDefinitionEquationSpecWithEligibility
+ :: CheckedObjectClosure
+ -> ObjectId
+ -> FactSearchEligibility
+ -> SemanticName
+ -> Either DeclarationError CandidateSpec
+prepareDefinitionEquationSpecWithEligibility
+ closure identity eligibility alias = do
+ content <-
+ maybe
+ (Left (DefinitionEquationObjectMissing identity))
+ Right
+ (lookupCheckedObjectContent identity closure)
+ (coreType, body) <-
+ case content of
+ TransparentObjectContent _theory objectType objectBody ->
+ Right (objectType, objectBody)
+ _ ->
+ Left (DefinitionEquationObjectNotTransparent identity)
+ proposition <-
+ first DeclarationPropositionValidationFailed
+ (validatePropositionContent
+ closure
+ (CEq coreType (CGlobal identity) body))
+ pure (candidateSpec proposition eligibility [alias])
+
+prepareNamedSetConstruction
+ :: CheckedFoundation
+ -> CheckedObjectClosure
+ -> ObjectId
+ -> NamedSetConstruction ObjectId
+ -> Either
+ DeclarationError
+ (FrozenCheckedCore ObjectId, KernelConstructionDescriptor)
+prepareNamedSetConstruction foundation closure identity construction = do
+ let expectedContent = namedSetConstructionClosedBody construction
+ case lookupCheckedObjectContent identity closure of
+ Just (TransparentObjectContent _theory coreType body)
+ | coreType == frozenCoreType expectedContent
+ , body == frozenCoreTerm expectedContent -> pure ()
+ _ -> Left KernelConstructionDescriptorMismatch
+ derived <-
+ maybe
+ (Left KernelConstructionDescriptorMismatch)
+ Right
+ (namedSetConstructionObjectFact
+ (checkedFoundationSetConstruction foundation)
+ identity
+ construction)
+ pure
+ ( namedSetConstructionFactProposition derived
+ , CheckedSetConstructionExtensionality
+ identity
+ (namedSetConstructionFactDescriptor derived)
+ )
+
+prepareRelationalSetConstruction
+ :: CheckedFoundation
+ -> CheckedObjectClosure
+ -> ObjectId
+ -> CheckedRelationalSetConstruction ObjectId
+ -> FrozenCheckedCore ObjectId
+ -> Either
+ DeclarationError
+ (FrozenCheckedCore ObjectId, KernelConstructionDescriptor)
+prepareRelationalSetConstruction
+ foundation closure identity construction functionality = do
+ let expectedContent = relationalSetConstructionClosedBody construction
+ case lookupCheckedObjectContent identity closure of
+ Just (TransparentObjectContent _theory coreType body)
+ | coreType == frozenCoreType expectedContent
+ , body == frozenCoreTerm expectedContent -> pure ()
+ _ -> Left KernelConstructionDescriptorMismatch
+ derived <-
+ maybe
+ (Left KernelConstructionDescriptorMismatch)
+ Right
+ (relationalSetConstructionObjectFact
+ (checkedFoundationSetConstruction foundation)
+ identity
+ construction
+ functionality)
+ pure
+ ( relationalSetConstructionFactProposition derived
+ , CheckedSetConstructionExtensionality
+ identity
+ (relationalSetConstructionFactDescriptor derived)
+ )
+
+preparePointwiseDefinitionEquationSpec
+ :: CheckedObjectClosure
+ -> ObjectId
+ -> SemanticName
+ -> Either DeclarationError CandidateSpec
+preparePointwiseDefinitionEquationSpec closure identity alias = do
+ content <-
+ maybe
+ (Left (DefinitionEquationObjectMissing identity))
+ Right
+ (lookupCheckedObjectContent identity closure)
+ body <-
+ case content of
+ TransparentObjectContent _theory
+ (TyArrow TySet TyProp) (CLam TySet predicate) ->
+ Right predicate
+ _ ->
+ Left
+ (DefinitionEquationObjectNotPointwisePredicate identity)
+ proposition <-
+ first DeclarationPropositionValidationFailed
+ (validatePropositionContent
+ closure
+ (CForall TySet
+ (CEq TyProp
+ (CApp (CGlobal identity) (CBound 0))
+ body)))
+ pure (candidateSpec proposition SearchEligible [alias])
+
+reserveCandidate
+ :: CandidateSpec
+ -> Declaration ReservedCandidate
+reserveCandidate spec =
+ NonEmpty.head <$> reserveCandidateBatch (spec :| [])
+
+-- | Validate and reserve one closed checked proposition in the current
+-- declaration closure.
+reservePropositionCandidate
+ :: ScopedCheckedCore ObjectId
+ -> FactSearchEligibility
+ -> [SemanticName]
+ -> Declaration ReservedCandidate
+reservePropositionCandidate scoped eligibility aliases = Declaration do
+ unprepared <- State.get
+ prepared <-
+ State.lift
+ (Except.liftEither
+ (prepareDeclarationClosure unprepared))
+ spec <-
+ State.lift
+ (Except.liftEither
+ (prepareScopedCandidateSpec
+ (fromMaybe
+ (impossible
+ "prepared proposition closure is absent")
+ (declarationObjectClosure prepared))
+ scoped eligibility aliases))
+ State.put prepared
+ runDeclaration (reserveCandidate spec)
+
+-- | Validate and reserve one stage of already closed checked propositions in
+-- the current declaration closure.
+reserveFrozenPropositionCandidateBatch
+ :: NonEmpty
+ ( FrozenCheckedCore ObjectId
+ , FactSearchEligibility
+ , [SemanticName]
+ )
+ -> Declaration (NonEmpty ReservedCandidate)
+reserveFrozenPropositionCandidateBatch inputs = Declaration do
+ unprepared <- State.get
+ prepared <-
+ State.lift
+ (Except.liftEither
+ (prepareDeclarationClosure unprepared))
+ let closure =
+ maybe
+ (impossible "prepared proposition closure is absent")
+ id
+ (declarationObjectClosure prepared)
+ specs <- traverse (prepareInput closure) inputs
+ State.put prepared
+ runDeclaration (reserveCandidateBatch specs)
+ where
+ prepareInput closure (frozen, eligibility, aliases) =
+ State.lift
+ (Except.liftEither
+ (prepareFrozenCandidateSpec
+ closure frozen eligibility aliases))
+
+reserveCandidateBatch
+ :: NonEmpty CandidateSpec
+ -> Declaration (NonEmpty ReservedCandidate)
+reserveCandidateBatch specs = Declaration do
+ state <- State.get
+ case declarationValidationSelection state of
+ DeclarationValidationUnselected -> pure ()
+ _ ->
+ State.lift
+ (Except.throwError
+ DeclarationShapeChangedAfterValidationLookup)
+ let builder = declarationBuilder state
+ stage = CandidateStage (declarationNextStage state)
+ firstFact = declarationNextFact state
+ candidates =
+ NonEmpty.zipWith
+ (makeReserved builder state stage)
+ (0 :| [1 ..])
+ specs
+ reservations' =
+ foldl'
+ (\reservations candidate ->
+ Map.insert
+ (reservedCandidateSlot candidate)
+ candidate
+ reservations)
+ (declarationReservations state)
+ candidates
+ candidateCount = fromIntegral (NonEmpty.length specs)
+ State.put
+ state
+ { declarationReservations = reservations'
+ , declarationNextFact = firstFact + candidateCount
+ , declarationNextStage =
+ declarationNextStage state + 1
+ }
+ pure candidates
+ where
+ makeReserved builder state stage offset spec =
+ ReservedCandidate
+ (logicalBuilderIdentity builder)
+ (logicalBuilderPrefix builder)
+ (declarationInvocation state)
+ stage
+ (factSlot
+ (logicalBuilderOwner builder)
+ (localFactOrdinal
+ (declarationNextFact state + offset)))
+ spec
+
+-- | Construct the one defining equation owned by a checked transparent
+-- object. The equation is derived from authoritative object content, not from
+-- a caller-supplied proposition.
+reserveDefinitionEquationCandidate
+ :: ObjectId
+ -> SemanticName
+ -> Declaration ReservedCandidate
+reserveDefinitionEquationCandidate identity alias = Declaration do
+ unprepared <- State.get
+ prepared <-
+ State.lift
+ (Except.liftEither
+ (prepareDeclarationClosure unprepared))
+ spec <-
+ State.lift
+ (Except.liftEither
+ (prepareDefinitionEquationSpec
+ (fromMaybe
+ (impossible
+ "prepared definition closure is absent")
+ (declarationObjectClosure prepared))
+ identity alias))
+ State.put prepared
+ runDeclaration (reserveCandidate spec)
+
+-- | Construct the pointwise presentation of one unary transparent predicate
+-- definition. The candidate remains tied to the object's checked content.
+reservePointwiseDefinitionEquationCandidate
+ :: ObjectId
+ -> SemanticName
+ -> Declaration ReservedCandidate
+reservePointwiseDefinitionEquationCandidate identity alias = Declaration do
+ unprepared <- State.get
+ prepared <-
+ State.lift
+ (Except.liftEither
+ (prepareDeclarationClosure unprepared))
+ spec <-
+ State.lift
+ (Except.liftEither
+ (preparePointwiseDefinitionEquationSpec
+ (fromMaybe
+ (impossible "prepared predicate closure is absent")
+ (declarationObjectClosure prepared))
+ identity alias))
+ State.put prepared
+ runDeclaration (reserveCandidate spec)
+
+-- | Reserve one defining equation and a nonempty generated-fact batch at the
+-- same declaration stage. No candidate in the batch can cite a sibling.
+reserveDefinitionEquationCandidateBatch
+ :: ObjectId
+ -> SemanticName
+ -> NonEmpty
+ ( ScopedCheckedCore ObjectId
+ , FactSearchEligibility
+ , [SemanticName]
+ )
+ -> Declaration
+ ( ReservedCandidate
+ , NonEmpty ReservedCandidate
+ )
+reserveDefinitionEquationCandidateBatch identity alias generated =
+ Declaration do
+ unprepared <- State.get
+ prepared <-
+ State.lift
+ (Except.liftEither
+ (prepareDeclarationClosure unprepared))
+ let closure =
+ maybe
+ (impossible
+ "prepared declaration closure is absent")
+ id
+ (declarationObjectClosure prepared)
+ definition <-
+ State.lift
+ (Except.liftEither
+ (prepareDefinitionEquationSpec closure identity alias))
+ generatedSpecs <-
+ traverse
+ (prepareGeneratedSpec closure)
+ generated
+ State.put prepared
+ candidates <-
+ runDeclaration
+ (reserveCandidateBatch
+ (definition :| toList generatedSpecs))
+ case candidates of
+ definitionCandidate :| firstGenerated : remainingGenerated ->
+ pure
+ ( definitionCandidate
+ , firstGenerated :| remainingGenerated
+ )
+ _ ->
+ impossible
+ "a nonempty generated batch produced no candidate"
+ where
+ prepareGeneratedSpec closure (scoped, eligibility, aliases) =
+ State.lift
+ (Except.liftEither
+ (prepareScopedCandidateSpec
+ closure scoped eligibility aliases))
+
+-- | Resolve one complete compiled declaration against its exact validation
+-- key, then run the source-level authorization action under that selection.
+-- Objects and candidates must already be prepared so the lookup key cannot
+-- change after a hit or miss is chosen.
+authorizeCompiledDeclaration
+ :: Declaration value
+ -> Declaration value
+authorizeCompiledDeclaration action = Declaration do
+ unprepared <- State.get
+ (syntax, objects, theorems, ordinals, prepared) <-
+ State.lift
+ (Except.liftEither
+ (prepareCompiledValidation unprepared))
+ let key =
+ declarationValidationKey
+ syntax
+ (logicalBuilderPrefix
+ (declarationBuilder prepared))
+ objects
+ theorems
+ cached <-
+ case declarationValidationRun prepared of
+ FreshValidation ->
+ pure Nothing
+ WarmValidation
+ (ValidationLookup _lookupProof lookupDeclaration) ->
+ liftIO (lookupDeclaration key)
+ traverse_
+ (\record ->
+ case Materialization.checkDeclarationValidationRecord
+ (logicalBuilderPrefix
+ (declarationBuilder prepared))
+ syntax
+ objects
+ theorems
+ record of
+ Left failure ->
+ liftIO
+ (throwIO
+ (CachedValidationIntegrityError failure))
+ Right () ->
+ pure ())
+ cached
+ let selection =
+ case cached of
+ Nothing ->
+ FreshDeclarationValidation
+ Just record ->
+ CachedDeclarationValidation
+ syntax objects theorems record ordinals
+ State.put
+ prepared
+ { declarationValidationSelection = selection
+ }
+ runDeclaration action
+
+prepareCompiledValidation
+ :: DeclarationState
+ -> Either
+ DeclarationError
+ ( DeclarationSyntaxId
+ , [ObjectId]
+ , [TheoremId]
+ , Map FactSlot Natural
+ , DeclarationState
+ )
+prepareCompiledValidation declaration = do
+ syntax <-
+ case declarationValidationMode declaration of
+ DeclarationValidationMode value ->
+ Right value
+ _ ->
+ Left DeclarationValidationOutsideCompiledDeclaration
+ case declarationValidationSelection declaration of
+ DeclarationValidationUnselected -> pure ()
+ _ -> Left DeclarationValidationAlreadySelected
+ closure <- declarationClosure declaration
+ let builder = declarationBuilder declaration
+ candidates = Map.elems (declarationReservations declaration)
+ objects =
+ assertedObjectId
+ <$> reverse
+ (declarationObjectsReversed declaration)
+ theorems =
+ theoremId . candidateTheoremReference builder
+ <$> candidates
+ ordinals =
+ Map.fromList
+ (zip
+ (reservedCandidateSlot <$> candidates)
+ [0 ..])
+ pure
+ ( syntax
+ , objects
+ , theorems
+ , ordinals
+ , declaration
+ { declarationObjectClosure = Just closure
+ }
+ )
+
+lookupProofValidation
+ :: ReservedCandidate
+ -> Declaration (ProofSyntaxId, Maybe ProofValidationRecord)
+lookupProofValidation candidate = Declaration do
+ state <- State.get
+ syntax <-
+ case declarationValidationMode state of
+ EnvironmentImportMode ->
+ State.lift
+ (Except.throwError
+ ProofValidationOutsideProofDeclaration)
+ ProofValidationMode proofSyntax ->
+ pure proofSyntax
+ DeclarationValidationMode{} ->
+ State.lift
+ (Except.throwError
+ ProofValidationOutsideProofDeclaration)
+ let builder = declarationBuilder state
+ reference = candidateTheoremReference builder candidate
+ key =
+ proofValidationKey
+ (theoremId
+ (factAuthorityTheorem
+ (factAuthority
+ reference
+ cleanAuthoritySafety)))
+ syntax
+ (logicalBuilderPrefix builder)
+ record <-
+ case declarationValidationRun state of
+ FreshValidation ->
+ pure Nothing
+ WarmValidation (ValidationLookup lookupProof _lookupDeclaration) ->
+ State.lift
+ (liftIO
+ (lookupProof key))
+ pure (syntax, record)
+data CandidatePremise = CandidatePremise
+ !CheckedPropositionContent
+
+data CandidateProofState = CandidateProofState
+ { candidateProofDeclaration :: !DeclarationState
+ , candidateProofObjectClosure :: !CheckedObjectClosure
+ , candidateProofCandidate :: !ReservedCandidate
+ , candidateProofCachedValidation
+ :: !(Maybe Materialization.CandidateValidation)
+ , candidateProofPremisesReversed :: ![CandidatePremise]
+ , candidateProofPremiseCount :: !Natural
+ , candidateProofSafety :: !CandidateSafety
+ , candidateProofAcceptedRequestsReversed
+ :: ![PreparedRequestId]
+ }
+
+candidateProofBuilder :: CandidateProofState -> LogicalBuilder
+candidateProofBuilder =
+ declarationBuilder . candidateProofDeclaration
+
+newtype CandidateProof value = CandidateProof
+ { runCandidateProof
+ :: StateT
+ CandidateProofState
+ (ExceptT DeclarationError IO)
+ value
+ }
+ deriving newtype (Functor, Applicative, Monad)
+
+-- | Attach source trivia to a proof-obligation failure. The location does
+-- not participate in validation or request identity.
+locateProofObligation
+ :: Location
+ -> CandidateProof value
+ -> CandidateProof value
+locateProofObligation location (CandidateProof action) =
+ CandidateProof
+ (State.mapStateT
+ (Except.withExceptT
+ (ProofObligationFailedAt location))
+ action)
+
+-- | Record that the current exact proof discharged one goal with @Omitted@.
+recordOmittedUse :: CandidateProof ()
+recordOmittedUse = CandidateProof do
+ state <- State.get
+ State.put
+ state
+ { candidateProofSafety =
+ addCandidateEscape
+ Omitted
+ (candidateProofSafety state)
+ }
+
+data VampirePremiseSelection
+ = VampireImplicitPremises
+ | VampireExplicitPremises
+ !(NonEmpty SemanticFactOccurrenceFingerprint)
+ | VampireLocalPremises
+ deriving stock (Show, Eq)
+
+data VampireObligationPreparationError local
+ = VampireObligationIndirectTargetNotFalsum
+ | VampireObligationClaimProjectionFailed
+ !(Backend.SupportedPropositionProjectionError local)
+ | VampireObligationLocalProjectionFailed
+ !Backend.LocalPremiseOrdinal
+ !(Backend.SupportedPropositionProjectionError local)
+ | VampireObligationLocalClassificationFailed
+ !Backend.LocalPremiseOrdinal
+ !(Backend.BackendClassificationError ObjectId)
+ | VampireObligationFactNotVisible
+ !SemanticFactOccurrenceFingerprint
+ | VampireObligationFactInvalid
+ !SemanticFactOccurrenceFingerprint
+ | VampireObligationPlanningFailed
+ !(Backend.TypedProblemError local ObjectId)
+ | VampireObligationEncodingFailed
+ !(Tptp.TypedTptpPreparationError local ObjectId)
+ deriving stock (Show, Eq)
+
+data ScopedVampirePremise local origin = ScopedVampirePremise
+ !Backend.LocalPremiseOrdinal
+ !origin
+ !(Vector (local, CoreType))
+ !(ScopedCheckedCore ObjectId)
+
+scopedVampirePremise
+ :: Backend.LocalPremiseOrdinal
+ -> origin
+ -> Vector (local, CoreType)
+ -> ScopedCheckedCore ObjectId
+ -> ScopedVampirePremise local origin
+scopedVampirePremise =
+ ScopedVampirePremise
+
+data PreparedVampireObligation local origin =
+ PreparedVampireObligation
+ !(Backend.TypedProblem
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId)
+ !(Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId)
+
+-- | Prepare a scoped task for a trusted structural proof checker. This
+-- validates the typed problem but does not establish how its local judgments
+-- derive from the enclosing theorem. 'Checking.Exact.Proof' owns that
+-- composition on the production path.
+prepareScopedVampireObligationDriver
+ :: Ord local
+ => Vector (local, CoreType)
+ -> ScopedCheckedCore ObjectId
+ -> [ScopedVampirePremise local origin]
+ -> [FoundationAxiomTag]
+ -> VampirePremiseSelection
+ -> ModuleDriver failure
+ (Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin))
+prepareScopedVampireObligationDriver
+ claimSupport claim scopedLocals auxiliaryTags selection =
+ prepareScopedVampireObligationForMode
+ Provers.DirectTask
+ claimSupport
+ claim
+ scopedLocals
+ auxiliaryTags
+ selection
+
+-- | Prepare the one indirect task admitted by the exact proof compiler.
+-- The target check keeps contradictory input from authorizing another goal.
+prepareScopedContradictionObligationDriver
+ :: Ord local
+ => Vector (local, CoreType)
+ -> ScopedCheckedCore ObjectId
+ -> [ScopedVampirePremise local origin]
+ -> [FoundationAxiomTag]
+ -> VampirePremiseSelection
+ -> ModuleDriver failure
+ (Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin))
+prepareScopedContradictionObligationDriver
+ claimSupport claim scopedLocals auxiliaryTags selection
+ | scopedCoreType claim /= TyProp || scopedCoreTerm claim /= CFalsum =
+ pure (Left VampireObligationIndirectTargetNotFalsum)
+ | otherwise =
+ prepareScopedVampireObligationForMode
+ Provers.IndirectTask
+ claimSupport
+ claim
+ scopedLocals
+ auxiliaryTags
+ selection
+
+prepareScopedVampireObligationLowering
+ :: Ord local
+ => Vector (local, CoreType)
+ -> ScopedCheckedCore ObjectId
+ -> [ScopedVampirePremise local origin]
+ -> [FoundationAxiomTag]
+ -> VampirePremiseSelection
+ -> LoweringDriver
+ (Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin))
+prepareScopedVampireObligationLowering
+ claimSupport claim scopedLocals auxiliaryTags selection =
+ prepareScopedVampireObligationForModeLowering
+ Provers.DirectTask
+ claimSupport claim scopedLocals auxiliaryTags selection
+
+prepareScopedContradictionObligationLowering
+ :: Ord local
+ => Vector (local, CoreType)
+ -> ScopedCheckedCore ObjectId
+ -> [ScopedVampirePremise local origin]
+ -> [FoundationAxiomTag]
+ -> VampirePremiseSelection
+ -> LoweringDriver
+ (Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin))
+prepareScopedContradictionObligationLowering
+ claimSupport claim scopedLocals auxiliaryTags selection
+ | scopedCoreType claim /= TyProp || scopedCoreTerm claim /= CFalsum =
+ pure (Left VampireObligationIndirectTargetNotFalsum)
+ | otherwise =
+ prepareScopedVampireObligationForModeLowering
+ Provers.IndirectTask
+ claimSupport claim scopedLocals auxiliaryTags selection
+
+prepareScopedVampireObligationForModeLowering
+ :: Ord local
+ => Provers.VampireTaskMode
+ -> Vector (local, CoreType)
+ -> ScopedCheckedCore ObjectId
+ -> [ScopedVampirePremise local origin]
+ -> [FoundationAxiomTag]
+ -> VampirePremiseSelection
+ -> LoweringDriver
+ (Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin))
+prepareScopedVampireObligationForModeLowering
+ taskMode claimSupport claim scopedLocals auxiliaryTags selection =
+ LoweringDriver do
+ LoweringState _resolver (ProspectiveBuilder builder) _validation <-
+ State.get
+ liftIO
+ (prepareScopedVampireObligationWith
+ builder taskMode claimSupport claim scopedLocals
+ auxiliaryTags selection)
+
+prepareScopedVampireObligationForMode
+ :: Ord local
+ => Provers.VampireTaskMode
+ -> Vector (local, CoreType)
+ -> ScopedCheckedCore ObjectId
+ -> [ScopedVampirePremise local origin]
+ -> [FoundationAxiomTag]
+ -> VampirePremiseSelection
+ -> ModuleDriver failure
+ (Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin))
+prepareScopedVampireObligationForMode
+ taskMode claimSupport claim scopedLocals auxiliaryTags selection =
+ ModuleDriver do
+ DriverState _resolver builder _prefix _validation <- State.get
+ State.lift
+ (Except.liftIO
+ (prepareScopedVampireObligationWith
+ builder taskMode claimSupport claim scopedLocals
+ auxiliaryTags selection))
+
+prepareScopedVampireObligationWith
+ :: (Ord local)
+ => BuilderState evidence
+ -> Provers.VampireTaskMode
+ -> Vector (local, CoreType)
+ -> ScopedCheckedCore ObjectId
+ -> [ScopedVampirePremise local origin]
+ -> [FoundationAxiomTag]
+ -> VampirePremiseSelection
+ -> IO
+ (Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin))
+prepareScopedVampireObligationWith
+ builder taskMode claimSupport claim scopedLocals
+ auxiliaryTags selection =
+ forceVampirePreparation preparation
+ where
+ closure = logicalBuilderObjectClosure builder
+ globalType = (`lookupCheckedObjectType` closure)
+ preparation = do
+ supportedClaim <-
+ first VampireObligationClaimProjectionFailed
+ (Backend.projectSupportedProposition
+ globalType
+ claimSupport
+ claim)
+ locals <- traverse prepareLocal scopedLocals
+ prepareVampireObligationWith
+ taskMode
+ builder
+ closure
+ supportedClaim
+ locals
+ auxiliaryTags
+ selection
+
+ prepareLocal
+ (ScopedVampirePremise
+ ordinal origin support proposition) = do
+ supported <-
+ first
+ (VampireObligationLocalProjectionFailed ordinal)
+ (Backend.projectSupportedProposition
+ globalType
+ support
+ proposition)
+ first
+ (VampireObligationLocalClassificationFailed ordinal)
+ (Backend.typedLocalPremise
+ globalType
+ ordinal
+ origin
+ supported)
+
+prepareVampireObligationWith
+ :: Ord local
+ => Provers.VampireTaskMode
+ -> BuilderState evidence
+ -> CheckedObjectClosure
+ -> Backend.SupportedProposition local ObjectId
+ -> [Backend.TypedLocalPremise local origin ObjectId]
+ -> [FoundationAxiomTag]
+ -> VampirePremiseSelection
+ -> Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin)
+prepareVampireObligationWith
+ taskMode builder closure claim locals auxiliaryTags selection = do
+ let globalType = (`lookupCheckedObjectType` closure)
+ selected <-
+ selectVampireFacts
+ globalType
+ builder
+ selection
+ let localPremisePolicy =
+ case selection of
+ VampireImplicitPremises ->
+ Backend.FirstOrderLocals
+ VampireExplicitPremises{}
+ | any
+ (\fact ->
+ case Backend.typedBackendFactCapability fact of
+ Backend.FofProjectable{} -> False
+ Backend.RequiresTh0{} -> True)
+ selected ->
+ Backend.CompleteLocals
+ | otherwise ->
+ Backend.FirstOrderLocals
+ VampireLocalPremises ->
+ Backend.CompleteLocals
+ higherOrderPolicy =
+ case selection of
+ VampireImplicitPremises ->
+ Backend.ImplicitConstructionJustification
+ VampireExplicitPremises{} ->
+ Backend.ExplicitHigherOrderJustification
+ VampireLocalPremises ->
+ Backend.ExplicitHigherOrderJustification
+ propositionDependencies =
+ foundationAxiomDependencies
+ . Backend.supportedPropositionTerm
+ requiredAuxiliaryTags =
+ Set.toAscList
+ ( Set.fromList auxiliaryTags
+ <> propositionDependencies claim
+ <> foldMap
+ (propositionDependencies
+ . Backend.typedBackendFactProposition)
+ selected
+ <> foldMap
+ (propositionDependencies
+ . Backend.typedLocalPremiseProposition)
+ (Backend.selectTypedLocalPremises
+ localPremisePolicy
+ locals)
+ )
+ auxiliaries =
+ Backend.typedFoundationAuxiliaryInput
+ (logicalBuilderFoundation builder)
+ <$> requiredAuxiliaryTags
+ problem <-
+ first VampireObligationPlanningFailed
+ (Backend.planTypedProblem
+ globalType
+ selected
+ claim
+ locals
+ auxiliaries
+ localPremisePolicy
+ higherOrderPolicy)
+ task <-
+ first VampireObligationEncodingFailed
+ (Provers.prepareTypedProverTask
+ taskMode
+ problem)
+ pure (PreparedVampireObligation problem task)
+
+forceVampirePreparation
+ :: Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin)
+ -> IO
+ (Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin))
+forceVampirePreparation preparation =
+ Exception.evaluate (forcePrepared preparation)
+ where
+ forcePrepared result =
+ case result of
+ Left err -> err `seq` result
+ Right prepared@(PreparedVampireObligation _ task) ->
+ let request = Provers.preparedTypedProverRequest task
+ in Provers.preparedVerificationByteCount request `seq`
+ Provers.preparedVerificationRequestId request `seq`
+ prepared `seq`
+ result
+
+selectVampireFacts
+ :: (ObjectId -> Maybe CoreType)
+ -> BuilderState evidence
+ -> VampirePremiseSelection
+ -> Either
+ (VampireObligationPreparationError local)
+ (Vector
+ (Backend.TypedBackendFact
+ SemanticFactOccurrenceFingerprint
+ ObjectId))
+selectVampireFacts globalType builder selection =
+ Vector.fromList <$> case selection of
+ VampireImplicitPremises ->
+ mapMaybeM prepareImplicit
+ (Map.toAscList (logicalBuilderFacts builder))
+ VampireExplicitPremises fingerprints ->
+ traverse prepareExplicit
+ (stableUniqueBy id (toList fingerprints))
+ VampireLocalPremises ->
+ Right []
+ where
+ prepareImplicit (fingerprint, authorized@(FactEntry
+ _proposition occurrence _authorization))
+ | semanticFactSearchEligibility occurrence
+ /= SearchEligible =
+ Right Nothing
+ | otherwise = do
+ fact <- prepareFact fingerprint authorized
+ pure case Backend.typedBackendFactCapability fact of
+ Backend.FofProjectable{} -> Just fact
+ Backend.RequiresTh0{} -> Nothing
+
+ prepareExplicit fingerprint =
+ case Map.lookup fingerprint (logicalBuilderFacts builder) of
+ Nothing ->
+ Left (VampireObligationFactNotVisible fingerprint)
+ Just authorized ->
+ prepareFact fingerprint authorized
+
+ prepareFact fingerprint
+ (FactEntry proposition occurrence _authorization)
+ | semanticFactFingerprint occurrence /= fingerprint =
+ Left (VampireObligationFactInvalid fingerprint)
+ | otherwise = do
+ supported <-
+ first
+ (const
+ (VampireObligationFactInvalid fingerprint))
+ (Backend.supportedProposition
+ Vector.empty
+ (embedClosedCore
+ []
+ (checkedPropositionTerm proposition)))
+ capability <-
+ first
+ (const
+ (VampireObligationFactInvalid fingerprint))
+ (Backend.classifySupportedProposition
+ globalType
+ supported)
+ pure
+ (Backend.typedBackendFact
+ fingerprint
+ supported
+ capability)
+
+ mapMaybeM action =
+ fmap catMaybes . traverse action
+
+data LocalClaim = LocalClaim
+ !BuilderIdentity
+ !DeclarationInvocation
+ !FactSlot
+ !CheckedPropositionContent
+
+useAuthorizedFact
+ :: SemanticFactOccurrenceFingerprint
+ -> CandidateProof ImportIx
+useAuthorizedFact fingerprint = do
+ proposition <- consumeAuthorizedFact fingerprint
+ registerKernelImport proposition
+
+-- | Validate one fact capability and accumulate only its safety effect.
+-- Backend use does not register a kernel import.
+consumeAuthorizedFact
+ :: SemanticFactOccurrenceFingerprint
+ -> CandidateProof CheckedPropositionContent
+consumeAuthorizedFact fingerprint = CandidateProof do
+ state <- State.get
+ let builder = candidateProofBuilder state
+ declaration = candidateProofDeclaration state
+ staged =
+ findPendingByFingerprint
+ fingerprint
+ (declarationPending declaration)
+ case staged of
+ Just pending ->
+ consumePendingFact pending state
+ Nothing ->
+ case Map.lookup
+ fingerprint
+ (logicalBuilderFacts builder) of
+ Nothing ->
+ State.lift
+ (Except.throwError
+ (AuthorizedFactNotVisible fingerprint))
+ Just authorized ->
+ consumeBuilderAuthorizedFact authorized state
+
+-- | Materialize one sealed direct import and its transitive parents. The
+-- interface DAG is folded imported-before-importer and each producer
+-- occurrence receives a fresh capability for this consuming builder.
+importSealedModule
+ :: ImportedModuleEvidence
+ -> Declaration ()
+importSealedModule evidence = Declaration do
+ state <- State.get
+ when
+ (isJust (declarationObjectClosure state))
+ (State.lift
+ (Except.throwError ImportedModuleAfterAuthorization))
+ let builder = declarationBuilder state
+ interfaceId =
+ semanticInterfaceAssertedId
+ (evidenceInterface evidence)
+ unless
+ (interfaceId `elem`
+ logicalBuilderDirectSemanticInputs builder)
+ (State.lift
+ (Except.throwError
+ (ImportedModuleNotDirect interfaceId)))
+ imported <-
+ State.lift
+ (Except.liftEither
+ (foldImportedEvidence evidence builder))
+ State.put
+ state
+ { declarationBuilder = imported
+ , declarationObjectClosure = Nothing
+ }
+
+-- The walking path keeps complete aggregate maps. Measure long chains before
+-- replacing this representation.
+foldImportedEvidence
+ :: ImportedModuleEvidence
+ -> LogicalBuilder
+ -> Either DeclarationError LogicalBuilder
+foldImportedEvidence evidence builder
+ | interfaceId
+ `Set.member` logicalBuilderImportedInterfaces builder =
+ Right builder
+ | otherwise = do
+ unless
+ (semanticInterfaceDirectInputs interface
+ == (semanticInterfaceAssertedId . evidenceInterface
+ <$> parents))
+ (Left
+ (ImportedEvidenceDirectMismatch
+ (semanticInterfaceDirectInputs interface)
+ (semanticInterfaceAssertedId . evidenceInterface
+ <$> parents)))
+ withParents <- foldM
+ (flip foldImportedEvidence)
+ builder
+ parents
+ objectClosure <-
+ first
+ DeclarationObjectValidationFailed
+ (extendImportedObjectClosure
+ (logicalBuilderObjectClosure withParents)
+ objects)
+ importedFacts <- foldM
+ (insertImportedFact withParents)
+ (logicalBuilderFacts withParents)
+ (Map.elems entries)
+ importedAliases <- foldM
+ (insertImportedAlias importedFacts)
+ (logicalBuilderAliases withParents)
+ [ ( alias
+ , ImportedAliasOrigin
+ (declarationDeltaSlot delta)
+ (semanticAliasTarget alias)
+ )
+ | delta <- semanticInterfaceDeclarations interface
+ , alias <- declarationDeltaAliases delta
+ ]
+ importedStructures <- foldM
+ (insertSemanticStructure objectClosure)
+ (logicalBuilderStructures withParents)
+ [ descriptor
+ | delta <- semanticInterfaceDeclarations interface
+ , descriptor <- semanticEnvironmentStructures
+ (declarationDeltaEnvironment delta)
+ ]
+ importedGlobals <- foldM
+ (insertImportedGlobal objectClosure importedStructures)
+ (logicalBuilderGlobals withParents)
+ [ binding
+ | delta <- semanticInterfaceDeclarations interface
+ , binding <- semanticEnvironmentBindings
+ (declarationDeltaEnvironment delta)
+ ]
+ pure
+ withParents
+ { logicalBuilderFacts = importedFacts
+ , logicalBuilderAliases = importedAliases
+ , logicalBuilderGlobals = importedGlobals
+ , logicalBuilderStructures = importedStructures
+ , logicalBuilderObjectClosure = objectClosure
+ , logicalBuilderImportedInterfaces =
+ Set.insert
+ interfaceId
+ (logicalBuilderImportedInterfaces withParents)
+ }
+ where
+ ImportedModuleEvidence interface parents entries objects = evidence
+ interfaceId = semanticInterfaceAssertedId interface
+
+ extendImportedObjectClosure closure asserted =
+ extendObjectClosure
+ closure
+ [ object
+ | object <- asserted
+ , assertedObjectId object
+ `Set.notMember` checkedObjectIds closure
+ ]
+
+ insertImportedFact
+ current facts pair = do
+ let (occurrence, proposition) = pair
+ fingerprint = semanticFactFingerprint occurrence
+ authority = semanticFactAuthority occurrence
+ _ <-
+ first ImportedFactMaterializationFailed
+ (Materialization.checkImportedOccurrence
+ (logicalBuilderTheory current)
+ fingerprint
+ occurrence
+ proposition
+ authority)
+ let authorization =
+ BuilderFactAuthorization
+ (logicalBuilderIdentity current)
+ (semanticFactSlot occurrence)
+ authority
+ authorized =
+ FactEntry proposition occurrence authorization
+ case Map.lookup fingerprint facts of
+ Nothing ->
+ pure (Map.insert fingerprint authorized facts)
+ Just existing
+ | equivalentAuthorizedFact existing authorized ->
+ pure facts
+ | otherwise ->
+ Left (ImportedFactCollision fingerprint)
+
+ insertImportedAlias facts aliases (alias, origin) = do
+ let name = semanticAliasName alias
+ target = semanticAliasTarget alias
+ unless
+ (Map.member target facts)
+ (Left (ImportedAliasTargetMissing target))
+ case Map.lookup name aliases of
+ Nothing ->
+ pure
+ (Map.insert
+ name
+ (ImportedAliasBinding target origin)
+ aliases)
+ Just (ImportedAliasBinding existingTarget existingOrigin)
+ | existingTarget == target ->
+ pure aliases
+ | otherwise ->
+ Left
+ (ImportedAliasCollision
+ name existingOrigin origin)
+
+ insertImportedGlobal closure structures globals binding = do
+ let key = semanticGlobalBindingKey binding
+ target = semanticGlobalBindingTarget binding
+ _ <-
+ first
+ (ImportedGlobalTargetInvalid key target)
+ (validateSemanticGlobalBindingTarget
+ (structureOperationBindings structures)
+ closure binding)
+ case Map.lookup key globals of
+ Nothing -> pure (Map.insert key target globals)
+ Just existing
+ | existing == target -> pure globals
+ | otherwise ->
+ Left (ImportedGlobalCollision key existing target)
+
+insertSemanticStructure
+ :: CheckedObjectClosure
+ -> Map SemanticStructurePhrase ResolvedStructure
+ -> SemanticStructureDescriptor
+ -> Either
+ DeclarationError
+ (Map SemanticStructurePhrase ResolvedStructure)
+insertSemanticStructure closure structures descriptor = do
+ validateSemanticStructureTargets closure descriptor
+ let structurePhrase = semanticStructureDescriptorPhrase descriptor
+ case Map.lookup structurePhrase structures of
+ Just (ResolvedStructure existing _ _)
+ | existing == descriptor -> Right structures
+ | otherwise ->
+ Left
+ (ImportedStructureCollision
+ structurePhrase existing descriptor)
+ Nothing -> do
+ parents <- traverse resolveParent
+ (semanticStructureDescriptorParents descriptor)
+ inherited <- foldM mergeParentOperations Map.empty parents
+ complete <- foldM insertOwnOperation inherited
+ (semanticStructureDescriptorOperations descriptor)
+ let ancestors =
+ Set.unions
+ [ Set.insert
+ (semanticStructureDescriptorPhrase
+ (resolvedStructureDescriptor parent))
+ parentAncestors
+ | parent@(ResolvedStructure _ parentAncestors _) <- parents
+ ]
+ resolved = ResolvedStructure descriptor ancestors complete
+ Right (Map.insert structurePhrase resolved structures)
+ where
+ resolveParent parentPhrase =
+ maybe
+ (Left
+ (SemanticStructureParentMissing
+ (semanticStructureDescriptorPhrase descriptor)
+ parentPhrase))
+ Right
+ (Map.lookup parentPhrase structures)
+
+ mergeParentOperations operations
+ (ResolvedStructure _descriptor _ancestors parentOperations) =
+ foldM insertInheritedOperation operations
+ (Map.toAscList parentOperations)
+
+ insertInheritedOperation operations (symbol, operation) =
+ insertResolvedOperation symbol operation operations
+
+ insertOwnOperation operations operation =
+ insertResolvedOperation
+ (semanticStructureOperationSymbol operation)
+ (ResolvedStructureOperation
+ (semanticStructureOperationObject operation)
+ (semanticStructureDescriptorPhrase descriptor))
+ operations
+
+ insertResolvedOperation symbol incoming operations =
+ case Map.lookup symbol operations of
+ Nothing -> Right (Map.insert symbol incoming operations)
+ Just (ResolvedStructureOperation existingObject existingOrigin) ->
+ case incoming of
+ ResolvedStructureOperation incomingObject incomingOrigin
+ | existingObject == incomingObject -> Right operations
+ | otherwise ->
+ Left
+ (SemanticStructureOperationConflict
+ symbol existingOrigin incomingOrigin)
+
+validateSemanticStructureTargets
+ :: CheckedObjectClosure
+ -> SemanticStructureDescriptor
+ -> Either DeclarationError ()
+validateSemanticStructureTargets closure descriptor = do
+ traverse_ validatePredicate
+ (semanticStructureDescriptorPredicate descriptor)
+ traverse_ validateOperation
+ (semanticStructureDescriptorOperations descriptor)
+ where
+ validatePredicate object =
+ validateObjectType
+ (SemanticStructurePredicateTargetInvalid
+ (semanticStructureDescriptorPhrase descriptor))
+ object
+ (TyArrow TySet TyProp)
+
+ validateOperation operation =
+ validateObjectType
+ (SemanticStructureOperationTargetInvalid
+ (semanticStructureDescriptorPhrase descriptor)
+ (semanticStructureOperationSymbol operation))
+ (semanticStructureOperationObject operation)
+ (TyArrow TySet TySet)
+
+ validateObjectType failure object expected =
+ case lookupCheckedObjectType object closure of
+ Nothing -> Left (failure object Nothing expected)
+ Just actual
+ | actual == expected -> Right ()
+ | otherwise -> Left (failure object (Just actual) expected)
+
+equivalentAuthorizedFact
+ :: FactEntry BuilderFactAuthorization
+ -> FactEntry BuilderFactAuthorization
+ -> Bool
+equivalentAuthorizedFact
+ (FactEntry leftProposition leftOccurrence
+ (BuilderFactAuthorization leftIdentity leftSlot leftAuthority))
+ (FactEntry rightProposition rightOccurrence
+ (BuilderFactAuthorization rightIdentity rightSlot rightAuthority)) =
+ checkedPropositionId leftProposition
+ == checkedPropositionId rightProposition
+ && leftOccurrence == rightOccurrence
+ && leftIdentity == rightIdentity
+ && leftSlot == rightSlot
+ && leftAuthority == rightAuthority
+
+-- | Driver-level form used before the first declaration of a consuming
+-- module. It changes only the private builder's imported fact environment;
+-- no declaration prefix or durable envelope is emitted.
+importSealedModuleDriver
+ :: ImportedModuleEvidence
+ -> ModuleDriver failure ()
+importSealedModuleDriver evidence = ModuleDriver do
+ DriverState resolver builder prefix validationRun <-
+ State.get
+ let declaration =
+ initialDeclarationState
+ resolver
+ validationRun
+ EnvironmentImportMode
+ builder
+ result <-
+ liftIO
+ (Except.runExceptT
+ (State.runStateT
+ (runDeclaration
+ (importSealedModule evidence))
+ declaration))
+ case result of
+ Left failure ->
+ Except.throwError (DriverDeclarationFailed failure)
+ Right (_value, importedState) ->
+ State.put
+ (DriverState
+ resolver
+ (declarationBuilder importedState)
+ prefix
+ validationRun)
+
+useStagedCandidate
+ :: ReservedCandidate
+ -> CandidateProof ImportIx
+useStagedCandidate premise = CandidateProof do
+ state <- State.get
+ let current = candidateProofCandidate state
+ declaration = candidateProofDeclaration state
+ State.lift
+ (Except.liftEither
+ (validateReservedCandidate
+ declaration
+ premise))
+ State.lift
+ (Except.liftEither
+ (validateStrictCandidateStage premise current))
+ pending <-
+ maybe
+ (State.lift
+ (Except.throwError
+ (StagedPremiseNotAuthorized
+ (reservedCandidateSlot premise))))
+ pure
+ (Map.lookup
+ (reservedCandidateSlot premise)
+ (declarationPending declaration))
+ let proposedAuthority =
+ validationTarget (pendingCandidateCertificate pending)
+ checkedStage <-
+ State.lift
+ (Except.liftEither
+ (prepareCheckedDeclarationStage
+ declaration
+ (candidateProofObjectClosure state)
+ [(premise, proposedAuthority)]))
+ stagedProposition <-
+ State.lift
+ (Except.liftEither
+ (useCheckedStageFact checkedStage current premise))
+ proposition <- consumePendingFact pending state
+ unless
+ (checkedPropositionId stagedProposition
+ == checkedPropositionId proposition)
+ (State.lift
+ (Except.throwError
+ (PlanningFactContractMismatch
+ (reservedCandidateSlot premise))))
+ updated <- State.get
+ appendCandidatePremise proposition updated
+
+validateStrictCandidateStage
+ :: ReservedCandidate
+ -> ReservedCandidate
+ -> Either DeclarationError ()
+validateStrictCandidateStage premise current =
+ unless
+ (reservedStage premise < reservedStage current)
+ (Left
+ (StagedPremiseNotEarlier
+ (reservedCandidateSlot premise)
+ (reservedCandidateStage premise)
+ (reservedCandidateSlot current)
+ (reservedCandidateStage current)))
+
+prepareCheckedDeclarationStage
+ :: DeclarationState
+ -> CheckedObjectClosure
+ -> [(ReservedCandidate, FactAuthority)]
+ -> Either DeclarationError CheckedDeclarationStage
+prepareCheckedDeclarationStage declaration closure candidates = do
+ entries <- foldM insertCandidate Map.empty candidates
+ pure (CheckedDeclarationStage ownSlot closure entries)
+ where
+ builder = declarationBuilder declaration
+ ownSlot = declarationOwnSlot declaration
+
+ insertCandidate entries (candidate, authority) = do
+ validateReservedCandidate declaration candidate
+ let slot = reservedCandidateSlot candidate
+ unless
+ (factAuthorityTheorem authority
+ == candidateTheoremReference builder candidate)
+ (Left (PlanningFactContractMismatch slot))
+ when
+ (Map.member slot entries)
+ (Left (PlanningFactContractMismatch slot))
+ let occurrence = candidateOccurrence candidate authority
+ evidence = PlanningEvidence
+ authority
+ (Just
+ (PlanningProvenance
+ ownSlot
+ (reservedStage candidate)
+ slot))
+ pure
+ (Map.insert
+ slot
+ (FactEntry
+ (candidateCheckedProposition candidate)
+ occurrence
+ evidence)
+ entries)
+
+useCheckedStageFact
+ :: CheckedDeclarationStage
+ -> ReservedCandidate
+ -> ReservedCandidate
+ -> Either DeclarationError CheckedPropositionContent
+useCheckedStageFact
+ (CheckedDeclarationStage ownSlot closure entries)
+ current
+ premise = do
+ validateStrictCandidateStage premise current
+ let slot = reservedCandidateSlot premise
+ FactEntry proposition occurrence
+ (PlanningEvidence authority provenance) <-
+ maybe
+ (Left (PlanningFactContractMismatch slot))
+ Right
+ (Map.lookup slot entries)
+ unless
+ ( provenance
+ == Just
+ (PlanningProvenance
+ ownSlot
+ (reservedStage premise)
+ slot)
+ && semanticFactSlot occurrence == slot
+ && semanticFactAuthority occurrence == authority
+ && checkedPropositionId proposition
+ == checkedPropositionId
+ (candidateCheckedProposition premise)
+ && propositionMatchesClosure closure proposition
+ )
+ (Left (PlanningFactContractMismatch slot))
+ pure proposition
+
+propositionMatchesClosure
+ :: CheckedObjectClosure
+ -> CheckedPropositionContent
+ -> Bool
+propositionMatchesClosure closure proposition =
+ case validateAssertedPropositionContent
+ closure
+ (checkedPropositionId proposition)
+ (frozenCoreTerm (checkedPropositionTerm proposition)) of
+ Right _ -> True
+ Left _ -> False
+
+candidateOccurrence
+ :: ReservedCandidate
+ -> FactAuthority
+ -> SemanticFactOccurrence
+candidateOccurrence
+ (ReservedCandidate _ _ _ _ slot
+ (CandidateSpec _ eligibility _aliases))
+ authority =
+ semanticFactOccurrence slot authority eligibility
+
+candidateAliases :: ReservedCandidate -> [SemanticName]
+candidateAliases
+ (ReservedCandidate _ _ _ _ _
+ (CandidateSpec _ _ aliases)) =
+ aliases
+
+proveLocalKernelClaim
+ :: CheckedPropositionContent
+ -> KernelDerivation ObjectId
+ -> CandidateProof LocalClaim
+proveLocalKernelClaim proposition derivation = CandidateProof do
+ state <- State.get
+ void
+ (State.lift
+ (Except.liftEither
+ (replayCandidateKernel proposition derivation state)))
+ let candidate = candidateProofCandidate state
+ declaration = candidateProofDeclaration state
+ pure
+ (LocalClaim
+ (logicalBuilderIdentity
+ (candidateProofBuilder state))
+ (declarationInvocation declaration)
+ (reservedCandidateSlot candidate)
+ proposition)
+
+useLocalClaim :: LocalClaim -> CandidateProof ImportIx
+useLocalClaim
+ (LocalClaim identity invocation slot proposition) =
+ CandidateProof do
+ state <- State.get
+ let builder = candidateProofBuilder state
+ declaration = candidateProofDeclaration state
+ candidate = candidateProofCandidate state
+ unless
+ ( identity == logicalBuilderIdentity builder
+ && invocation == declarationInvocation declaration
+ && slot == reservedCandidateSlot candidate
+ )
+ (State.lift
+ (Except.throwError LocalClaimOutsideCandidate))
+ appendCandidatePremise proposition state
+
+
+authorizeKernelProofCandidate
+ :: ReservedCandidate
+ -> CandidateProof (KernelDerivation ObjectId)
+ -> Declaration ()
+authorizeKernelProofCandidate candidate proof =
+ authorizeOneCandidate candidate \initial -> do
+ (derivation, final) <-
+ State.runStateT (runCandidateProof proof) initial
+ when
+ (isNothing (candidateProofCachedValidation final))
+ (void
+ (Except.liftEither
+ (replayCandidateKernel
+ (candidateCheckedProposition candidate)
+ derivation
+ final)))
+ completeCandidateWithValidation
+ candidate
+ (CheckedSourceProof
+ (acceptedRequestIds final))
+ (candidateProofSafety final)
+ final
+
+authorizeKernelConstructionCandidate
+ :: KernelConstructionDescriptor
+ -> ReservedCandidate
+ -> CandidateProof (KernelDerivation ObjectId)
+ -> Declaration ()
+authorizeKernelConstructionCandidate descriptor candidate proof =
+ authorizeOneCandidate candidate \initial -> do
+ (derivation, final) <-
+ State.runStateT (runCandidateProof proof) initial
+ when
+ (isNothing (candidateProofCachedValidation final)) do
+ replayed <-
+ Except.liftEither
+ (replayCandidateKernel
+ (candidateCheckedProposition candidate)
+ derivation
+ final)
+ Except.liftEither
+ (validateKernelConstruction
+ descriptor
+ (candidateCheckedProposition candidate)
+ replayed
+ final)
+ completeCandidateWithValidation
+ candidate
+ (CheckedKernelConstruction descriptor)
+ (candidateProofSafety final)
+ final
+
+authorizeDefinitionEquationCandidate
+ :: ObjectId
+ -> ReservedCandidate
+ -> Declaration ()
+authorizeDefinitionEquationCandidate identity candidate =
+ authorizeOneCandidate candidate \initial -> do
+ when
+ (isNothing (candidateProofCachedValidation initial))
+ (unless
+ (matchesDefinitionEquation
+ identity
+ (candidateCheckedProposition candidate)
+ initial)
+ (Except.throwError
+ DefinitionEquationCandidateMismatch))
+ completeCandidateWithValidation
+ candidate
+ (CheckedKernelConstruction
+ (CheckedDefinitionEquation identity))
+ (candidateProofSafety initial)
+ initial
+
+-- | Authorize only the extensional fact deterministically derived from the
+-- checked source construction and its committed transparent object.
+authorizeNamedSetConstructionCandidate
+ :: ObjectId
+ -> NamedSetConstruction ObjectId
+ -> ReservedCandidate
+ -> Declaration ()
+authorizeNamedSetConstructionCandidate identity construction candidate =
+ authorizeOneCandidate candidate \initial -> do
+ let builder = candidateProofBuilder initial
+ (expected, descriptor) <-
+ Except.liftEither
+ (prepareNamedSetConstruction
+ (logicalBuilderFoundation builder)
+ (candidateProofObjectClosure initial)
+ identity
+ construction)
+ when (isNothing (candidateProofCachedValidation initial)) do
+ unless
+ (frozenCoreTerm expected
+ == frozenCoreTerm
+ (checkedPropositionTerm
+ (candidateCheckedProposition candidate))
+ )
+ (Except.throwError
+ KernelConstructionDescriptorMismatch)
+ completeCandidateWithValidation
+ candidate
+ (CheckedKernelConstruction descriptor)
+ (candidateProofSafety initial)
+ initial
+
+-- | Authorize the relational extensional view only after consuming the exact
+-- strictly-earlier functionality candidate. The consumed candidate supplies
+-- both real authority safety and the proposition rechecked by the confined
+-- construction schema; a caller cannot substitute an arbitrary theorem.
+authorizeRelationalSetConstructionCandidate
+ :: ObjectId
+ -> CheckedRelationalSetConstruction ObjectId
+ -> ReservedCandidate
+ -> ReservedCandidate
+ -> Declaration ()
+authorizeRelationalSetConstructionCandidate
+ identity construction functionality candidate =
+ authorizeOneCandidate candidate \initial -> do
+ (_used, final) <-
+ State.runStateT
+ (runCandidateProof (useStagedCandidate functionality))
+ initial
+ let builder = candidateProofBuilder final
+ functionalityTerm =
+ checkedPropositionTerm
+ (candidateCheckedProposition functionality)
+ (expected, descriptor) <-
+ Except.liftEither
+ (prepareRelationalSetConstruction
+ (logicalBuilderFoundation builder)
+ (candidateProofObjectClosure final)
+ identity
+ construction
+ functionalityTerm)
+ when (isNothing (candidateProofCachedValidation final)) do
+ unless
+ (frozenCoreTerm expected
+ == frozenCoreTerm
+ (checkedPropositionTerm
+ (candidateCheckedProposition candidate)))
+ (Except.throwError
+ KernelConstructionDescriptorMismatch)
+ completeCandidateWithValidation
+ candidate
+ (CheckedKernelConstruction descriptor)
+ (candidateProofSafety final)
+ final
+
+authorizeSourceAxiomCandidate
+ :: ReservedCandidate
+ -> Declaration ()
+authorizeSourceAxiomCandidate candidate =
+ authorizeOneCandidate candidate \initial ->
+ let safety =
+ addCandidateEscape
+ SourceAxiom
+ initialCandidateSafety
+ in completeCandidateWithValidation
+ candidate
+ SourceAxiomAuthorization
+ safety
+ initial
+
+-- | Complete one exact deterministic datatype as a single trusted family.
+-- The descriptor, objects, and complete candidate batch must agree exactly.
+-- Law semantics are trusted to the sole production compiler in
+-- "Checking.Exact.Datatype"; this boundary validates its complete inventory,
+-- not the meaning of each generated proposition.
+authorizeDatatypeCompilationCandidates
+ :: DatatypeCompilationDescriptor
+ -> ObjectId
+ -> NonEmpty ObjectId
+ -> NonEmpty ReservedCandidate
+ -> Declaration ()
+authorizeDatatypeCompilationCandidates
+ suppliedDescriptor carrier constructors candidates =
+ Declaration do
+ state <- State.get
+ let builder = declarationBuilder state
+ objectInventory = carrier : NonEmpty.toList constructors
+ declaredObjects =
+ assertedObjectId
+ <$> reverse (declarationObjectsReversed state)
+ reservedCandidates =
+ Map.elems (declarationReservations state)
+ theoremInventory =
+ candidateTheoremReference builder
+ <$> NonEmpty.toList candidates
+ expectedDescriptor =
+ datatypeCompilationDescriptor
+ carrier
+ constructors
+ theoremInventory
+ stages =
+ nubOrd
+ (reservedCandidateStage
+ <$> NonEmpty.toList candidates)
+ unless
+ ( suppliedDescriptor == expectedDescriptor
+ && declaredObjects == objectInventory
+ && reservedCandidates == NonEmpty.toList candidates
+ && length stages == 1
+ )
+ (State.lift
+ (Except.throwError
+ DatatypeCompilationDescriptorMismatch))
+ runDeclaration
+ (traverse_
+ (authorizeDatatypeCandidate suppliedDescriptor)
+ candidates)
+
+authorizeDatatypeCandidate
+ :: DatatypeCompilationDescriptor
+ -> ReservedCandidate
+ -> Declaration ()
+authorizeDatatypeCandidate descriptor candidate =
+ authorizeOneCandidate candidate \initial ->
+ completeCandidateWithValidation
+ candidate
+ (TrustedCompilation
+ (DatatypeCompilation descriptor))
+ initialCandidateSafety
+ initial
+
+authorizeOmittedCandidate
+ :: ReservedCandidate
+ -> CandidateProof ()
+ -> Declaration ()
+authorizeOmittedCandidate candidate proof =
+ authorizeOneCandidate candidate \initial -> do
+ cached <-
+ selectProofCandidateValidation candidate initial
+ let preparedInitial =
+ initial
+ { candidateProofCachedValidation = cached
+ }
+ ((), final) <-
+ State.runStateT (runCandidateProof proof) preparedInitial
+ let safety = candidateProofSafety final
+ unless
+ (Omitted
+ `elem` escapeKindsToList
+ (authoritySafetyEscapeKinds
+ (candidateSafetyAuthority safety)))
+ (Except.throwError OmittedProofDidNotRecordUse)
+ completeCandidateWithValidation
+ candidate
+ OmittedAuthorization
+ safety
+ final
+
+-- | Retain the closed candidate-target path while scoped proof composition is
+-- introduced. It uses the same environment validation as local obligations.
+acceptVampireObligation
+ :: Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ Void
+ origin
+ ObjectId
+ -> CandidateProof ()
+acceptVampireObligation prepared =
+ acceptValidatedVampireTask
+ (validateCandidateVampireProblem prepared)
+ prepared
+
+-- | Validate one internally prepared scoped obligation, then retain its exact
+-- accepted request. The trusted caller remains responsible for structural
+-- local-proof composition.
+acceptPreparedVampireObligation
+ :: (Eq local, Eq origin)
+ => PreparedVampireObligation local origin
+ -> CandidateProof ()
+acceptPreparedVampireObligation
+ (PreparedVampireObligation expected prepared) =
+ acceptValidatedVampireTask
+ (validatePreparedVampireProblem expected prepared)
+ prepared
+
+-- | Prepare and execute the current closed candidate using exactly the
+-- staged premises already consumed by its trusted declaration compiler.
+acceptCurrentCandidateVampire :: CandidateProof ()
+acceptCurrentCandidateVampire =
+ prepareCurrentCandidateVampire
+ >>= acceptPreparedVampireObligation
+
+-- | Prepare the current closed candidate without executing its request. The
+-- declaration-owned batch authorizer uses this seam only after all strictly
+-- earlier staged premises have been consumed.
+prepareCurrentCandidateVampire
+ :: CandidateProof (PreparedVampireObligation Void ())
+prepareCurrentCandidateVampire = CandidateProof do
+ initial <- State.get
+ let builder = candidateProofBuilder initial
+ closure = candidateProofObjectClosure initial
+ target = candidateCheckedProposition
+ (candidateProofCandidate initial)
+ premises =
+ [ proposition
+ | CandidatePremise proposition <-
+ reverse (candidateProofPremisesReversed initial)
+ ]
+ preparation =
+ prepareClosedCandidateVampire
+ builder closure target premises
+ preparedResult <-
+ State.lift
+ (liftIO
+ (forceVampirePreparation preparation))
+ prepared <-
+ State.lift
+ (Except.liftEither
+ (first
+ CurrentCandidateVampirePreparationFailed
+ preparedResult))
+ pure prepared
+
+prepareClosedCandidateVampire
+ :: BuilderState evidence
+ -> CheckedObjectClosure
+ -> CheckedPropositionContent
+ -> [CheckedPropositionContent]
+ -> Either
+ (VampireObligationPreparationError Void)
+ (PreparedVampireObligation Void ())
+prepareClosedCandidateVampire builder closure target premises = do
+ supportedTarget <-
+ first
+ VampireObligationClaimProjectionFailed
+ (Backend.projectSupportedProposition
+ globalType
+ (Vector.empty :: Vector (Void, CoreType))
+ (closed target))
+ locals <-
+ traverse
+ prepareLocal
+ (zip [0 :: Natural ..] premises)
+ prepareVampireObligationWith
+ Provers.DirectTask
+ builder
+ closure
+ supportedTarget
+ locals
+ []
+ VampireLocalPremises
+ where
+ globalType = (`lookupCheckedObjectType` closure)
+ closed proposition =
+ embedClosedCore []
+ (checkedPropositionTerm proposition)
+
+ prepareLocal (index, proposition) = do
+ let ordinal = Backend.localPremiseOrdinal index
+ supported <-
+ first
+ (VampireObligationLocalProjectionFailed ordinal)
+ (Backend.projectSupportedProposition
+ globalType
+ (Vector.empty :: Vector (Void, CoreType))
+ (closed proposition))
+ first
+ (VampireObligationLocalClassificationFailed ordinal)
+ (Backend.typedLocalPremise
+ globalType ordinal () supported)
+
+acceptValidatedVampireTask
+ :: (CandidateProofState
+ -> ExceptT DeclarationError IO CandidateProofState)
+ -> Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId
+ -> CandidateProof ()
+acceptValidatedVampireTask validate prepared = CandidateProof do
+ initial <- State.get
+ validated <- State.lift (validate initial)
+ result <-
+ case candidateProofCachedValidation initial of
+ Nothing -> do
+ let resolver =
+ declarationVampireResolver
+ (candidateProofDeclaration initial)
+ Just <$> State.lift (resolveOneVampire resolver prepared)
+ Just _validation ->
+ pure Nothing
+ final <-
+ State.lift
+ (acceptVampireBatchMemberResult
+ prepared result validated)
+ State.put final
+
+-- | Complete one checked source candidate after all of its obligations have
+-- been accepted in source order. The enclosing proof declaration owns the
+-- exact syntax key and selects live or cached execution before this action.
+authorizeVampireCandidate
+ :: ReservedCandidate
+ -> CandidateProof ()
+ -> Declaration ()
+authorizeVampireCandidate candidate proof =
+ authorizeOneCandidate candidate \initial -> do
+ cached <-
+ selectProofCandidateValidation candidate initial
+ let preparedInitial =
+ initial
+ { candidateProofCachedValidation = cached
+ }
+ ((), final) <-
+ State.runStateT (runCandidateProof proof) preparedInitial
+ case acceptedRequestIds final of
+ [] ->
+ Except.throwError
+ VampireProofHasNoAcceptedObligations
+ requests ->
+ completeCandidateWithValidation
+ candidate
+ (CheckedSourceProof requests)
+ (candidateProofSafety final)
+ final
+
+data PreparedVampireCandidateBatchMember =
+ PreparedVampireCandidateBatchMember
+ !Location
+ !ReservedCandidate
+ !(Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ Void
+ ()
+ ObjectId)
+ !CandidateProofState
+
+data ResolvedVampireCandidateBatchMember =
+ ResolvedVampireCandidateBatchMember
+ !PreparedVampireCandidateBatchMember
+ !(Maybe
+ (Either
+ Provers.ProverProcessError
+ Provers.ProverAnswer))
+
+-- | Authorize one complete, source-ordered candidate stage whose members are
+-- mutually independent and each require exactly one closed Vampire request.
+-- Every member is prepared and validated against the same declaration
+-- baseline. Results are applied only after the complete live batch returns,
+-- so no same-stage sibling can become authority for another member.
+authorizeVampireCandidateBatch
+ :: NonEmpty
+ ( Location
+ , ReservedCandidate
+ , CandidateProof (PreparedVampireObligation Void ())
+ )
+ -> Declaration ()
+authorizeVampireCandidateBatch inputs = Declaration do
+ unprepared <- State.get
+ baseline <-
+ State.lift
+ (Except.liftEither
+ (prepareDeclarationClosure unprepared))
+ let supplied =
+ [ candidate
+ | (_location, candidate, _prepare) <- NonEmpty.toList inputs
+ ]
+ frontier = declarationAuthorizationFrontier baseline
+ expected =
+ List.filter
+ ((== CandidateStage frontier) . reservedStage)
+ (Map.elems (declarationReservations baseline))
+ traverse_
+ (State.lift
+ . Except.liftEither
+ . validateReservedCandidate baseline)
+ supplied
+ traverse_
+ (\candidate ->
+ when
+ (Map.member
+ (reservedCandidateSlot candidate)
+ (declarationPending baseline))
+ (State.lift
+ (Except.throwError
+ (CandidateAlreadyAuthorized
+ (reservedCandidateSlot candidate)))))
+ supplied
+ unless (supplied == expected)
+ (State.lift
+ (Except.throwError
+ (VampireCandidateBatchMismatch
+ (reservedCandidateSlot <$> expected)
+ (reservedCandidateSlot <$> supplied))))
+ prepared <-
+ State.lift
+ (traverse
+ (prepareVampireCandidateBatchMember baseline)
+ inputs)
+ State.lift
+ (validateCachedVampireCandidateBatch
+ (NonEmpty.toList prepared))
+ let live =
+ [ task
+ | PreparedVampireCandidateBatchMember
+ _location _candidate task final <-
+ NonEmpty.toList prepared
+ , isNothing (candidateProofCachedValidation final)
+ ]
+ liveResults <-
+ case NonEmpty.nonEmpty live of
+ Nothing -> pure []
+ Just nonempty -> do
+ let resolver =
+ declarationVampireResolver baseline
+ results <-
+ liftIO
+ (resolveSynchronousVampireBatch resolver nonempty)
+ State.lift
+ (Except.liftEither
+ (validateVampireResolverResultCount
+ (length live)
+ results))
+ pending <-
+ State.lift
+ (completeVampireCandidateBatch
+ (NonEmpty.toList prepared)
+ liveResults)
+ let withPending =
+ baseline
+ { declarationPending =
+ foldl'
+ (\entries item@(PendingCandidate candidate _ _) ->
+ Map.insert
+ (reservedCandidateSlot
+ candidate)
+ item
+ entries)
+ (declarationPending baseline)
+ pending
+ }
+ State.put (advanceAuthorizationFrontier withPending)
+
+prepareVampireCandidateBatchMember
+ :: DeclarationState
+ -> ( Location
+ , ReservedCandidate
+ , CandidateProof (PreparedVampireObligation Void ())
+ )
+ -> ExceptT DeclarationError IO PreparedVampireCandidateBatchMember
+prepareVampireCandidateBatchMember
+ baseline (location, candidate, prepare) = do
+ initial <-
+ Except.liftEither
+ (initialCandidateProofState baseline candidate)
+ cached <- selectProofCandidateValidation candidate initial
+ let selected =
+ initial{candidateProofCachedValidation = cached}
+ (obligation, preparedState) <-
+ State.runStateT
+ (runCandidateProof
+ (locateProofObligation location prepare))
+ selected
+ unless (null (acceptedRequestIds preparedState))
+ (Except.throwError
+ (VampireCandidateBatchProofShapeMismatch
+ (reservedCandidateSlot candidate)))
+ let PreparedVampireObligation expected task = obligation
+ validated <-
+ Except.withExceptT
+ (ProofObligationFailedAt location)
+ (validatePreparedVampireProblem
+ expected task preparedState)
+ pure
+ (PreparedVampireCandidateBatchMember
+ location candidate task validated)
+
+completeVampireCandidateBatch
+ :: [PreparedVampireCandidateBatchMember]
+ -> [Either Provers.ProverProcessError Provers.ProverAnswer]
+ -> ExceptT DeclarationError IO [PendingCandidate]
+completeVampireCandidateBatch members liveResults = do
+ resolved <-
+ Except.liftEither
+ (associateVampireCandidateBatchResults
+ members liveResults)
+ -- Integrity dominates ranked proof failure: validate every accepted run
+ -- before inspecting any ordinary process or prover rejection.
+ traverse_ validateAcceptedVampireBatchMember resolved
+ traverse_ rejectOrdinaryVampireBatchMember resolved
+ traverse completeVampireCandidateBatchMember resolved
+
+validateCachedVampireCandidateBatch
+ :: [PreparedVampireCandidateBatchMember]
+ -> ExceptT DeclarationError IO ()
+validateCachedVampireCandidateBatch =
+ traverse_ \member ->
+ when
+ (preparedVampireCandidateBatchMemberIsCached member)
+ (void
+ (completeVampireCandidateBatchMember
+ (ResolvedVampireCandidateBatchMember member Nothing)))
+
+associateVampireCandidateBatchResults
+ :: [PreparedVampireCandidateBatchMember]
+ -> [Either Provers.ProverProcessError Provers.ProverAnswer]
+ -> Either
+ DeclarationError
+ [ResolvedVampireCandidateBatchMember]
+associateVampireCandidateBatchResults members =
+ go members
+ where
+ go [] [] = Right []
+ go [] results =
+ Left
+ (VampireResolverBatchSizeMismatch
+ 0
+ (length results))
+ go (member : remaining) results
+ | preparedVampireCandidateBatchMemberIsCached member =
+ (ResolvedVampireCandidateBatchMember member Nothing :)
+ <$> go remaining results
+ | otherwise =
+ case results of
+ [] ->
+ Left (VampireResolverBatchSizeMismatch 1 0)
+ current : later ->
+ (ResolvedVampireCandidateBatchMember
+ member (Just current) :)
+ <$> go remaining later
+
+preparedVampireCandidateBatchMemberIsCached
+ :: PreparedVampireCandidateBatchMember
+ -> Bool
+preparedVampireCandidateBatchMemberIsCached
+ (PreparedVampireCandidateBatchMember
+ _location _candidate _task state) =
+ isJust (candidateProofCachedValidation state)
+
+validateAcceptedVampireBatchMember
+ :: ResolvedVampireCandidateBatchMember
+ -> ExceptT DeclarationError IO ()
+validateAcceptedVampireBatchMember
+ (ResolvedVampireCandidateBatchMember
+ (PreparedVampireCandidateBatchMember
+ location _candidate task _validated)
+ result) =
+ Except.withExceptT
+ (ProofObligationFailedAt location)
+ (validateAcceptedVampireResult task result)
+
+rejectOrdinaryVampireBatchMember
+ :: ResolvedVampireCandidateBatchMember
+ -> ExceptT DeclarationError IO ()
+rejectOrdinaryVampireBatchMember
+ (ResolvedVampireCandidateBatchMember
+ (PreparedVampireCandidateBatchMember
+ location _candidate _task _validated)
+ result) =
+ traverse_
+ (Except.throwError . ProofObligationFailedAt location)
+ (result >>= vampireResultFailure)
+
+completeVampireCandidateBatchMember
+ :: ResolvedVampireCandidateBatchMember
+ -> ExceptT DeclarationError IO PendingCandidate
+completeVampireCandidateBatchMember
+ (ResolvedVampireCandidateBatchMember
+ (PreparedVampireCandidateBatchMember
+ location candidate task validated)
+ result) = do
+ final <-
+ Except.withExceptT
+ (ProofObligationFailedAt location)
+ (applyAcceptedVampireResult task result validated)
+ completeCandidateWithValidation
+ candidate
+ (CheckedSourceProof (acceptedRequestIds final))
+ (candidateProofSafety final)
+ final
+
+acceptVampireBatchMemberResult
+ :: Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId
+ -> Maybe (Either Provers.ProverProcessError Provers.ProverAnswer)
+ -> CandidateProofState
+ -> ExceptT DeclarationError IO CandidateProofState
+acceptVampireBatchMemberResult prepared result validated = do
+ validateAcceptedVampireResult prepared result
+ traverse_ Except.throwError (result >>= vampireResultFailure)
+ applyAcceptedVampireResult prepared result validated
+
+validateAcceptedVampireResult
+ :: Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId
+ -> Maybe (Either Provers.ProverProcessError Provers.ProverAnswer)
+ -> ExceptT DeclarationError IO ()
+validateAcceptedVampireResult prepared result = do
+ let request = Provers.preparedTypedProverRequest prepared
+ traverse_
+ (\resolved ->
+ traverse_
+ (\accepted ->
+ unless
+ (Provers.acceptedVampireRequestId accepted
+ == Provers.preparedVerificationRequestId request)
+ (Except.throwError VampireRequestMismatch))
+ (either (const Nothing) Provers.provedVampireRun resolved))
+ result
+
+vampireResultFailure
+ :: Either Provers.ProverProcessError Provers.ProverAnswer
+ -> Maybe DeclarationError
+vampireResultFailure = \case
+ Left failure ->
+ Just (VampireProcessFailed failure)
+ Right answer ->
+ case Provers.provedVampireRun answer of
+ Nothing ->
+ Just (VampireObligationRejected answer)
+ Just _accepted ->
+ Nothing
+
+applyAcceptedVampireResult
+ :: Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId
+ -> Maybe (Either Provers.ProverProcessError Provers.ProverAnswer)
+ -> CandidateProofState
+ -> ExceptT DeclarationError IO CandidateProofState
+applyAcceptedVampireResult prepared result validated = do
+ traverse_
+ (\resolved ->
+ case vampireResultFailure resolved of
+ Just failure ->
+ Except.throwError failure
+ Nothing ->
+ pure ())
+ result
+ let request = Provers.preparedTypedProverRequest prepared
+ requestId = Provers.preparedVerificationRequestId request
+ pure
+ validated
+ { candidateProofAcceptedRequestsReversed =
+ requestId
+ : candidateProofAcceptedRequestsReversed validated
+ }
+
+selectProofCandidateValidation
+ :: ReservedCandidate
+ -> CandidateProofState
+ -> ExceptT
+ DeclarationError
+ IO
+ (Maybe Materialization.CandidateValidation)
+selectProofCandidateValidation candidate initial =
+ case declarationValidationMode
+ (candidateProofDeclaration initial) of
+ EnvironmentImportMode ->
+ Except.throwError
+ ProofValidationOutsideProofDeclaration
+ ProofValidationMode{} -> do
+ (syntax, cached) <-
+ runDeclarationLookup
+ (lookupProofValidation candidate)
+ initial
+ pure
+ ((\record ->
+ Materialization.candidateProofValidation
+ record syntax)
+ <$> cached)
+ DeclarationValidationMode{} ->
+ pure (candidateProofCachedValidation initial)
+
+completeCandidateWithValidation
+ :: ReservedCandidate
+ -> DirectAuthorization
+ -> CandidateSafety
+ -> CandidateProofState
+ -> ExceptT DeclarationError IO PendingCandidate
+completeCandidateWithValidation candidate direct safety final = do
+ pending <-
+ Except.liftEither
+ (freshCompletion
+ candidate
+ direct
+ safety
+ final)
+ traverse_
+ (\validation ->
+ case Materialization.checkCandidateValidation
+ (logicalBuilderTheory
+ (candidateProofBuilder final))
+ (logicalBuilderPrefix
+ (candidateProofBuilder final))
+ (candidateCheckedProposition candidate)
+ (validationTarget
+ (pendingCandidateCertificate pending))
+ direct
+ validation of
+ Left failure ->
+ liftIO
+ (throwIO
+ (CachedValidationIntegrityError failure))
+ Right () ->
+ pure ())
+ (candidateProofCachedValidation final)
+ pure pending
+
+runDeclarationLookup
+ :: Declaration value
+ -> CandidateProofState
+ -> ExceptT DeclarationError IO value
+runDeclarationLookup action initial =
+ fst
+ <$> State.runStateT
+ (runDeclaration action)
+ (candidateProofDeclaration initial)
+
+pendingCandidateCertificate :: PendingCandidate -> ValidationCertificate
+pendingCandidateCertificate
+ (PendingCandidate _ certificate _) =
+ certificate
+
+acceptedRequestIds :: CandidateProofState -> [PreparedRequestId]
+acceptedRequestIds =
+ reverse . candidateProofAcceptedRequestsReversed
+
+freshCompletion
+ :: ReservedCandidate
+ -> DirectAuthorization
+ -> CandidateSafety
+ -> CandidateProofState
+ -> Either DeclarationError PendingCandidate
+freshCompletion candidate direct safety proofState = do
+ let builder = candidateProofBuilder proofState
+ declaration = candidateProofDeclaration proofState
+ reference = candidateTheoremReference builder candidate
+ authority = candidateFactAuthority reference safety
+ void
+ (first DeclarationPropositionValidationFailed
+ (validateAssertedPropositionContent
+ (candidateProofObjectClosure proofState)
+ (checkedPropositionId
+ (candidateCheckedProposition candidate))
+ (frozenCoreTerm
+ (checkedPropositionTerm
+ (candidateCheckedProposition candidate)))))
+ certificate <-
+ first ValidationCertificateFailed
+ (validationCertificate authority direct)
+ let authorization =
+ PendingFactAuthorization
+ (logicalBuilderIdentity builder)
+ (logicalBuilderPrefix builder)
+ (declarationInvocation declaration)
+ (reservedCandidateSlot candidate)
+ authority
+ (reservedStage candidate)
+ pure
+ (PendingCandidate
+ candidate
+ certificate
+ authorization)
+
+authorizeOneCandidate
+ :: ReservedCandidate
+ -> (CandidateProofState
+ -> ExceptT DeclarationError IO PendingCandidate)
+ -> Declaration ()
+authorizeOneCandidate candidate complete = Declaration do
+ unprepared <- State.get
+ state <-
+ State.lift
+ (Except.liftEither
+ (prepareDeclarationClosure unprepared))
+ State.lift
+ (Except.liftEither
+ (validateReservedCandidate state candidate))
+ when
+ (Map.member
+ (reservedCandidateSlot candidate)
+ (declarationPending state))
+ (State.lift
+ (Except.throwError
+ (CandidateAlreadyAuthorized
+ (reservedCandidateSlot candidate))))
+ unless
+ (reservedStage candidate
+ == CandidateStage
+ (declarationAuthorizationFrontier state))
+ (State.lift
+ (Except.throwError
+ (CandidateOutsideAuthorizationFrontier
+ (reservedCandidateSlot candidate)
+ (reservedCandidateStage candidate)
+ (declarationAuthorizationFrontier state))))
+ initial <- State.lift
+ (Except.liftEither
+ (initialCandidateProofState state candidate))
+ pending <- State.lift (complete initial)
+ let state' =
+ state
+ { declarationPending =
+ Map.insert
+ (reservedCandidateSlot candidate)
+ pending
+ (declarationPending state)
+ }
+ State.put (advanceAuthorizationFrontier state')
+
+advanceAuthorizationFrontier
+ :: DeclarationState
+ -> DeclarationState
+advanceAuthorizationFrontier state
+ | all stageCompleted currentCandidates =
+ state
+ { declarationAuthorizationFrontier =
+ frontier + 1
+ }
+ | otherwise =
+ state
+ where
+ frontier = declarationAuthorizationFrontier state
+ currentCandidates =
+ List.filter
+ ((== CandidateStage frontier) . reservedStage)
+ (Map.elems (declarationReservations state))
+ stageCompleted candidate =
+ Map.member
+ (reservedCandidateSlot candidate)
+ (declarationPending state)
+
+initialCandidateProofState
+ :: DeclarationState
+ -> ReservedCandidate
+ -> Either DeclarationError CandidateProofState
+initialCandidateProofState declaration candidate = do
+ closure <- declarationClosure declaration
+ cached <-
+ cachedDeclarationCandidateValidation declaration candidate
+ pure CandidateProofState
+ { candidateProofDeclaration = declaration
+ , candidateProofObjectClosure = closure
+ , candidateProofCandidate = candidate
+ , candidateProofCachedValidation = cached
+ , candidateProofPremisesReversed = []
+ , candidateProofPremiseCount = 0
+ , candidateProofSafety = initialCandidateSafety
+ , candidateProofAcceptedRequestsReversed = []
+ }
+
+cachedDeclarationCandidateValidation
+ :: DeclarationState
+ -> ReservedCandidate
+ -> Either
+ DeclarationError
+ (Maybe Materialization.CandidateValidation)
+cachedDeclarationCandidateValidation declaration candidate =
+ case declarationValidationSelection declaration of
+ DeclarationValidationUnselected ->
+ Right Nothing
+ FreshDeclarationValidation ->
+ Right Nothing
+ CachedDeclarationValidation
+ syntax objects theorems record ordinals ->
+ maybe
+ (Left
+ (CachedDeclarationCandidateMissing
+ (reservedCandidateSlot candidate)))
+ (Right
+ . Just
+ . Materialization.candidateDeclarationValidation
+ record syntax objects theorems)
+ (Map.lookup
+ (reservedCandidateSlot candidate)
+ ordinals)
+
+reservedStage :: ReservedCandidate -> CandidateStage
+reservedStage
+ (ReservedCandidate _ _ _ stage _ _) =
+ stage
+
+validateReservedCandidate
+ :: DeclarationState
+ -> ReservedCandidate
+ -> Either DeclarationError ()
+validateReservedCandidate declaration candidate = do
+ let builder = declarationBuilder declaration
+ ReservedCandidate
+ identity prefix invocation _stage slot _spec = candidate
+ unless
+ ( identity == logicalBuilderIdentity builder
+ && prefix == logicalBuilderPrefix builder
+ && invocation == declarationInvocation declaration
+ && Map.lookup slot
+ (declarationReservations declaration)
+ == Just candidate
+ )
+ (Left (CandidateOutsideDeclaration slot))
+
+instance Eq ReservedCandidate where
+ left == right =
+ reservedCandidateKey left == reservedCandidateKey right
+
+reservedCandidateKey
+ :: ReservedCandidate
+ -> (BuilderIdentity, PrefixContextId, DeclarationInvocation, CandidateStage, FactSlot)
+reservedCandidateKey
+ (ReservedCandidate identity prefix invocation stage slot _spec) =
+ (identity, prefix, invocation, stage, slot)
+
+
+consumeBuilderAuthorizedFact
+ :: FactEntry BuilderFactAuthorization
+ -> CandidateProofState
+ -> StateT
+ CandidateProofState
+ (ExceptT DeclarationError IO)
+ CheckedPropositionContent
+consumeBuilderAuthorizedFact
+ (FactEntry proposition occurrence authorization)
+ state = do
+ let builder = candidateProofBuilder state
+ BuilderFactAuthorization
+ identity slot authority = authorization
+ unless
+ ( identity == logicalBuilderIdentity builder
+ && slot == semanticFactSlot occurrence
+ && authority == semanticFactAuthority occurrence
+ )
+ (State.lift
+ (Except.throwError BuilderFactAuthorizationMismatch))
+ accumulateAuthorizedSafety
+ proposition
+ authority
+ state
+ pure proposition
+
+consumePendingFact
+ :: PendingCandidate
+ -> CandidateProofState
+ -> StateT
+ CandidateProofState
+ (ExceptT DeclarationError IO)
+ CheckedPropositionContent
+consumePendingFact
+ (PendingCandidate premise certificate authorization)
+ state = do
+ let builder = candidateProofBuilder state
+ declaration = candidateProofDeclaration state
+ PendingFactAuthorization
+ identity prefix invocation slot authority
+ premiseStageValue = authorization
+ current = candidateProofCandidate state
+ unless
+ ( identity == logicalBuilderIdentity builder
+ && prefix == logicalBuilderPrefix builder
+ && invocation == declarationInvocation declaration
+ && slot == reservedCandidateSlot premise
+ && authority == validationTarget certificate
+ && premiseStageValue == reservedStage premise
+ )
+ (State.lift
+ (Except.throwError PendingFactAuthorizationMismatch))
+ unless
+ (reservedStage premise < reservedStage current)
+ (State.lift
+ (Except.throwError
+ (StagedPremiseNotEarlier
+ (reservedCandidateSlot premise)
+ (reservedCandidateStage premise)
+ (reservedCandidateSlot current)
+ (reservedCandidateStage current))))
+ let proposition = candidateCheckedProposition premise
+ accumulateAuthorizedSafety
+ proposition
+ authority
+ state
+ pure proposition
+
+accumulateAuthorizedSafety
+ :: CheckedPropositionContent
+ -> FactAuthority
+ -> CandidateProofState
+ -> StateT
+ CandidateProofState
+ (ExceptT DeclarationError IO)
+ ()
+accumulateAuthorizedSafety proposition authority state = do
+ let expected =
+ theoremRef
+ (logicalBuilderTheory
+ (candidateProofBuilder state))
+ (checkedPropositionId proposition)
+ safety <-
+ State.lift
+ (Except.liftEither
+ (first FactSafetyFailed
+ (accumulateFactSafety
+ expected
+ authority
+ (candidateProofSafety state))))
+ State.put state{candidateProofSafety = safety}
+
+registerKernelImport
+ :: CheckedPropositionContent
+ -> CandidateProof ImportIx
+registerKernelImport proposition = CandidateProof do
+ state <- State.get
+ appendCandidatePremise proposition state
+
+appendCandidatePremise
+ :: CheckedPropositionContent
+ -> CandidateProofState
+ -> StateT
+ CandidateProofState
+ (ExceptT DeclarationError IO)
+ ImportIx
+appendCandidatePremise proposition state = do
+ let index = candidateProofPremiseCount state
+ State.put
+ state
+ { candidateProofPremisesReversed =
+ CandidatePremise proposition
+ : candidateProofPremisesReversed state
+ , candidateProofPremiseCount = index + 1
+ }
+ pure (importIx index)
+
+findPendingByFingerprint
+ :: SemanticFactOccurrenceFingerprint
+ -> Map FactSlot PendingCandidate
+ -> Maybe PendingCandidate
+findPendingByFingerprint fingerprint =
+ find
+ (\(PendingCandidate candidate certificate _authorization) ->
+ semanticFactOccurrenceFingerprint
+ (reservedCandidateSlot candidate)
+ (validationTarget certificate)
+ == fingerprint)
+ . Map.elems
+
+candidateImportJudgments
+ :: CandidateProofState
+ -> Either DeclarationError (Vector (DerivationImportJudgment ObjectId))
+candidateImportJudgments state =
+ Vector.fromList
+ <$> traverse
+ (\(CandidatePremise proposition) ->
+ first DerivationImportFailed
+ (derivationImportJudgment
+ (checkedPropositionTerm proposition)))
+ (reverse
+ (candidateProofPremisesReversed state))
+
+replayCandidateKernel
+ :: CheckedPropositionContent
+ -> KernelDerivation ObjectId
+ -> CandidateProofState
+ -> Either
+ DeclarationError
+ (ReplayedKernelDerivation ObjectId)
+replayCandidateKernel proposition derivation state = do
+ imports <- candidateImportJudgments state
+ let builder = candidateProofBuilder state
+ closure = candidateProofObjectClosure state
+ first KernelCompletionFailed
+ (replayKernelDerivation
+ (logicalBuilderFoundation builder)
+ defaultKernelReplayLimits
+ (`lookupCheckedObjectType` closure)
+ imports
+ (checkedPropositionTerm proposition)
+ derivation)
+
+validateKernelConstruction
+ :: KernelConstructionDescriptor
+ -> CheckedPropositionContent
+ -> ReplayedKernelDerivation ObjectId
+ -> CandidateProofState
+ -> Either DeclarationError ()
+validateKernelConstruction descriptor proposition replayed proofState =
+ unless matches
+ (Left KernelConstructionDescriptorMismatch)
+ where
+ builder = candidateProofBuilder proofState
+ noImports = Set.null (replayedKernelImportUses replayed)
+ noFoundation = Set.null (replayedKernelFoundationUses replayed)
+ noRules = Set.null (replayedKernelRuleUses replayed)
+
+ matches =
+ case descriptor of
+ FoundationLeaf tag ->
+ noImports
+ && replayedKernelFoundationUses replayed
+ == Set.singleton tag
+ && noRules
+ && checkedPropositionTerm proposition
+ == mapFrozenGlobals
+ absurd
+ (foundationAxiomFrozen
+ (logicalBuilderFoundation builder)
+ tag)
+ GuardedFoundationRules rules ->
+ replayedKernelRuleUses replayed
+ == guardedRuleTags rules
+ CheckedDefinitionEquation identity ->
+ noImports
+ && noFoundation
+ && noRules
+ && matchesDefinitionEquation identity proposition proofState
+ CheckedSetConstructionExtensionality{} ->
+ False
+
+matchesDefinitionEquation
+ :: ObjectId
+ -> CheckedPropositionContent
+ -> CandidateProofState
+ -> Bool
+matchesDefinitionEquation identity proposition proofState =
+ case lookupCheckedObjectContent
+ identity
+ (candidateProofObjectClosure proofState) of
+ Just
+ (TransparentObjectContent
+ contentTheory coreType body) ->
+ contentTheory
+ == logicalBuilderTheory
+ (candidateProofBuilder proofState)
+ && frozenCoreTerm (checkedPropositionTerm proposition)
+ `elem` definitionEquationTargets identity coreType body
+ _ ->
+ False
+
+definitionEquationTargets
+ :: ObjectId
+ -> CoreType
+ -> CanonicalTerm ObjectId
+ -> [CanonicalTerm ObjectId]
+definitionEquationTargets identity coreType body =
+ CEq coreType (CGlobal identity) body
+ : case (coreType, body) of
+ (TyArrow TySet TyProp, CLam TySet predicate) ->
+ [ CForall TySet
+ (CEq TyProp
+ (CApp (CGlobal identity) (CBound 0))
+ predicate)
+ ]
+ _ -> []
+
+
+validateCandidateVampireProblem
+ :: Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ Void
+ origin
+ ObjectId
+ -> CandidateProofState
+ -> ExceptT DeclarationError IO CandidateProofState
+validateCandidateVampireProblem prepared initial = do
+ let problem = Provers.preparedTypedProverLogicalProblem prepared
+ claim = Backend.typedProblemClaim problem
+ target = candidateCheckedProposition
+ (candidateProofCandidate initial)
+ Except.liftEither do
+ unless
+ ( Vector.null (Backend.supportedPropositionSupport claim)
+ && Backend.supportedPropositionTerm claim
+ == frozenCoreTerm (checkedPropositionTerm target)
+ )
+ (Left VampireTargetMismatch)
+ unless
+ (Vector.null (Backend.typedProblemLocalPremises problem))
+ (Left VampireLocalPremisesNotSupported)
+ validateVampireProblemEnvironment prepared initial
+
+validatePreparedVampireProblem
+ :: (Eq local, Eq origin)
+ => Backend.TypedProblem
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId
+ -> Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId
+ -> CandidateProofState
+ -> ExceptT DeclarationError IO CandidateProofState
+validatePreparedVampireProblem expectedProblem prepared initial = do
+ let problem = Provers.preparedTypedProverLogicalProblem prepared
+ Except.liftEither
+ (unless (problem == expectedProblem)
+ (Left VampireTargetMismatch)
+ )
+ validateVampireProblemEnvironment prepared initial
+
+validateVampireProblemEnvironment
+ :: Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId
+ -> CandidateProofState
+ -> ExceptT DeclarationError IO CandidateProofState
+validateVampireProblemEnvironment prepared initial = do
+ let problem = Provers.preparedTypedProverLogicalProblem prepared
+ builder = candidateProofBuilder initial
+ closure = candidateProofObjectClosure initial
+ Except.liftEither do
+ traverse_
+ (\(identity, reportedType) ->
+ unless
+ (lookupCheckedObjectType identity closure
+ == Just reportedType)
+ (Left (VampireGlobalTypeMismatch identity)))
+ (Map.toList (Backend.typedProblemGlobalTypes problem))
+ traverse_
+ (validateFoundationAuxiliary builder)
+ (Backend.typedProblemAuxiliaries problem)
+ execCandidateProof initial do
+ traverse_
+ validatePremise
+ (Backend.typedProblemGlobalPremises problem)
+ where
+ validatePremise selected = do
+ let fingerprint = Backend.typedBackendFactReference selected
+ proposition <- consumeAuthorizedFact fingerprint
+ state <- CandidateProof State.get
+ unless
+ ( Backend.supportedPropositionTerm
+ (Backend.typedBackendFactProposition selected)
+ == frozenCoreTerm
+ (checkedPropositionTerm proposition)
+ )
+ (failCandidateProof
+ (VampirePremiseMismatch fingerprint))
+ let closure =
+ candidateProofObjectClosure state
+ supported <-
+ either
+ (const
+ (failCandidateProof
+ (VampirePremiseCapabilityMismatch
+ fingerprint)))
+ pure
+ (Backend.supportedProposition
+ Vector.empty
+ (embedClosedCore
+ []
+ (checkedPropositionTerm proposition)))
+ capability <-
+ either
+ (const
+ (failCandidateProof
+ (VampirePremiseCapabilityMismatch
+ fingerprint)))
+ pure
+ (Backend.classifySupportedProposition
+ (`lookupCheckedObjectType` closure)
+ supported)
+ unless
+ (capability
+ == Backend.typedBackendFactCapability selected)
+ (failCandidateProof
+ (VampirePremiseCapabilityMismatch fingerprint))
+
+ validateFoundationAuxiliary builder auxiliary = do
+ let tag = Backend.typedProblemAuxiliaryTag auxiliary
+ actual =
+ Backend.supportedPropositionTerm
+ (Backend.typedProblemAuxiliaryProposition auxiliary)
+ expectedTerm =
+ frozenCoreTerm
+ (mapFrozenGlobals
+ absurd
+ (foundationAxiomFrozen
+ (logicalBuilderFoundation builder)
+ tag))
+ unless (actual == expectedTerm)
+ (Left (VampireFoundationMismatch tag))
+
+execCandidateProof
+ :: CandidateProofState
+ -> CandidateProof value
+ -> ExceptT DeclarationError IO CandidateProofState
+execCandidateProof initial action =
+ snd <$> State.runStateT (runCandidateProof action) initial
+
+failCandidateProof
+ :: DeclarationError
+ -> CandidateProof value
+failCandidateProof =
+ CandidateProof . State.lift . Except.throwError
+
+data ValidationMode
+ = EnvironmentImportMode
+ | ProofValidationMode !ProofSyntaxId
+ | DeclarationValidationMode !DeclarationSyntaxId
+
+data PlannedVampireDisposition
+ = PlannedCachedRequest !Provers.PreparedVerificationRequest
+ | PlannedLiveRequest
+ !Provers.PreparedVerificationRequest
+ !Provers.VampireHandle
+ | PlannedSynchronousRequest !Provers.PreparedVerificationRequest
+
+data PlannedCandidate = PlannedCandidate
+ !ReservedCandidate
+ !FactAuthority
+ !DirectAuthorization
+ ![PlannedVampireDisposition]
+
+data PlannedReplayRequest
+ = ReplayLive
+ !Provers.PreparedVerificationRequest
+ !Provers.VampireHandle
+ | ReplaySynchronous !Provers.PreparedVerificationRequest
+
+data PlannedValidationSelection
+ = PlannedFreshValidation
+ | PlannedProofValidation
+ !ProofValidationKey
+ !ProofValidationRecord
+ | PlannedCompiledValidation
+ !DeclarationValidationKey
+ !DeclarationValidationRecord
+
+-- | One completely lowered and submitted declaration. It carries no
+-- builder authorization or materialized certificate; all authority is
+-- reconstructed by the source-ordered admission cursor.
+data PlannedDeclaration body = PlannedDeclaration
+ !(CheckedDeclaration body)
+ !PrefixContextId
+ !PrefixContextId
+ !DeclarationInterfaceDelta
+ ![AssertedObject]
+ ![CheckedPropositionContent]
+ ![NonEmpty PlannedCandidate]
+ !PlannedValidationSelection
+
+plannedDeclarationPreviousPrefix
+ :: PlannedDeclaration body
+ -> PrefixContextId
+plannedDeclarationPreviousPrefix
+ (PlannedDeclaration _checked previous _next _delta
+ _objects _propositions _candidates _validation) =
+ previous
+
+plannedDeclarationNextPrefix
+ :: PlannedDeclaration body
+ -> PrefixContextId
+plannedDeclarationNextPrefix
+ (PlannedDeclaration _checked _previous next _delta
+ _objects _propositions _candidates _validation) =
+ next
+
+plannedDeclarationDelta
+ :: PlannedDeclaration body
+ -> DeclarationInterfaceDelta
+plannedDeclarationDelta
+ (PlannedDeclaration _checked _previous _next delta
+ _objects _propositions _candidates _validation) =
+ delta
+
+newtype PlanningIntegrityError = PlanningIntegrityError Text
+ deriving stock (Show)
+
+instance Exception.Exception PlanningIntegrityError
+
+-- | Advance the authority-free semantic projection through one complete
+-- checked declaration. Validation selection and every deterministic check
+-- finish before the first live request is submitted.
+planCheckedDeclaration
+ :: CheckedDeclaration body
+ -> LoweringDriver (Either DeclarationError (PlannedDeclaration body))
+planCheckedDeclaration checked@(CheckedDeclaration
+ mode objects explicitPropositions globals descriptors stages
+ _body) = LoweringDriver do
+ state@(LoweringState resolver (ProspectiveBuilder builder) validationRun) <-
+ State.get
+ prepared <- liftIO (Except.runExceptT do
+ closure <-
+ Except.liftEither
+ (first DeclarationObjectValidationFailed
+ (extendObjectClosure
+ (logicalBuilderObjectClosure builder)
+ objects))
+ let reservedStages =
+ reservePlanningStages builder stages
+ plannedWithoutDisposition <-
+ Except.liftEither
+ (preparePlannedCandidates builder closure reservedStages)
+ let orderedCandidates = fmap (fmap fst) plannedWithoutDisposition
+ occurrences =
+ [ candidateOccurrence reserved authority
+ | stage <- plannedWithoutDisposition
+ , (PlannedCandidate reserved authority _direct _requests,
+ _requestValues) <- toList stage
+ ]
+ aliases =
+ [ semanticAlias alias
+ (semanticFactFingerprint
+ (candidateOccurrence reserved authority))
+ | stage <- plannedWithoutDisposition
+ , (PlannedCandidate reserved authority _direct _requests,
+ _requestValues) <- toList stage
+ , alias <- candidateAliases reserved
+ ]
+ allCandidatePropositions =
+ candidateCheckedProposition . fst
+ <$> concatMap toList reservedStages
+ propositions =
+ stableUniqueBy checkedPropositionId
+ (explicitPropositions <> allCandidatePropositions)
+ objectIds = assertedObjectId <$> objects
+ propositionIds = checkedPropositionId <$> propositions
+ ownSlot = declarationSlot
+ (logicalBuilderOwner builder)
+ (localDeclarationOrdinal
+ (logicalBuilderNextDeclaration builder))
+ traverse_
+ (\proposition ->
+ void
+ (Except.liftEither
+ (first DeclarationPropositionValidationFailed
+ (validateAssertedPropositionContent
+ closure
+ (checkedPropositionId proposition)
+ (frozenCoreTerm
+ (checkedPropositionTerm proposition))))))
+ propositions
+ (resolvedStructures, delta, next) <-
+ Except.liftEither
+ (buildCheckedDeclarationDelta
+ builder closure ownSlot occurrences aliases objectIds
+ propositionIds (reverse globals) (reverse descriptors))
+ selection <-
+ selectPlannedValidation
+ validationRun mode builder objects orderedCandidates
+ planned <-
+ submitPlannedCandidates resolver selection
+ plannedWithoutDisposition
+ let nextBuilder =
+ appendProspectiveBuilder
+ closure resolvedStructures delta next planned builder
+ declaration =
+ PlannedDeclaration checked
+ (logicalBuilderPrefix builder)
+ next delta objects propositions planned selection
+ pure (declaration, nextBuilder))
+ case prepared of
+ Left failure -> pure (Left failure)
+ Right (declaration, nextBuilder) -> do
+ let LoweringState currentResolver _current currentValidation = state
+ State.put
+ (LoweringState
+ currentResolver
+ (ProspectiveBuilder nextBuilder)
+ currentValidation)
+ pure (Right declaration)
+
+reservePlanningStages
+ :: BuilderState PlanningEvidence
+ -> [NonEmpty CheckedCandidate]
+ -> [NonEmpty (ReservedCandidate, CandidatePlanningSpec)]
+reservePlanningStages builder =
+ snd . mapAccumPlanning reserveStage (logicalBuilderNextFact builder)
+ where
+ invocation = DeclarationInvocation (logicalBuilderNextInvocation builder)
+ reserveStage nextFact (stageIndex, specs) =
+ let candidates =
+ NonEmpty.zipWith
+ (\offset (CheckedCandidate spec planning) ->
+ ( ReservedCandidate
+ (logicalBuilderIdentity builder)
+ (logicalBuilderPrefix builder)
+ invocation
+ (CandidateStage stageIndex)
+ (factSlot
+ (logicalBuilderOwner builder)
+ (localFactOrdinal (nextFact + offset)))
+ spec
+ , planning
+ ))
+ (0 :| [1..])
+ specs
+ in ( nextFact + fromIntegral (NonEmpty.length specs)
+ , candidates
+ )
+
+ mapAccumPlanning action initial values =
+ go initial (zip [0..] values)
+ where
+ go accumulator [] = (accumulator, [])
+ go accumulator (value : remaining) =
+ let (next, result) = action accumulator value
+ (final, results) = go next remaining
+ in (final, result : results)
+
+preparePlannedCandidates
+ :: BuilderState PlanningEvidence
+ -> CheckedObjectClosure
+ -> [NonEmpty (ReservedCandidate, CandidatePlanningSpec)]
+ -> Either
+ DeclarationError
+ [NonEmpty (PlannedCandidate, [CheckedPlannedVampireRequest])]
+preparePlannedCandidates builder closure stages =
+ snd <$> mapAccumM prepareStage Map.empty (zip [0..] stages)
+ where
+ prepareStage earlier (stageIndex, stage) = do
+ (sameStage, planned) <-
+ mapAccumM
+ (prepareCandidate stageIndex earlier)
+ Map.empty
+ (zip [0..] (toList stage))
+ pure (Map.union earlier sameStage, NonEmpty.fromList planned)
+
+ prepareCandidate stageIndex earlier sameStage
+ (candidateIndex, (candidate, contract)) = do
+ let position = PlannedCandidatePosition stageIndex candidateIndex
+ (direct, safety, requests) <-
+ prepareCandidatePlanningContract
+ builder closure earlier position candidate contract
+ let authority =
+ candidateFactAuthority
+ (candidateTheoremReference builder candidate)
+ safety
+ planned = PlannedCandidate candidate authority direct []
+ pure
+ ( Map.insert position (candidate, authority) sameStage
+ , (planned, requests)
+ )
+
+ mapAccumM action initial values =
+ go initial [] values
+ where
+ go accumulator outputs = \case
+ [] -> pure (accumulator, reverse outputs)
+ value : remaining -> do
+ (next, output) <- action accumulator value
+ go next (output : outputs) remaining
+
+prepareCandidatePlanningContract
+ :: BuilderState PlanningEvidence
+ -> CheckedObjectClosure
+ -> Map PlannedCandidatePosition (ReservedCandidate, FactAuthority)
+ -> PlannedCandidatePosition
+ -> ReservedCandidate
+ -> CandidatePlanningSpec
+ -> Either
+ DeclarationError
+ ( DirectAuthorization
+ , CandidateSafety
+ , [CheckedPlannedVampireRequest]
+ )
+prepareCandidatePlanningContract builder closure earlier current candidate
+ (CandidatePlanningSpec direct extraFacts staged requests) = do
+ safetyWithFacts <-
+ foldM (accumulatePlanningFact builder closure)
+ initialCandidateSafety
+ (stableUniqueBy id
+ ( extraFacts
+ <> concatMap requestFacts requests
+ ))
+ safetyWithStages <-
+ foldM consumeStage safetyWithFacts staged
+ let safety = case direct of
+ PlanningSourceAxiom ->
+ addCandidateEscape SourceAxiom safetyWithStages
+ PlanningOmitted ->
+ addCandidateEscape Omitted safetyWithStages
+ _ -> safetyWithStages
+ requestIds = requestIdentity <$> requests
+ directAuthorization = case direct of
+ PlanningDefinitionEquation identity ->
+ CheckedKernelConstruction
+ (CheckedDefinitionEquation identity)
+ PlanningSourceAxiom -> SourceAxiomAuthorization
+ PlanningTrustedDatatype descriptor ->
+ TrustedCompilation (DatatypeCompilation descriptor)
+ PlanningKernelConstruction descriptor ->
+ CheckedKernelConstruction descriptor
+ PlanningCheckedSourceProof ->
+ CheckedSourceProof requestIds
+ PlanningOmitted -> OmittedAuthorization
+ validatePlanningDirect directAuthorization safety
+ pure (directAuthorization, safety, requests)
+ where
+ requestFacts
+ (CheckedPlannedVampireRequest _location _request facts) = facts
+ requestIdentity
+ (CheckedPlannedVampireRequest _location request _facts) =
+ Provers.preparedVerificationRequestId request
+
+ consumeStage safety position = do
+ (premise, authority) <-
+ maybe
+ (Left
+ (PlanningFactContractMismatch
+ (reservedCandidateSlot candidate)))
+ Right
+ (Map.lookup position earlier)
+ let PlannedCandidatePosition premiseStage _premiseIndex = position
+ PlannedCandidatePosition currentStage _currentIndex = current
+ unless (premiseStage < currentStage)
+ (Left
+ (StagedPremiseNotEarlier
+ (reservedCandidateSlot premise)
+ premiseStage
+ (reservedCandidateSlot candidate)
+ currentStage))
+ first FactSafetyFailed
+ (accumulateFactSafety
+ (candidateTheoremReference builder premise)
+ authority
+ safety)
+
+accumulatePlanningFact
+ :: BuilderState PlanningEvidence
+ -> CheckedObjectClosure
+ -> CandidateSafety
+ -> SemanticFactOccurrenceFingerprint
+ -> Either DeclarationError CandidateSafety
+accumulatePlanningFact builder closure safety fingerprint = do
+ FactEntry proposition occurrence (PlanningEvidence authority _provenance) <-
+ maybe
+ (Left (AuthorizedFactNotVisible fingerprint))
+ Right
+ (Map.lookup fingerprint (logicalBuilderFacts builder))
+ unless
+ ( semanticFactFingerprint occurrence == fingerprint
+ && semanticFactAuthority occurrence == authority
+ && propositionMatchesClosure closure proposition
+ )
+ (Left (PlanningFactContractMismatch (semanticFactSlot occurrence)))
+ first FactSafetyFailed
+ (accumulateFactSafety
+ (theoremRef
+ (logicalBuilderTheory builder)
+ (checkedPropositionId proposition))
+ authority
+ safety)
+
+validatePlanningDirect
+ :: DirectAuthorization
+ -> CandidateSafety
+ -> Either DeclarationError ()
+validatePlanningDirect direct safety =
+ case direct of
+ SourceAxiomAuthorization ->
+ unless
+ (candidateSafetyAuthority safety
+ == authoritySafety (singletonEscapeKind SourceAxiom))
+ (Left
+ (ValidationCertificateFailed
+ (SourceAxiomSafetyMismatch
+ (candidateSafetyAuthority safety))))
+ OmittedAuthorization ->
+ unless
+ (Omitted `elem` escapeKindsToList
+ (authoritySafetyEscapeKinds
+ (candidateSafetyAuthority safety)))
+ (Left
+ (ValidationCertificateFailed
+ (OmittedSafetyMissing
+ (candidateSafetyAuthority safety))))
+ _ -> pure ()
+
+selectPlannedValidation
+ :: ValidationRun
+ -> CheckedDeclarationMode
+ -> BuilderState PlanningEvidence
+ -> [AssertedObject]
+ -> [NonEmpty PlannedCandidate]
+ -> ExceptT DeclarationError IO PlannedValidationSelection
+selectPlannedValidation validationRun mode builder objects stages =
+ case mode of
+ CheckedProofMode syntax ->
+ case concatMap toList stages of
+ [planned@(PlannedCandidate _candidate authority _direct _)] -> do
+ let key = proofValidationKey
+ (theoremId (factAuthorityTheorem authority))
+ syntax
+ (logicalBuilderPrefix builder)
+ cached <- case validationRun of
+ FreshValidation -> pure Nothing
+ WarmValidation (ValidationLookup lookupProof _) ->
+ liftIO (lookupProof key)
+ traverse_
+ (validateProof planned syntax)
+ cached
+ pure case cached of
+ Nothing -> PlannedFreshValidation
+ Just record -> PlannedProofValidation key record
+ candidates ->
+ Except.throwError
+ (CheckedProofCandidateCountMismatch
+ (length candidates))
+ CheckedCompiledMode syntax -> do
+ let planned = concatMap toList stages
+ objectIds = assertedObjectId <$> objects
+ theorems =
+ theoremId . factAuthorityTheorem . plannedAuthority
+ <$> planned
+ key = declarationValidationKey syntax
+ (logicalBuilderPrefix builder) objectIds theorems
+ cached <- case validationRun of
+ FreshValidation -> pure Nothing
+ WarmValidation (ValidationLookup _ lookupDeclaration) ->
+ liftIO (lookupDeclaration key)
+ traverse_
+ (validateCompiled planned syntax objectIds theorems)
+ cached
+ pure case cached of
+ Nothing -> PlannedFreshValidation
+ Just record -> PlannedCompiledValidation key record
+ where
+ plannedAuthority (PlannedCandidate _ authority _ _) = authority
+
+ validateProof
+ (PlannedCandidate candidate authority direct _)
+ syntax record =
+ case Materialization.checkCandidateValidation
+ (logicalBuilderTheory builder)
+ (logicalBuilderPrefix builder)
+ (candidateCheckedProposition candidate)
+ authority direct
+ (Materialization.candidateProofValidation record syntax) of
+ Left failure ->
+ liftIO
+ (throwIO (CachedValidationIntegrityError failure))
+ Right () -> pure ()
+
+ validateCompiled planned syntax objectIds theorems record = do
+ case Materialization.checkDeclarationValidationRecord
+ (logicalBuilderPrefix builder)
+ syntax objectIds theorems record of
+ Left failure ->
+ liftIO
+ (throwIO (CachedValidationIntegrityError failure))
+ Right () -> pure ()
+ traverse_
+ (\(ordinal,
+ PlannedCandidate candidate authority direct _) ->
+ case Materialization.checkCandidateValidation
+ (logicalBuilderTheory builder)
+ (logicalBuilderPrefix builder)
+ (candidateCheckedProposition candidate)
+ authority direct
+ (Materialization.candidateDeclarationValidation
+ record syntax objectIds theorems ordinal) of
+ Left failure ->
+ liftIO
+ (throwIO
+ (CachedValidationIntegrityError failure))
+ Right () -> pure ())
+ (zip [0..] planned)
+
+submitPlannedCandidates
+ :: VampireResolver
+ -> PlannedValidationSelection
+ -> [NonEmpty (PlannedCandidate, [CheckedPlannedVampireRequest])]
+ -> ExceptT DeclarationError IO [NonEmpty PlannedCandidate]
+submitPlannedCandidates resolver selection candidates
+ | cached =
+ pure (fmap (fmap cachedCandidate) candidates)
+ | otherwise = do
+ let requests =
+ [ VampireSubmission location request
+ | stage <- candidates
+ , (_candidate, plannedRequests) <- toList stage
+ , CheckedPlannedVampireRequest
+ location request _facts <- plannedRequests
+ ]
+ case resolver of
+ VampireResolver SynchronousVampireResolution{} ->
+ pure (fmap (fmap synchronousCandidate) candidates)
+ VampireResolver
+ (AsynchronousVampireSubmission submit) -> do
+ handles <- case NonEmpty.nonEmpty requests of
+ Nothing -> pure []
+ Just nonempty -> do
+ submitted <- liftIO (submit nonempty)
+ unless
+ (NonEmpty.length submitted == length requests)
+ (Except.throwError
+ (VampireResolverBatchSizeMismatch
+ (length requests)
+ (NonEmpty.length submitted)))
+ pure (toList submitted)
+ let (planned, remaining) =
+ State.runState
+ (traverse (traverse liveCandidate) candidates)
+ handles
+ unless (null remaining)
+ (Except.throwError
+ (VampireResolverBatchSizeMismatch
+ (length requests)
+ (length requests + length remaining)))
+ pure planned
+ where
+ cached = case selection of
+ PlannedFreshValidation -> False
+ PlannedProofValidation{} -> True
+ PlannedCompiledValidation{} -> True
+
+ cachedCandidate
+ (PlannedCandidate candidate authority direct _dispositions,
+ requests) =
+ PlannedCandidate candidate authority direct
+ [ PlannedCachedRequest request
+ | CheckedPlannedVampireRequest
+ _location request _facts <- requests
+ ]
+
+ liveCandidate
+ (PlannedCandidate candidate authority direct _dispositions,
+ requests) = do
+ dispositions <- traverse takeHandle requests
+ pure (PlannedCandidate candidate authority direct dispositions)
+
+ synchronousCandidate
+ (PlannedCandidate candidate authority direct _dispositions,
+ requests) =
+ PlannedCandidate candidate authority direct
+ [ PlannedSynchronousRequest request
+ | CheckedPlannedVampireRequest
+ _location request _facts <- requests
+ ]
+
+ takeHandle
+ (CheckedPlannedVampireRequest _location request _facts) = do
+ remaining <- State.get
+ case remaining of
+ [] ->
+ impossible
+ "validated prospective handle count became exhausted"
+ handle : later -> do
+ State.put later
+ pure (PlannedLiveRequest request handle)
+
+appendProspectiveBuilder
+ :: CheckedObjectClosure
+ -> Map SemanticStructurePhrase ResolvedStructure
+ -> DeclarationInterfaceDelta
+ -> PrefixContextId
+ -> [NonEmpty PlannedCandidate]
+ -> BuilderState PlanningEvidence
+ -> BuilderState PlanningEvidence
+appendProspectiveBuilder closure structures delta next planned builder =
+ builder
+ { logicalBuilderPrefix = next
+ , logicalBuilderObjectClosure = closure
+ , logicalBuilderFacts =
+ foldl' insertPlanningFact
+ (logicalBuilderFacts builder)
+ (concatMap toList planned)
+ , logicalBuilderAliases =
+ foldl'
+ (\entries alias ->
+ Map.insert
+ (semanticAliasName alias)
+ (ImportedAliasBinding
+ (semanticAliasTarget alias)
+ (ImportedAliasOrigin
+ (declarationDeltaSlot delta)
+ (semanticAliasTarget alias)))
+ entries)
+ (logicalBuilderAliases builder)
+ (declarationDeltaAliases delta)
+ , logicalBuilderGlobals =
+ foldl'
+ (\entries binding ->
+ Map.insert
+ (semanticGlobalBindingKey binding)
+ (semanticGlobalBindingTarget binding)
+ entries)
+ (logicalBuilderGlobals builder)
+ (semanticEnvironmentBindings
+ (declarationDeltaEnvironment delta))
+ , logicalBuilderStructures = structures
+ , logicalBuilderDeltas = delta : logicalBuilderDeltas builder
+ , logicalBuilderNextDeclaration =
+ logicalBuilderNextDeclaration builder + 1
+ , logicalBuilderNextFact =
+ logicalBuilderNextFact builder
+ + fromIntegral (sum (fmap NonEmpty.length planned))
+ , logicalBuilderNextInvocation =
+ logicalBuilderNextInvocation builder + 1
+ }
+ where
+ insertPlanningFact entries
+ (PlannedCandidate candidate authority _direct _requests) =
+ let occurrence = candidateOccurrence candidate authority
+ fingerprint = semanticFactFingerprint occurrence
+ evidence = PlanningEvidence authority
+ (Just
+ (PlanningProvenance
+ (declarationDeltaSlot delta)
+ (reservedStageValue candidate)
+ (reservedCandidateSlot candidate)))
+ in Map.insert fingerprint
+ (FactEntry
+ (candidateCheckedProposition candidate)
+ occurrence
+ evidence)
+ entries
+
+ -- Keep the constructor match private while storing exact stage provenance.
+ reservedStageValue
+ (ReservedCandidate _identity _prefix _invocation stage _slot _spec) =
+ stage
+
+-- | Admit one checked declaration through the existing authoritative
+-- source-order cursor. The callback interprets a closed family recipe over
+-- freshly reserved stage slots; it is not retained in the checked value.
+admitCheckedDeclaration
+ :: CheckedDeclaration body
+ -> (body -> [NonEmpty ReservedCandidate] -> Declaration ())
+ -> ModuleDriver failure CommittedDeclarationBatch
+admitCheckedDeclaration
+ (CheckedDeclaration
+ mode objects propositions globals structures stages body)
+ authorize =
+ snd <$> do
+ case mode of
+ CheckedProofMode syntax ->
+ commitProofDeclaration syntax do
+ reserved <- installCheckedStructure
+ authorize body reserved
+ CheckedCompiledMode syntax ->
+ commitCompiledDeclaration syntax do
+ reserved <- installCheckedStructure
+ authorizeCompiledDeclaration
+ (authorize body reserved)
+ where
+ installCheckedStructure = do
+ traverse_ addDeclarationObject objects
+ traverse_ addDeclarationProposition propositions
+ traverse_
+ (\binding ->
+ stageSemanticGlobalBinding
+ (semanticGlobalBindingKey binding)
+ (semanticGlobalBindingTarget binding))
+ globals
+ traverse_ stageSemanticStructureDescriptor structures
+ traverse
+ (reserveCandidateBatch . fmap checkedCandidateSemanticSpec)
+ stages
+
+ checkedCandidateSemanticSpec
+ (CheckedCandidate spec _planning) = spec
+
+-- | Admit one retained plan against the real authorized builder. The family
+-- callback performs the existing authority checks, but every live resolver
+-- result comes from the handle submitted during planning and every validation
+-- lookup is the inert selection retained by the plan.
+admitPlannedCheckedDeclaration
+ :: PlannedDeclaration body
+ -> (body -> [NonEmpty ReservedCandidate] -> Declaration ())
+ -> ModuleDriver failure CommittedDeclarationBatch
+admitPlannedCheckedDeclaration planned@(PlannedDeclaration
+ checked previous _expectedNext _expectedDelta _objects _propositions
+ candidates selection) authorize = ModuleDriver do
+ DriverState originalResolver builder prefix originalValidation <-
+ State.get
+ unless (logicalBuilderPrefix builder == previous)
+ (liftIO
+ (throwIO
+ (PlanningIntegrityError
+ "planned declaration predecessor does not match the admitted builder")))
+ liveRef <- liftIO
+ (newIORef
+ [ replay
+ | stage <- candidates
+ , PlannedCandidate _candidate _authority _direct dispositions <-
+ toList stage
+ , disposition <- dispositions
+ , replay <- case disposition of
+ PlannedCachedRequest{} -> []
+ PlannedLiveRequest request handle ->
+ [ReplayLive request handle]
+ PlannedSynchronousRequest request ->
+ [ReplaySynchronous request]
+ ])
+ replay <- liftIO (plannedReplayResolver originalResolver liveRef)
+ let validation = plannedAdmissionValidation selection
+ State.put (DriverState replay builder prefix validation)
+ batch <- runDriverStep (admitCheckedDeclaration checked authorize)
+ remaining <- liftIO (readIORef liveRef)
+ unless (null remaining)
+ (liftIO
+ (throwIO
+ (PlanningIntegrityError
+ "admission did not consume every planned live request")))
+ liftIO (validateAdmittedPlan planned batch)
+ DriverState _replay admittedBuilder admittedPrefix _plannedValidation <-
+ State.get
+ State.put
+ (DriverState
+ originalResolver admittedBuilder admittedPrefix originalValidation)
+ pure batch
+
+plannedReplayResolver
+ :: VampireResolver
+ -> IORef [PlannedReplayRequest]
+ -> IO VampireResolver
+plannedReplayResolver original liveRef =
+ pure (vampireBatchResolver resolve)
+ where
+ resolve
+ :: forall local origin.
+ NonEmpty
+ (Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId)
+ -> IO
+ (NonEmpty
+ (Either
+ Provers.ProverProcessError
+ Provers.ProverAnswer))
+ resolve tasks = do
+ expected <- atomicModifyIORef' liveRef \remaining ->
+ let amount = NonEmpty.length tasks
+ (selected, later) = splitAt amount remaining
+ in (later, selected)
+ unless (length expected == NonEmpty.length tasks)
+ (throwIO
+ (PlanningIntegrityError
+ "admission requested a different number of live Vampire results"))
+ traverse_ validateExpected (zip (toList tasks) expected)
+ case expected of
+ ReplayLive{} : _ -> do
+ unless (all isLive expected)
+ (throwIO
+ (PlanningIntegrityError
+ "planned resolver batch mixed execution modes"))
+ results <- traverse awaitOne expected
+ maybe
+ (throwIO
+ (PlanningIntegrityError
+ "an admission resolver batch was unexpectedly empty"))
+ pure
+ (NonEmpty.nonEmpty results)
+ ReplaySynchronous{} : _ ->
+ resolveSynchronousVampireBatch original tasks
+ [] ->
+ throwIO
+ (PlanningIntegrityError
+ "an admission resolver batch was unexpectedly empty")
+
+ validateExpected (task, replay) = do
+ let actual = Provers.preparedTypedProverRequest task
+ expected = replayRequest replay
+ unless (actual == expected)
+ (throwIO
+ (PlanningIntegrityError
+ "admission request differs from its planned exact request"))
+
+ awaitOne (ReplayLive expected handle) =
+ Provers.awaitPreparedVampireRequest expected handle
+ awaitOne ReplaySynchronous{} =
+ impossible "validated live resolver batch changed execution mode"
+
+ replayRequest = \case
+ ReplayLive request _handle -> request
+ ReplaySynchronous request -> request
+
+ isLive = \case
+ ReplayLive{} -> True
+ ReplaySynchronous{} -> False
+
+plannedAdmissionValidation
+ :: PlannedValidationSelection
+ -> ValidationRun
+plannedAdmissionValidation = \case
+ PlannedFreshValidation -> FreshValidation
+ PlannedProofValidation expected record ->
+ WarmValidation
+ (validationLookup
+ (\actual ->
+ if actual == expected
+ then pure (Just record)
+ else throwIO
+ (PlanningIntegrityError
+ "proof validation key changed during admission"))
+ (\_actual -> pure Nothing))
+ PlannedCompiledValidation expected record ->
+ WarmValidation
+ (validationLookup
+ (\_actual -> pure Nothing)
+ (\actual ->
+ if actual == expected
+ then pure (Just record)
+ else throwIO
+ (PlanningIntegrityError
+ "declaration validation key changed during admission")))
+
+validateAdmittedPlan
+ :: PlannedDeclaration body
+ -> CommittedDeclarationBatch
+ -> IO ()
+validateAdmittedPlan
+ (PlannedDeclaration _checked expectedPrevious expectedNext expectedDelta
+ expectedObjects expectedPropositions candidates selection)
+ batch = do
+ let expectedContracts =
+ [ (authority, direct)
+ | stage <- candidates
+ , PlannedCandidate _candidate authority direct _requests <-
+ toList stage
+ ]
+ actualCertificates =
+ (proofValidationRecordCertificate
+ <$> committedBatchProofValidations batch)
+ <> maybe
+ []
+ declarationValidationRecordCertificates
+ (committedBatchDeclarationValidation batch)
+ actualContracts =
+ [ ( validationTarget certificate
+ , validationDirectAuthorization certificate
+ )
+ | certificate <- actualCertificates
+ ]
+ validationAgrees = case selection of
+ PlannedFreshValidation -> True
+ PlannedProofValidation _ record ->
+ committedBatchProofValidations batch == [record]
+ PlannedCompiledValidation _ record ->
+ committedBatchDeclarationValidation batch == Just record
+ unless
+ ( committedBatchPreviousPrefix batch == expectedPrevious
+ && committedBatchNextPrefix batch == expectedNext
+ && committedBatchDelta batch == expectedDelta
+ && committedBatchObjects batch == expectedObjects
+ && fmap propositionContract (committedBatchPropositions batch)
+ == fmap propositionContract expectedPropositions
+ && actualContracts == expectedContracts
+ && validationAgrees
+ )
+ (throwIO
+ (PlanningIntegrityError
+ "admitted declaration differs from its prospective contract"))
+ where
+ propositionContract proposition =
+ ( checkedPropositionId proposition
+ , checkedPropositionTerm proposition
+ )
+
+commitProofDeclaration
+ :: ProofSyntaxId
+ -> Declaration value
+ -> ModuleDriver failure
+ (value, CommittedDeclarationBatch)
+commitProofDeclaration syntax =
+ commitDeclaration (ProofValidationMode syntax)
+
+commitCompiledDeclaration
+ :: DeclarationSyntaxId
+ -> Declaration value
+ -> ModuleDriver failure
+ (value, CommittedDeclarationBatch)
+commitCompiledDeclaration syntax =
+ commitDeclaration (DeclarationValidationMode syntax)
+
+commitDeclaration
+ :: ValidationMode
+ -> Declaration value
+ -> ModuleDriver failure
+ (value, CommittedDeclarationBatch)
+commitDeclaration mode action = ModuleDriver do
+ DriverState resolver builder pendingPrefix validationRun <-
+ State.get
+ let
+ declaration =
+ initialDeclarationState
+ resolver
+ validationRun
+ mode
+ builder
+ result <-
+ liftIO
+ (Except.runExceptT
+ (State.runStateT
+ (runDeclaration action)
+ declaration))
+ case result of
+ Left err ->
+ Except.throwError (DriverDeclarationFailed err)
+ Right (value, prepared) ->
+ case appendDeclaration mode prepared of
+ Left err ->
+ Except.throwError (DriverDeclarationFailed err)
+ Right (builder', batch) ->
+ let prefix' = appendPendingBatch batch pendingPrefix
+ in do
+ State.put
+ (DriverState
+ resolver
+ builder'
+ prefix'
+ validationRun)
+ pure (value, batch)
+
+initialDeclarationState
+ :: VampireResolver
+ -> ValidationRun
+ -> ValidationMode
+ -> LogicalBuilder
+ -> DeclarationState
+initialDeclarationState resolver validationRun validationMode builder =
+ DeclarationState
+ { declarationVampireResolver = resolver
+ , declarationValidationRun = validationRun
+ , declarationValidationMode = validationMode
+ , declarationValidationSelection =
+ DeclarationValidationUnselected
+ , declarationBuilder = builder
+ , declarationOwnSlot =
+ declarationSlot
+ (logicalBuilderOwner builder)
+ (localDeclarationOrdinal
+ (logicalBuilderNextDeclaration builder))
+ , declarationInvocation =
+ DeclarationInvocation
+ (logicalBuilderNextInvocation builder)
+ , declarationObjectsReversed = []
+ , declarationObjectClosure = Nothing
+ , declarationPropositionsReversed = []
+ , declarationGlobalBindingsReversed = []
+ , declarationStructureDescriptorsReversed = []
+ , declarationReservations = Map.empty
+ , declarationPending = Map.empty
+ , declarationNextFact = logicalBuilderNextFact builder
+ , declarationNextStage = 0
+ , declarationAuthorizationFrontier = 0
+ }
+
+appendDeclaration
+ :: ValidationMode
+ -> DeclarationState
+ -> Either
+ DeclarationError
+ (LogicalBuilder, CommittedDeclarationBatch)
+appendDeclaration mode declaration = do
+ let builder = declarationBuilder declaration
+ reservations = Map.elems (declarationReservations declaration)
+ pending = declarationPending declaration
+ unless
+ (Map.keysSet pending
+ == Map.keysSet (declarationReservations declaration))
+ (Left DeclarationHasUnauthorizedCandidates)
+ unless
+ (declarationAuthorizationFrontier declaration
+ == declarationNextStage declaration)
+ (Left DeclarationAuthorizationFrontierIncomplete)
+ case mode of
+ DeclarationValidationMode{} ->
+ case declarationValidationSelection declaration of
+ DeclarationValidationUnselected ->
+ Left DeclarationValidationNotSelected
+ _ -> pure ()
+ _ -> pure ()
+ closure <- declarationClosure declaration
+ propositions <- validateDeclarationPropositions closure declaration
+ traverse_ (validatePendingAuthorization declaration) (Map.elems pending)
+ let orderedPending =
+ [ pendingCandidate
+ | reservation <- reservations
+ , let slot = reservedCandidateSlot reservation
+ , Just pendingCandidate <- [Map.lookup slot pending]
+ ]
+ occurrences = occurrenceFromPending <$> orderedPending
+ aliases = concatMap aliasesFromPending orderedPending
+ objectIds =
+ assertedObjectId
+ <$> reverse
+ (declarationObjectsReversed declaration)
+ propositionIds =
+ checkedPropositionId <$> propositions
+ (structures, delta, next) <-
+ buildCheckedDeclarationDelta
+ builder
+ closure
+ (declarationOwnSlot declaration)
+ occurrences
+ aliases
+ objectIds
+ propositionIds
+ (declarationGlobalBindingsReversed declaration)
+ (declarationStructureDescriptorsReversed declaration)
+ let previous = logicalBuilderPrefix builder
+ (proofValidations, declarationValidation) <-
+ buildValidationRecords
+ mode previous objectIds orderedPending
+ let builder' =
+ appendBuilderState
+ closure
+ structures
+ delta
+ next
+ orderedPending
+ builder
+ declaration
+ batch =
+ CommittedDeclarationBatch
+ (logicalBuilderOwner builder)
+ (declarationOwnSlot declaration)
+ previous
+ next
+ delta
+ (reverse
+ (declarationObjectsReversed declaration))
+ propositions
+ proofValidations
+ declarationValidation
+ pure (builder', batch)
+
+-- | Construct and collision-check one canonical semantic delta without
+-- inspecting fact evidence. This remains the admitted append path now and
+-- is the shared delta seam for the later prospective builder.
+buildCheckedDeclarationDelta
+ :: BuilderState evidence
+ -> CheckedObjectClosure
+ -> DeclarationSlot
+ -> [SemanticFactOccurrence]
+ -> [SemanticAlias]
+ -> [ObjectId]
+ -> [PropositionId]
+ -> [SemanticGlobalBinding]
+ -> [SemanticStructureDescriptor]
+ -> Either
+ DeclarationError
+ ( Map SemanticStructurePhrase ResolvedStructure
+ , DeclarationInterfaceDelta
+ , PrefixContextId
+ )
+buildCheckedDeclarationDelta
+ builder closure slot occurrences aliases objectIds propositionIds
+ reversedBindings reversedDescriptors = do
+ let bindings =
+ List.sortOn
+ semanticGlobalBindingKey
+ (reverse reversedBindings)
+ descriptors =
+ List.sortOn
+ semanticStructureDescriptorPhrase
+ (reverse reversedDescriptors)
+ traverse_
+ (\binding ->
+ first
+ (DeclarationGlobalTargetInvalid
+ (semanticGlobalBindingKey binding)
+ (semanticGlobalBindingTarget binding))
+ (validateSemanticGlobalBindingTarget
+ (structureOperationBindings
+ (logicalBuilderStructures builder))
+ closure binding))
+ bindings
+ traverse_
+ (\descriptor ->
+ let structurePhrase = semanticStructureDescriptorPhrase descriptor
+ in when
+ (Map.member structurePhrase
+ (logicalBuilderStructures builder))
+ (Left (BuilderStructureCollision structurePhrase)))
+ descriptors
+ structures <- foldM
+ (insertSemanticStructure closure)
+ (logicalBuilderStructures builder)
+ descriptors
+ environment <-
+ first DeclarationEnvironmentFailed
+ (semanticEnvironmentWithStructures bindings descriptors)
+ delta <-
+ first DeclarationInterfaceFailed
+ (declarationInterfaceDelta
+ slot
+ occurrences
+ aliases
+ objectIds
+ propositionIds
+ environment)
+ validateBuilderCollisions builder delta
+ pure
+ ( structures
+ , delta
+ , nextPrefixContextId (logicalBuilderPrefix builder) delta
+ )
+
+declarationClosure
+ :: DeclarationState
+ -> Either DeclarationError CheckedObjectClosure
+declarationClosure declaration =
+ case declarationObjectClosure declaration of
+ Just closure ->
+ Right closure
+ Nothing ->
+ first DeclarationObjectValidationFailed
+ (extendObjectClosure
+ (logicalBuilderObjectClosure
+ (declarationBuilder declaration))
+ (reverse
+ (declarationObjectsReversed declaration)))
+
+prepareDeclarationClosure
+ :: DeclarationState
+ -> Either DeclarationError DeclarationState
+prepareDeclarationClosure declaration = do
+ closure <- declarationClosure declaration
+ pure declaration{declarationObjectClosure = Just closure}
+
+validateDeclarationPropositions
+ :: CheckedObjectClosure
+ -> DeclarationState
+ -> Either DeclarationError [CheckedPropositionContent]
+validateDeclarationPropositions closure declaration = do
+ let explicit =
+ reverse
+ (declarationPropositionsReversed declaration)
+ reserved =
+ candidateCheckedProposition
+ <$> Map.elems
+ (declarationReservations declaration)
+ propositions = stableUniqueBy checkedPropositionId (explicit <> reserved)
+ traverse_
+ (\proposition ->
+ void
+ (first DeclarationPropositionValidationFailed
+ (validateAssertedPropositionContent
+ closure
+ (checkedPropositionId proposition)
+ (frozenCoreTerm
+ (checkedPropositionTerm proposition)))))
+ propositions
+ pure propositions
+
+validatePendingAuthorization
+ :: DeclarationState
+ -> PendingCandidate
+ -> Either DeclarationError ()
+validatePendingAuthorization declaration
+ (PendingCandidate candidate certificate authorization) = do
+ validateReservedCandidate declaration candidate
+ let builder = declarationBuilder declaration
+ PendingFactAuthorization
+ identity prefix invocation slot authority stage =
+ authorization
+ unless
+ ( identity == logicalBuilderIdentity builder
+ && prefix == logicalBuilderPrefix builder
+ && invocation == declarationInvocation declaration
+ && slot == reservedCandidateSlot candidate
+ && authority == validationTarget certificate
+ && stage == reservedStage candidate
+ )
+ (Left PendingFactAuthorizationMismatch)
+
+occurrenceFromPending
+ :: PendingCandidate
+ -> SemanticFactOccurrence
+occurrenceFromPending
+ (PendingCandidate candidate certificate _authorization) =
+ candidateOccurrence candidate (validationTarget certificate)
+
+aliasesFromPending :: PendingCandidate -> [SemanticAlias]
+aliasesFromPending pending@(PendingCandidate
+ candidate _certificate _authorization) =
+ let occurrence = occurrenceFromPending pending
+ fingerprint = semanticFactFingerprint occurrence
+ in (`semanticAlias` fingerprint) <$> candidateAliases candidate
+
+buildValidationRecords
+ :: ValidationMode
+ -> PrefixContextId
+ -> [ObjectId]
+ -> [PendingCandidate]
+ -> Either
+ DeclarationError
+ ([ProofValidationRecord], Maybe DeclarationValidationRecord)
+buildValidationRecords mode prefix objects pending =
+ case mode of
+ EnvironmentImportMode ->
+ impossible
+ "environment import produced declaration validation records"
+ ProofValidationMode syntax ->
+ case pending of
+ [PendingCandidate _ certificate _] ->
+ let authority = validationTarget certificate
+ key =
+ proofValidationKey
+ (theoremId
+ (factAuthorityTheorem authority))
+ syntax
+ prefix
+ in Right
+ ( [proofValidationRecord key certificate]
+ , Nothing
+ )
+ _ ->
+ Left ProofDeclarationMustProduceOneFact
+ DeclarationValidationMode syntax ->
+ let certificates =
+ [ certificate
+ | PendingCandidate _ certificate _ <- pending
+ ]
+ theorems =
+ theoremId
+ . factAuthorityTheorem
+ . validationTarget
+ <$> certificates
+ key =
+ declarationValidationKey
+ syntax prefix objects theorems
+ in Right
+ ( []
+ , Just
+ (declarationValidationRecord
+ key certificates)
+ )
+
+validateBuilderCollisions
+ :: BuilderState evidence
+ -> DeclarationInterfaceDelta
+ -> Either DeclarationError ()
+validateBuilderCollisions builder delta = do
+ traverse_
+ (\occurrence ->
+ when
+ (Map.member
+ (semanticFactFingerprint occurrence)
+ (logicalBuilderFacts builder))
+ (Left
+ (BuilderFactCollision
+ (semanticFactFingerprint occurrence))))
+ (declarationDeltaFacts delta)
+ traverse_
+ (\alias ->
+ when
+ (Map.member
+ (semanticAliasName alias)
+ (logicalBuilderAliases builder))
+ (Left
+ (BuilderAliasCollision
+ (semanticAliasName alias))))
+ (declarationDeltaAliases delta)
+ traverse_
+ (\identity ->
+ when
+ (identity
+ `Set.member` checkedObjectIds
+ (logicalBuilderObjectClosure builder))
+ (Left (BuilderObjectCollision identity)))
+ (declarationDeltaObjects delta)
+ traverse_
+ (\binding ->
+ case Map.lookup
+ (semanticGlobalBindingKey binding)
+ (logicalBuilderGlobals builder) of
+ Nothing -> pure ()
+ Just existing ->
+ Left
+ (BuilderGlobalCollision
+ (semanticGlobalBindingKey binding)
+ existing))
+ (semanticEnvironmentBindings
+ (declarationDeltaEnvironment delta))
+
+appendBuilderState
+ :: CheckedObjectClosure
+ -> Map SemanticStructurePhrase ResolvedStructure
+ -> DeclarationInterfaceDelta
+ -> PrefixContextId
+ -> [PendingCandidate]
+ -> LogicalBuilder
+ -> DeclarationState
+ -> LogicalBuilder
+appendBuilderState closure structures delta next pending builder declaration =
+ builder
+ { logicalBuilderPrefix = next
+ , logicalBuilderObjectClosure = closure
+ , logicalBuilderFacts =
+ foldl'
+ (insertAuthorizedFact builder)
+ (logicalBuilderFacts builder)
+ pending
+ , logicalBuilderAliases =
+ foldl'
+ (\entries alias ->
+ Map.insert
+ (semanticAliasName alias)
+ (ImportedAliasBinding
+ (semanticAliasTarget alias)
+ (ImportedAliasOrigin
+ (declarationDeltaSlot delta)
+ (semanticAliasTarget alias)))
+ entries)
+ (logicalBuilderAliases builder)
+ (declarationDeltaAliases delta)
+ , logicalBuilderGlobals =
+ foldl'
+ (\entries binding ->
+ Map.insert
+ (semanticGlobalBindingKey binding)
+ (semanticGlobalBindingTarget binding)
+ entries)
+ (logicalBuilderGlobals builder)
+ (semanticEnvironmentBindings
+ (declarationDeltaEnvironment delta))
+ , logicalBuilderStructures = structures
+ , logicalBuilderDeltas =
+ delta : logicalBuilderDeltas builder
+ , logicalBuilderNextDeclaration =
+ logicalBuilderNextDeclaration builder + 1
+ , logicalBuilderNextFact =
+ declarationNextFact declaration
+ , logicalBuilderNextInvocation =
+ logicalBuilderNextInvocation builder + 1
+ }
+
+insertAuthorizedFact
+ :: LogicalBuilder
+ -> Map
+ SemanticFactOccurrenceFingerprint
+ (FactEntry BuilderFactAuthorization)
+ -> PendingCandidate
+ -> Map
+ SemanticFactOccurrenceFingerprint
+ (FactEntry BuilderFactAuthorization)
+insertAuthorizedFact builder entries pending@(PendingCandidate
+ candidate certificate _pendingAuthorization) =
+ let occurrence = occurrenceFromPending pending
+ fingerprint = semanticFactFingerprint occurrence
+ authority = validationTarget certificate
+ authorization =
+ BuilderFactAuthorization
+ (logicalBuilderIdentity builder)
+ (reservedCandidateSlot candidate)
+ authority
+ in Map.insert
+ fingerprint
+ (FactEntry
+ (candidateCheckedProposition candidate)
+ occurrence
+ authorization)
+ entries
+
+stableUniqueBy :: Ord key => (value -> key) -> [value] -> [value]
+stableUniqueBy key =
+ reverse . snd
+ . foldl'
+ (\(seen, values) value ->
+ let identity = key value
+ in if identity `Set.member` seen
+ then (seen, values)
+ else
+ ( Set.insert identity seen
+ , value : values
+ ))
+ (Set.empty, [])
+
+
+data DeclarationError
+ = CandidateOutsideDeclaration !FactSlot
+ | CandidateAlreadyAuthorized !FactSlot
+ | CandidateOutsideAuthorizationFrontier
+ !FactSlot !Natural !Natural
+ | DeclarationHasUnauthorizedCandidates
+ | DeclarationAuthorizationFrontierIncomplete
+ | CandidatePropositionNotClosed
+ | CandidatePropositionNotProposition !CoreType
+ | StagedPremiseNotEarlier
+ !FactSlot !Natural !FactSlot !Natural
+ | StagedPremiseNotAuthorized !FactSlot
+ | AuthorizedFactNotVisible
+ !SemanticFactOccurrenceFingerprint
+ | BuilderFactAuthorizationMismatch
+ | PendingFactAuthorizationMismatch
+ | PlanningFactContractMismatch !FactSlot
+ | CheckedAuthorizationCandidateShapeMismatch !Int !Int
+ | CheckedProofCandidateCountMismatch !Int
+ | LocalClaimOutsideCandidate
+ | FactSafetyFailed !FactSafetyError
+ | ValidationCertificateFailed !ValidationCertificateError
+ | DerivationImportFailed !DerivationImportError
+ | KernelCompletionFailed !KernelReplayError
+ | KernelConstructionDescriptorMismatch
+ | DefinitionEquationObjectMissing !ObjectId
+ | DefinitionEquationObjectNotTransparent !ObjectId
+ | DefinitionEquationObjectNotPointwisePredicate !ObjectId
+ | DefinitionEquationCandidateMismatch
+ | DatatypeCompilationDescriptorMismatch
+ | ProofObligationFailedAt !Location !DeclarationError
+ | VampireProcessFailed !Provers.ProverProcessError
+ | VampireObligationRejected !Provers.ProverAnswer
+ | VampireProofHasNoAcceptedObligations
+ | VampireCandidateBatchMismatch ![FactSlot] ![FactSlot]
+ | VampireCandidateBatchProofShapeMismatch !FactSlot
+ | VampireResolverBatchSizeMismatch !Int !Int
+ | OmittedProofDidNotRecordUse
+ | VampireRequestMismatch
+ | VampireTargetMismatch
+ | VampireLocalPremisesNotSupported
+ | VampireGlobalTypeMismatch !ObjectId
+ | VampirePremiseMismatch
+ !SemanticFactOccurrenceFingerprint
+ | VampirePremiseCapabilityMismatch
+ !SemanticFactOccurrenceFingerprint
+ | VampireFoundationMismatch !FoundationAxiomTag
+ | CurrentCandidateVampirePreparationFailed
+ !(VampireObligationPreparationError Void)
+ | ProofValidationOutsideProofDeclaration
+ | DeclarationValidationOutsideCompiledDeclaration
+ | DeclarationValidationAlreadySelected
+ | DeclarationValidationNotSelected
+ | DeclarationShapeChangedAfterValidationLookup
+ | CachedDeclarationCandidateMissing !FactSlot
+ | ImportedModuleNotDirect !SemanticInterfaceId
+ | ImportedFactMaterializationFailed
+ !Materialization.MaterializationError
+ | ImportedFactCollision
+ !SemanticFactOccurrenceFingerprint
+ | ImportedAliasCollision
+ !SemanticName !ImportedAliasOrigin !ImportedAliasOrigin
+ | ImportedAliasTargetMissing
+ !SemanticFactOccurrenceFingerprint
+ | ImportedGlobalCollision
+ !SemanticGlobalKey !SemanticGlobalTarget !SemanticGlobalTarget
+ | ImportedGlobalTargetInvalid
+ !SemanticGlobalKey
+ !SemanticGlobalTarget
+ !SemanticGlobalTargetError
+ | ImportedStructureCollision
+ !SemanticStructurePhrase
+ !SemanticStructureDescriptor
+ !SemanticStructureDescriptor
+ | SemanticStructureParentMissing
+ !SemanticStructurePhrase !SemanticStructurePhrase
+ | SemanticStructureOperationConflict
+ !StructSymbol !SemanticStructurePhrase !SemanticStructurePhrase
+ | SemanticStructurePredicateTargetInvalid
+ !SemanticStructurePhrase !ObjectId !(Maybe CoreType) !CoreType
+ | SemanticStructureOperationTargetInvalid
+ !SemanticStructurePhrase !StructSymbol !ObjectId !(Maybe CoreType) !CoreType
+ | ImportedEvidenceDirectMismatch
+ ![SemanticInterfaceId] ![SemanticInterfaceId]
+ | ImportedEvidenceInterfaceFailed !SemanticInterfaceError
+ | ImportedEvidenceObjectMissing !ObjectId
+ | ImportedEvidencePropositionMissing !PropositionId
+ | ImportedModuleAfterAuthorization
+ | DeclarationObjectAddedAfterAuthorization
+ | DeclarationObjectValidationFailed !ObjectValidationError
+ | DeclarationPropositionValidationFailed
+ !PropositionValidationError
+ | DeclarationInterfaceFailed !DeclarationInterfaceError
+ | DeclarationEnvironmentFailed !SemanticEnvironmentError
+ | DeclarationGlobalAlreadyStaged !SemanticGlobalKey
+ | DeclarationStructureAlreadyStaged !SemanticStructurePhrase
+ | DeclarationGlobalTargetInvalid
+ !SemanticGlobalKey
+ !SemanticGlobalTarget
+ !SemanticGlobalTargetError
+ | ProofDeclarationMustProduceOneFact
+ | BuilderFactCollision
+ !SemanticFactOccurrenceFingerprint
+ | BuilderAliasCollision !SemanticName
+ | BuilderObjectCollision !ObjectId
+ | BuilderGlobalCollision !SemanticGlobalKey !SemanticGlobalTarget
+ | BuilderStructureCollision !SemanticStructurePhrase
+ deriving stock (Show, Eq)
+
+declarationErrorLocation :: DeclarationError -> Maybe Location
+declarationErrorLocation = \case
+ ProofObligationFailedAt location _failure ->
+ Just location
+ _failure ->
+ Nothing
+
+renderDeclarationError :: DeclarationError -> Text
+renderDeclarationError = \case
+ CandidateOutsideDeclaration slot ->
+ "candidate " <> shown slot <> " does not belong to this declaration"
+ CandidateAlreadyAuthorized slot ->
+ "candidate " <> shown slot <> " was authorized more than once"
+ CandidateOutsideAuthorizationFrontier slot expected actual ->
+ "candidate " <> shown slot <> " is outside authorization frontier "
+ <> shown expected <> " (found " <> shown actual <> ")"
+ DeclarationHasUnauthorizedCandidates ->
+ "the declaration has unauthorized candidates"
+ DeclarationAuthorizationFrontierIncomplete ->
+ "the declaration authorization frontier is incomplete"
+ CandidatePropositionNotClosed ->
+ "the candidate proposition still has open local binders"
+ CandidatePropositionNotProposition actual ->
+ "the candidate has type " <> shown actual <> " instead of Prop"
+ StagedPremiseNotEarlier candidate candidateOrdinal premise premiseOrdinal ->
+ "candidate " <> shown candidate <> " at stage " <> shown candidateOrdinal
+ <> " depends on non-earlier premise " <> shown premise
+ <> " at stage " <> shown premiseOrdinal
+ StagedPremiseNotAuthorized slot ->
+ "staged premise " <> shown slot <> " is not authorized"
+ AuthorizedFactNotVisible fingerprint ->
+ "authorized fact " <> shown fingerprint <> " is not visible"
+ BuilderFactAuthorizationMismatch ->
+ "builder fact authority does not match the checked declaration"
+ PendingFactAuthorizationMismatch ->
+ "pending fact authority does not match the checked declaration"
+ PlanningFactContractMismatch slot ->
+ "checked declaration-stage fact " <> shown slot
+ <> " has inconsistent semantic provenance"
+ CheckedAuthorizationCandidateShapeMismatch expected actual ->
+ "checked declaration recipe expected " <> shown expected
+ <> " candidate stages but received " <> shown actual
+ CheckedProofCandidateCountMismatch actual ->
+ "checked proof declaration requires exactly one candidate but received "
+ <> shown actual
+ LocalClaimOutsideCandidate ->
+ "proof-local claim was used outside a candidate proof"
+ FactSafetyFailed{} ->
+ "fact-safety validation failed"
+ ValidationCertificateFailed{} ->
+ "validation-certificate checking failed"
+ DerivationImportFailed{} ->
+ "kernel derivation import failed"
+ KernelCompletionFailed{} ->
+ "kernel replay failed"
+ KernelConstructionDescriptorMismatch ->
+ "kernel construction does not match its checked descriptor"
+ DefinitionEquationObjectMissing identity ->
+ "definition equation references missing object " <> shown identity
+ DefinitionEquationObjectNotTransparent identity ->
+ "definition equation references non-transparent object " <> shown identity
+ DefinitionEquationObjectNotPointwisePredicate identity ->
+ "definition equation object " <> shown identity
+ <> " is not a unary predicate definition"
+ DefinitionEquationCandidateMismatch ->
+ "definition equation does not match its checked object content"
+ DatatypeCompilationDescriptorMismatch ->
+ "datatype compilation does not match its complete checked family"
+ ProofObligationFailedAt location failure ->
+ locationToText location <> ": " <> renderDeclarationError failure
+ VampireProcessFailed{} ->
+ "Vampire process failed while authorizing the declaration"
+ VampireObligationRejected{} ->
+ "Vampire did not accept a declaration obligation"
+ VampireProofHasNoAcceptedObligations ->
+ "Vampire proof contains no accepted obligations"
+ VampireCandidateBatchMismatch expected actual ->
+ "Vampire candidate batch does not match the complete authorization "
+ <> "stage (expected " <> shown expected
+ <> ", found " <> shown actual <> ")"
+ VampireCandidateBatchProofShapeMismatch slot ->
+ "Vampire candidate " <> shown slot
+ <> " performed execution before its ready batch"
+ VampireResolverBatchSizeMismatch expected actual ->
+ "Vampire resolver returned " <> shown actual
+ <> " results for " <> shown expected <> " requests"
+ OmittedProofDidNotRecordUse ->
+ "omitted proof completion did not record an omission"
+ VampireRequestMismatch ->
+ "accepted Vampire run does not match the prepared request"
+ VampireTargetMismatch ->
+ "Vampire request target does not match the candidate theorem"
+ VampireLocalPremisesNotSupported ->
+ "typed Vampire requests do not yet support local premises"
+ VampireGlobalTypeMismatch object ->
+ "Vampire request has the wrong type for global object " <> shown object
+ VampirePremiseMismatch fingerprint ->
+ "Vampire request premise does not match visible fact " <> shown fingerprint
+ VampirePremiseCapabilityMismatch fingerprint ->
+ "Vampire request lacks authority for premise " <> shown fingerprint
+ VampireFoundationMismatch tag ->
+ "Vampire request has inconsistent foundation axiom " <> shown tag
+ CurrentCandidateVampirePreparationFailed failure ->
+ "the staged Vampire obligation could not be prepared: " <> shown failure
+ ProofValidationOutsideProofDeclaration ->
+ "proof validation requires a proof declaration"
+ DeclarationValidationOutsideCompiledDeclaration ->
+ "declaration validation requires a compiled declaration"
+ DeclarationValidationAlreadySelected ->
+ "compiled declaration validation was selected more than once"
+ DeclarationValidationNotSelected ->
+ "compiled declaration validation was not selected"
+ DeclarationShapeChangedAfterValidationLookup ->
+ "compiled declaration shape changed after validation lookup"
+ CachedDeclarationCandidateMissing slot ->
+ "cached declaration validation has no candidate for " <> shown slot
+ ImportedModuleNotDirect interface ->
+ "sealed semantic interface is not a direct import: " <> shown interface
+ ImportedFactMaterializationFailed{} ->
+ "imported fact failed materialization checks"
+ ImportedFactCollision fingerprint ->
+ "imported fact " <> shown fingerprint <> " is already registered"
+ ImportedAliasCollision alias earlier later ->
+ "imported semantic alias " <> shown alias
+ <> " conflicts between " <> shown earlier
+ <> " and " <> shown later
+ ImportedAliasTargetMissing fingerprint ->
+ "imported alias targets missing fact " <> shown fingerprint
+ ImportedGlobalCollision key earlier later ->
+ "imported global " <> shown key
+ <> " has conflicting targets " <> shown earlier
+ <> " and " <> shown later
+ ImportedGlobalTargetInvalid key target _failure ->
+ "imported global " <> shown key
+ <> " has invalid target " <> shown target
+ ImportedEvidenceDirectMismatch expected actual ->
+ "imported semantic parents differ: expected " <> shown expected
+ <> ", found " <> shown actual
+ ImportedStructureCollision structurePhrase _existing _incoming ->
+ "structure " <> shown structurePhrase <> " has conflicting descriptors"
+ SemanticStructureParentMissing structurePhrase parent ->
+ "structure " <> shown structurePhrase <> " has unknown parent " <> shown parent
+ SemanticStructureOperationConflict symbol firstOrigin secondOrigin ->
+ "structure operation " <> shown symbol <> " conflicts between "
+ <> shown firstOrigin <> " and " <> shown secondOrigin
+ SemanticStructurePredicateTargetInvalid structurePhrase object _actual expected ->
+ "structure " <> shown structurePhrase <> " has invalid predicate object "
+ <> shown object <> " (expected " <> shown expected <> ")"
+ SemanticStructureOperationTargetInvalid structurePhrase symbol object _actual expected ->
+ "structure " <> shown structurePhrase <> " has invalid operation "
+ <> shown symbol <> " object " <> shown object
+ <> " (expected " <> shown expected <> ")"
+ ImportedEvidenceInterfaceFailed{} ->
+ "imported semantic interface failed validation"
+ ImportedEvidenceObjectMissing identity ->
+ "imported semantic interface is missing object " <> shown identity
+ ImportedEvidencePropositionMissing identity ->
+ "imported semantic interface is missing proposition " <> shown identity
+ ImportedModuleAfterAuthorization ->
+ "a sealed module was imported after candidate authorization"
+ DeclarationObjectAddedAfterAuthorization ->
+ "the declaration added an object after candidate authorization"
+ DeclarationObjectValidationFailed{} ->
+ "declaration object validation failed"
+ DeclarationPropositionValidationFailed{} ->
+ "declaration proposition validation failed"
+ DeclarationInterfaceFailed{} ->
+ "declaration interface validation failed"
+ DeclarationEnvironmentFailed{} ->
+ "declaration environment delta is inconsistent"
+ DeclarationGlobalAlreadyStaged key ->
+ "global " <> shown key <> " was staged more than once"
+ DeclarationStructureAlreadyStaged structurePhrase ->
+ "structure " <> shown structurePhrase <> " was staged more than once"
+ DeclarationGlobalTargetInvalid key target _failure ->
+ "global " <> shown key <> " has invalid target " <> shown target
+ ProofDeclarationMustProduceOneFact ->
+ "a proof declaration must produce exactly one fact"
+ BuilderFactCollision fingerprint ->
+ "fact " <> shown fingerprint <> " is already registered"
+ BuilderAliasCollision alias ->
+ "semantic alias " <> shown alias <> " is already registered"
+ BuilderObjectCollision object ->
+ "object " <> shown object <> " is already registered"
+ BuilderGlobalCollision key object ->
+ "global " <> shown key <> " is already bound to " <> shown object
+ BuilderStructureCollision structurePhrase ->
+ "structure " <> shown structurePhrase <> " is already registered"
+ where
+ shown :: Show value => value -> Text
+ shown = Text.pack . show
diff --git a/source/Felix/Checking/Exact.hs b/source/Felix/Checking/Exact.hs
new file mode 100644
index 0000000..c829743
--- /dev/null
+++ b/source/Felix/Checking/Exact.hs
@@ -0,0 +1,3936 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Direct compiler for the first exact monomorphic declaration family.
+module Felix.Checking.Exact
+ ( ExactLocalId
+ , exactLocalId
+ , exactLocalIdValue
+ , ExactBinderContext
+ , emptyExactBinderContext
+ , extendExactBinderContext
+ , extendExactAnonymousBinderContext
+ , exactBinderContextSupport
+ , exactBinderContextIndex
+ , PreparedExactProposition
+ , preparedExactPropositionCore
+ , prepareExactProposition
+ , prepareExactSymbolicBoundConstraints
+ , prepareExactSymbolicWitnessConstraints
+ , prepareExactNounWitnessConstraints
+ , PreparedExactSetExpression
+ , PreparedExactSetConstruction(..)
+ , preparedExactSetExpressionCore
+ , preparedExactSetExpressionConstruction
+ , prepareExactSetExpression
+ , PreparedExactLocalFunctionGraph
+ , preparedExactLocalFunctionGraphCore
+ , preparedExactLocalFunctionGraphDomain
+ , preparedExactLocalFunctionGraphMap
+ , prepareExactLocalFunctionGraph
+ , PreparedExactClaimEnvelope
+ , preparedExactClaimTarget
+ , preparedExactClaimVariables
+ , preparedExactClaimContext
+ , preparedExactClaimAntecedentCount
+ , prepareExactClaimEnvelope
+ , PreparedExactDeclaration
+ , preparedExactLocation
+ , preparedExactGlobalKey
+ , preparedExactObjectId
+ , preparedExactObject
+ , preparedExactSyntaxId
+ , preparedExactIsDefinition
+ , prepareExactDeclaration
+ , lowerPreparedExactBinding
+ , CheckedExactBindingAuthorization
+ , authorizeCheckedExactBinding
+ , PreparedExactStructure
+ , prepareExactStructure
+ , CheckedExactStructureAuthorization
+ , lowerPreparedExactStructure
+ , authorizeCheckedExactStructure
+ , PreparedExactSourceAxiom
+ , prepareExactSourceAxiom
+ , lowerPreparedExactSourceAxiom
+ , authorizeCheckedExactSourceAxiom
+ , ExactCompileError(..)
+ , exactCompileErrorLocation
+ , renderExactCompileError
+ ) where
+
+import Base hiding (Empty)
+import Felix.Cache.Codec
+import Felix.Checking.Core
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Exact.Vocabulary
+import Felix.Checking.Identity
+import Felix.Checking.Semantic
+import Felix.Checking.SetConstruction
+import Felix.Module
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Interface (CanonicalLexicalEntry(..))
+import Felix.Syntax.Lexicon qualified as Lexicon
+
+import Control.Monad.Except (ExceptT)
+import Control.Monad.Except (MonadError, throwError)
+import Control.Monad.Except qualified as Except
+import Control.Monad (foldM, unless, when)
+import Control.Monad.State.Strict (StateT)
+import Control.Monad.State.Strict qualified as State
+import Data.ByteString (ByteString)
+import Data.Bifunctor (first)
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Map.Strict qualified as Map
+import Data.Maybe (catMaybes)
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+import Data.Vector (Vector)
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+
+
+-- | A disposable identity allocated in source order within one proof.
+newtype ExactLocalId = ExactLocalId Natural
+ deriving stock (Show, Eq, Ord)
+
+exactLocalId :: Natural -> ExactLocalId
+exactLocalId = ExactLocalId
+
+exactLocalIdValue :: ExactLocalId -> Natural
+exactLocalIdValue (ExactLocalId value) = value
+
+data ExactBinder = ExactBinder
+ !ExactLocalId
+ !(Maybe Raw.VarSymbol)
+ !CoreType
+ !(Maybe ExactStructureAnnotation)
+
+data ExactStructureAnnotation = ExactStructureAnnotation
+ !SemanticStructurePhrase
+ !(Maybe ObjectId)
+ !(Map.Map Raw.StructSymbol ObjectId)
+
+-- | The active nearest-first binders of one exact proof scope.
+newtype ExactBinderContext = ExactBinderContext [ExactBinder]
+
+emptyExactBinderContext :: ExactBinderContext
+emptyExactBinderContext = ExactBinderContext []
+
+extendExactBinderContext
+ :: NonEmpty (ExactLocalId, Raw.VarSymbol)
+ -> ExactBinderContext
+ -> Either ExactCompileError ExactBinderContext
+extendExactBinderContext additions (ExactBinderContext initial) =
+ ExactBinderContext <$> foldM add initial (toList additions)
+ where
+ add binders (identity, variable)
+ | any (sameVariable variable) binders =
+ Left (ExactDuplicateLocalBinder (locate variable) variable)
+ | any (sameIdentity identity) binders =
+ Left (ExactDuplicateLocalIdentity (locate variable) identity)
+ | otherwise =
+ Right (ExactBinder identity (Just variable) TySet Nothing : binders)
+
+ sameVariable variable (ExactBinder _identity existing _coreType _structure) =
+ existing == Just variable
+
+ sameIdentity identity (ExactBinder existing _variable _coreType _structure) =
+ existing == identity
+
+-- | Add one proof-owned binder which deliberately has no source-resolvable
+-- spelling. This is used for a nameless singular witness; it participates in
+-- checked support and de Bruijn weakening but cannot shadow or be looked up by
+-- a later source variable.
+extendExactAnonymousBinderContext
+ :: ExactLocalId
+ -> ExactBinderContext
+ -> Either ExactCompileError ExactBinderContext
+extendExactAnonymousBinderContext identity (ExactBinderContext binders)
+ | any sameIdentity binders =
+ Left (ExactDuplicateLocalIdentity Nowhere identity)
+ | otherwise =
+ Right
+ (ExactBinderContext
+ (ExactBinder identity Nothing TySet Nothing : binders))
+ where
+ sameIdentity (ExactBinder existing _variable _coreType _structure) =
+ existing == identity
+
+exactBinderContextSupport
+ :: ExactBinderContext
+ -> Vector (ExactLocalId, CoreType)
+exactBinderContextSupport (ExactBinderContext binders) =
+ Vector.fromList
+ [ (identity, coreType)
+ | ExactBinder identity _variable coreType _structure <- binders
+ ]
+
+exactBinderContextIndex
+ :: Raw.VarSymbol
+ -> ExactBinderContext
+ -> Maybe Natural
+exactBinderContextIndex variable (ExactBinderContext binders) =
+ go 0 binders
+ where
+ go _index [] =
+ Nothing
+ go index (ExactBinder _identity candidate _coreType _structure : rest)
+ | candidate == Just variable = Just index
+ | otherwise = go (index + 1) rest
+
+newtype PreparedExactProposition = PreparedExactProposition
+ (ScopedCheckedCore ObjectId)
+
+preparedExactPropositionCore
+ :: PreparedExactProposition
+ -> ScopedCheckedCore ObjectId
+preparedExactPropositionCore (PreparedExactProposition proposition) =
+ proposition
+
+data PreparedExactSetExpression = PreparedExactSetExpression
+ !(ScopedCheckedCore ObjectId)
+ !(Maybe PreparedExactSetConstruction)
+
+data PreparedExactSetConstruction
+ = PreparedUnconditionalSetConstruction
+ !(NamedSetConstruction ObjectId)
+ | PreparedRelationalSetConstruction
+ !(CheckedRelationalSetConstruction ObjectId)
+
+preparedExactSetExpressionCore
+ :: PreparedExactSetExpression
+ -> ScopedCheckedCore ObjectId
+preparedExactSetExpressionCore (PreparedExactSetExpression expression _construction) =
+ expression
+
+preparedExactSetExpressionConstruction
+ :: PreparedExactSetExpression
+ -> Maybe PreparedExactSetConstruction
+preparedExactSetExpressionConstruction
+ (PreparedExactSetExpression _expression construction) =
+ construction
+
+-- | A checked replacement graph and the two checked arguments used to
+-- specialize its foundation characteristic. This is transient proof
+-- preparation data, not a declaration or durable object.
+data PreparedExactLocalFunctionGraph = PreparedExactLocalFunctionGraph
+ !(ScopedCheckedCore ObjectId)
+ !(ScopedCheckedCore ObjectId)
+ !(ScopedCheckedCore ObjectId)
+
+preparedExactLocalFunctionGraphCore
+ :: PreparedExactLocalFunctionGraph
+ -> ScopedCheckedCore ObjectId
+preparedExactLocalFunctionGraphCore
+ (PreparedExactLocalFunctionGraph graph _domain _function) =
+ graph
+
+preparedExactLocalFunctionGraphDomain
+ :: PreparedExactLocalFunctionGraph
+ -> ScopedCheckedCore ObjectId
+preparedExactLocalFunctionGraphDomain
+ (PreparedExactLocalFunctionGraph _graph domain _function) =
+ domain
+
+preparedExactLocalFunctionGraphMap
+ :: PreparedExactLocalFunctionGraph
+ -> ScopedCheckedCore ObjectId
+preparedExactLocalFunctionGraphMap
+ (PreparedExactLocalFunctionGraph _graph _domain function) =
+ function
+
+-- | One authoritative closed proposition prepared from a top-level claim
+-- header and conclusion. The remaining fields are transient opening data for
+-- the proof compiler.
+data PreparedExactClaimEnvelope = PreparedExactClaimEnvelope
+ !(ScopedCheckedCore ObjectId)
+ ![Raw.VarSymbol]
+ !ExactBinderContext
+ !Natural
+
+preparedExactClaimTarget
+ :: PreparedExactClaimEnvelope
+ -> ScopedCheckedCore ObjectId
+preparedExactClaimTarget
+ (PreparedExactClaimEnvelope target _variables _context _antecedents) =
+ target
+
+preparedExactClaimVariables
+ :: PreparedExactClaimEnvelope
+ -> [Raw.VarSymbol]
+preparedExactClaimVariables
+ (PreparedExactClaimEnvelope _target variables _context _antecedents) =
+ variables
+
+preparedExactClaimContext
+ :: PreparedExactClaimEnvelope
+ -> ExactBinderContext
+preparedExactClaimContext
+ (PreparedExactClaimEnvelope _target _variables context _antecedents) =
+ context
+
+preparedExactClaimAntecedentCount
+ :: PreparedExactClaimEnvelope
+ -> Natural
+preparedExactClaimAntecedentCount
+ (PreparedExactClaimEnvelope _target _variables _context antecedents) =
+ antecedents
+
+
+data ExactDeclarationFamily
+ = ExactSignature
+ | ExactAbbreviation
+ | ExactDefinition
+ deriving stock (Show, Eq, Ord)
+
+data PreparedExactDeclaration = PreparedExactDeclaration
+ !Location
+ !ExactDeclarationFamily
+ !SemanticGlobalKey
+ !SemanticGlobalTarget
+ !(Maybe AssertedObject)
+ !(Maybe SemanticName)
+ !(Maybe PreparedExactSetConstruction)
+ !DeclarationSyntaxId
+
+preparedExactLocation :: PreparedExactDeclaration -> Location
+preparedExactLocation
+ (PreparedExactDeclaration location _family _key _target _object _alias _construction _syntax) =
+ location
+
+preparedExactGlobalKey :: PreparedExactDeclaration -> SemanticGlobalKey
+preparedExactGlobalKey
+ (PreparedExactDeclaration _location _family key _target _object _alias _construction _syntax) =
+ key
+
+preparedExactObjectId :: PreparedExactDeclaration -> ObjectId
+preparedExactObjectId
+ (PreparedExactDeclaration _location _family _key target _object _alias _construction _syntax) =
+ semanticGlobalTargetObject target
+
+preparedExactObject
+ :: PreparedExactDeclaration
+ -> Maybe AssertedObject
+preparedExactObject
+ (PreparedExactDeclaration _location _family _key _target object _alias _construction _syntax) =
+ object
+
+preparedExactSyntaxId
+ :: PreparedExactDeclaration
+ -> DeclarationSyntaxId
+preparedExactSyntaxId
+ (PreparedExactDeclaration _location _family _key _target _object _alias _construction syntax) =
+ syntax
+
+preparedExactIsDefinition :: PreparedExactDeclaration -> Bool
+preparedExactIsDefinition
+ (PreparedExactDeclaration _location family _key _target _object _alias _construction _syntax) =
+ family == ExactDefinition
+
+preparedExactGlobalTarget
+ :: PreparedExactDeclaration
+ -> SemanticGlobalTarget
+preparedExactGlobalTarget
+ (PreparedExactDeclaration
+ _location _family _key target _object _alias _construction _syntax) =
+ target
+
+preparedDefinitionAlias
+ :: PreparedExactDeclaration
+ -> Maybe SemanticName
+preparedDefinitionAlias
+ (PreparedExactDeclaration
+ _location _family _key _target _object alias _construction _syntax) =
+ alias
+
+preparedDefinitionConstruction
+ :: PreparedExactDeclaration
+ -> Maybe PreparedExactSetConstruction
+preparedDefinitionConstruction
+ (PreparedExactDeclaration
+ _location _family _key _target _object _alias construction _syntax) =
+ construction
+
+data PreparedExactSourceAxiom = PreparedExactSourceAxiom
+ !Location
+ !SemanticName
+ !(ScopedCheckedCore ObjectId)
+ !DeclarationSyntaxId
+
+data PreparedExactStructureFact = PreparedExactStructureFact
+ !Location
+ !(FrozenCheckedCore ObjectId)
+ !SemanticName
+
+data PreparedExactStructure = PreparedExactStructure
+ !Location
+ ![AssertedObject]
+ !ObjectId
+ !SemanticStructureDescriptor
+ !SemanticName
+ ![PreparedExactStructureFact]
+ !DeclarationSyntaxId
+
+data CheckedExactStructureAuthorization =
+ CheckedExactStructureAuthorization
+ !ObjectId
+ ![(Location, Declaration.PreparedVampireObligation Void ())]
+
+data ExactCompileError
+ = ExactUnsupportedDeclaration !Location
+ | ExactUnsupportedDeclarationBody !Location
+ | ExactNonCanonicalSetDefinitionAnnotation !Location
+ | ExactGuardedTransparentDefinition !Location
+ | ExactGuardedOpaqueSignature !Location
+ | ExactDefinitionCombinedSymbolicAlias !Location
+ | ExactRelationalReplacementRequiresNamedDefinition !Location
+ | ExactDeclarationOccurrenceMissing !Location
+ | ExactDeclarationOccurrenceAmbiguous !Location
+ | ExactDeclarationHeadMismatch !Location
+ | ExactFixedSemanticCollision !Location !SemanticGlobalKey
+ | ExactGlobalAlreadyVisible !Location !SemanticGlobalKey
+ | ExactGlobalNotVisible !Location !SemanticGlobalKey
+ | ExactDuplicateParameter !Location !Raw.VarSymbol
+ | ExactDuplicateLocalBinder !Location !Raw.VarSymbol
+ | ExactDuplicateLocalIdentity !Location !ExactLocalId
+ | ExactFreeVariable !Location !Raw.VarSymbol
+ | ExactApplicationExpectedFunction !Location !CoreType
+ | ExactApplicationArgumentMismatch
+ !Location !CoreType !CoreType
+ | ExactExpressionExpectedSet !Location !CoreType
+ | ExactFormulaExpectedProposition !Location !CoreType
+ | ExactCoreCheckFailed !Location !CoreCheckError
+ | ExactObjectTypeMismatch !Location !CoreType !CoreType
+ | ExactUnsupportedHeaderAssumption !Location
+ | ExactQuantifiedTermRequiresPropositionContext !Location
+ | ExactStructureNotVisible !Location !SemanticStructurePhrase
+ | ExactBaseStructureNotAssertable !Location !SemanticStructurePhrase
+ | ExactDuplicateStructureAnnotation !Location !Raw.VarSymbol
+ | ExactStructureOperationNotAvailable !Location !Raw.StructSymbol
+ | ExactStructureOperationAmbiguous
+ !Location !Raw.StructSymbol ![ObjectId]
+ | ExactContextualExpansionNotAvailable
+ !Location !SemanticGlobalKey
+ | ExactContextualRequirementConflict
+ !Location !Raw.StructSymbol !ObjectId !ObjectId
+ | ExactStructureOccurrenceMismatch !Location
+ | ExactStructureSelfParent !Location !SemanticStructurePhrase
+ | ExactStructureDuplicateParent !Location !SemanticStructurePhrase
+ | ExactStructureAlreadyVisible !Location !SemanticStructurePhrase
+ | ExactStructureDuplicateOperation !Location !Raw.StructSymbol
+ | ExactStructureOperationAlreadyInherited !Location !Raw.StructSymbol
+ | ExactStructureOperationConflict
+ !Location !Raw.StructSymbol
+ !SemanticStructurePhrase !SemanticStructurePhrase
+ | ExactStructureHasNoCarrier !Location !SemanticStructurePhrase
+ | ExactStructureDescriptorInvalid !Location !SemanticEnvironmentError
+ | ExactStructureObjectNotVisible !Location !ObjectId
+ deriving stock (Show, Eq)
+
+exactCompileErrorLocation :: ExactCompileError -> Location
+exactCompileErrorLocation = \case
+ ExactUnsupportedDeclaration location -> location
+ ExactUnsupportedDeclarationBody location -> location
+ ExactNonCanonicalSetDefinitionAnnotation location -> location
+ ExactGuardedTransparentDefinition location -> location
+ ExactGuardedOpaqueSignature location -> location
+ ExactDefinitionCombinedSymbolicAlias location -> location
+ ExactRelationalReplacementRequiresNamedDefinition location -> location
+ ExactDeclarationOccurrenceMissing location -> location
+ ExactDeclarationOccurrenceAmbiguous location -> location
+ ExactDeclarationHeadMismatch location -> location
+ ExactFixedSemanticCollision location _key -> location
+ ExactGlobalAlreadyVisible location _key -> location
+ ExactGlobalNotVisible location _key -> location
+ ExactDuplicateParameter location _parameter -> location
+ ExactDuplicateLocalBinder location _variable -> location
+ ExactDuplicateLocalIdentity location _identity -> location
+ ExactFreeVariable location _variable -> location
+ ExactApplicationExpectedFunction location _actual -> location
+ ExactApplicationArgumentMismatch location _expected _actual -> location
+ ExactExpressionExpectedSet location _actual -> location
+ ExactFormulaExpectedProposition location _actual -> location
+ ExactCoreCheckFailed location _failure -> location
+ ExactObjectTypeMismatch location _expected _actual -> location
+ ExactUnsupportedHeaderAssumption location -> location
+ ExactQuantifiedTermRequiresPropositionContext location -> location
+ ExactStructureNotVisible location _phrase -> location
+ ExactBaseStructureNotAssertable location _phrase -> location
+ ExactDuplicateStructureAnnotation location _variable -> location
+ ExactStructureOperationNotAvailable location _symbol -> location
+ ExactStructureOperationAmbiguous location _symbol _objects -> location
+ ExactContextualExpansionNotAvailable location _key -> location
+ ExactContextualRequirementConflict location _symbol _first _second -> location
+ ExactStructureOccurrenceMismatch location -> location
+ ExactStructureSelfParent location _phrase -> location
+ ExactStructureDuplicateParent location _phrase -> location
+ ExactStructureAlreadyVisible location _phrase -> location
+ ExactStructureDuplicateOperation location _symbol -> location
+ ExactStructureOperationAlreadyInherited location _symbol -> location
+ ExactStructureOperationConflict location _symbol _first _second -> location
+ ExactStructureHasNoCarrier location _phrase -> location
+ ExactStructureDescriptorInvalid location _failure -> location
+ ExactStructureObjectNotVisible location _object -> location
+
+renderExactCompileError :: ExactCompileError -> Text
+renderExactCompileError = \case
+ ExactUnsupportedDeclaration location ->
+ at location <> "this declaration is not yet supported by the typed checker"
+ ExactUnsupportedDeclarationBody location ->
+ at location <> "this source form is not yet supported by exact elaboration"
+ ExactNonCanonicalSetDefinitionAnnotation location ->
+ at location
+ <> "only the unmodified built-in noun `set` is a harmless definition annotation; "
+ <> "state a total condition in the definiens, or, where a corresponding opaque signature form exists, use it with a following explicit axiom; otherwise migrate the spelling or leave it unsupported"
+ ExactGuardedTransparentDefinition location ->
+ at location
+ <> "a transparent definition cannot have a header assumption; "
+ <> "state a total condition in the definiens, or, where a corresponding opaque signature form exists, use it with a following explicit axiom; otherwise migrate the spelling or leave it unsupported"
+ ExactGuardedOpaqueSignature location ->
+ at location
+ <> "an opaque signature cannot have a header assumption; "
+ <> "state the condition in a following explicit axiom"
+ ExactDefinitionCombinedSymbolicAlias location ->
+ at location
+ <> "a functional definition cannot declare a symbolic equivalent at the same time; "
+ <> "define the symbolic operator first, then define the functional phrase as an abbreviation applying it"
+ ExactRelationalReplacementRequiresNamedDefinition location ->
+ at location
+ <> "relational replacement is supported only as the outer body of a named definition"
+ ExactDeclarationOccurrenceMissing location ->
+ at location <> "the declaration has no associated syntax occurrence"
+ ExactDeclarationOccurrenceAmbiguous location ->
+ at location <> "the declaration has more than one semantic head"
+ ExactDeclarationHeadMismatch location ->
+ at location <> "the parsed declaration head does not match its syntax occurrence"
+ ExactFixedSemanticCollision location key ->
+ at location <> "the declaration collides with fixed semantics for "
+ <> shown key
+ ExactGlobalAlreadyVisible location key ->
+ at location <> "the global " <> shown key <> " is already declared"
+ ExactGlobalNotVisible location key ->
+ at location <> "the global " <> shown key <> " is not visible"
+ ExactDuplicateParameter location parameter ->
+ at location <> "the declaration parameter " <> shown parameter <> " is repeated"
+ ExactDuplicateLocalBinder location variable ->
+ at location <> "the proof binder " <> shown variable <> " is already active"
+ ExactDuplicateLocalIdentity location identity ->
+ at location <> "the proof-local identity " <> shown identity <> " is already active"
+ ExactFreeVariable location variable ->
+ at location <> "the exact source form contains the free variable " <> shown variable
+ ExactApplicationExpectedFunction location actual ->
+ at location <> "an application expected a function, but found " <> shown actual
+ ExactApplicationArgumentMismatch location expected actual ->
+ at location <> "an application expected " <> shown expected
+ <> ", but found " <> shown actual
+ ExactExpressionExpectedSet location actual ->
+ at location <> "an expression has type " <> shown actual <> " instead of Set"
+ ExactFormulaExpectedProposition location actual ->
+ at location <> "a formula has type " <> shown actual <> " instead of Prop"
+ ExactCoreCheckFailed location failure ->
+ at location <> "the checked declaration core is invalid: " <> shown failure
+ ExactObjectTypeMismatch location expected actual ->
+ at location <> "the declaration object has type " <> shown actual
+ <> " instead of " <> shown expected
+ ExactUnsupportedHeaderAssumption location ->
+ at location <> "this top-level header assumption is not yet supported by exact elaboration"
+ ExactQuantifiedTermRequiresPropositionContext location ->
+ at location
+ <> "a quantified term requires a containing proposition"
+ ExactStructureNotVisible location structurePhrase ->
+ at location <> "the structure " <> shown structurePhrase <> " is not visible"
+ ExactBaseStructureNotAssertable location structurePhrase ->
+ at location <> "the metadata-only structure " <> shown structurePhrase
+ <> " cannot be asserted"
+ ExactDuplicateStructureAnnotation location variable ->
+ at location <> "the structure binder " <> shown variable
+ <> " is annotated more than once"
+ ExactStructureOperationNotAvailable location symbol ->
+ at location <> "the structure operation " <> shown symbol
+ <> " is not available in the active structure scope"
+ ExactStructureOperationAmbiguous location symbol objects ->
+ at location <> "the structure operation " <> shown symbol
+ <> " is ambiguous between " <> shown objects
+ ExactContextualExpansionNotAvailable location key ->
+ at location <> "the contextual abbreviation " <> shown key
+ <> " has no compatible active structure"
+ ExactContextualRequirementConflict location symbol firstObject secondObject ->
+ at location <> "the contextual abbreviation requires incompatible "
+ <> shown symbol <> " operations " <> shown firstObject
+ <> " and " <> shown secondObject
+ ExactStructureOccurrenceMismatch location ->
+ at location <> "the structure syntax occurrences do not match the declaration"
+ ExactStructureSelfParent location structurePhrase ->
+ at location <> "the structure " <> shown structurePhrase
+ <> " cannot inherit from itself"
+ ExactStructureDuplicateParent location structurePhrase ->
+ at location <> "the parent structure " <> shown structurePhrase
+ <> " is repeated"
+ ExactStructureAlreadyVisible location structurePhrase ->
+ at location <> "the structure " <> shown structurePhrase
+ <> " is already declared"
+ ExactStructureDuplicateOperation location symbol ->
+ at location <> "the structure operation " <> shown symbol
+ <> " is repeated"
+ ExactStructureOperationAlreadyInherited location symbol ->
+ at location <> "the structure operation " <> shown symbol
+ <> " is already inherited"
+ ExactStructureOperationConflict location symbol firstOrigin secondOrigin ->
+ at location <> "the inherited structure operation " <> shown symbol
+ <> " conflicts between " <> shown firstOrigin
+ <> " and " <> shown secondOrigin
+ ExactStructureHasNoCarrier location structurePhrase ->
+ at location <> "the structure " <> shown structurePhrase
+ <> " does not inherit the base carrier operation"
+ ExactStructureDescriptorInvalid location _failure ->
+ at location <> "the canonical structure descriptor is inconsistent"
+ ExactStructureObjectNotVisible location identity ->
+ at location <> "the structure fact mentions unavailable object "
+ <> shown identity
+ where
+ at location = locationToText location <> ": "
+ shown :: Show value => value -> Text
+ shown = Text.pack . show
+
+data ElaborationState = ElaborationState
+ { elaborationBinders :: !(Map.Map Raw.VarSymbol Natural)
+ -- Counts every active de Bruijn binder, including anonymous and
+ -- contextual binders which have no entry in 'elaborationBinders'.
+ , elaborationBinderDepth :: !Natural
+ , elaborationStructures :: !(Map.Map Natural ExactStructureAnnotation)
+ , elaborationGlobals :: !(Map.Map ObjectId CoreType)
+ , elaborationContextualBinder :: !(Maybe Natural)
+ , elaborationContextualRequirements
+ :: !(Map.Map Raw.StructSymbol ObjectId)
+ }
+
+type Elaborate =
+ StateT
+ ElaborationState
+ (ExceptT ExactCompileError (Declaration.LoweringDriver))
+
+data PreparedHead = PreparedHead
+ !SemanticGlobalKey
+ ![Raw.VarSymbol]
+ !CoreType
+
+data PreparedBody
+ = OpaqueBody
+ | TransparentBody
+ !(CanonicalTerm ObjectId)
+ !(Maybe PreparedExactSetConstruction)
+ | ContextualTransparentBody
+ !(Map.Map Raw.StructSymbol ObjectId)
+ !(CanonicalTerm ObjectId)
+
+data CompiledBody = CompiledBody
+ !(CanonicalTerm ObjectId)
+ !(Maybe CompiledNamedSetConstruction)
+
+data CompiledNamedSetConstruction
+ = CompiledSeparationConstruction
+ !(CanonicalTerm ObjectId)
+ !(CanonicalTerm ObjectId)
+ | CompiledFunctionalReplacementConstruction
+ !(NonEmpty (CanonicalTerm ObjectId))
+ !(CanonicalTerm ObjectId)
+ !(Maybe (CanonicalTerm ObjectId))
+ | CompiledRelationalReplacementConstruction
+ !(CanonicalTerm ObjectId)
+ !(CanonicalTerm ObjectId)
+
+prepareExactProposition
+ :: ExactBinderContext
+ -> Raw.Stmt
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactProposition)
+prepareExactProposition context statement =
+ prepareExactPropositionTerm
+ context
+ (locate statement)
+ (compileStatement statement)
+
+-- | Compile the source bound of already-opened symbolic binders. This is the
+-- shared checked constraint seam used by quantified statements and proof
+-- binders, so relation signs, carrier casts, and global occurrences are
+-- elaborated exactly once by the ordinary expression compiler.
+prepareExactSymbolicBoundConstraints
+ :: ExactBinderContext
+ -> NonEmpty Raw.VarSymbol
+ -> Raw.Bound
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactProposition)
+prepareExactSymbolicBoundConstraints context variables bound =
+ prepareExactPropositionTerm
+ context
+ (case bound of
+ Raw.Unbounded -> locate (NonEmpty.head variables)
+ _ -> locate bound)
+ (logicalConjunction
+ <$> compileSymbolicBoundConstraintList variables bound)
+
+-- | Compile the opened body used by a symbolic existential witness. Its
+-- grouping is deliberately identical to 'SymbolicExists': all bound
+-- constraints form the existential restriction and the stated proposition is
+-- its body.
+prepareExactSymbolicWitnessConstraints
+ :: ExactBinderContext
+ -> NonEmpty Raw.VarSymbol
+ -> Raw.Bound
+ -> Raw.Stmt
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactProposition)
+prepareExactSymbolicWitnessConstraints context variables bound statement =
+ prepareExactPropositionTerm context (locate statement) do
+ constraints <-
+ logicalConjunction
+ <$> compileSymbolicBoundConstraintList variables bound
+ body <- compileStatement statement
+ pure
+ (if constraints == logicalTruth
+ then body
+ else logicalAnd constraints body)
+
+-- | Compile the checked constraint of an already-opened noun witness. Named
+-- binders are resolved normally; a nameless singular noun uses the nearest
+-- anonymous binder and therefore introduces no lookup spelling.
+prepareExactNounWitnessConstraints
+ :: ExactBinderContext
+ -> Raw.NounPhrase []
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactProposition)
+prepareExactNounWitnessConstraints context nounPhrase =
+ case nounPhrase of
+ Raw.NounPhrase left noun variables right suchThat ->
+ prepareExactPropositionTerm context (locate noun) do
+ subjects <-
+ case NonEmpty.nonEmpty variables of
+ Just binders ->
+ toList
+ <$> traverse compileIntroducedVariable binders
+ Nothing ->
+ pure [CBound 0]
+ compileNounPhraseConstraints
+ subjects left noun right suchThat
+
+prepareExactPropositionTerm
+ :: ExactBinderContext
+ -> Location
+ -> Elaborate (CanonicalTerm ObjectId)
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactProposition)
+prepareExactPropositionTerm context location compile =
+ Except.runExceptT do
+ let initialElaboration = initialElaborationState context
+ (term, finalElaboration) <-
+ State.runStateT compile initialElaboration
+ checked <-
+ either
+ (Except.throwError . ExactCoreCheckFailed location)
+ pure
+ (checkScopedCanonicalCore
+ (`Map.lookup` elaborationGlobals finalElaboration)
+ (binderTypes context)
+ term)
+ unless (scopedCoreType checked == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition
+ location
+ (scopedCoreType checked)))
+ pure (PreparedExactProposition checked)
+
+prepareExactSetExpression
+ :: ExactBinderContext
+ -> Raw.Expr
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactSetExpression)
+prepareExactSetExpression context expression =
+ Except.runExceptT do
+ let initialElaboration = initialElaborationState context
+ (compiled, finalElaboration) <-
+ State.runStateT
+ (compileNamedSetExpression expression)
+ initialElaboration
+ let CompiledBody term rawConstruction = compiled
+ checked <-
+ either
+ (Except.throwError
+ . ExactCoreCheckFailed (locate expression))
+ pure
+ (checkScopedCanonicalCore
+ (`Map.lookup` elaborationGlobals finalElaboration)
+ (binderTypes context)
+ term)
+ unless (scopedCoreType checked == TySet)
+ (Except.throwError
+ (ExactExpressionExpectedSet
+ (locate expression)
+ (scopedCoreType checked)))
+ construction <-
+ Except.liftEither
+ (traverse
+ (checkCompiledNamedSetConstruction
+ (`Map.lookup` elaborationGlobals finalElaboration)
+ (binderTypes context))
+ rawConstruction)
+ traverse_
+ (\checkedConstruction ->
+ unless
+ (preparedSetConstructionTerm checkedConstruction == checked)
+ (impossible
+ "exact named construction disagrees with its checked expression"))
+ construction
+ pure (PreparedExactSetExpression checked construction)
+
+checkCompiledNamedSetConstruction
+ :: (ObjectId -> Maybe CoreType)
+ -> [CoreType]
+ -> CompiledNamedSetConstruction
+ -> Either ExactCompileError PreparedExactSetConstruction
+checkCompiledNamedSetConstruction globalType context = \case
+ CompiledSeparationConstruction bound predicate -> do
+ checkedBound <- checkAt context TySet bound
+ checkedPredicate <- checkAt (TySet : context) TyProp predicate
+ maybe
+ (Left
+ (ExactCoreCheckFailed
+ Nowhere
+ (ExpectedCoreType TySet TyProp)))
+ (Right . PreparedUnconditionalSetConstruction)
+ (checkedSeparationConstruction
+ globalType checkedBound checkedPredicate)
+ CompiledFunctionalReplacementConstruction domains value condition -> do
+ let domainList = NonEmpty.toList domains
+ fullContext = replicate (length domainList) TySet <> context
+ checkedDomains <-
+ traverse
+ (\(depth, domain) ->
+ checkAt
+ (replicate depth TySet <> context)
+ TySet
+ domain)
+ (zip [0..] domainList)
+ checkedValue <- checkAt fullContext TySet value
+ checkedCondition <- traverse (checkAt fullContext TyProp) condition
+ maybe
+ (Left
+ (ExactCoreCheckFailed
+ Nowhere
+ (ExpectedCoreType TySet TyProp)))
+ (Right . PreparedUnconditionalSetConstruction)
+ (checkedFunctionalReplacementConstruction
+ globalType
+ (NonEmpty.fromList checkedDomains)
+ checkedValue
+ checkedCondition)
+ CompiledRelationalReplacementConstruction domain relation -> do
+ checkedDomain <- checkAt context TySet domain
+ checkedRelation <- checkAt (TySet : TySet : context) TyProp relation
+ maybe
+ (Left
+ (ExactCoreCheckFailed
+ Nowhere
+ (ExpectedCoreType TySet TyProp)))
+ (Right . PreparedRelationalSetConstruction)
+ (checkedRelationalReplacementConstruction
+ globalType checkedDomain checkedRelation)
+ where
+ checkAt expectedContext expectedType term = do
+ checked <-
+ first
+ (ExactCoreCheckFailed Nowhere)
+ (checkScopedCanonicalCore globalType expectedContext term)
+ unless
+ (scopedCoreType checked == expectedType)
+ (Left
+ (ExactCoreCheckFailed
+ Nowhere
+ (ExpectedCoreType
+ expectedType
+ (scopedCoreType checked))))
+ pure checked
+
+preparedSetConstructionTerm
+ :: PreparedExactSetConstruction
+ -> ScopedCheckedCore ObjectId
+preparedSetConstructionTerm = \case
+ PreparedUnconditionalSetConstruction construction ->
+ namedSetConstructionTerm construction
+ PreparedRelationalSetConstruction construction ->
+ relationalSetConstructionTerm construction
+
+preparedSetConstructionClosedBody
+ :: PreparedExactSetConstruction
+ -> FrozenCheckedCore ObjectId
+preparedSetConstructionClosedBody = \case
+ PreparedUnconditionalSetConstruction construction ->
+ namedSetConstructionClosedBody construction
+ PreparedRelationalSetConstruction construction ->
+ relationalSetConstructionClosedBody construction
+
+prepareExactLocalFunctionGraph
+ :: Location
+ -> ExactBinderContext
+ -> ExactBinderContext
+ -> Raw.Expr
+ -> Raw.Expr
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactLocalFunctionGraph)
+prepareExactLocalFunctionGraph
+ location context argumentContext domainExpression valueExpression =
+ Except.runExceptT do
+ domain <-
+ preparedExactSetExpressionCore
+ <$> ( Except.lift
+ (prepareExactSetExpression context domainExpression)
+ >>= Except.liftEither
+ )
+ value <-
+ preparedExactSetExpressionCore
+ <$> ( Except.lift
+ (prepareExactSetExpression
+ argumentContext valueExpression)
+ >>= Except.liftEither
+ )
+ pair <- prepareOrderedPair
+ case scopedReplacementGraph pair domain value of
+ Just (graph, checkedDomain, function) ->
+ pure
+ (PreparedExactLocalFunctionGraph
+ graph checkedDomain function)
+ Nothing ->
+ impossible
+ "checked local-function components did not form a replacement graph"
+ where
+ prepareOrderedPair = do
+ let initialElaboration = initialElaborationState context
+ key =
+ SemanticExpressionFunction
+ (Raw.mixfixPattern Raw.PairSymbol)
+ expected = TySet `TyArrow` (TySet `TyArrow` TySet)
+ ((term, actual), finalElaboration) <-
+ State.runStateT
+ (applyResolvedTyped location key [])
+ initialElaboration
+ unless (actual == expected)
+ (Except.throwError
+ (ExactObjectTypeMismatch location expected actual))
+ either
+ (Except.throwError . ExactCoreCheckFailed location)
+ pure
+ (checkScopedCanonicalCore
+ (`Map.lookup` elaborationGlobals finalElaboration)
+ (binderTypes context)
+ term)
+
+prepareExactClaimEnvelope
+ :: [Raw.Asm]
+ -> Raw.Stmt
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactClaimEnvelope)
+prepareExactClaimEnvelope assumptions statement =
+ discover [] emptyExactBinderContext
+ where
+ discover variables context = do
+ attempted <- prepareExactClaimAttempt context assumptions statement
+ case attempted of
+ Left (ExactFreeVariable _location variable)
+ | variable `elem` variables ->
+ impossible
+ "exact claim discovery repeated an active free variable"
+ | otherwise ->
+ case extendExactBinderContext
+ ( ( exactLocalId
+ (fromIntegral (length variables))
+ , variable
+ ) :| []
+ )
+ context of
+ Left failure ->
+ pure (Left failure)
+ Right extended ->
+ discover (variables <> [variable]) extended
+ Left failure ->
+ pure (Left failure)
+ Right (target, antecedentCount, structures) ->
+ pure
+ (Right
+ (PreparedExactClaimEnvelope
+ target
+ variables
+ (annotateBinderContext structures context)
+ antecedentCount))
+
+prepareExactClaimAttempt
+ :: ExactBinderContext
+ -> [Raw.Asm]
+ -> Raw.Stmt
+ -> Declaration.LoweringDriver
+ (Either
+ ExactCompileError
+ ( ScopedCheckedCore ObjectId
+ , Natural
+ , Map.Map Natural ExactStructureAnnotation
+ ))
+prepareExactClaimAttempt context assumptions statement =
+ Except.runExceptT do
+ let initialElaboration = initialElaborationState context
+ ((antecedents, conclusion), finalElaboration) <-
+ State.runStateT
+ ( do
+ antecedents <-
+ concat <$> traverse compileHeaderAssumption assumptions
+ conclusion <- compileStatement statement
+ pure (antecedents, conclusion)
+ )
+ initialElaboration
+ checkedAntecedents <-
+ traverse
+ (uncurry
+ (checkEnvelopeProposition
+ finalElaboration
+ context))
+ antecedents
+ checkedConclusion <-
+ checkEnvelopeProposition
+ finalElaboration
+ context
+ (locate statement)
+ conclusion
+ let implication =
+ foldr
+ (\antecedent continuation ->
+ fromMaybe
+ (impossible
+ "checked claim antecedents have unequal contexts")
+ (implyScopedCore antecedent continuation))
+ checkedConclusion
+ checkedAntecedents
+ closed = closeClaimBinders implication
+ unless (null (scopedCoreContext closed))
+ (impossible "exact claim closure retained a binder")
+ pure
+ ( closed
+ , fromIntegral (length antecedents)
+ , elaborationStructures finalElaboration
+ )
+
+checkEnvelopeProposition
+ :: ElaborationState
+ -> ExactBinderContext
+ -> Location
+ -> CanonicalTerm ObjectId
+ -> ExceptT
+ ExactCompileError
+ (Declaration.LoweringDriver)
+ (ScopedCheckedCore ObjectId)
+checkEnvelopeProposition elaboration context location term = do
+ checked <-
+ either
+ (Except.throwError . ExactCoreCheckFailed location)
+ pure
+ (checkScopedCanonicalCore
+ (`Map.lookup` elaborationGlobals elaboration)
+ (binderTypes context)
+ term)
+ unless (scopedCoreType checked == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition
+ location
+ (scopedCoreType checked)))
+ pure checked
+
+closeClaimBinders
+ :: ScopedCheckedCore global
+ -> ScopedCheckedCore global
+closeClaimBinders scoped =
+ case scopedCoreContext scoped of
+ [] -> scoped
+ _binder : _remaining ->
+ closeClaimBinders
+ (fromMaybe
+ (impossible
+ "a checked claim binder could not be closed")
+ (closeScopedForall scoped))
+
+binderIndices :: ExactBinderContext -> Map.Map Raw.VarSymbol Natural
+binderIndices (ExactBinderContext binders) =
+ Map.fromList
+ [ (variable, fromIntegral index)
+ | (index, ExactBinder _identity (Just variable) _coreType _structure) <-
+ zip [0 :: Int ..] binders
+ ]
+
+binderStructures
+ :: ExactBinderContext
+ -> Map.Map Natural ExactStructureAnnotation
+binderStructures (ExactBinderContext binders) =
+ Map.fromList
+ [ (fromIntegral index, structure)
+ | (index, ExactBinder _identity _variable _coreType (Just structure)) <-
+ zip [0 :: Int ..] binders
+ ]
+
+binderTypes :: ExactBinderContext -> [CoreType]
+binderTypes (ExactBinderContext binders) =
+ [ coreType
+ | ExactBinder _identity _variable coreType _structure <- binders
+ ]
+
+initialElaborationState :: ExactBinderContext -> ElaborationState
+initialElaborationState context =
+ ElaborationState
+ { elaborationBinders = binderIndices context
+ , elaborationBinderDepth =
+ fromIntegral (length (binderTypes context))
+ , elaborationStructures = binderStructures context
+ , elaborationGlobals = mempty
+ , elaborationContextualBinder = Nothing
+ , elaborationContextualRequirements = mempty
+ }
+
+annotateBinderContext
+ :: Map.Map Natural ExactStructureAnnotation
+ -> ExactBinderContext
+ -> ExactBinderContext
+annotateBinderContext structures (ExactBinderContext binders) =
+ ExactBinderContext
+ [ ExactBinder identity variable coreType
+ (Map.lookup (fromIntegral index) structures)
+ | (index, ExactBinder identity variable coreType _old) <-
+ zip [0 :: Int ..] binders
+ ]
+
+compileHeaderAssumption
+ :: Raw.Asm
+ -> Elaborate [(Location, CanonicalTerm ObjectId)]
+compileHeaderAssumption = \case
+ Raw.AsmSuppose statement -> do
+ proposition <- compileStatement statement
+ pure [(locate statement, proposition)]
+ Raw.AsmLetNoun variables nounPhrase
+ | exactSetNounPhrase nounPhrase -> do
+ traverse_ compileIntroducedVariable variables
+ pure []
+ | otherwise -> do
+ subjects <- traverse compileIntroducedVariable variables
+ constraints <-
+ traverse (`compileNounPhraseMaybe` nounPhrase) subjects
+ pure
+ [ (locate variable, constraint)
+ | (variable, constraint) <-
+ zip (toList variables) (toList constraints)
+ ]
+ Raw.AsmLetIn variables domain -> do
+ variableTerms <- traverse compileIntroducedVariable variables
+ domainTerm <- compileExpressionAsSet domain
+ traverse
+ (\(variable, variableTerm) -> do
+ proposition <-
+ compileMembership
+ (locate domain)
+ Raw.Positive
+ variableTerm
+ domainTerm
+ pure (locate variable, proposition))
+ (zip (toList variables) (toList variableTerms))
+ Raw.AsmLetEq variable expression -> do
+ variableTerm <- compileIntroducedVariable variable
+ expressionTerm <- compileExpressionAsSet expression
+ pure
+ [ ( locate variable
+ , CEq TySet variableTerm expressionTerm
+ )
+ ]
+ Raw.AsmLetThe variable _function ->
+ Except.throwError
+ (ExactUnsupportedHeaderAssumption (locate variable))
+ Raw.AsmLetStruct variable structure -> do
+ subject <- compileIntroducedVariable variable
+ annotation <-
+ resolveStructureAnnotation
+ (locate variable)
+ structure
+ index <-
+ maybe
+ (impossible "an introduced structure variable is unbound")
+ pure
+ =<< Map.lookup variable <$> State.gets elaborationBinders
+ existing <- State.gets (Map.lookup index . elaborationStructures)
+ when
+ (isJust existing)
+ (Except.throwError
+ (ExactDuplicateStructureAnnotation
+ (locate variable) variable))
+ State.modify' \state ->
+ state
+ { elaborationStructures =
+ Map.insert index annotation
+ (elaborationStructures state)
+ }
+ predicate <-
+ maybe
+ (impossible "an assertable structure has no predicate")
+ pure
+ (structureAnnotationPredicate annotation)
+ recordExactGlobal
+ predicate
+ (TyArrow TySet TyProp)
+ pure
+ [ ( locate variable
+ , CApp
+ (CGlobal predicate)
+ subject
+ )
+ ]
+
+compileIntroducedVariable
+ :: Raw.VarSymbol
+ -> Elaborate (CanonicalTerm ObjectId)
+compileIntroducedVariable variable =
+ compileExpressionAsSet (Raw.ExprVar variable)
+
+resolveStructureAnnotation
+ :: Location
+ -> Raw.StructPhrase
+ -> Elaborate ExactStructureAnnotation
+resolveStructureAnnotation location rawPhrase = do
+ let structurePhrase = semanticStructurePhrase rawPhrase
+ resolved <-
+ State.lift
+ (Except.lift
+ (Declaration.resolveVisibleStructureLowering structurePhrase))
+ structure <-
+ maybe
+ (Except.throwError
+ (ExactStructureNotVisible location structurePhrase))
+ pure
+ resolved
+ predicate <-
+ maybe
+ (Except.throwError
+ (ExactBaseStructureNotAssertable location structurePhrase))
+ pure
+ (Declaration.resolvedStructurePredicate structure)
+ pure
+ (ExactStructureAnnotation
+ structurePhrase
+ (Just predicate)
+ (Declaration.resolvedStructureOperations structure))
+
+structureAnnotationPredicate :: ExactStructureAnnotation -> Maybe ObjectId
+structureAnnotationPredicate
+ (ExactStructureAnnotation _ predicate _operations) =
+ predicate
+
+structureAnnotationOperation
+ :: Raw.StructSymbol
+ -> ExactStructureAnnotation
+ -> Maybe ObjectId
+structureAnnotationOperation symbol
+ (ExactStructureAnnotation _phrase _predicate operations) =
+ Map.lookup symbol operations
+
+recordExactGlobal :: ObjectId -> CoreType -> Elaborate ()
+recordExactGlobal identity coreType = do
+ existing <- State.gets (Map.lookup identity . elaborationGlobals)
+ case existing of
+ Nothing ->
+ State.modify' \state ->
+ state
+ { elaborationGlobals =
+ Map.insert identity coreType
+ (elaborationGlobals state)
+ }
+ Just actual
+ | actual == coreType -> pure ()
+ | otherwise ->
+ impossible "one exact global acquired two checked types"
+
+prepareExactSourceAxiom
+ :: Raw.Block
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactSourceAxiom)
+prepareExactSourceAxiom = \case
+ Raw.BlockAxiom
+ location _title (Raw.Marker marker)
+ (Raw.Axiom assumptions statement) -> do
+ prepared <- prepareExactClaimEnvelope assumptions statement
+ pure do
+ envelope <- prepared
+ let core = preparedExactClaimTarget envelope
+ alias = semanticName marker
+ unless (null (scopedCoreContext core))
+ (Left (ExactUnsupportedDeclarationBody location))
+ pure
+ (PreparedExactSourceAxiom
+ location
+ alias
+ core
+ (declarationSyntaxId
+ (encodePreparedSourceAxiom core alias)))
+ block ->
+ pure (Left (ExactUnsupportedDeclaration (locate block)))
+
+lowerPreparedExactSourceAxiom
+ :: PreparedExactSourceAxiom
+ -> Declaration.LoweringDriver
+ (Either
+ Declaration.DeclarationError
+ (Declaration.CheckedDeclaration ()))
+lowerPreparedExactSourceAxiom
+ (PreparedExactSourceAxiom _location alias target syntax) =
+ fmap
+ (\spec ->
+ Declaration.checkedCompiledDeclaration
+ syntax [] [] [] []
+ [ Declaration.checkedCandidate
+ spec
+ Declaration.checkedSourceAxiomPlanning
+ :| []
+ ]
+ ())
+ <$> Declaration.prepareCandidateSpecLowering
+ [] target SearchEligible [alias]
+
+authorizeCheckedExactSourceAxiom
+ :: ()
+ -> [NonEmpty Declaration.ReservedCandidate]
+ -> Declaration.Declaration ()
+authorizeCheckedExactSourceAxiom () = \case
+ [candidate :| []] ->
+ Declaration.authorizeSourceAxiomCandidate candidate
+ stages ->
+ Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch
+ 1 (length stages))
+
+prepareExactDeclaration
+ :: Raw.Block
+ -> [CanonicalLexicalEntry]
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactDeclaration)
+prepareExactDeclaration block entries =
+ Except.runExceptT do
+ entry <-
+ case entries of
+ [] -> Except.throwError
+ (ExactDeclarationOccurrenceMissing (locate block))
+ [single] -> pure single
+ _ -> Except.throwError
+ (ExactDeclarationOccurrenceAmbiguous (locate block))
+ key <-
+ maybe
+ (Except.throwError
+ (ExactUnsupportedDeclaration (locate block)))
+ pure
+ (semanticGlobalKeyFromLexicalEntry entry)
+ when
+ (isJust (fixedSemanticMeaning key))
+ (Except.throwError
+ (ExactFixedSemanticCollision (locate block) key))
+ visible <- Except.lift
+ (Declaration.resolveVisibleGlobalLowering key)
+ when
+ (isJust visible)
+ (Except.throwError
+ (ExactGlobalAlreadyVisible (locate block) key))
+ (head', family, rawBody) <-
+ prepareHead block key
+ slot <- Except.lift Declaration.nextDeclarationSlotLowering
+ theory <- Except.lift Declaration.currentTheoryLowering
+ (body, globals) <-
+ case rawBody of
+ Nothing -> pure (OpaqueBody, Map.empty)
+ Just buildBody -> do
+ let initialElaboration =
+ ElaborationState
+ { elaborationBinders = mempty
+ , elaborationBinderDepth = 0
+ , elaborationStructures = mempty
+ , elaborationGlobals = mempty
+ , elaborationContextualBinder = Nothing
+ , elaborationContextualRequirements = mempty
+ }
+ (CompiledBody canonical rawConstruction, finalElaboration) <-
+ State.runStateT buildBody initialElaboration
+ let PreparedHead _semanticKey parameters _coreType = head'
+ construction <-
+ Except.liftEither
+ (traverse
+ (checkCompiledNamedSetConstruction
+ (`Map.lookup`
+ elaborationGlobals finalElaboration)
+ (replicate (length parameters) TySet))
+ rawConstruction)
+ traverse_
+ (\checkedConstruction ->
+ unless
+ (frozenCoreTerm
+ (preparedSetConstructionClosedBody
+ checkedConstruction)
+ == canonical)
+ (impossible
+ "exact named construction disagrees with its transparent body"))
+ construction
+ let requirements =
+ elaborationContextualRequirements finalElaboration
+ body
+ | Map.null requirements =
+ TransparentBody canonical construction
+ | family == ExactAbbreviation =
+ ContextualTransparentBody
+ requirements
+ (CLam TySet canonical)
+ | otherwise =
+ impossible
+ "a non-abbreviation acquired contextual requirements"
+ pure (body, elaborationGlobals finalElaboration)
+ let PreparedHead semanticKey _parameters coreType = head'
+ unless (semanticKey == key)
+ (Except.throwError
+ (ExactDeclarationHeadMismatch (locate block)))
+ (target, content) <-
+ case body of
+ OpaqueBody -> do
+ let seed =
+ opaqueDeclarationSeed
+ (declarationSlotModule slot)
+ (declarationSlotOrdinal slot)
+ SignatureDeclaration
+ (generatedObjectSlot 0)
+ content' =
+ OpaqueObjectContent theory seed coreType
+ identity = opaqueObjectId theory seed coreType
+ pure (GlobalReference identity, content')
+ TransparentBody canonical _construction -> do
+ checked <-
+ either
+ (Except.throwError
+ . ExactCoreCheckFailed (locate block))
+ pure
+ (checkCanonicalCore
+ (`Map.lookup` globals)
+ canonical)
+ unless
+ (frozenCoreType checked == coreType)
+ (Except.throwError
+ (ExactObjectTypeMismatch
+ (locate block)
+ coreType
+ (frozenCoreType checked)))
+ let content' =
+ TransparentObjectContent
+ theory
+ coreType
+ canonical
+ let identity =
+ transparentObjectId theory coreType canonical
+ semanticTarget =
+ case family of
+ ExactAbbreviation ->
+ TransparentExpansion identity
+ ExactDefinition -> GlobalReference identity
+ ExactSignature ->
+ impossible
+ "a signature acquired a transparent body"
+ pure (semanticTarget, content')
+ ContextualTransparentBody requirements canonical -> do
+ let contextualType = TyArrow TySet coreType
+ checked <-
+ either
+ (Except.throwError
+ . ExactCoreCheckFailed (locate block))
+ pure
+ (checkCanonicalCore
+ (`Map.lookup` globals)
+ canonical)
+ unless
+ (frozenCoreType checked == contextualType)
+ (Except.throwError
+ (ExactObjectTypeMismatch
+ (locate block)
+ contextualType
+ (frozenCoreType checked)))
+ let content' =
+ TransparentObjectContent
+ theory
+ contextualType
+ canonical
+ identity =
+ transparentObjectId
+ theory contextualType canonical
+ pure
+ ( ContextualTransparentExpansion
+ identity requirements
+ , content'
+ )
+ let targetObject = semanticGlobalTargetObject target
+ available <-
+ Except.lift (Declaration.objectAvailableLowering targetObject)
+ let alias = definitionAlias block
+ asserted
+ | available = Nothing
+ | otherwise = Just (assertedObject targetObject content)
+ syntax =
+ declarationSyntaxId
+ (encodePreparedSyntax family head' body alias)
+ pure
+ (PreparedExactDeclaration
+ (locate block)
+ family
+ key
+ target
+ asserted
+ alias
+ (case family of
+ ExactDefinition -> case body of
+ TransparentBody _canonical construction -> construction
+ _ -> Nothing
+ _ -> Nothing)
+ syntax)
+
+lowerPreparedExactBinding
+ :: PreparedExactDeclaration
+ -> Declaration.LoweringDriver
+ (Either
+ Declaration.DeclarationError
+ (Declaration.CheckedDeclaration CheckedExactBindingAuthorization))
+lowerPreparedExactBinding prepared =
+ case preparedDefinitionAlias prepared of
+ Nothing ->
+ pure
+ (Right
+ (checked [] CheckedExactBindingNone))
+ Just alias -> case preparedDefinitionConstruction prepared of
+ Nothing ->
+ fmap
+ (\spec ->
+ checked
+ [ Declaration.checkedCandidate
+ spec
+ (Declaration.checkedDefinitionEquationPlanning
+ identity)
+ :| []
+ ]
+ (CheckedExactBindingDefinition identity))
+ <$> Declaration.prepareDefinitionEquationSpecLowering
+ objects identity alias
+ Just (PreparedUnconditionalSetConstruction construction) ->
+ Except.runExceptT do
+ equation <-
+ Except.lift
+ (Declaration.prepareDefinitionEquationSpecWithEligibilityLowering
+ objects identity SearchIneligible alias)
+ >>= Except.liftEither
+ (extensional, descriptor) <-
+ Except.lift
+ (Declaration.prepareNamedSetConstructionSpecLowering
+ objects identity construction)
+ >>= Except.liftEither
+ pure
+ (checked
+ [ Declaration.checkedCandidate
+ equation
+ (Declaration.checkedDefinitionEquationPlanning
+ identity)
+ :| [ Declaration.checkedCandidate
+ extensional
+ (Declaration.checkedKernelPlanning
+ descriptor [])
+ ]
+ ]
+ (CheckedExactBindingConstruction
+ identity construction))
+ Just (PreparedRelationalSetConstruction construction) ->
+ Except.runExceptT do
+ equation <-
+ Except.lift
+ (Declaration.prepareDefinitionEquationSpecWithEligibilityLowering
+ objects identity SearchIneligible alias)
+ >>= Except.liftEither
+ let functionality =
+ relationalSetConstructionClosedFunctionality
+ construction
+ functionalityScoped =
+ embedClosedCore [] functionality
+ functionalitySpec <-
+ Except.lift
+ (Declaration.prepareFrozenCandidateSpecLowering
+ objects functionality SearchIneligible [])
+ >>= Except.liftEither
+ obligation <-
+ Except.lift
+ (Declaration.prepareScopedVampireObligationLowering
+ Vector.empty
+ functionalityScoped
+ []
+ []
+ Declaration.VampireImplicitPremises)
+ >>= either
+ (Except.throwError
+ . Declaration.ProofObligationFailedAt location
+ . Declaration.CurrentCandidateVampirePreparationFailed)
+ pure
+ (extensional, descriptor) <-
+ Except.lift
+ (Declaration.prepareRelationalSetConstructionSpecLowering
+ objects identity construction functionality)
+ >>= Except.liftEither
+ pure
+ (checked
+ [ Declaration.checkedCandidate
+ equation
+ (Declaration.checkedDefinitionEquationPlanning
+ identity)
+ :| [ Declaration.checkedCandidate
+ functionalitySpec
+ (Declaration.checkedSourceProofPlanning
+ [Declaration.checkedPlannedVampireRequest
+ location obligation]
+ [])
+ ]
+ , Declaration.checkedCandidate
+ extensional
+ (Declaration.checkedStagedKernelPlanning
+ descriptor
+ [Declaration.plannedEarlierCandidate 0 1])
+ :| []
+ ]
+ (CheckedExactBindingRelationalConstruction
+ identity construction obligation))
+ where
+ identity = preparedExactObjectId prepared
+ objects = maybeToList (preparedExactObject prepared)
+ location = preparedExactLocation prepared
+ checked stages body =
+ Declaration.checkedCompiledDeclaration
+ (preparedExactSyntaxId prepared)
+ objects
+ []
+ [semanticGlobalBinding
+ (preparedExactGlobalKey prepared)
+ (preparedExactGlobalTarget prepared)]
+ []
+ stages
+ body
+
+data CheckedExactBindingAuthorization
+ = CheckedExactBindingNone
+ | CheckedExactBindingDefinition !ObjectId
+ | CheckedExactBindingConstruction
+ !ObjectId
+ !(NamedSetConstruction ObjectId)
+ | CheckedExactBindingRelationalConstruction
+ !ObjectId
+ !(CheckedRelationalSetConstruction ObjectId)
+ !(Declaration.PreparedVampireObligation Void ())
+
+authorizeCheckedExactBinding
+ :: CheckedExactBindingAuthorization
+ -> [NonEmpty Declaration.ReservedCandidate]
+ -> Declaration.Declaration ()
+authorizeCheckedExactBinding body stages =
+ case (body, stages) of
+ (CheckedExactBindingNone, []) -> pure ()
+ (CheckedExactBindingDefinition identity, [candidate :| []]) ->
+ Declaration.authorizeDefinitionEquationCandidate
+ identity candidate
+ ( CheckedExactBindingConstruction identity construction
+ , [equation :| [extensional]]
+ ) -> do
+ Declaration.authorizeDefinitionEquationCandidate
+ identity equation
+ Declaration.authorizeNamedSetConstructionCandidate
+ identity construction extensional
+ ( CheckedExactBindingRelationalConstruction
+ identity construction obligation
+ , [equation :| [functionality], extensional :| []]
+ ) -> do
+ Declaration.authorizeDefinitionEquationCandidate
+ identity equation
+ Declaration.authorizeVampireCandidate
+ functionality
+ (Declaration.acceptPreparedVampireObligation obligation)
+ Declaration.authorizeRelationalSetConstructionCandidate
+ identity construction functionality extensional
+ _ ->
+ Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch
+ (case body of
+ CheckedExactBindingNone -> 0
+ CheckedExactBindingDefinition{} -> 1
+ CheckedExactBindingConstruction{} -> 1
+ CheckedExactBindingRelationalConstruction{} -> 2)
+ (length stages))
+
+prepareExactStructure
+ :: Raw.Block
+ -> [CanonicalLexicalEntry]
+ -> Declaration.LoweringDriver
+ (Either ExactCompileError PreparedExactStructure)
+prepareExactStructure block entries =
+ Except.runExceptT do
+ (location, marker, structure) <-
+ case block of
+ Raw.BlockStruct location _title (Raw.Marker marker) structure ->
+ pure (location, marker, structure)
+ _ -> Except.throwError
+ (ExactUnsupportedDeclaration (locate block))
+ validateStructureOccurrences location structure entries
+ let structurePhrase =
+ semanticStructurePhrase (Raw.structPhrase structure)
+ parentPhrases =
+ semanticStructurePhrase <$> Raw.structParents structure
+ when
+ (structurePhrase `elem` parentPhrases)
+ (Except.throwError
+ (ExactStructureSelfParent location structurePhrase))
+ case firstDuplicate parentPhrases of
+ Just duplicate ->
+ Except.throwError
+ (ExactStructureDuplicateParent location duplicate)
+ Nothing -> pure ()
+ visible <- Except.lift
+ (Declaration.resolveVisibleStructureLowering structurePhrase)
+ when
+ (isJust visible)
+ (Except.throwError
+ (ExactStructureAlreadyVisible location structurePhrase))
+ parents <- traverse (resolveParent location) parentPhrases
+ inherited <-
+ foldM mergeParentOperations Map.empty
+ (zip parentPhrases parents)
+ case firstDuplicate (Raw.structFixes structure) of
+ Just duplicate ->
+ Except.throwError
+ (ExactStructureDuplicateOperation location duplicate)
+ Nothing -> pure ()
+ traverse_
+ (\symbol ->
+ when
+ (Map.member symbol inherited)
+ (Except.throwError
+ (ExactStructureOperationAlreadyInherited
+ location symbol)))
+ (Raw.structFixes structure)
+ slot <- Except.lift Declaration.nextDeclarationSlotLowering
+ theory <- Except.lift Declaration.currentTheoryLowering
+ let operationType = TyArrow TySet TySet
+ makeOperation index symbol =
+ let seed =
+ opaqueDeclarationSeed
+ (declarationSlotModule slot)
+ (declarationSlotOrdinal slot)
+ StructureDeclaration
+ (generatedObjectSlot index)
+ content = OpaqueObjectContent theory seed operationType
+ identity = opaqueObjectId theory seed operationType
+ in ( symbol
+ , identity
+ , assertedObject identity content
+ )
+ ownOperations =
+ zipWith makeOperation [0 ..] (Raw.structFixes structure)
+ operationObjects =
+ [ asserted
+ | (_symbol, _identity, asserted) <- ownOperations
+ ]
+ completeOperations =
+ Map.union
+ (Map.fromList
+ [ (symbol, identity)
+ | (symbol, identity, _asserted) <- ownOperations
+ ])
+ (fst <$> inherited)
+ unless
+ (Map.member Raw.CarrierSymbol completeOperations)
+ (Except.throwError
+ (ExactStructureHasNoCarrier location structurePhrase))
+ context <-
+ either Except.throwError pure
+ (extendExactBinderContext
+ ((exactLocalId 0, Raw.structLabel structure) :| [])
+ emptyExactBinderContext)
+ let provisionalAnnotation =
+ ExactStructureAnnotation
+ structurePhrase Nothing completeOperations
+ structureContext =
+ annotateBinderContext
+ (Map.singleton 0 provisionalAnnotation)
+ context
+ checkedAssumptions <-
+ traverse
+ (\(assumptionMarker, assumption) -> do
+ prepared <- Except.lift
+ (prepareExactProposition structureContext assumption)
+ proposition <- Except.liftEither prepared
+ pure
+ ( locate assumption
+ , assumptionMarker
+ , preparedExactPropositionCore proposition
+ ))
+ (Raw.structAssumes structure)
+ parentTerms <-
+ fmap catMaybes
+ (traverse
+ (\parent ->
+ case Declaration.resolvedStructurePredicate parent of
+ Nothing -> pure Nothing
+ Just predicate ->
+ pure
+ (Just
+ (CApp
+ (CGlobal predicate)
+ (CBound 0))))
+ parents)
+ let assumptionTerms =
+ [ scopedCoreTerm proposition
+ | (_assumptionLocation, _assumptionMarker, proposition) <-
+ checkedAssumptions
+ ]
+ predicateBody =
+ CLam TySet
+ (logicalConjunction
+ (parentTerms <> assumptionTerms))
+ predicateType = TyArrow TySet TyProp
+ predicateContent =
+ TransparentObjectContent theory predicateType predicateBody
+ predicate =
+ transparentObjectId theory predicateType predicateBody
+ predicateAvailable <-
+ Except.lift (Declaration.objectAvailableLowering predicate)
+ let predicateObject =
+ [ assertedObject predicate predicateContent
+ | not predicateAvailable
+ ]
+ ownBindings =
+ [ semanticStructureOperation symbol identity
+ | (symbol, identity, _asserted) <- ownOperations
+ ]
+ descriptor <-
+ Except.liftEither
+ (first
+ (ExactStructureDescriptorInvalid location)
+ (semanticStructureDescriptor
+ structurePhrase
+ (Just predicate)
+ parentPhrases
+ ownBindings))
+ let structureApplication =
+ CApp (CGlobal predicate) (CBound 0)
+ inheritance =
+ [ ( location
+ , semanticName (marker <> "inherit")
+ , CForall TySet
+ (CImp structureApplication
+ (logicalConjunction parentTerms))
+ )
+ | not (null parentTerms)
+ ]
+ projections =
+ [ ( assumptionLocation
+ , semanticName assumptionMarker
+ , CForall TySet
+ (CImp structureApplication
+ (scopedCoreTerm proposition))
+ )
+ | ( assumptionLocation
+ , Raw.Marker assumptionMarker
+ , proposition
+ ) <- checkedAssumptions
+ ]
+ generatedTerms = inheritance <> projections
+ localTypes =
+ Map.fromList
+ ((predicate, predicateType)
+ : [ (identity, operationType)
+ | (_symbol, identity, _asserted) <- ownOperations
+ ])
+ resolvedTypes <-
+ resolveStructureGlobalTypes
+ localTypes
+ [ term
+ | (_factLocation, _alias, term) <- generatedTerms
+ ]
+ generated <-
+ traverse
+ (\(factLocation, alias, term) -> do
+ frozen <-
+ either
+ (Except.throwError
+ . ExactCoreCheckFailed factLocation)
+ pure
+ (checkCanonicalCore
+ (`Map.lookup` resolvedTypes)
+ term)
+ pure
+ (PreparedExactStructureFact
+ factLocation frozen alias))
+ generatedTerms
+ environment <-
+ Except.liftEither
+ (first
+ (ExactStructureDescriptorInvalid location)
+ (semanticEnvironmentWithStructures [] [descriptor]))
+ let syntax =
+ declarationSyntaxId
+ (encodePreparedStructure
+ environment
+ predicate
+ generated)
+ pure
+ (PreparedExactStructure
+ location
+ (operationObjects <> predicateObject)
+ predicate
+ descriptor
+ (semanticName marker)
+ generated
+ syntax)
+ where
+ resolveParent location structurePhrase = do
+ resolved <- Except.lift
+ (Declaration.resolveVisibleStructureLowering structurePhrase)
+ maybe
+ (Except.throwError
+ (ExactStructureNotVisible location structurePhrase))
+ pure
+ resolved
+
+ mergeParentOperations inherited (parentPhrase, parent) =
+ foldM
+ (insertParentOperation parentPhrase)
+ inherited
+ (Map.toAscList
+ (Declaration.resolvedStructureOperations parent))
+
+ insertParentOperation parentPhrase inherited (symbol, identity) =
+ case Map.lookup symbol inherited of
+ Nothing ->
+ pure
+ (Map.insert symbol (identity, parentPhrase) inherited)
+ Just (existing, existingOrigin)
+ | existing == identity -> pure inherited
+ | otherwise ->
+ Except.throwError
+ (ExactStructureOperationConflict
+ (locate block)
+ symbol
+ existingOrigin
+ parentPhrase)
+
+ resolveStructureGlobalTypes localTypes terms = do
+ let dependencies = Set.unions (canonicalTermGlobals <$> terms)
+ foldM
+ (\types identity ->
+ case Map.lookup identity types of
+ Just{} -> pure types
+ Nothing -> do
+ coreType <- Except.lift
+ (Declaration.objectTypeLowering identity)
+ case coreType of
+ Nothing ->
+ Except.throwError
+ (ExactStructureObjectNotVisible
+ (locate block) identity)
+ Just actual ->
+ pure (Map.insert identity actual types))
+ localTypes
+ (Set.toAscList dependencies)
+
+lowerPreparedExactStructure
+ :: PreparedExactStructure
+ -> Declaration.LoweringDriver
+ (Either
+ Declaration.DeclarationError
+ (Declaration.CheckedDeclaration
+ CheckedExactStructureAuthorization))
+lowerPreparedExactStructure
+ (PreparedExactStructure
+ _location objects predicate descriptor alias generatedFacts syntax) =
+ Except.runExceptT do
+ definition <-
+ Except.lift
+ (Declaration.preparePointwiseDefinitionEquationSpecLowering
+ objects predicate alias)
+ >>= Except.liftEither
+ generatedCandidates <-
+ traverse
+ (\(PreparedExactStructureFact
+ factLocation target factAlias) -> do
+ generatedSpec <- Except.lift
+ (Declaration.prepareFrozenCandidateSpecLowering
+ objects target SearchEligible [factAlias])
+ >>= Except.liftEither
+ obligation <- Except.lift
+ (Declaration.prepareStagedCandidateVampireLowering
+ factLocation objects definition generatedSpec)
+ >>= Except.liftEither
+ pure
+ ( Declaration.checkedCandidate generatedSpec
+ (Declaration.checkedSourceProofPlanning
+ [ Declaration.checkedPlannedVampireRequest
+ factLocation obligation
+ ]
+ [ Declaration.plannedEarlierCandidate 0 0
+ ])
+ , (factLocation, obligation)
+ ))
+ generatedFacts
+ let generatedAuthorizations =
+ snd <$> generatedCandidates
+ stages =
+ [ Declaration.checkedCandidate definition
+ (Declaration.checkedDefinitionEquationPlanning predicate)
+ :| []
+ ]
+ <> maybeToList
+ (NonEmpty.nonEmpty
+ (fst <$> generatedCandidates))
+ body =
+ CheckedExactStructureAuthorization
+ predicate
+ generatedAuthorizations
+ pure
+ (Declaration.checkedCompiledDeclaration
+ syntax objects [] [] [descriptor] stages
+ body)
+
+authorizeCheckedExactStructure
+ :: CheckedExactStructureAuthorization
+ -> [NonEmpty Declaration.ReservedCandidate]
+ -> Declaration.Declaration ()
+authorizeCheckedExactStructure
+ (CheckedExactStructureAuthorization predicate obligations)
+ stages =
+ case (obligations, stages) of
+ ([], [definition :| []]) ->
+ Declaration.authorizeDefinitionEquationCandidate
+ predicate definition
+ (_, [definition :| [], generatedCandidates])
+ | length obligations == NonEmpty.length generatedCandidates -> do
+ Declaration.authorizeDefinitionEquationCandidate
+ predicate definition
+ Declaration.authorizeVampireCandidateBatch
+ (NonEmpty.zipWith
+ (\candidate (factLocation, obligation) ->
+ ( factLocation
+ , candidate
+ , do
+ void
+ (Declaration.useStagedCandidate
+ definition)
+ pure obligation
+ ))
+ generatedCandidates
+ (NonEmpty.fromList obligations))
+ _ ->
+ Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch
+ (if null obligations then 1 else 2)
+ (length stages))
+
+validateStructureOccurrences
+ :: Location
+ -> Raw.StructDefn
+ -> [CanonicalLexicalEntry]
+ -> ExceptT ExactCompileError (Declaration.LoweringDriver) ()
+validateStructureOccurrences location structure entries = do
+ let Raw.LexicalItemSgPl forms structureMarker =
+ Raw.structPhrase structure
+ expected =
+ CanonicalStructureNoun
+ (Raw.sg forms)
+ (Raw.pl forms)
+ structureMarker
+ : [ CanonicalStructureOperation command
+ | Raw.StructSymbol command <- Raw.structFixes structure
+ ]
+ unless
+ (entries == expected)
+ (Except.throwError
+ (ExactStructureOccurrenceMismatch location))
+
+encodePreparedStructure
+ :: SemanticEnvironmentDelta
+ -> ObjectId
+ -> [PreparedExactStructureFact]
+ -> ByteString
+encodePreparedStructure environment predicate generated =
+ encodeCache do
+ putCacheTag 0x04
+ putSemanticEnvironmentDeltaCache environment
+ putObjectIdCache predicate
+ putCacheList
+ (\(PreparedExactStructureFact _location target alias) -> do
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm target)
+ putCacheText (semanticNameText alias))
+ generated
+
+prepareHead
+ :: Raw.Block
+ -> SemanticGlobalKey
+ -> ExceptT
+ ExactCompileError
+ (Declaration.LoweringDriver)
+ ( PreparedHead
+ , ExactDeclarationFamily
+ , Maybe (Elaborate CompiledBody)
+ )
+prepareHead block key =
+ case block of
+ Raw.BlockSig location _title _marker assumptions signature -> do
+ rejectHeaderAssumptions ExactGuardedOpaqueSignature assumptions
+ head' <- prepareSignature location key signature
+ pure (head', ExactSignature, Nothing)
+ Raw.BlockAbbr location _title _marker abbreviation -> do
+ (head', buildBody) <-
+ prepareAbbreviation location key abbreviation
+ pure (head', ExactAbbreviation, Just buildBody)
+ Raw.BlockDefn location _title _marker definition -> do
+ (head', buildBody) <-
+ prepareDefinition location key definition
+ pure (head', ExactDefinition, Just buildBody)
+ _ ->
+ Except.throwError (ExactUnsupportedDeclaration (locate block))
+
+prepareSignature
+ :: Location
+ -> SemanticGlobalKey
+ -> Raw.Signature
+ -> ExceptT
+ ExactCompileError
+ (Declaration.LoweringDriver)
+ PreparedHead
+prepareSignature location key = \case
+ Raw.SignatureAdj subject (Raw.Adj _ item arguments) -> do
+ ensureAdjectiveKey location item key
+ makePreparedHead
+ location key (subject : arguments) TyProp
+ Raw.SignatureSymbolic (Raw.SymbolPattern symbol parameters) nounPhrase -> do
+ unless
+ ( key
+ == SemanticExpressionFunction
+ (Raw.mixfixPattern symbol)
+ )
+ (Except.throwError (ExactDeclarationHeadMismatch location))
+ unless
+ (exactSetNounPhrase nounPhrase)
+ (Except.throwError (ExactUnsupportedDeclarationBody location))
+ makePreparedHead location key parameters TySet
+ _ ->
+ Except.throwError (ExactUnsupportedDeclaration location)
+
+prepareAbbreviation
+ :: Location
+ -> SemanticGlobalKey
+ -> Raw.Abbreviation
+ -> ExceptT
+ ExactCompileError
+ (Declaration.LoweringDriver)
+ ( PreparedHead
+ , Elaborate CompiledBody
+ )
+prepareAbbreviation location key = \case
+ Raw.AbbreviationEq (Raw.SymbolPattern symbol parameters) expression -> do
+ ensureExpressionKey location symbol key
+ makeContextualTransparentHead
+ location key parameters TySet
+ (ordinaryCompiledBody <$> compileExpressionAsSet expression)
+ Raw.AbbreviationFun (Raw.Fun _ item parameters) term -> do
+ ensureFunctionPhraseKey location item key
+ makeContextualTransparentHead
+ location key parameters TySet
+ (ordinaryCompiledBody <$> compileTermAsSet term)
+ Raw.AbbreviationAdj subject (Raw.Adj _ item arguments) statement -> do
+ ensureAdjectiveKey location item key
+ makeContextualTransparentHead
+ location key (subject : arguments) TyProp
+ (ordinaryCompiledBody <$> compileStatement statement)
+ Raw.AbbreviationVerb subject (Raw.Verb _ item arguments) statement -> do
+ ensureVerbKey location item key
+ makeContextualTransparentHead
+ location key (subject : arguments) TyProp
+ (ordinaryCompiledBody <$> compileStatement statement)
+ Raw.AbbreviationNoun subject (Raw.Noun _ item arguments) statement -> do
+ ensureNounKey location item key
+ makeContextualTransparentHead
+ location key (subject : arguments) TyProp
+ (ordinaryCompiledBody <$> compileStatement statement)
+ Raw.AbbreviationRel left relation parameters right statement -> do
+ ensureRelationKey location relation key
+ makeContextualTransparentHead
+ location key (parameters <> [left, right]) TyProp
+ (ordinaryCompiledBody <$> compileStatement statement)
+
+prepareDefinition
+ :: Location
+ -> SemanticGlobalKey
+ -> Raw.Defn
+ -> ExceptT
+ ExactCompileError
+ (Declaration.LoweringDriver)
+ ( PreparedHead
+ , Elaborate CompiledBody
+ )
+prepareDefinition location key = \case
+ Raw.Defn assumptions head' statement -> do
+ rejectHeaderAssumptions ExactGuardedTransparentDefinition assumptions
+ (parameters, resultType) <-
+ definitionHead location key head'
+ makeTransparentHead
+ location key parameters resultType
+ (ordinaryCompiledBody <$> compileStatement statement)
+ Raw.DefnFun assumptions (Raw.Fun _ item parameters) symbolic term -> do
+ rejectHeaderAssumptions ExactGuardedTransparentDefinition assumptions
+ traverse_
+ (Except.throwError
+ . ExactDefinitionCombinedSymbolicAlias
+ . locate)
+ symbolic
+ ensureFunctionPhraseKey location item key
+ makeTransparentHead
+ location key parameters TySet
+ (compileNamedSetTerm term)
+ Raw.DefnOp (Raw.SymbolPattern symbol parameters) expression -> do
+ ensureExpressionKey location symbol key
+ makeTransparentHead
+ location key parameters TySet
+ (compileNamedSetExpression expression)
+
+definitionHead
+ :: Location
+ -> SemanticGlobalKey
+ -> Raw.DefnHead
+ -> ExceptT
+ ExactCompileError
+ (Declaration.LoweringDriver)
+ ([Raw.VarSymbol], CoreType)
+definitionHead location key = \case
+ Raw.DefnAdj annotation subject (Raw.Adj _ item arguments) -> do
+ validateDefinitionAnnotation annotation
+ ensureAdjectiveKey location item key
+ pure (subject : arguments, TyProp)
+ Raw.DefnVerb annotation subject (Raw.Verb _ item arguments) -> do
+ validateDefinitionAnnotation annotation
+ ensureVerbKey location item key
+ pure (subject : arguments, TyProp)
+ Raw.DefnNoun subject (Raw.Noun _ item arguments) -> do
+ ensureNounKey location item key
+ pure (subject : arguments, TyProp)
+ Raw.DefnRel left relation parameters right -> do
+ ensureRelationKey location relation key
+ pure (parameters <> [left, right], TyProp)
+ Raw.DefnSymbolicPredicate
+ (Raw.PrefixPredicate command arity)
+ _marker
+ parameters -> do
+ unless
+ ( key
+ == SemanticPrefixPredicate
+ command
+ (fromIntegral arity)
+ )
+ (Except.throwError (ExactDeclarationHeadMismatch location))
+ pure (toList parameters, TyProp)
+
+validateDefinitionAnnotation
+ :: MonadError ExactCompileError monad
+ => Maybe (Raw.NounPhrase Maybe)
+ -> monad ()
+validateDefinitionAnnotation = traverse_ \nounPhrase ->
+ unless (exactSetNounPhrase nounPhrase)
+ (throwError
+ (ExactNonCanonicalSetDefinitionAnnotation
+ (exactNounPhraseLocation nounPhrase)))
+
+rejectHeaderAssumptions
+ :: MonadError ExactCompileError monad
+ => (Location -> ExactCompileError)
+ -> [Raw.Asm]
+ -> monad ()
+rejectHeaderAssumptions makeError = \case
+ [] -> pure ()
+ assumption : _ ->
+ throwError (makeError (exactAssumptionLocation assumption))
+
+exactAssumptionLocation :: Raw.Asm -> Location
+exactAssumptionLocation = \case
+ Raw.AsmSuppose statement -> locate statement
+ Raw.AsmLetNoun variables _nounPhrase -> locate variables
+ Raw.AsmLetIn variables _expression -> locate variables
+ Raw.AsmLetThe variable _function -> locate variable
+ Raw.AsmLetEq variable _expression -> locate variable
+ Raw.AsmLetStruct variable _structure -> locate variable
+
+exactNounPhraseLocation :: Raw.NounPhraseOf t argument -> Location
+exactNounPhraseLocation
+ (Raw.NounPhrase _left noun _variables _right _suchThat) =
+ locate noun
+
+makeTransparentHead
+ :: Location
+ -> SemanticGlobalKey
+ -> [Raw.VarSymbol]
+ -> CoreType
+ -> Elaborate CompiledBody
+ -> ExceptT
+ ExactCompileError
+ (Declaration.LoweringDriver)
+ ( PreparedHead
+ , Elaborate CompiledBody
+ )
+makeTransparentHead location key parameters resultType body = do
+ (prepared, binders) <-
+ prepareParameters location key parameters resultType
+ let close = do
+ State.modify' \state ->
+ state
+ { elaborationBinders = binders
+ , elaborationBinderDepth =
+ fromIntegral (length parameters)
+ }
+ CompiledBody body' construction <- body
+ pure
+ (CompiledBody
+ (foldr (const (CLam TySet)) body' parameters)
+ construction)
+ pure (prepared, close)
+
+makeContextualTransparentHead
+ :: Location
+ -> SemanticGlobalKey
+ -> [Raw.VarSymbol]
+ -> CoreType
+ -> Elaborate CompiledBody
+ -> ExceptT
+ ExactCompileError
+ (Declaration.LoweringDriver)
+ ( PreparedHead
+ , Elaborate CompiledBody
+ )
+makeContextualTransparentHead location key parameters resultType body = do
+ (prepared, binders) <-
+ prepareParameters location key parameters resultType
+ let close = do
+ State.modify' \state ->
+ state
+ { elaborationBinders = binders
+ , elaborationBinderDepth =
+ fromIntegral (length parameters) + 1
+ , elaborationContextualBinder =
+ Just (fromIntegral (length parameters))
+ }
+ CompiledBody body' _construction <- body
+ pure
+ (CompiledBody
+ (foldr (const (CLam TySet)) body' parameters)
+ Nothing)
+ pure (prepared, close)
+
+makePreparedHead
+ :: Location
+ -> SemanticGlobalKey
+ -> [Raw.VarSymbol]
+ -> CoreType
+ -> ExceptT
+ ExactCompileError
+ (Declaration.LoweringDriver)
+ PreparedHead
+makePreparedHead location key parameters resultType = do
+ (prepared, _binders) <-
+ prepareParameters location key parameters resultType
+ pure prepared
+
+prepareParameters
+ :: Location
+ -> SemanticGlobalKey
+ -> [Raw.VarSymbol]
+ -> CoreType
+ -> ExceptT
+ ExactCompileError
+ (Declaration.LoweringDriver)
+ ( PreparedHead
+ , Map.Map Raw.VarSymbol Natural
+ )
+prepareParameters
+ location key parameters resultType = do
+ case firstDuplicate parameters of
+ Just duplicate ->
+ Except.throwError
+ (ExactDuplicateParameter location duplicate)
+ Nothing -> pure ()
+ let indices =
+ reverse (take (length parameters) [0 ..])
+ binders =
+ Map.fromList
+ (zip parameters indices)
+ coreType = foldr (const (TyArrow TySet)) resultType parameters
+ pure
+ ( PreparedHead key parameters coreType
+ , binders
+ )
+
+compileExpressionAsSet
+ :: Raw.Expr
+ -> Elaborate (CanonicalTerm ObjectId)
+compileExpressionAsSet expression = do
+ (term, actual) <- compileExpression expression
+ unless (actual == TySet)
+ (Except.throwError
+ (ExactExpressionExpectedSet (locate expression) actual))
+ pure term
+
+-- | Compile one set expression once while retaining the checked-source shape
+-- needed only when that expression is subsequently named by a definition.
+-- Nested constructions remain ordinary exact terms.
+compileNamedSetExpression
+ :: Raw.Expr
+ -> Elaborate CompiledBody
+compileNamedSetExpression = \case
+ Raw.ExprSep _location variable bound predicate -> do
+ (term, bound', predicate') <-
+ compileSeparation variable bound predicate
+ pure
+ (CompiledBody term
+ (Just
+ (CompiledSeparationConstruction
+ bound' predicate')))
+ Raw.ExprReplace _location value bounds condition -> do
+ replacement <-
+ compileFunctionalReplacement value bounds condition
+ pure
+ (CompiledBody
+ (compiledFunctionalReplacementTerm replacement)
+ (Just
+ (CompiledFunctionalReplacementConstruction
+ (compiledFunctionalReplacementDomains replacement)
+ (compiledFunctionalReplacementValue replacement)
+ (compiledFunctionalReplacementCondition replacement))))
+ Raw.ExprReplacePred _location range domainVariable bound predicate -> do
+ (term, domain, relation) <-
+ compileRelationalReplacement
+ range domainVariable bound predicate
+ pure
+ (CompiledBody term
+ (Just
+ (CompiledRelationalReplacementConstruction
+ domain relation)))
+ expression ->
+ (`CompiledBody` Nothing)
+ <$> compileExpressionAsSet expression
+
+compileNamedSetTerm :: Raw.Term -> Elaborate CompiledBody
+compileNamedSetTerm = \case
+ Raw.TermExpr expression -> compileNamedSetExpression expression
+ term -> ordinaryCompiledBody <$> compileTermAsSet term
+
+ordinaryCompiledBody :: CanonicalTerm ObjectId -> CompiledBody
+ordinaryCompiledBody term = CompiledBody term Nothing
+
+compileTermAsSet
+ :: Raw.Term
+ -> Elaborate (CanonicalTerm ObjectId)
+compileTermAsSet = \case
+ Raw.TermExpr expression ->
+ compileExpressionAsSet expression
+ Raw.TermFun (Raw.Fun location item arguments) -> do
+ let patterns = Raw.lexicalItemSgPlPattern item
+ key = SemanticFunctionPhrase (Raw.sg patterns) (Raw.pl patterns)
+ compiled <- traverse compileTermAsSet arguments
+ applyResolved location key compiled
+ Raw.TermQuantified _quantifier location _nounPhrase ->
+ Except.throwError
+ (ExactQuantifiedTermRequiresPropositionContext location)
+ term ->
+ Except.throwError
+ (ExactUnsupportedDeclarationBody (locate term))
+
+-- | Compile a source term only at a proposition consumer. Indefinite terms
+-- own the continuation, so their noun constraints and quantifier surround
+-- exactly the proposition which consumes the resulting set. Function-phrase
+-- arguments recurse through the same seam and therefore never masquerade as
+-- independently set-valued terms.
+compileTermInProposition
+ :: Raw.Term
+ -> (CanonicalTerm ObjectId
+ -> Elaborate (CanonicalTerm ObjectId))
+ -> Elaborate (CanonicalTerm ObjectId)
+compileTermInProposition term continuation =
+ case term of
+ Raw.TermExpr expression ->
+ compileExpressionAsSet expression >>= continuation
+ Raw.TermFun (Raw.Fun location item arguments) -> do
+ let patterns = Raw.lexicalItemSgPlPattern item
+ key =
+ SemanticFunctionPhrase
+ (Raw.sg patterns)
+ (Raw.pl patterns)
+ compileTermsInProposition arguments \compiled -> do
+ value <- applyResolved location key compiled
+ continuation value
+ Raw.TermQuantified quantifier _location nounPhrase ->
+ compileQuantifiedTermInProposition
+ quantifier nounPhrase continuation
+ Raw.TermIota location _variable _statement ->
+ Except.throwError (ExactUnsupportedDeclarationBody location)
+
+-- | Compile source-ordered proposition terms. The first source occurrence
+-- receives the outermost continuation and therefore the widest scope.
+compileTermsInProposition
+ :: [Raw.Term]
+ -> ([CanonicalTerm ObjectId]
+ -> Elaborate (CanonicalTerm ObjectId))
+ -> Elaborate (CanonicalTerm ObjectId)
+compileTermsInProposition terms continuation =
+ case terms of
+ [] -> continuation []
+ term : remaining ->
+ compileTermInProposition term \compiled -> do
+ compiledDepth <- State.gets elaborationBinderDepth
+ compileTermsInProposition remaining \rest -> do
+ compiled' <-
+ weakenElaboratedTermFrom compiledDepth compiled
+ continuation (compiled' : rest)
+
+weakenElaboratedTermFrom
+ :: Natural
+ -> CanonicalTerm ObjectId
+ -> Elaborate (CanonicalTerm ObjectId)
+weakenElaboratedTermFrom originalDepth term = do
+ currentDepth <- State.gets elaborationBinderDepth
+ when (currentDepth < originalDepth)
+ (impossible
+ "a proposition-term continuation escaped its binder scope")
+ pure
+ (shiftCanonical
+ (currentDepth - originalDepth)
+ 0
+ term)
+
+compileExpression
+ :: Raw.Expr
+ -> Elaborate (CanonicalTerm ObjectId, CoreType)
+compileExpression = \case
+ Raw.ExprVar variable -> do
+ binders <- State.gets elaborationBinders
+ case Map.lookup variable binders of
+ Just index ->
+ pure (CBound index, TySet)
+ Nothing ->
+ Except.throwError
+ (ExactFreeVariable (locate variable) variable)
+ Raw.ExprInteger _location integer ->
+ pure (COpaqueInteger (toInteger integer), TySet)
+ Raw.ExprOp location symbol arguments -> do
+ let key =
+ SemanticExpressionFunction
+ (Raw.mixfixPattern symbol)
+ compiled <- traverse compileExpression arguments
+ case fixedSemanticMeaning key of
+ Just (FixedIntrinsic intrinsic) ->
+ applyTyped
+ location
+ (CIntrinsic intrinsic)
+ (coreIntrinsicType intrinsic)
+ compiled
+ Just (FixedNegatedIntrinsic _intrinsic) ->
+ impossible
+ "expression key resolved to a negated intrinsic"
+ Just FixedEquality ->
+ impossible
+ "expression key resolved to fixed equality"
+ Just FixedDisequality ->
+ impossible
+ "expression key resolved to fixed disequality"
+ Nothing ->
+ applyResolvedTyped
+ location
+ key
+ compiled
+ Raw.ExprStructOp location symbol maybeArgument ->
+ compileStructureOperation location symbol maybeArgument
+ Raw.ExprFiniteSet _location elements -> do
+ compiled <- traverse compileExpressionAsSet elements
+ pure
+ ( foldr
+ canonicalSetInsert
+ (CIntrinsic Empty)
+ compiled
+ , TySet
+ )
+ Raw.ExprSep _location variable bound predicate -> do
+ (term, _bound, _predicate) <-
+ compileSeparation variable bound predicate
+ pure (term, TySet)
+ Raw.ExprReplace _location value bounds condition -> do
+ replacement <-
+ compileFunctionalReplacement value bounds condition
+ pure (compiledFunctionalReplacementTerm replacement, TySet)
+ Raw.ExprReplacePred location _value _variable _bound _predicate ->
+ Except.throwError
+ (ExactRelationalReplacementRequiresNamedDefinition location)
+
+compileStructureOperation
+ :: Location
+ -> Raw.StructSymbol
+ -> Maybe Raw.Expr
+ -> Elaborate (CanonicalTerm ObjectId, CoreType)
+compileStructureOperation location symbol maybeArgument = do
+ (argument, object) <-
+ case maybeArgument of
+ Just expression -> do
+ term <- compileExpressionAsSet expression
+ structures <- State.gets elaborationStructures
+ case termStructureAnnotation term structures of
+ Just annotation -> do
+ object <-
+ maybe
+ (Except.throwError
+ (ExactStructureOperationNotAvailable
+ location symbol))
+ pure
+ (structureAnnotationOperation
+ symbol annotation)
+ pure (term, object)
+ Nothing -> do
+ object <-
+ resolveUniqueStructureOperation location symbol
+ pure (term, object)
+ Nothing -> do
+ structures <- State.gets elaborationStructures
+ case
+ [ (CBound index, object)
+ | (index, structure) <- Map.toAscList structures
+ , Just object <-
+ [structureAnnotationOperation symbol structure]
+ ] of
+ firstMatch : _ -> pure firstMatch
+ [] -> do
+ contextual <- State.gets elaborationContextualBinder
+ case contextual of
+ Nothing ->
+ Except.throwError
+ (ExactStructureOperationNotAvailable
+ location symbol)
+ Just index -> do
+ object <-
+ resolveUniqueStructureOperation
+ location symbol
+ recordContextualRequirement
+ location symbol object
+ pure (CBound index, object)
+ recordExactGlobal object (TyArrow TySet TySet)
+ pure (CApp (CGlobal object) argument, TySet)
+ where
+ termStructureAnnotation term structures =
+ case term of
+ CBound index -> Map.lookup index structures
+ _ -> Nothing
+
+resolveUniqueStructureOperation
+ :: Location
+ -> Raw.StructSymbol
+ -> Elaborate ObjectId
+resolveUniqueStructureOperation location symbol = do
+ objects <-
+ State.lift
+ (Except.lift
+ (Declaration.resolveVisibleStructureOperationObjectsLowering
+ symbol))
+ case objects of
+ [] ->
+ Except.throwError
+ (ExactStructureOperationNotAvailable location symbol)
+ [object] -> pure object
+ _ ->
+ Except.throwError
+ (ExactStructureOperationAmbiguous location symbol objects)
+
+recordContextualRequirement
+ :: Location
+ -> Raw.StructSymbol
+ -> ObjectId
+ -> Elaborate ()
+recordContextualRequirement location symbol object = do
+ existing <-
+ State.gets
+ (Map.lookup symbol . elaborationContextualRequirements)
+ case existing of
+ Nothing ->
+ State.modify' \state ->
+ state
+ { elaborationContextualRequirements =
+ Map.insert symbol object
+ (elaborationContextualRequirements state)
+ }
+ Just actual
+ | actual == object -> pure ()
+ | otherwise ->
+ Except.throwError
+ (ExactContextualRequirementConflict
+ location symbol actual object)
+
+structureCarrierCast
+ :: Location
+ -> CanonicalTerm ObjectId
+ -> Elaborate (CanonicalTerm ObjectId)
+structureCarrierCast location term =
+ case term of
+ CBound index -> do
+ annotation <- State.gets (Map.lookup index . elaborationStructures)
+ case annotation of
+ Nothing -> pure term
+ Just structure -> do
+ carrier <-
+ maybe
+ (Except.throwError
+ (ExactStructureOperationNotAvailable
+ location Raw.CarrierSymbol))
+ pure
+ (structureAnnotationOperation
+ Raw.CarrierSymbol structure)
+ recordExactGlobal carrier (TyArrow TySet TySet)
+ pure (CApp (CGlobal carrier) term)
+ _ -> pure term
+
+compileMembership
+ :: Location
+ -> Raw.Sign
+ -> CanonicalTerm ObjectId
+ -> CanonicalTerm ObjectId
+ -> Elaborate (CanonicalTerm ObjectId)
+compileMembership location sign element set = do
+ checkedSet <- structureCarrierCast location set
+ let proposition =
+ CApp
+ (CApp (CIntrinsic Member) element)
+ checkedSet
+ pure case sign of
+ Raw.Positive -> proposition
+ Raw.Negative -> logicalNot proposition
+
+compileSeparation
+ :: Raw.VarSymbol
+ -> Raw.Expr
+ -> Raw.Stmt
+ -> Elaborate
+ ( CanonicalTerm ObjectId
+ , CanonicalTerm ObjectId
+ , CanonicalTerm ObjectId
+ )
+compileSeparation variable bound predicate = do
+ bound' <- compileExpressionAsSet bound
+ predicate' <-
+ withSetBinders (variable :| [])
+ (compileStatement predicate)
+ pure
+ ( CApp
+ (CApp (CIntrinsic Sep) bound')
+ (CLam TySet predicate')
+ , bound'
+ , predicate'
+ )
+
+compileRelationalReplacement
+ :: Raw.VarSymbol
+ -> Raw.VarSymbol
+ -> Raw.Expr
+ -> Raw.Stmt
+ -> Elaborate
+ ( CanonicalTerm ObjectId
+ , CanonicalTerm ObjectId
+ , CanonicalTerm ObjectId
+ )
+compileRelationalReplacement range domainVariable bound predicate = do
+ domain <- compileExpressionAsSet bound
+ relation <-
+ withSetBinders (domainVariable :| [range])
+ (compileStatement predicate)
+ let restrictedDomain =
+ CApp
+ (CApp (CIntrinsic Sep) domain)
+ (CLam TySet (logicalExists relation))
+ choiceFunction =
+ CLam TySet
+ (CApp (CIntrinsic SetChoose) (CLam TySet relation))
+ replacement =
+ CApp
+ (CApp (CIntrinsic Repl) restrictedDomain)
+ choiceFunction
+ pure (replacement, domain, relation)
+
+data CompiledFunctionalReplacement = CompiledFunctionalReplacement
+ !(CanonicalTerm ObjectId)
+ !(NonEmpty (CanonicalTerm ObjectId))
+ !(CanonicalTerm ObjectId)
+ !(Maybe (CanonicalTerm ObjectId))
+
+compiledFunctionalReplacementTerm
+ :: CompiledFunctionalReplacement
+ -> CanonicalTerm ObjectId
+compiledFunctionalReplacementTerm
+ (CompiledFunctionalReplacement term _domains _value _condition) =
+ term
+
+compiledFunctionalReplacementDomains
+ :: CompiledFunctionalReplacement
+ -> NonEmpty (CanonicalTerm ObjectId)
+compiledFunctionalReplacementDomains
+ (CompiledFunctionalReplacement _term domains _value _condition) =
+ domains
+
+compiledFunctionalReplacementValue
+ :: CompiledFunctionalReplacement
+ -> CanonicalTerm ObjectId
+compiledFunctionalReplacementValue
+ (CompiledFunctionalReplacement _term _domains value _condition) =
+ value
+
+compiledFunctionalReplacementCondition
+ :: CompiledFunctionalReplacement
+ -> Maybe (CanonicalTerm ObjectId)
+compiledFunctionalReplacementCondition
+ (CompiledFunctionalReplacement _term _domains _value condition) =
+ condition
+
+compileFunctionalReplacement
+ :: Raw.Expr
+ -> NonEmpty (Raw.VarSymbol, Raw.Expr)
+ -> Maybe Raw.Stmt
+ -> Elaborate CompiledFunctionalReplacement
+compileFunctionalReplacement
+ value ((variable, domain) :| remaining) condition = do
+ domain' <- compileExpressionAsSet domain
+ case remaining of
+ [] -> do
+ (value', condition') <-
+ withSetBinders (variable :| []) do
+ value' <- compileExpressionAsSet value
+ condition' <- traverse compileStatement condition
+ pure (value', condition')
+ let filteredDomain =
+ case condition' of
+ Nothing -> domain'
+ Just predicate ->
+ CApp
+ (CApp (CIntrinsic Sep) domain')
+ (CLam TySet predicate)
+ pure
+ (CompiledFunctionalReplacement
+ (CApp
+ (CApp (CIntrinsic Repl) filteredDomain)
+ (CLam TySet value'))
+ (domain' :| [])
+ value'
+ condition')
+ next : rest -> do
+ nested <-
+ withSetBinders (variable :| [])
+ (compileFunctionalReplacement
+ value (next :| rest) condition)
+ pure
+ (CompiledFunctionalReplacement
+ (CApp
+ (CIntrinsic FamilyUnion)
+ (CApp
+ (CApp (CIntrinsic Repl) domain')
+ (CLam TySet
+ (compiledFunctionalReplacementTerm nested))))
+ (domain'
+ NonEmpty.<|
+ compiledFunctionalReplacementDomains nested)
+ (compiledFunctionalReplacementValue nested)
+ (compiledFunctionalReplacementCondition nested))
+
+compileStatement
+ :: Raw.Stmt
+ -> Elaborate (CanonicalTerm ObjectId)
+compileStatement = \case
+ Raw.StmtFormula formula ->
+ compileFormula formula
+ Raw.StmtVerbPhrase terms verbPhrase ->
+ compileTermsInProposition (toList terms) \subjects ->
+ logicalConjunction
+ <$> traverse (`compileVerbPhrase` verbPhrase) subjects
+ Raw.StmtNoun terms nounPhrase ->
+ compileTermsInProposition (toList terms) \subjects ->
+ logicalConjunction
+ <$> traverse (`compileNounPhraseMaybe` nounPhrase) subjects
+ Raw.StmtExists _location nounPhrase ->
+ compileExistentialNounPhrase nounPhrase
+ Raw.StmtQuantPhrase
+ _location
+ (Raw.QuantPhrase quantifier nounPhrase)
+ statement ->
+ compileQuantifiedNounPhrase quantifier nounPhrase statement
+ Raw.StmtConnected connective location left right ->
+ compileConnective
+ (fromMaybe (locate left) location)
+ connective
+ compileStatement
+ left
+ right
+ Raw.StmtNeg _location statement ->
+ logicalNot <$> compileStatement statement
+ Raw.SymbolicQuantified
+ _location quantifier variables bound suchThat statement ->
+ compileSymbolicQuantified
+ quantifier variables bound suchThat (compileStatement statement)
+ Raw.StmtStruct term rawPhrase ->
+ compileTermInProposition term \subject -> do
+ annotation <-
+ resolveStructureAnnotation (locate term) rawPhrase
+ predicate <-
+ maybe
+ (impossible "an assertable structure has no predicate")
+ pure
+ (structureAnnotationPredicate annotation)
+ recordExactGlobal
+ predicate
+ (TyArrow TySet TyProp)
+ pure
+ (CApp
+ (CGlobal predicate)
+ subject)
+
+compileQuantifiedTermInProposition
+ :: Raw.Quantifier
+ -> Raw.NounPhrase Maybe
+ -> (CanonicalTerm ObjectId
+ -> Elaborate (CanonicalTerm ObjectId))
+ -> Elaborate (CanonicalTerm ObjectId)
+compileQuantifiedTermInProposition quantifier
+ (Raw.NounPhrase left noun named right suchThat)
+ compileBody =
+ case named of
+ Nothing ->
+ withAnonymousSetBinder compileFor
+ Just variable ->
+ withSetBinders (variable :| []) do
+ subject <- compileIntroducedVariable variable
+ compileFor subject
+ where
+ compileFor subject = do
+ constraints <-
+ compileNounPhraseConstraints
+ [subject] left noun right suchThat
+ body <- compileBody subject
+ pure (quantifyNounPhrase quantifier 1 constraints body)
+
+compileSymbolicQuantified
+ :: Raw.Quantifier
+ -> NonEmpty Raw.VarSymbol
+ -> Raw.Bound
+ -> Maybe Raw.Stmt
+ -> Elaborate (CanonicalTerm ObjectId)
+ -> Elaborate (CanonicalTerm ObjectId)
+compileSymbolicQuantified quantifier variables bound suchThat compileBody =
+ withSetBinders variables do
+ boundConstraints <-
+ compileSymbolicBoundConstraintList variables bound
+ suchThatConstraints <-
+ maybeToList <$> traverse compileStatement suchThat
+ body <- compileBody
+ pure
+ (quantifyNounPhrase
+ quantifier
+ (length (toList variables))
+ (logicalConjunction
+ (boundConstraints <> suchThatConstraints))
+ body)
+
+compileSymbolicBoundConstraintList
+ :: NonEmpty Raw.VarSymbol
+ -> Raw.Bound
+ -> Elaborate [CanonicalTerm ObjectId]
+compileSymbolicBoundConstraintList variables = \case
+ Raw.Unbounded ->
+ pure []
+ Raw.Bounded _location sign relation domain -> do
+ subjects <- traverse compileIntroducedVariable variables
+ domain' <- compileExpressionAsSet domain
+ traverse
+ (\subject -> do
+ proposition <-
+ compileAtomicRelationTerms subject relation domain'
+ pure case sign of
+ Raw.Positive -> proposition
+ Raw.Negative -> logicalNot proposition)
+ (toList subjects)
+
+compileAtomicRelationTerms
+ :: CanonicalTerm ObjectId
+ -> Raw.Relation
+ -> CanonicalTerm ObjectId
+ -> Elaborate (CanonicalTerm ObjectId)
+compileAtomicRelationTerms left relation right =
+ case relation of
+ Raw.Relation location symbol parameters -> do
+ let key =
+ SemanticRelation
+ (Raw.relationSymbolToken symbol)
+ (Raw.relationSymbolParameterArity symbol)
+ compiledParameters <- traverse compileExpressionAsSet parameters
+ case fixedSemanticMeaning key of
+ Just FixedEquality
+ | null parameters -> pure (CEq TySet left right)
+ Just FixedDisequality
+ | null parameters ->
+ pure (logicalNot (CEq TySet left right))
+ Just (FixedIntrinsic Member)
+ | null parameters ->
+ compileMembership location Raw.Positive left right
+ Just (FixedNegatedIntrinsic Member)
+ | null parameters ->
+ compileMembership location Raw.Negative left right
+ Just (FixedIntrinsic intrinsic) -> do
+ (term, actual) <-
+ applyTyped
+ location
+ (CIntrinsic intrinsic)
+ (coreIntrinsicType intrinsic)
+ ((\term -> (term, TySet))
+ <$> (compiledParameters <> [left, right]))
+ unless (actual == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition location actual))
+ pure term
+ Just (FixedNegatedIntrinsic intrinsic) -> do
+ (term, actual) <-
+ applyTyped
+ location
+ (CIntrinsic intrinsic)
+ (coreIntrinsicType intrinsic)
+ ((\term -> (term, TySet))
+ <$> (compiledParameters <> [left, right]))
+ unless (actual == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition location actual))
+ pure (logicalNot term)
+ _ -> do
+ (term, actual) <-
+ applyResolvedTyped
+ location key
+ ((\term -> (term, TySet))
+ <$> (compiledParameters <> [left, right]))
+ unless (actual == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition location actual))
+ pure term
+ Raw.RelationExpr location expression ->
+ compileRelationExpression location expression left right
+
+compileVerbPhrase
+ :: CanonicalTerm ObjectId
+ -> Raw.VerbPhrase
+ -> Elaborate (CanonicalTerm ObjectId)
+compileVerbPhrase subject = \case
+ Raw.VPVerb verb ->
+ compileVerb subject verb
+ Raw.VPVerbNot verb ->
+ logicalNot <$> compileVerb subject verb
+ Raw.VPAdj adjectives ->
+ logicalConjunction
+ <$> traverse (compileAdjective subject) adjectives
+ Raw.VPAdjNot adjectives ->
+ logicalNot . logicalConjunction
+ <$> traverse (compileAdjective subject) adjectives
+
+compilePredicateArguments
+ :: CanonicalTerm ObjectId
+ -> [Raw.Term]
+ -> ( CanonicalTerm ObjectId
+ -> [CanonicalTerm ObjectId]
+ -> Elaborate (CanonicalTerm ObjectId)
+ )
+ -> Elaborate (CanonicalTerm ObjectId)
+compilePredicateArguments subject arguments continuation = do
+ subjectDepth <- State.gets elaborationBinderDepth
+ compileTermsInProposition arguments \compiled -> do
+ subject' <- weakenElaboratedTermFrom subjectDepth subject
+ continuation subject' compiled
+
+compileVerb
+ :: CanonicalTerm ObjectId
+ -> Raw.Verb
+ -> Elaborate (CanonicalTerm ObjectId)
+compileVerb subject (Raw.Verb location item arguments) = do
+ let patterns = Raw.lexicalItemSgPlPattern item
+ compilePredicateArguments subject arguments \subject' compiled ->
+ applyResolvedPredicate
+ location
+ (SemanticVerb (Raw.sg patterns) (Raw.pl patterns))
+ (subject' : compiled)
+
+compileAdjective
+ :: CanonicalTerm ObjectId
+ -> Raw.Adj
+ -> Elaborate (CanonicalTerm ObjectId)
+compileAdjective subject (Raw.Adj location item arguments) =
+ compilePredicateArguments subject arguments \subject' compiled ->
+ applyResolvedPredicateChoice
+ location
+ ( SemanticRightAdjective (Raw.lexicalItemPattern item)
+ :| [SemanticLeftAdjective (Raw.lexicalItemPattern item)]
+ )
+ (subject' : compiled)
+
+compileLeftAdjective
+ :: CanonicalTerm ObjectId
+ -> Raw.AdjL
+ -> Elaborate (CanonicalTerm ObjectId)
+compileLeftAdjective subject (Raw.AdjL location item arguments) =
+ compilePredicateArguments subject arguments \subject' compiled ->
+ applyResolvedPredicate
+ location
+ (SemanticLeftAdjective (Raw.lexicalItemPattern item))
+ (subject' : compiled)
+
+compileRightAttribute
+ :: CanonicalTerm ObjectId
+ -> Raw.AdjR
+ -> Elaborate (CanonicalTerm ObjectId)
+compileRightAttribute subject = \case
+ Raw.AdjR location item arguments ->
+ compilePredicateArguments subject arguments \subject' compiled ->
+ applyResolvedPredicate
+ location
+ (SemanticRightAdjective (Raw.lexicalItemPattern item))
+ (subject' : compiled)
+ Raw.AttrRThat verbPhrase ->
+ compileVerbPhrase subject verbPhrase
+
+compileNoun
+ :: CanonicalTerm ObjectId
+ -> Raw.Noun
+ -> Elaborate (CanonicalTerm ObjectId)
+compileNoun subject (Raw.Noun location item arguments)
+ | Lexicon.isBuiltinSetNoun item =
+ pure logicalTruth
+ | otherwise = do
+ let patterns = Raw.lexicalItemSgPlPattern item
+ key = SemanticNoun (Raw.sg patterns) (Raw.pl patterns)
+ compilePredicateArguments subject arguments \subject' compiled ->
+ case fixedSemanticMeaning key of
+ Just (FixedIntrinsic Member) ->
+ case compiled of
+ [set] ->
+ compileMembership
+ location Raw.Positive subject' set
+ _ ->
+ impossible
+ "the fixed element noun does not have one argument"
+ Just (FixedIntrinsic intrinsic) -> do
+ (term, actual) <-
+ applyTyped
+ location
+ (CIntrinsic intrinsic)
+ (coreIntrinsicType intrinsic)
+ ((\argument -> (argument, TySet))
+ <$> (subject' : compiled))
+ unless (actual == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition location actual))
+ pure term
+ Just{} ->
+ impossible "a fixed noun is not a predicate intrinsic"
+ Nothing ->
+ applyResolvedPredicate
+ location key (subject' : compiled)
+
+compileNounPhraseConstraints
+ :: [CanonicalTerm ObjectId]
+ -> [Raw.AdjL]
+ -> Raw.Noun
+ -> [Raw.AdjR]
+ -> Maybe Raw.Stmt
+ -> Elaborate (CanonicalTerm ObjectId)
+compileNounPhraseConstraints subjects left noun right suchThat = do
+ nounConstraints <- traverse (`compileNoun` noun) subjects
+ leftConstraints <- concat
+ <$> traverse
+ (\subject -> traverse (compileLeftAdjective subject) left)
+ subjects
+ rightConstraints <- concat
+ <$> traverse
+ (\subject -> traverse (compileRightAttribute subject) right)
+ subjects
+ suchThatConstraint <- traverse compileStatement suchThat
+ pure
+ (logicalConjunction
+ ( nounConstraints
+ <> leftConstraints
+ <> rightConstraints
+ <> maybeToList suchThatConstraint
+ ))
+
+compileNounPhraseMaybe
+ :: CanonicalTerm ObjectId
+ -> Raw.NounPhrase Maybe
+ -> Elaborate (CanonicalTerm ObjectId)
+compileNounPhraseMaybe subject
+ (Raw.NounPhrase left noun named right suchThat) =
+ case named of
+ Nothing ->
+ compileNounPhraseConstraints
+ [subject] left noun right suchThat
+ Just variable -> do
+ abstracted <-
+ withSetBinders (variable :| [])
+ (compileNounPhraseConstraints
+ [CBound 0] left noun right suchThat)
+ pure (instantiateCanonical subject abstracted)
+
+compileExistentialNounPhrase
+ :: Raw.NounPhrase []
+ -> Elaborate (CanonicalTerm ObjectId)
+compileExistentialNounPhrase
+ (Raw.NounPhrase left noun variables right suchThat) =
+ case NonEmpty.nonEmpty variables of
+ Just binders ->
+ withSetBinders binders do
+ subjects <- traverse compileIntroducedVariable binders
+ constraints <-
+ compileNounPhraseConstraints
+ (toList subjects) left noun right suchThat
+ pure
+ (foldr
+ (const logicalExists)
+ constraints
+ binders)
+ Nothing ->
+ withAnonymousSetBinder \subject ->
+ logicalExists
+ <$> compileNounPhraseConstraints
+ [subject] left noun right suchThat
+
+compileQuantifiedNounPhrase
+ :: Raw.Quantifier
+ -> Raw.NounPhrase []
+ -> Raw.Stmt
+ -> Elaborate (CanonicalTerm ObjectId)
+compileQuantifiedNounPhrase quantifier
+ (Raw.NounPhrase left noun variables right suchThat)
+ statement =
+ case NonEmpty.nonEmpty variables of
+ Just binders ->
+ withSetBinders binders do
+ subjects <- traverse compileIntroducedVariable binders
+ constraints <-
+ compileNounPhraseConstraints
+ (toList subjects) left noun right suchThat
+ body <- compileStatement statement
+ pure
+ (quantifyNounPhrase
+ quantifier
+ (length (toList binders))
+ constraints
+ body)
+ Nothing ->
+ withAnonymousSetBinder \subject -> do
+ constraints <-
+ compileNounPhraseConstraints
+ [subject] left noun right suchThat
+ body <- compileStatement statement
+ pure (quantifyNounPhrase quantifier 1 constraints body)
+
+quantifyNounPhrase
+ :: Raw.Quantifier
+ -> Int
+ -> CanonicalTerm ObjectId
+ -> CanonicalTerm ObjectId
+ -> CanonicalTerm ObjectId
+quantifyNounPhrase quantifier binderCount constraints body =
+ case quantifier of
+ Raw.Universally ->
+ quantify
+ (if constraints == logicalTruth
+ then body
+ else CImp constraints body)
+ Raw.Existentially ->
+ quantify
+ (if constraints == logicalTruth
+ then body
+ else logicalAnd constraints body)
+ Raw.Nonexistentially ->
+ logicalNot
+ (quantify
+ (if constraints == logicalTruth
+ then body
+ else logicalAnd constraints body))
+ where
+ quantify scoped =
+ foldr (const binder) scoped [1 .. binderCount]
+ binder = case quantifier of
+ Raw.Universally -> CForall TySet
+ Raw.Existentially -> logicalExists
+ Raw.Nonexistentially -> logicalExists
+
+withAnonymousSetBinder
+ :: (CanonicalTerm ObjectId -> Elaborate value)
+ -> Elaborate value
+withAnonymousSetBinder action = do
+ outer <- State.gets elaborationBinders
+ outerDepth <- State.gets elaborationBinderDepth
+ outerStructures <- State.gets elaborationStructures
+ outerContextual <- State.gets elaborationContextualBinder
+ State.modify' \state ->
+ state
+ { elaborationBinders = (+ 1) <$> outer
+ , elaborationBinderDepth = outerDepth + 1
+ , elaborationStructures =
+ Map.mapKeysMonotonic (+ 1) outerStructures
+ , elaborationContextualBinder = (+ 1) <$> outerContextual
+ }
+ result <- action (CBound 0)
+ State.modify' \state ->
+ state
+ { elaborationBinders = outer
+ , elaborationBinderDepth = outerDepth
+ , elaborationStructures = outerStructures
+ , elaborationContextualBinder = outerContextual
+ }
+ pure result
+
+withSetBinders
+ :: NonEmpty Raw.VarSymbol
+ -> Elaborate value
+ -> Elaborate value
+withSetBinders variables action = do
+ outer <- State.gets elaborationBinders
+ outerDepth <- State.gets elaborationBinderDepth
+ outerStructures <- State.gets elaborationStructures
+ outerContextual <- State.gets elaborationContextualBinder
+ case firstDuplicate (toList variables) of
+ Just duplicate ->
+ Except.throwError
+ (ExactDuplicateLocalBinder
+ (locate duplicate)
+ duplicate)
+ Nothing -> pure ()
+ case find (`Map.member` outer) (toList variables) of
+ Just shadowed ->
+ Except.throwError
+ (ExactDuplicateLocalBinder
+ (locate shadowed)
+ shadowed)
+ Nothing -> pure ()
+ let binderCount = fromIntegral (length (toList variables))
+ shifted = (+ binderCount) <$> outer
+ introduced =
+ Map.fromList
+ (zip
+ (toList variables)
+ (reverse [0 .. binderCount - 1]))
+ State.modify' \state ->
+ state
+ { elaborationBinders = introduced <> shifted
+ , elaborationBinderDepth = outerDepth + binderCount
+ , elaborationStructures =
+ Map.mapKeysMonotonic (+ binderCount) outerStructures
+ , elaborationContextualBinder =
+ (+ binderCount) <$> outerContextual
+ }
+ result <- action
+ State.modify' \state ->
+ state
+ { elaborationBinders = outer
+ , elaborationBinderDepth = outerDepth
+ , elaborationStructures = outerStructures
+ , elaborationContextualBinder = outerContextual
+ }
+ pure result
+
+compileFormula
+ :: Raw.Formula
+ -> Elaborate (CanonicalTerm ObjectId)
+compileFormula = \case
+ Raw.FormulaChain chain ->
+ compileRelationChain chain
+ Raw.PropositionalConstant _ Raw.IsBottom ->
+ pure CFalsum
+ Raw.PropositionalConstant _ Raw.IsTop ->
+ pure (CImp CFalsum CFalsum)
+ Raw.FormulaNeg _ formula ->
+ logicalNot <$> compileFormula formula
+ Raw.FormulaPredicate
+ location
+ (Raw.PrefixPredicate command arity)
+ _marker
+ arguments -> do
+ compiled <- traverse compileExpression arguments
+ (term, actual) <-
+ applyResolvedTyped
+ location
+ (SemanticPrefixPredicate
+ command
+ (fromIntegral arity))
+ (toList compiled)
+ unless (actual == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition location actual))
+ pure term
+ Raw.Connected location connective left right ->
+ compileConnective
+ location
+ connective
+ compileFormula
+ left
+ right
+ Raw.FormulaQuantified
+ _location quantifier variables bound formula ->
+ compileSymbolicQuantified
+ quantifier variables bound Nothing (compileFormula formula)
+compileConnective
+ :: Location
+ -> Raw.Connective
+ -> (input -> Elaborate (CanonicalTerm ObjectId))
+ -> input
+ -> input
+ -> Elaborate (CanonicalTerm ObjectId)
+compileConnective _location connective compile left right = do
+ left' <- compile left
+ right' <- compile right
+ case connective of
+ Raw.Conjunction ->
+ pure (logicalAnd left' right')
+ Raw.Disjunction ->
+ pure (logicalOr left' right')
+ Raw.Implication ->
+ pure (CImp left' right')
+ Raw.Equivalence ->
+ pure (CEq TyProp left' right')
+ Raw.ExclusiveOr ->
+ pure
+ (logicalAnd
+ (logicalOr left' right')
+ (logicalNot (logicalAnd left' right')))
+ Raw.NegatedDisjunction ->
+ pure (logicalNot (logicalOr left' right'))
+
+logicalAnd
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+logicalAnd left right =
+ logicalNot (CImp left (logicalNot right))
+
+logicalTruth :: CanonicalTerm global
+logicalTruth =
+ CImp CFalsum CFalsum
+
+logicalConjunction
+ :: (Foldable collection, Eq global)
+ => collection (CanonicalTerm global)
+ -> CanonicalTerm global
+logicalConjunction =
+ foldr combine logicalTruth
+ where
+ combine proposition remaining
+ | proposition == logicalTruth = remaining
+ | remaining == logicalTruth = proposition
+ | otherwise = logicalAnd proposition remaining
+
+logicalOr
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+logicalOr left right =
+ CImp (logicalNot left) right
+
+logicalExists
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+logicalExists body =
+ logicalNot (CForall TySet (logicalNot body))
+
+compileAtomicRelation
+ :: NonEmpty Raw.Expr
+ -> Raw.Relation
+ -> NonEmpty Raw.Expr
+ -> Elaborate (CanonicalTerm ObjectId)
+compileAtomicRelation left relation right =
+ case (toList left, relation, toList right) of
+ ([leftExpression], Raw.Relation location symbol parameters, [rightExpression]) -> do
+ let key =
+ SemanticRelation
+ (Raw.relationSymbolToken symbol)
+ (Raw.relationSymbolParameterArity symbol)
+ case fixedSemanticMeaning key of
+ Just FixedEquality
+ | null parameters -> do
+ left' <- compileExpressionAsSet leftExpression
+ right' <- compileExpressionAsSet rightExpression
+ pure (CEq TySet left' right')
+ | otherwise ->
+ Except.throwError
+ (ExactUnsupportedDeclarationBody location)
+ Just FixedDisequality
+ | null parameters -> do
+ left' <- compileExpressionAsSet leftExpression
+ right' <- compileExpressionAsSet rightExpression
+ pure (logicalNot (CEq TySet left' right'))
+ | otherwise ->
+ Except.throwError
+ (ExactUnsupportedDeclarationBody location)
+ Just (FixedIntrinsic Member)
+ | null parameters -> do
+ left' <- compileExpressionAsSet leftExpression
+ right' <- compileExpressionAsSet rightExpression
+ compileMembership
+ location Raw.Positive left' right'
+ Just (FixedNegatedIntrinsic Member)
+ | null parameters -> do
+ left' <- compileExpressionAsSet leftExpression
+ right' <- compileExpressionAsSet rightExpression
+ compileMembership
+ location Raw.Negative left' right'
+ Just (FixedIntrinsic intrinsic) -> do
+ compiled <- traverse compileExpression
+ (parameters <> [leftExpression, rightExpression])
+ (term, actual) <-
+ applyTyped
+ location
+ (CIntrinsic intrinsic)
+ (coreIntrinsicType intrinsic)
+ compiled
+ unless (actual == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition location actual))
+ pure term
+ Just (FixedNegatedIntrinsic intrinsic) -> do
+ compiled <- traverse compileExpression
+ (parameters <> [leftExpression, rightExpression])
+ (term, actual) <-
+ applyTyped
+ location
+ (CIntrinsic intrinsic)
+ (coreIntrinsicType intrinsic)
+ compiled
+ unless (actual == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition location actual))
+ pure (logicalNot term)
+ Nothing -> do
+ compiled <- traverse compileExpression
+ (parameters <> [leftExpression, rightExpression])
+ (term, actual) <-
+ applyResolvedTyped location key compiled
+ unless (actual == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition location actual))
+ pure term
+ ([leftExpression], Raw.RelationExpr location expression, [rightExpression]) -> do
+ left' <- compileExpressionAsSet leftExpression
+ right' <- compileExpressionAsSet rightExpression
+ compileRelationExpression location expression left' right'
+ _ ->
+ Except.throwError
+ (ExactUnsupportedDeclarationBody (locate relation))
+
+compileRelationExpression
+ :: Location
+ -> Raw.Expr
+ -> CanonicalTerm ObjectId
+ -> CanonicalTerm ObjectId
+ -> Elaborate (CanonicalTerm ObjectId)
+compileRelationExpression location expression left right = do
+ relation <- compileExpressionAsSet expression
+ pair <-
+ applyResolved
+ location
+ (SemanticExpressionFunction
+ (Raw.mixfixPattern Raw.PairSymbol))
+ [left, right]
+ compileMembership location Raw.Positive pair relation
+
+compileRelationChain
+ :: Raw.Chain
+ -> Elaborate (CanonicalTerm ObjectId)
+compileRelationChain chain =
+ logicalConjunction <$> traverse compileLink (chainLinks chain)
+ where
+ compileLink (sign, relation, left, right) = do
+ proposition <-
+ compileAtomicRelation
+ (left :| []) relation (right :| [])
+ pure case sign of
+ Raw.Positive -> proposition
+ Raw.Negative -> logicalNot proposition
+
+ chainLinks = \case
+ Raw.ChainBase left sign relation right ->
+ [ (sign, relation, leftExpression, rightExpression)
+ | leftExpression <- toList left
+ , rightExpression <- toList right
+ ]
+ Raw.ChainCons left sign relation rest ->
+ let firstRight = chainFirstLeft rest
+ in
+ [ (sign, relation, leftExpression, rightExpression)
+ | leftExpression <- toList left
+ , rightExpression <- toList firstRight
+ ]
+ <> chainLinks rest
+
+ chainFirstLeft = \case
+ Raw.ChainBase left _sign _relation _right -> left
+ Raw.ChainCons left _sign _relation _rest -> left
+
+applyResolved
+ :: Location
+ -> SemanticGlobalKey
+ -> [CanonicalTerm ObjectId]
+ -> Elaborate (CanonicalTerm ObjectId)
+applyResolved location key arguments = do
+ (term, actual) <-
+ applyResolvedTyped
+ location key ((\argument -> (argument, TySet)) <$> arguments)
+ unless (actual == TySet)
+ (Except.throwError
+ (ExactExpressionExpectedSet location actual))
+ pure term
+
+applyResolvedPredicate
+ :: Location
+ -> SemanticGlobalKey
+ -> [CanonicalTerm ObjectId]
+ -> Elaborate (CanonicalTerm ObjectId)
+applyResolvedPredicate location key =
+ applyResolvedPredicateChoice location (key :| [])
+
+applyResolvedPredicateChoice
+ :: Location
+ -> NonEmpty SemanticGlobalKey
+ -> [CanonicalTerm ObjectId]
+ -> Elaborate (CanonicalTerm ObjectId)
+applyResolvedPredicateChoice location keys arguments = do
+ case firstFixedMeaning (toList keys) of
+ Just meaning ->
+ maybe
+ (impossible
+ "a fixed equality predicate has an invalid source arity")
+ pure
+ (lowerFixedEqualityPredicate meaning arguments)
+ Nothing -> do
+ visible <- for (toList keys) \key -> do
+ found <-
+ State.lift
+ (Except.lift
+ (Declaration.resolveVisibleGlobalLowering key))
+ pure ((\target -> (key, target)) <$> found)
+ case catMaybes visible of
+ [(key, _target)] -> do
+ (term, actual) <-
+ applyResolvedTyped
+ location key
+ ((\argument -> (argument, TySet)) <$> arguments)
+ unless (actual == TyProp)
+ (Except.throwError
+ (ExactFormulaExpectedProposition location actual))
+ pure term
+ [] ->
+ Except.throwError
+ (ExactGlobalNotVisible location (NonEmpty.head keys))
+ _ ->
+ impossible
+ "one adjective surface resolves to several exact globals"
+ where
+ firstFixedMeaning =
+ foldr
+ (\key found -> fixedSemanticMeaning key <|> found)
+ Nothing
+
+applyResolvedTyped
+ :: Location
+ -> SemanticGlobalKey
+ -> [(CanonicalTerm ObjectId, CoreType)]
+ -> Elaborate (CanonicalTerm ObjectId, CoreType)
+applyResolvedTyped location key arguments = do
+ visible <-
+ State.lift
+ (Except.lift
+ (Declaration.resolveVisibleGlobalContentLowering key))
+ (target, content, dependencies) <-
+ maybe
+ (Except.throwError (ExactGlobalNotVisible location key))
+ pure
+ visible
+ case target of
+ GlobalReference identity -> do
+ let coreType = objectContentType content
+ State.modify' \state ->
+ state
+ { elaborationGlobals =
+ Map.insert
+ identity coreType
+ (elaborationGlobals state)
+ }
+ applyTyped location (CGlobal identity) coreType arguments
+ TransparentExpansion _identity ->
+ case content of
+ TransparentObjectContent _theory coreType body -> do
+ State.modify' \state ->
+ state
+ { elaborationGlobals =
+ Map.union
+ dependencies
+ (elaborationGlobals state)
+ }
+ applyExpandedTyped
+ location body coreType arguments
+ _ ->
+ impossible
+ "validated transparent expansion has opaque content"
+ ContextualTransparentExpansion _identity requirements ->
+ case content of
+ TransparentObjectContent _theory coreType body -> do
+ State.modify' \state ->
+ state
+ { elaborationGlobals =
+ Map.union
+ dependencies
+ (elaborationGlobals state)
+ }
+ contextArgument <-
+ resolveContextualExpansionArgument
+ location key requirements
+ applyExpandedTyped
+ location body coreType
+ ((contextArgument, TySet) : arguments)
+ _ ->
+ impossible
+ "validated contextual expansion has opaque content"
+
+resolveContextualExpansionArgument
+ :: Location
+ -> SemanticGlobalKey
+ -> Map.Map Raw.StructSymbol ObjectId
+ -> Elaborate (CanonicalTerm ObjectId)
+resolveContextualExpansionArgument location key requirements = do
+ contextual <- State.gets elaborationContextualBinder
+ case contextual of
+ Just index -> do
+ traverse_
+ (uncurry (recordContextualRequirement location))
+ (Map.toAscList requirements)
+ pure (CBound index)
+ Nothing -> do
+ structures <- State.gets elaborationStructures
+ case
+ [ CBound index
+ | (index, structure) <- Map.toAscList structures
+ , all
+ (\(symbol, object) ->
+ structureAnnotationOperation symbol structure
+ == Just object)
+ (Map.toAscList requirements)
+ ] of
+ firstMatch : _ -> pure firstMatch
+ [] ->
+ Except.throwError
+ (ExactContextualExpansionNotAvailable location key)
+
+applyExpandedTyped
+ :: Location
+ -> CanonicalTerm ObjectId
+ -> CoreType
+ -> [(CanonicalTerm ObjectId, CoreType)]
+ -> Elaborate (CanonicalTerm ObjectId, CoreType)
+applyExpandedTyped location body coreType arguments =
+ foldM step (body, coreType) arguments
+ where
+ step (current, currentType) (argument, argumentType) =
+ case currentType of
+ TyArrow expected result
+ | expected == argumentType ->
+ pure
+ ( case current of
+ CLam binderType lambdaBody
+ | binderType == expected ->
+ instantiateCanonical
+ argument lambdaBody
+ _ -> CApp current argument
+ , result
+ )
+ | otherwise ->
+ Except.throwError
+ (ExactApplicationArgumentMismatch
+ location expected argumentType)
+ actual ->
+ Except.throwError
+ (ExactApplicationExpectedFunction location actual)
+
+applyTyped
+ :: Location
+ -> CanonicalTerm ObjectId
+ -> CoreType
+ -> [(CanonicalTerm ObjectId, CoreType)]
+ -> Elaborate (CanonicalTerm ObjectId, CoreType)
+applyTyped location function functionType arguments =
+ foldM step (function, functionType) arguments
+ where
+ step (currentFunction, currentType) (argument, argumentType) =
+ case currentType of
+ TyArrow expected result
+ | expected == argumentType ->
+ pure (CApp currentFunction argument, result)
+ | otherwise ->
+ Except.throwError
+ (ExactApplicationArgumentMismatch
+ location expected argumentType)
+ actual ->
+ Except.throwError
+ (ExactApplicationExpectedFunction location actual)
+
+logicalNot :: CanonicalTerm global -> CanonicalTerm global
+logicalNot proposition =
+ CImp proposition CFalsum
+
+ensureExpressionKey
+ :: MonadError ExactCompileError monad
+ => Location
+ -> Raw.FunctionSymbol
+ -> SemanticGlobalKey
+ -> monad ()
+ensureExpressionKey location symbol key =
+ unless
+ (key == SemanticExpressionFunction (Raw.mixfixPattern symbol))
+ (throwError (ExactDeclarationHeadMismatch location))
+
+ensureAdjectiveKey
+ :: MonadError ExactCompileError monad
+ => Location
+ -> Raw.LexicalItem
+ -> SemanticGlobalKey
+ -> monad ()
+ensureAdjectiveKey location item key =
+ unless
+ ( key == SemanticLeftAdjective (Raw.lexicalItemPattern item)
+ || key == SemanticRightAdjective (Raw.lexicalItemPattern item)
+ )
+ (throwError (ExactDeclarationHeadMismatch location))
+
+ensureFunctionPhraseKey
+ :: MonadError ExactCompileError monad
+ => Location
+ -> Raw.LexicalItemSgPl
+ -> SemanticGlobalKey
+ -> monad ()
+ensureFunctionPhraseKey location item key =
+ let patterns = Raw.lexicalItemSgPlPattern item
+ in unless
+ (key == SemanticFunctionPhrase (Raw.sg patterns) (Raw.pl patterns))
+ (throwError (ExactDeclarationHeadMismatch location))
+
+ensureNounKey
+ :: MonadError ExactCompileError monad
+ => Location
+ -> Raw.LexicalItemSgPl
+ -> SemanticGlobalKey
+ -> monad ()
+ensureNounKey location item key =
+ let patterns = Raw.lexicalItemSgPlPattern item
+ in unless
+ (key == SemanticNoun (Raw.sg patterns) (Raw.pl patterns))
+ (throwError (ExactDeclarationHeadMismatch location))
+
+ensureVerbKey
+ :: MonadError ExactCompileError monad
+ => Location
+ -> Raw.LexicalItemSgPl
+ -> SemanticGlobalKey
+ -> monad ()
+ensureVerbKey location item key =
+ let patterns = Raw.lexicalItemSgPlPattern item
+ in unless
+ (key == SemanticVerb (Raw.sg patterns) (Raw.pl patterns))
+ (throwError (ExactDeclarationHeadMismatch location))
+
+ensureRelationKey
+ :: MonadError ExactCompileError monad
+ => Location
+ -> Raw.RelationSymbol
+ -> SemanticGlobalKey
+ -> monad ()
+ensureRelationKey location relation key =
+ unless
+ ( key
+ == SemanticRelation
+ (Raw.relationSymbolToken relation)
+ (Raw.relationSymbolParameterArity relation)
+ )
+ (throwError (ExactDeclarationHeadMismatch location))
+
+exactSetNounPhrase :: Raw.NounPhrase Maybe -> Bool
+exactSetNounPhrase = \case
+ Raw.NounPhrase
+ []
+ (Raw.Noun _ item [])
+ Nothing
+ []
+ Nothing ->
+ Lexicon.isBuiltinSetNoun item
+ _ -> False
+
+encodePreparedSyntax
+ :: ExactDeclarationFamily
+ -> PreparedHead
+ -> PreparedBody
+ -> Maybe SemanticName
+ -> ByteString
+encodePreparedSyntax
+ family (PreparedHead key _parameters coreType) body alias =
+ encodeCache do
+ putCacheTag case family of
+ ExactSignature -> 0x00
+ ExactAbbreviation -> 0x01
+ ExactDefinition -> 0x02
+ putSemanticGlobalKeyCache key
+ putCoreTypeCache coreType
+ case body of
+ OpaqueBody -> putCacheTag 0x00
+ TransparentBody canonical _construction -> do
+ putCacheTag 0x01
+ putCanonicalTermCache putObjectIdCache canonical
+ ContextualTransparentBody requirements canonical -> do
+ putCacheTag 0x02
+ putCanonicalCacheMap
+ (\(Raw.StructSymbol symbol) -> putCacheText symbol)
+ putObjectIdCache
+ requirements
+ putCanonicalTermCache putObjectIdCache canonical
+ putCacheMaybe
+ (putCacheText . semanticNameText)
+ alias
+
+encodePreparedSourceAxiom
+ :: ScopedCheckedCore ObjectId
+ -> SemanticName
+ -> ByteString
+encodePreparedSourceAxiom proposition alias =
+ encodeCache do
+ putCacheTag 0x03
+ putCanonicalTermCache putObjectIdCache
+ (scopedCoreTerm proposition)
+ putCacheText (semanticNameText alias)
+
+definitionAlias :: Raw.Block -> Maybe SemanticName
+definitionAlias = \case
+ Raw.BlockDefn _location _title (Raw.Marker marker) _definition ->
+ Just (semanticName marker)
+ _ -> Nothing
+
+firstDuplicate :: Ord value => [value] -> Maybe value
+firstDuplicate =
+ go Set.empty
+ where
+ go _seen [] = Nothing
+ go seen (value : rest)
+ | value `Set.member` seen = Just value
+ | otherwise = go (Set.insert value seen) rest
diff --git a/source/Felix/Checking/Exact/Datatype.hs b/source/Felix/Checking/Exact/Datatype.hs
new file mode 100644
index 0000000..be943cd
--- /dev/null
+++ b/source/Felix/Checking/Exact/Datatype.hs
@@ -0,0 +1,751 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Exact preparation of deterministic datatype declarations.
+module Felix.Checking.Exact.Datatype
+ ( PreparedExactDatatype
+ , preparedExactDatatypeObjects
+ , preparedExactDatatypeBindings
+ , PreparedExactDatatypeFact
+ , preparedExactDatatypeFacts
+ , preparedExactDatatypeFactMarker
+ , preparedExactDatatypeFactTarget
+ , preparedExactDatatypeFactReference
+ , preparedExactDatatypeDescriptor
+ , prepareExactDatatype
+ , CheckedExactDatatypeAuthorization
+ , lowerPreparedExactDatatype
+ , authorizeCheckedExactDatatype
+ , ExactDatatypeError(..)
+ , exactDatatypeErrorLocation
+ , renderExactDatatypeError
+ ) where
+
+import Base hiding (Empty)
+import Felix.Checking.Authority
+import Felix.Checking.Core
+import Felix.Checking.Datatype qualified as Datatype
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Exact.Global qualified as ExactGlobal
+import Felix.Checking.Exact.Vocabulary
+import Felix.Checking.Identity
+import Felix.Checking.Semantic
+import Felix.Checking.Typed.Inductive qualified as Typed
+import Felix.Cache.Codec
+import Felix.Module
+import Felix.Meaning qualified as Meaning
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Interface
+import Felix.Syntax.Internal qualified as Internal
+
+import Control.Monad (unless, when)
+import Control.Monad.Except (ExceptT)
+import Control.Monad.Except qualified as Except
+import Data.Bifunctor (first)
+import Data.ByteString (ByteString)
+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 Numeric.Natural (Natural)
+
+
+data PreparedDatatypeObject = PreparedDatatypeObject
+ !Internal.Symbol
+ !SemanticGlobalKey
+ !CoreType
+ !ObjectId
+ !AssertedObject
+
+data PreparedExactDatatypeFact = PreparedExactDatatypeFact
+ !Internal.Marker
+ !(FrozenCheckedCore ObjectId)
+ !TheoremRef
+
+preparedExactDatatypeFactMarker
+ :: PreparedExactDatatypeFact
+ -> Internal.Marker
+preparedExactDatatypeFactMarker
+ (PreparedExactDatatypeFact marker _target _reference) =
+ marker
+
+preparedExactDatatypeFactTarget
+ :: PreparedExactDatatypeFact
+ -> FrozenCheckedCore ObjectId
+preparedExactDatatypeFactTarget
+ (PreparedExactDatatypeFact _marker target _reference) =
+ target
+
+preparedExactDatatypeFactReference
+ :: PreparedExactDatatypeFact
+ -> TheoremRef
+preparedExactDatatypeFactReference
+ (PreparedExactDatatypeFact _marker _target reference) =
+ reference
+
+data PreparedExactDatatype = PreparedExactDatatype
+ !Location
+ !DeclarationSyntaxId
+ !(NonEmpty PreparedDatatypeObject)
+ !(NonEmpty PreparedExactDatatypeFact)
+ !DatatypeCompilationDescriptor
+
+data CheckedExactDatatypeAuthorization =
+ CheckedExactDatatypeAuthorization
+ !DatatypeCompilationDescriptor
+ !ObjectId
+ !(NonEmpty ObjectId)
+
+preparedExactDatatypeObjects
+ :: PreparedExactDatatype
+ -> NonEmpty (ObjectId, CoreType)
+preparedExactDatatypeObjects
+ (PreparedExactDatatype _location _syntax objects _facts _descriptor) =
+ fmap
+ (\(PreparedDatatypeObject _symbol _key coreType identity _asserted) ->
+ (identity, coreType))
+ objects
+
+preparedExactDatatypeBindings
+ :: PreparedExactDatatype
+ -> NonEmpty (SemanticGlobalKey, SemanticGlobalTarget)
+preparedExactDatatypeBindings
+ (PreparedExactDatatype _location _syntax objects _facts _descriptor) =
+ fmap
+ (\(PreparedDatatypeObject _symbol key _coreType identity _asserted) ->
+ (key, GlobalReference identity))
+ objects
+
+preparedExactDatatypeFacts
+ :: PreparedExactDatatype
+ -> NonEmpty PreparedExactDatatypeFact
+preparedExactDatatypeFacts
+ (PreparedExactDatatype _location _syntax _objects facts _descriptor) =
+ facts
+
+preparedExactDatatypeDescriptor
+ :: PreparedExactDatatype
+ -> DatatypeCompilationDescriptor
+preparedExactDatatypeDescriptor
+ (PreparedExactDatatype _location _syntax _objects _facts descriptor) =
+ descriptor
+
+lowerPreparedExactDatatype
+ :: PreparedExactDatatype
+ -> Declaration.LoweringDriver
+ (Either
+ Declaration.DeclarationError
+ (Declaration.CheckedDeclaration
+ CheckedExactDatatypeAuthorization))
+lowerPreparedExactDatatype
+ (PreparedExactDatatype _location syntax objects facts descriptor) =
+ do
+ prepared <-
+ traverse
+ (\(PreparedExactDatatypeFact marker target _reference) ->
+ Declaration.prepareFrozenCandidateSpecLowering
+ assertedObjects
+ target
+ SearchEligible
+ [markerAlias marker])
+ facts
+ pure (buildChecked <$> sequence prepared)
+ where
+ assertedObjects =
+ toList
+ (fmap
+ (\(PreparedDatatypeObject
+ _symbol _key _coreType _identity asserted) -> asserted)
+ objects)
+ bindings =
+ toList
+ (fmap
+ (\(PreparedDatatypeObject
+ _symbol key _coreType identity _asserted) ->
+ semanticGlobalBinding key (GlobalReference identity))
+ objects)
+ carrier :| constructors = fmap objectIdentity objects
+ constructorIds =
+ case constructors of
+ firstConstructor : remainingConstructors ->
+ firstConstructor :| remainingConstructors
+ [] -> impossible "a prepared datatype has no constructor"
+ buildChecked specs =
+ Declaration.checkedCompiledDeclaration
+ syntax
+ assertedObjects
+ []
+ bindings
+ []
+ [ fmap
+ (\spec ->
+ Declaration.checkedCandidate
+ spec
+ (Declaration.checkedDatatypePlanning descriptor))
+ specs
+ ]
+ (CheckedExactDatatypeAuthorization
+ descriptor carrier constructorIds)
+
+ markerAlias (Internal.Marker name) = semanticName name
+
+ objectIdentity
+ (PreparedDatatypeObject
+ _symbol _key _coreType identity _asserted) =
+ identity
+
+authorizeCheckedExactDatatype
+ :: CheckedExactDatatypeAuthorization
+ -> [NonEmpty Declaration.ReservedCandidate]
+ -> Declaration.Declaration ()
+authorizeCheckedExactDatatype
+ (CheckedExactDatatypeAuthorization descriptor carrier constructors) =
+ \case
+ [candidates] ->
+ Declaration.authorizeDatatypeCompilationCandidates
+ descriptor carrier constructors candidates
+ stages ->
+ Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch
+ 1 (length stages))
+
+data ExactDatatypeError
+ = ExactDatatypeUnsupportedBlock !Location
+ | ExactDatatypeOccurrenceCountMismatch !Location !Int !Int
+ | ExactDatatypeOccurrenceMismatch !Location
+ | ExactDatatypeGlossFailed !Location !Meaning.GlossError
+ | ExactDatatypeInvalid !Location !Text
+ | ExactDatatypeDuplicateGlobal !Location !SemanticGlobalKey
+ | ExactDatatypeFixedSemanticCollision !Location !SemanticGlobalKey
+ | ExactDatatypeGlobalAlreadyVisible !Location !SemanticGlobalKey
+ | ExactDatatypeObjectAlreadyAvailable !Location !ObjectId
+ | ExactDatatypeGlobalResolutionFailed
+ !Location
+ !ExactGlobal.ExactGlobalResolutionError
+ | ExactDatatypeLoweringFailed !Location !Typed.TypedInductiveError
+ | ExactDatatypeExpectedSet !Location !CoreType
+ | ExactDatatypeExpectedProposition !Location !CoreType
+ deriving stock (Show, Eq)
+
+exactDatatypeErrorLocation :: ExactDatatypeError -> Location
+exactDatatypeErrorLocation = \case
+ ExactDatatypeUnsupportedBlock location -> location
+ ExactDatatypeOccurrenceCountMismatch location _expected _actual -> location
+ ExactDatatypeOccurrenceMismatch location -> location
+ ExactDatatypeGlossFailed location _failure -> location
+ ExactDatatypeInvalid location _message -> location
+ ExactDatatypeDuplicateGlobal location _key -> location
+ ExactDatatypeFixedSemanticCollision location _key -> location
+ ExactDatatypeGlobalAlreadyVisible location _key -> location
+ ExactDatatypeObjectAlreadyAvailable location _identity -> location
+ ExactDatatypeGlobalResolutionFailed location _failure -> location
+ ExactDatatypeLoweringFailed location _failure -> location
+ ExactDatatypeExpectedSet location _actual -> location
+ ExactDatatypeExpectedProposition location _actual -> location
+
+renderExactDatatypeError :: ExactDatatypeError -> Text
+renderExactDatatypeError failure =
+ locationToText (exactDatatypeErrorLocation failure)
+ <> ": "
+ <> case failure of
+ ExactDatatypeUnsupportedBlock{} ->
+ "this datatype source form is not supported by the typed checker"
+ ExactDatatypeOccurrenceCountMismatch _location expected actual ->
+ "the datatype has " <> shown actual
+ <> " syntax occurrences, but " <> shown expected
+ <> " are required"
+ ExactDatatypeOccurrenceMismatch{} ->
+ "a datatype syntax occurrence does not match its declaration"
+ ExactDatatypeGlossFailed _location glossFailure ->
+ "datatype elaboration failed: " <> shown glossFailure
+ ExactDatatypeInvalid _location message ->
+ "invalid datatype declaration: " <> message
+ ExactDatatypeDuplicateGlobal _location key ->
+ "the datatype declares the semantic key more than once: "
+ <> shown key
+ ExactDatatypeFixedSemanticCollision _location key ->
+ "the datatype collides with fixed semantics for " <> shown key
+ ExactDatatypeGlobalAlreadyVisible _location key ->
+ "the datatype global is already visible: " <> shown key
+ ExactDatatypeObjectAlreadyAvailable _location identity ->
+ "the datatype opaque object is already available: "
+ <> shown identity
+ ExactDatatypeGlobalResolutionFailed _location resolution ->
+ "datatype global resolution failed: " <> shown resolution
+ ExactDatatypeLoweringFailed _location typedFailure ->
+ "typed datatype lowering failed: " <> shown typedFailure
+ ExactDatatypeExpectedSet _location actual ->
+ "a datatype premise domain has type " <> shown actual
+ <> " instead of Set"
+ ExactDatatypeExpectedProposition _location actual ->
+ "a generated datatype fact has type " <> shown actual
+ <> " instead of Prop"
+ where
+ shown :: Show value => value -> Text
+ shown = Text.pack . show
+
+type Prepare =
+ ExceptT ExactDatatypeError (Declaration.LoweringDriver)
+
+type SourceOccurrence = (Location, Raw.Marker, CanonicalLexicalEntry)
+
+exactDatatypeInvalid
+ :: Location
+ -> Datatype.DatatypeValidationError
+ -> ExactDatatypeError
+exactDatatypeInvalid declarationLocation failure =
+ ExactDatatypeInvalid
+ (fromMaybe
+ declarationLocation
+ (Datatype.datatypeValidationErrorLocation failure))
+ (Datatype.renderDatatypeValidationError failure)
+
+prepareExactDatatype
+ :: Raw.Block
+ -> [SourceOccurrence]
+ -> Declaration.LoweringDriver
+ (Either ExactDatatypeError PreparedExactDatatype)
+prepareExactDatatype block occurrences =
+ Except.runExceptT do
+ (location, marker, rawDatatype) <-
+ case block of
+ Raw.BlockData blockLocation _title blockMarker datatype ->
+ pure (blockLocation, blockMarker, datatype)
+ _ ->
+ Except.throwError
+ (ExactDatatypeUnsupportedBlock (locate block))
+ keys <- validateOccurrences location marker rawDatatype occurrences
+ internal <-
+ case Meaning.meaning [block] of
+ Right
+ [Internal.BlockData
+ _internalLocation _internalMarker datatype] ->
+ pure datatype
+ Left failure ->
+ Except.throwError
+ (ExactDatatypeGlossFailed location failure)
+ Right _ ->
+ Except.throwError
+ (ExactDatatypeUnsupportedBlock location)
+ checked <-
+ -- The new opaque carrier has no semantic binding while its
+ -- declaration is prepared, so premise recursion cannot depend on
+ -- abbreviation expansion. Revisit this if forward aliases become
+ -- available.
+ Except.lift
+ (Datatype.prepareCheckedDatatype pure internal)
+ >>= Except.liftEither
+ . first (exactDatatypeInvalid location)
+ let symbols =
+ Datatype.checkedDatatypeHeadSymbol checked
+ :| toList
+ (Datatype.checkedDatatypeConstructorSymbols checked)
+ views = Datatype.checkedDatatypeClauseViews checked
+ arities =
+ 0 :| (length
+ . Datatype.checkedDatatypeClauseViewArguments
+ <$> toList views)
+ unless (NonEmpty.length symbols == NonEmpty.length keys)
+ (Except.throwError
+ (ExactDatatypeOccurrenceCountMismatch
+ location
+ (NonEmpty.length symbols)
+ (NonEmpty.length keys)))
+ validateKeys occurrences keys
+ slot <- Except.lift Declaration.nextDeclarationSlotLowering
+ theory <- Except.lift Declaration.currentTheoryLowering
+ objects <-
+ sequence
+ (NonEmpty.zipWith
+ (\index (occurrenceLocation, symbol, key, arity) ->
+ prepareObject
+ occurrenceLocation
+ slot theory index symbol key arity)
+ (0 :| [1 ..])
+ (NonEmpty.zipWith
+ (\occurrenceLocation (symbol, key, arity) ->
+ (occurrenceLocation, symbol, key, arity))
+ (validatedOccurrenceLocations occurrences)
+ (NonEmpty.zipWith
+ (\(symbol, key) arity ->
+ (symbol, key, arity))
+ (NonEmpty.zip symbols keys)
+ arities)))
+ let ownedSymbols = Set.fromList (toList symbols)
+ generated = Datatype.checkedDatatypeGeneratedFacts checked
+ externalSymbols =
+ (foldMap
+ (Internal.mentionedSymbols . snd)
+ generated
+ <> foldMap premiseSymbols views)
+ `Set.difference` ownedSymbols
+ external <-
+ Except.lift
+ (ExactGlobal.resolveExactSourceGlobals externalSymbols)
+ >>= Except.liftEither
+ . first (ExactDatatypeGlobalResolutionFailed location)
+ let (externalGlobals, externalTypes) = external
+ ownedGlobals =
+ Map.fromList
+ [ (symbol, Typed.SourceGlobal identity Nothing)
+ | PreparedDatatypeObject
+ symbol _key _coreType identity _asserted <-
+ toList objects
+ ]
+ ownedTypes =
+ Map.fromList
+ [ (identity, coreType)
+ | PreparedDatatypeObject
+ _symbol _key coreType identity _asserted <-
+ toList objects
+ ]
+ sourceGlobals = Map.union ownedGlobals externalGlobals
+ globalTypes = Map.union ownedTypes externalTypes
+ resolveGlobal = (`Map.lookup` sourceGlobals)
+ globalType identity =
+ fromMaybe
+ (impossible
+ "prepared datatype global has no checked type")
+ (Map.lookup identity globalTypes)
+ preparedClauses <-
+ Except.liftEither
+ (traverse
+ (prepareClause location globalType resolveGlobal)
+ views)
+ facts <-
+ Except.liftEither
+ (traverse
+ (prepareFact location theory globalType resolveGlobal)
+ generated)
+ let carrier = objectIdentity (NonEmpty.head objects)
+ constructors = objectIdentity <$> NonEmpty.tail objects
+ descriptor =
+ case NonEmpty.nonEmpty constructors of
+ Nothing ->
+ impossible
+ "a checked datatype has no constructors"
+ Just nonemptyConstructors ->
+ datatypeCompilationDescriptor
+ carrier
+ nonemptyConstructors
+ (preparedExactDatatypeFactReference
+ <$> toList facts)
+ syntax =
+ declarationSyntaxId
+ (encodePreparedDatatype
+ objects
+ preparedClauses
+ facts
+ descriptor)
+ pure
+ (PreparedExactDatatype
+ location
+ syntax
+ objects
+ facts
+ descriptor)
+ where
+ objectIdentity
+ (PreparedDatatypeObject _symbol _key _coreType identity _asserted) =
+ identity
+
+validateOccurrences
+ :: Location
+ -> Raw.Marker
+ -> Raw.Datatype
+ -> [SourceOccurrence]
+ -> Prepare (NonEmpty SemanticGlobalKey)
+validateOccurrences location marker datatype occurrences = do
+ expected <-
+ Except.liftEither
+ (expectedOccurrences location marker datatype)
+ unless (length occurrences == NonEmpty.length expected)
+ (Except.throwError
+ (ExactDatatypeOccurrenceCountMismatch
+ location
+ (NonEmpty.length expected)
+ (length occurrences)))
+ keys <-
+ sequence
+ (NonEmpty.zipWith validateOne expected
+ (case NonEmpty.nonEmpty occurrences of
+ Just nonempty -> nonempty
+ Nothing ->
+ impossible
+ "equal nonzero occurrence counts became empty"))
+ pure keys
+ where
+ validateOne
+ (expectedLocation, expectedMarker, expectedPattern)
+ (actualLocation, actualMarker, entry) =
+ case entry of
+ CanonicalExpressionFunction pat marker' _fixity
+ | actualMarker == expectedMarker
+ , marker' == expectedMarker
+ , pat == expectedPattern ->
+ pure
+ (SemanticExpressionFunction pat)
+ _ ->
+ Except.throwError
+ (ExactDatatypeOccurrenceMismatch
+ (bestLocation actualLocation expectedLocation))
+
+ bestLocation actual expected
+ | actual == Nowhere = expected
+ | otherwise = actual
+
+expectedOccurrences
+ :: Location
+ -> Raw.Marker
+ -> Raw.Datatype
+ -> Either
+ ExactDatatypeError
+ (NonEmpty (Location, Raw.Marker, Raw.Pattern))
+expectedOccurrences location blockMarker datatype = do
+ headOccurrence <-
+ expectedSymbol blockMarker (Raw.datatypeHeadExpr datatype)
+ clauses <-
+ traverse
+ (\clause ->
+ case Raw.datatypeClauseConstructorExpr clause of
+ Raw.ExprOp constructorLocation symbol _arguments ->
+ Right
+ ( constructorLocation
+ , Raw.mixfixMarker symbol
+ , Raw.mixfixPattern symbol
+ )
+ expression ->
+ Left
+ (ExactDatatypeOccurrenceMismatch
+ (locate expression)))
+ (Raw.datatypeClauses datatype)
+ pure (headOccurrence :| toList clauses)
+ where
+ expectedSymbol expectedMarker = \case
+ Raw.ExprOp symbolLocation symbol [] ->
+ Right
+ ( symbolLocation
+ , expectedMarker
+ , Raw.mixfixPattern symbol
+ )
+ expression ->
+ Left
+ (ExactDatatypeOccurrenceMismatch
+ (case locate expression of
+ Nowhere -> location
+ expressionLocation -> expressionLocation))
+
+validateKeys
+ :: [SourceOccurrence]
+ -> NonEmpty SemanticGlobalKey
+ -> Prepare ()
+validateKeys occurrences keys = do
+ case duplicateWithLocation of
+ Just (duplicate, duplicateLocation) ->
+ Except.throwError
+ (ExactDatatypeDuplicateGlobal
+ duplicateLocation
+ duplicate)
+ Nothing -> pure ()
+ traverse_ validateOne (NonEmpty.zip locations keys)
+ where
+ locations = validatedOccurrenceLocations occurrences
+
+ duplicateWithLocation =
+ go Set.empty
+ [ (key, occurrenceLocation)
+ | (key, (occurrenceLocation, _marker, _entry)) <-
+ zip (toList keys) occurrences
+ ]
+
+ go _seen [] = Nothing
+ go seen ((key, occurrenceLocation) : remaining)
+ | key `Set.member` seen = Just (key, occurrenceLocation)
+ | otherwise = go (Set.insert key seen) remaining
+
+ validateOne (occurrenceLocation, key) = do
+ when (isJust (fixedSemanticMeaning key))
+ (Except.throwError
+ (ExactDatatypeFixedSemanticCollision occurrenceLocation key))
+ visible <-
+ Except.lift
+ (Declaration.resolveVisibleGlobalLowering key)
+ when (isJust visible)
+ (Except.throwError
+ (ExactDatatypeGlobalAlreadyVisible occurrenceLocation key))
+
+validatedOccurrenceLocations
+ :: [SourceOccurrence]
+ -> NonEmpty Location
+validatedOccurrenceLocations occurrences =
+ case NonEmpty.nonEmpty
+ [ occurrenceLocation
+ | (occurrenceLocation, _marker, _entry) <- occurrences
+ ] of
+ Just nonempty -> nonempty
+ Nothing ->
+ impossible "validated datatype occurrences are empty"
+
+prepareObject
+ :: Location
+ -> DeclarationSlot
+ -> TheoryId
+ -> Natural
+ -> Internal.Symbol
+ -> SemanticGlobalKey
+ -> Int
+ -> Prepare PreparedDatatypeObject
+prepareObject location slot theory index symbol key arity = do
+ let coreType =
+ foldr (const (TyArrow TySet)) TySet [1 .. arity]
+ seed =
+ opaqueDeclarationSeed
+ (declarationSlotModule slot)
+ (declarationSlotOrdinal slot)
+ DatatypeDeclaration
+ (generatedObjectSlot index)
+ content = OpaqueObjectContent theory seed coreType
+ identity = opaqueObjectId theory seed coreType
+ available <- Except.lift (Declaration.objectAvailableLowering identity)
+ when available
+ (Except.throwError
+ (ExactDatatypeObjectAlreadyAvailable location identity))
+ pure
+ (PreparedDatatypeObject
+ symbol
+ key
+ coreType
+ identity
+ (assertedObject identity content))
+
+data PreparedClause = PreparedClause
+ !Internal.FunctionSymbol
+ ![PreparedPremise]
+
+data PreparedPremise
+ = PreparedRecursivePremise !(FrozenCheckedCore ObjectId)
+ | PreparedNonRecursivePremise !(FrozenCheckedCore ObjectId)
+
+prepareClause
+ :: Location
+ -> (ObjectId -> CoreType)
+ -> (Internal.Symbol -> Maybe (Typed.SourceGlobal ObjectId))
+ -> Datatype.CheckedDatatypeClauseView
+ -> Either ExactDatatypeError PreparedClause
+prepareClause location globalType resolveGlobal view =
+ PreparedClause
+ (Datatype.checkedDatatypeClauseViewConstructor view)
+ <$> traverse preparePremise
+ (Datatype.checkedDatatypeClauseViewPremises view)
+ where
+ preparePremise = \case
+ Datatype.CheckedRecursiveDatatypePremise _variable domain ->
+ PreparedRecursivePremise
+ <$> prepareDomain domain
+ Datatype.CheckedNonRecursiveDatatypePremise _variable domain ->
+ PreparedNonRecursivePremise
+ <$> prepareDomain domain
+
+ prepareDomain domain = do
+ checked <-
+ first (ExactDatatypeLoweringFailed location)
+ (Typed.prepareTypedClosedTerm
+ globalType
+ resolveGlobal
+ domain)
+ unless (frozenCoreType checked == TySet)
+ (Left
+ (ExactDatatypeExpectedSet
+ location
+ (frozenCoreType checked)))
+ pure checked
+
+prepareFact
+ :: Location
+ -> TheoryId
+ -> (ObjectId -> CoreType)
+ -> (Internal.Symbol -> Maybe (Typed.SourceGlobal ObjectId))
+ -> (Internal.Marker, Internal.Formula)
+ -> Either ExactDatatypeError PreparedExactDatatypeFact
+prepareFact location theory globalType resolveGlobal (marker, formula) = do
+ checked <-
+ first (ExactDatatypeLoweringFailed location)
+ (Typed.prepareTypedClosedFormula
+ globalType
+ resolveGlobal
+ formula)
+ unless (frozenCoreType checked == TyProp)
+ (Left
+ (ExactDatatypeExpectedProposition
+ location
+ (frozenCoreType checked)))
+ pure
+ (PreparedExactDatatypeFact
+ marker
+ checked
+ (theoremRef theory
+ (propositionIdOf (frozenCoreTerm checked))))
+
+premiseSymbols
+ :: Datatype.CheckedDatatypeClauseView
+ -> Set.Set Internal.Symbol
+premiseSymbols view =
+ foldMap symbols
+ (Datatype.checkedDatatypeClauseViewPremises view)
+ where
+ symbols = \case
+ Datatype.CheckedRecursiveDatatypePremise _variable domain ->
+ Internal.mentionedSymbols domain
+ Datatype.CheckedNonRecursiveDatatypePremise _variable domain ->
+ Internal.mentionedSymbols domain
+
+encodePreparedDatatype
+ :: NonEmpty PreparedDatatypeObject
+ -> NonEmpty PreparedClause
+ -> NonEmpty PreparedExactDatatypeFact
+ -> DatatypeCompilationDescriptor
+ -> ByteString
+encodePreparedDatatype objects clauses facts descriptor =
+ encodeCache do
+ putCacheTag 0x05
+ putCacheList putObject (toList objects)
+ putCacheList putClause (toList clauses)
+ putCacheList putFact (toList facts)
+ putDirectAuthorizationCache
+ (TrustedCompilation
+ (DatatypeCompilation descriptor))
+ where
+ putObject
+ (PreparedDatatypeObject
+ _symbol key coreType identity _asserted) = do
+ putSemanticGlobalKeyCache key
+ putCoreTypeCache coreType
+ putObjectIdCache identity
+
+ putClause (PreparedClause constructor premises) = do
+ putSemanticGlobalKeyCache
+ (SemanticExpressionFunction
+ (Raw.mixfixPattern constructor))
+ putCacheList putPremise premises
+
+ putPremise = \case
+ PreparedRecursivePremise domain -> do
+ putCacheTag 0x00
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm domain)
+ PreparedNonRecursivePremise domain -> do
+ putCacheTag 0x01
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm domain)
+
+ putFact fact = do
+ let Internal.Marker marker =
+ preparedExactDatatypeFactMarker fact
+ putCacheText marker
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm
+ (preparedExactDatatypeFactTarget fact))
+ putTheoremRefCache
+ (preparedExactDatatypeFactReference fact)
diff --git a/source/Felix/Checking/Exact/Global.hs b/source/Felix/Checking/Exact/Global.hs
new file mode 100644
index 0000000..d4040e6
--- /dev/null
+++ b/source/Felix/Checking/Exact/Global.hs
@@ -0,0 +1,116 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Exact resolution of source symbols to checked semantic globals.
+module Felix.Checking.Exact.Global
+ ( ExactGlobalResolutionError(..)
+ , resolveExactSourceGlobals
+ ) where
+
+import Base
+import Felix.Checking.Core
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Exact.Vocabulary
+import Felix.Checking.Identity
+import Felix.Checking.Semantic
+import Felix.Checking.Typed.Inductive qualified as Typed
+import Felix.Syntax.Internal qualified as Internal
+
+import Control.Monad (foldM)
+import Control.Monad.Except
+ ( liftEither
+ , runExceptT
+ , throwError
+ )
+import Control.Monad.Trans.Class (lift)
+import Data.Bifunctor (first)
+import Data.Map.Strict qualified as Map
+import Data.Maybe (catMaybes)
+import Data.Set qualified as Set
+
+
+data ExactGlobalResolutionError
+ = ExactGlobalNotVisible !Internal.Symbol
+ | ExactGlobalAmbiguous !Internal.Symbol
+ | ExactGlobalUnsupported !Internal.Symbol
+ | ExactGlobalContextualUnsupported !Internal.Symbol
+ | ExactGlobalContentInvalid !CoreCheckError
+ deriving stock (Show, Eq)
+
+resolveExactSourceGlobals
+ :: Set.Set Internal.Symbol
+ -> Declaration.LoweringDriver
+ (Either
+ ExactGlobalResolutionError
+ ( Map.Map
+ Internal.Symbol
+ (Typed.SourceGlobal ObjectId)
+ , Map.Map ObjectId CoreType
+ ))
+resolveExactSourceGlobals symbols =
+ runExceptT
+ (foldM resolve (Map.empty, Map.empty)
+ (Set.toAscList symbols))
+ where
+ resolve (resolved, types) symbol =
+ case classifyExactSymbol symbol of
+ ExactClosedLiteral ->
+ pure (resolved, types)
+ ExactFixedPrimitive _meaning ->
+ pure (resolved, types)
+ ExactUnsupportedSymbol ->
+ throwError (ExactGlobalUnsupported symbol)
+ ExactSourceGlobal keys -> do
+ matches <-
+ catMaybes
+ <$> traverse
+ (lift
+ . Declaration.resolveVisibleGlobalContentLowering)
+ (toList keys)
+ case matches of
+ [] ->
+ throwError (ExactGlobalNotVisible symbol)
+ [match] -> do
+ (source, sourceTypes) <-
+ liftEither (prepareSourceGlobal symbol match)
+ pure
+ ( Map.insert symbol source resolved
+ , Map.union sourceTypes types
+ )
+ _ ->
+ throwError (ExactGlobalAmbiguous symbol)
+
+prepareSourceGlobal
+ :: Internal.Symbol
+ -> ( SemanticGlobalTarget
+ , ObjectContent
+ , Map.Map ObjectId CoreType
+ )
+ -> Either
+ ExactGlobalResolutionError
+ (Typed.SourceGlobal ObjectId, Map.Map ObjectId CoreType)
+prepareSourceGlobal symbol (target, content, dependencies) = do
+ body <-
+ case target of
+ GlobalReference _identity ->
+ Right Nothing
+ TransparentExpansion _identity ->
+ case content of
+ TransparentObjectContent _theory _coreType canonical ->
+ Just
+ <$> first ExactGlobalContentInvalid
+ (checkCanonicalCore
+ (`Map.lookup` dependencies)
+ canonical)
+ _ ->
+ impossible
+ "validated transparent expansion has opaque content"
+ ContextualTransparentExpansion _identity _requirements ->
+ Left (ExactGlobalContextualUnsupported symbol)
+ let identity = semanticGlobalTargetObject target
+ types =
+ Map.insert
+ identity
+ (objectContentType content)
+ dependencies
+ pure (Typed.SourceGlobal identity body, types)
diff --git a/source/Felix/Checking/Exact/Inductive.hs b/source/Felix/Checking/Exact/Inductive.hs
new file mode 100644
index 0000000..5817b34
--- /dev/null
+++ b/source/Felix/Checking/Exact/Inductive.hs
@@ -0,0 +1,838 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Exact preparation and atomic publication of direct set inductives.
+module Felix.Checking.Exact.Inductive
+ ( PreparedExactInductive
+ , preparedExactInductiveCarrierId
+ , preparedExactInductiveCarrierType
+ , preparedExactInductiveCarrierBody
+ , preparedExactInductiveGuardTargets
+ , preparedExactInductiveFacts
+ , prepareExactInductive
+ , CheckedExactInductiveAuthorization
+ , lowerPreparedExactInductive
+ , authorizeCheckedExactInductive
+ , ExactInductiveError(..)
+ , exactInductiveErrorLocation
+ , renderExactInductiveError
+ ) where
+
+import Base hiding (Empty)
+import Felix.Checking.Authority
+import Felix.Checking.Core
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Exact.Global qualified as ExactGlobal
+import Felix.Checking.Exact.Vocabulary
+import Felix.Checking.Foundation
+import Felix.Checking.Identity
+import Felix.Checking.Semantic
+import Felix.Checking.Typed.Inductive qualified as Typed
+import Felix.Cache.Codec
+import Felix.Meaning qualified as Meaning
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Interface
+import Felix.Syntax.Internal qualified as Internal
+
+import Control.Monad (unless, when)
+import Control.Monad.Except (ExceptT)
+import Control.Monad.Except qualified as Except
+import Data.Bifunctor (first)
+import Data.ByteString (ByteString)
+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.Vector qualified as Vector
+
+
+data PreparedExactInductive = PreparedExactInductive
+ !Location
+ !SemanticGlobalKey
+ !ObjectId
+ !(Maybe AssertedObject)
+ !SemanticName
+ !DeclarationSyntaxId
+ !(Typed.PreparedTypedInductive ObjectId)
+ ![SemanticFactOccurrenceFingerprint]
+
+data CheckedExactInductiveAuthorization =
+ CheckedExactInductiveAuthorization
+ !ObjectId
+ !(Typed.PreparedTypedInductive ObjectId)
+ ![SemanticFactOccurrenceFingerprint]
+ ![(Location, Declaration.PreparedVampireObligation Void ())]
+
+preparedExactInductiveCarrierId
+ :: PreparedExactInductive
+ -> ObjectId
+preparedExactInductiveCarrierId
+ (PreparedExactInductive
+ _location _key identity _asserted _alias _syntax _typed _guards) =
+ identity
+
+preparedExactInductiveCarrierType
+ :: PreparedExactInductive
+ -> CoreType
+preparedExactInductiveCarrierType
+ (PreparedExactInductive
+ _location _key _identity _asserted _alias _syntax typed _guards) =
+ Typed.typedInductiveCarrierType typed
+
+preparedExactInductiveCarrierBody
+ :: PreparedExactInductive
+ -> FrozenCheckedCore ObjectId
+preparedExactInductiveCarrierBody
+ (PreparedExactInductive
+ _location _key _identity _asserted _alias _syntax typed _guards) =
+ Typed.typedInductiveCarrierBody typed
+
+preparedExactInductiveGuardTargets
+ :: PreparedExactInductive
+ -> Vector.Vector (FrozenCheckedCore ObjectId)
+preparedExactInductiveGuardTargets
+ (PreparedExactInductive
+ _location _key _identity _asserted _alias _syntax typed _guards) =
+ Typed.typedInductiveGuardTargets typed
+
+preparedExactInductiveFacts
+ :: PreparedExactInductive
+ -> NonEmpty (Typed.PreparedTypedInductiveFact ObjectId)
+preparedExactInductiveFacts
+ (PreparedExactInductive
+ _location _key _identity _asserted _alias _syntax typed _guards) =
+ Typed.typedInductiveFacts typed
+
+data ExactInductiveError
+ = ExactInductiveUnsupportedBlock !Location
+ | ExactInductiveOccurrenceMissing !Location
+ | ExactInductiveOccurrenceAmbiguous !Location
+ | ExactInductiveHeadMismatch !Location
+ | ExactInductiveGlossFailed !Location !Meaning.GlossError
+ | ExactInductiveDuplicateParameter !Location !Internal.VarSymbol
+ | ExactInductiveDomainFreeVariable !Location !Internal.VarSymbol
+ | ExactInductiveDomainMentionsCarrier !Location
+ | ExactInductiveResultShape !Location
+ | ExactInductiveResultMentionsCarrier !Location
+ | ExactInductiveRecursiveTermMentionsCarrier !Location
+ | ExactInductiveRecursiveCarrierWrongArguments !Location
+ | ExactInductiveRecursiveCarrierOutsideMembership !Location
+ | ExactInductiveUnsupportedRecursiveCarrierContext !Location
+ | ExactInductiveFixedSemanticCollision !Location !SemanticGlobalKey
+ | ExactInductiveGlobalAlreadyVisible !Location !SemanticGlobalKey
+ | ExactInductiveGlobalNotVisible !Location !Internal.Symbol
+ | ExactInductiveGlobalAmbiguous !Location !Internal.Symbol
+ | ExactInductiveUnsupportedSymbol !Location !Internal.Symbol
+ | ExactInductiveGlobalContentInvalid !Location !CoreCheckError
+ | ExactInductivePreparationFailed
+ !Location
+ !Typed.TypedInductiveError
+ | ExactInductiveGuardMissing !Location
+ | ExactInductiveGuardAmbiguous !Location
+ deriving stock (Show, Eq)
+
+exactInductiveErrorLocation :: ExactInductiveError -> Location
+exactInductiveErrorLocation = \case
+ ExactInductiveUnsupportedBlock location -> location
+ ExactInductiveOccurrenceMissing location -> location
+ ExactInductiveOccurrenceAmbiguous location -> location
+ ExactInductiveHeadMismatch location -> location
+ ExactInductiveGlossFailed location _failure -> location
+ ExactInductiveDuplicateParameter location _parameter -> location
+ ExactInductiveDomainFreeVariable location _variable -> location
+ ExactInductiveDomainMentionsCarrier location -> location
+ ExactInductiveResultShape location -> location
+ ExactInductiveResultMentionsCarrier location -> location
+ ExactInductiveRecursiveTermMentionsCarrier location -> location
+ ExactInductiveRecursiveCarrierWrongArguments location -> location
+ ExactInductiveRecursiveCarrierOutsideMembership location -> location
+ ExactInductiveUnsupportedRecursiveCarrierContext location -> location
+ ExactInductiveFixedSemanticCollision location _key -> location
+ ExactInductiveGlobalAlreadyVisible location _key -> location
+ ExactInductiveGlobalNotVisible location _symbol -> location
+ ExactInductiveGlobalAmbiguous location _symbol -> location
+ ExactInductiveUnsupportedSymbol location _symbol -> location
+ ExactInductiveGlobalContentInvalid location _failure -> location
+ ExactInductivePreparationFailed location _failure -> location
+ ExactInductiveGuardMissing location -> location
+ ExactInductiveGuardAmbiguous location -> location
+
+renderExactInductiveError :: ExactInductiveError -> Text
+renderExactInductiveError failure =
+ locationToText (exactInductiveErrorLocation failure)
+ <> ": "
+ <> case failure of
+ ExactInductiveUnsupportedBlock{} ->
+ "this inductive source form is not supported by the typed checker"
+ ExactInductiveOccurrenceMissing{} ->
+ "the inductive declaration has no associated syntax occurrence"
+ ExactInductiveOccurrenceAmbiguous{} ->
+ "the inductive declaration has more than one semantic head"
+ ExactInductiveHeadMismatch{} ->
+ "the inductive head does not match its syntax occurrence"
+ ExactInductiveGlossFailed _location glossFailure ->
+ "inductive elaboration failed: " <> shown glossFailure
+ ExactInductiveDuplicateParameter _location parameter ->
+ "the inductive parameter is repeated: " <> shown parameter
+ ExactInductiveDomainFreeVariable _location variable ->
+ "the inductive domain contains an unbound variable: "
+ <> shown variable
+ ExactInductiveDomainMentionsCarrier{} ->
+ "the inductive domain must be independent of its carrier"
+ ExactInductiveResultShape{} ->
+ "an inductive result must have the form t \\in F(args)"
+ ExactInductiveResultMentionsCarrier{} ->
+ "an inductive result term must not mention its carrier"
+ ExactInductiveRecursiveTermMentionsCarrier{} ->
+ "a recursive occurrence must be in the carrier of a membership premise"
+ ExactInductiveRecursiveCarrierWrongArguments{} ->
+ "the inductive carrier occurs with arguments other than its declared parameters"
+ ExactInductiveRecursiveCarrierOutsideMembership{} ->
+ "an inductive carrier occurrence must be in the set operand of a membership premise"
+ ExactInductiveUnsupportedRecursiveCarrierContext{} ->
+ "this recursive carrier context is outside the supported first-order set-term fragment"
+ ExactInductiveFixedSemanticCollision _location key ->
+ "the inductive carrier collides with fixed semantics for "
+ <> shown key
+ ExactInductiveGlobalAlreadyVisible _location key ->
+ "the inductive carrier is already visible: " <> shown key
+ ExactInductiveGlobalNotVisible _location symbol ->
+ "an inductive source symbol is not visible: " <> shown symbol
+ ExactInductiveGlobalAmbiguous _location symbol ->
+ "an inductive source symbol has more than one meaning: "
+ <> shown symbol
+ ExactInductiveUnsupportedSymbol _location symbol ->
+ "this inductive source symbol is not supported: "
+ <> shown symbol
+ ExactInductiveGlobalContentInvalid _location coreFailure ->
+ "an inductive global has invalid checked content: "
+ <> shown coreFailure
+ ExactInductivePreparationFailed _location typedFailure ->
+ "typed inductive preparation failed: " <> shown typedFailure
+ ExactInductiveGuardMissing{} ->
+ "an inductive domain guard has no visible authorized fact"
+ ExactInductiveGuardAmbiguous{} ->
+ "an inductive domain guard matches more than one visible fact"
+ where
+ shown :: Show value => value -> Text
+ shown = Text.pack . show
+
+type Prepare =
+ ExceptT
+ ExactInductiveError
+ (Declaration.LoweringDriver)
+
+prepareExactInductive
+ :: CheckedFoundation
+ -> Raw.Block
+ -> [CanonicalLexicalEntry]
+ -> Declaration.LoweringDriver
+ (Either ExactInductiveError PreparedExactInductive)
+prepareExactInductive foundation block entries =
+ Except.runExceptT do
+ (location, marker, rawInductive) <-
+ case block of
+ Raw.BlockInductive blockLocation _title blockMarker inductive ->
+ pure (blockLocation, blockMarker, inductive)
+ _ ->
+ Except.throwError
+ (ExactInductiveUnsupportedBlock (locate block))
+ key <- validateOccurrence location rawInductive entries
+ when
+ (isJust (fixedSemanticMeaning key))
+ (Except.throwError
+ (ExactInductiveFixedSemanticCollision location key))
+ visible <-
+ Except.lift
+ (Declaration.resolveVisibleGlobalLowering key)
+ when (isJust visible)
+ (Except.throwError
+ (ExactInductiveGlobalAlreadyVisible location key))
+ internal <-
+ case Meaning.meaning [block] of
+ Right
+ [Internal.BlockInductive
+ _internalLocation _internalMarker inductive] ->
+ pure inductive
+ Left failure ->
+ Except.throwError
+ (ExactInductiveGlossFailed location failure)
+ Right _ ->
+ Except.throwError
+ (ExactInductiveUnsupportedBlock location)
+ direct <-
+ Except.liftEither
+ (normalizeDirectInductive internal)
+ (sourceGlobals, globalTypes) <-
+ resolveSourceGlobals location internal direct
+ typed <-
+ Except.liftEither
+ (first
+ (ExactInductivePreparationFailed location)
+ (Typed.prepareTypedInductive
+ (requireGlobalType globalTypes)
+ foundation
+ (`Map.lookup` sourceGlobals)
+ marker
+ direct))
+ guards <-
+ traverse
+ (resolveGuard location)
+ (Vector.toList
+ (Typed.typedInductiveGuardTargets typed))
+ theory <- Except.lift Declaration.currentTheoryLowering
+ let carrierType = Typed.typedInductiveCarrierType typed
+ carrierBody = Typed.typedInductiveCarrierBody typed
+ carrierTerm = frozenCoreTerm carrierBody
+ identity =
+ transparentObjectId theory carrierType carrierTerm
+ content =
+ TransparentObjectContent theory carrierType carrierTerm
+ alias = case marker of
+ Raw.Marker name -> semanticName name
+ available <-
+ Except.lift
+ (Declaration.objectAvailableLowering identity)
+ let asserted
+ | available = Nothing
+ | otherwise = Just (assertedObject identity content)
+ syntax =
+ declarationSyntaxId
+ (encodePreparedInductive key alias typed)
+ pure
+ (PreparedExactInductive
+ location
+ key
+ identity
+ asserted
+ alias
+ syntax
+ typed
+ guards)
+ where
+ requireGlobalType types identity =
+ fromMaybe
+ (impossible
+ "prepared inductive global has no checked type")
+ (Map.lookup identity types)
+
+validateOccurrence
+ :: Location
+ -> Raw.Inductive
+ -> [CanonicalLexicalEntry]
+ -> Prepare SemanticGlobalKey
+validateOccurrence location rawInductive entries = do
+ entry <-
+ case entries of
+ [] ->
+ Except.throwError
+ (ExactInductiveOccurrenceMissing location)
+ [single] -> pure single
+ _ ->
+ Except.throwError
+ (ExactInductiveOccurrenceAmbiguous location)
+ key <-
+ maybe
+ (Except.throwError
+ (ExactInductiveHeadMismatch location))
+ pure
+ (semanticGlobalKeyFromLexicalEntry entry)
+ let Raw.SymbolPattern headSymbol _parameters =
+ Raw.inductiveSymbolPattern rawInductive
+ expected =
+ SemanticExpressionFunction
+ (Raw.mixfixPattern headSymbol)
+ unless (key == expected)
+ (Except.throwError
+ (ExactInductiveHeadMismatch location))
+ pure key
+
+normalizeDirectInductive
+ :: Internal.Inductive
+ -> Either ExactInductiveError Typed.DirectInductive
+normalizeDirectInductive inductive = do
+ case firstDuplicate (Internal.inductiveParams inductive) of
+ Just duplicate ->
+ Left
+ (ExactInductiveDuplicateParameter
+ (locate duplicate)
+ duplicate)
+ Nothing -> pure ()
+ let parameters = Internal.inductiveParams inductive
+ parameterSet = Set.fromList parameters
+ domain = Internal.inductiveDomain inductive
+ carrier = Internal.inductiveSymbol inductive
+ domainVariables =
+ orderedUnique
+ (toList domain)
+ case find (`Set.notMember` parameterSet) domainVariables of
+ Just variable ->
+ Left
+ (ExactInductiveDomainFreeVariable
+ (locate variable)
+ variable)
+ Nothing -> pure ()
+ when
+ (Internal.SymbolMixfix carrier
+ `Set.member` Internal.mentionedSymbols domain)
+ (Left
+ (ExactInductiveDomainMentionsCarrier
+ (termLocation domain)))
+ clauses <-
+ traverse
+ (normalizeClause carrier parameters)
+ (Internal.inductiveIntros inductive)
+ pure
+ (Typed.DirectInductive
+ parameters
+ domain
+ clauses)
+
+normalizeClause
+ :: Internal.FunctionSymbol
+ -> [Internal.VarSymbol]
+ -> Internal.IntroRule
+ -> Either ExactInductiveError Typed.DirectInductiveClause
+normalizeClause carrier parameters rule = do
+ conditions <-
+ traverse
+ (normalizeCondition carrier parameters)
+ (Internal.introConditions rule)
+ result <-
+ normalizeResult
+ carrier
+ parameters
+ (Internal.introResult rule)
+ let parameterSet = Set.fromList parameters
+ variables =
+ List.filter (`Set.notMember` parameterSet)
+ (orderedUnique
+ ( concatMap toList
+ (Internal.introConditions rule)
+ <> toList result
+ ))
+ pure
+ (Typed.DirectInductiveClause
+ variables
+ conditions
+ result)
+
+normalizeResult
+ :: Internal.FunctionSymbol
+ -> [Internal.VarSymbol]
+ -> Internal.Formula
+ -> Either ExactInductiveError Internal.Term
+normalizeResult carrier parameters = \case
+ Internal.IsElementOf _location result target
+ | not (matchesCarrier carrier parameters target) ->
+ Left (ExactInductiveResultShape (termLocation target))
+ | Internal.SymbolMixfix carrier
+ `Set.member` Internal.mentionedSymbols result ->
+ Left
+ (ExactInductiveResultMentionsCarrier
+ (termLocation result))
+ | otherwise ->
+ Right result
+ formula ->
+ Left (ExactInductiveResultShape (termLocation formula))
+
+normalizeCondition
+ :: Internal.FunctionSymbol
+ -> [Internal.VarSymbol]
+ -> Internal.Formula
+ -> Either ExactInductiveError Typed.DirectInductiveCondition
+normalizeCondition carrier parameters formula
+ | not
+ (Internal.SymbolMixfix carrier
+ `Set.member` Internal.mentionedSymbols formula) =
+ Right (Typed.DirectSideCondition formula)
+ | otherwise =
+ case formula of
+ Internal.IsElementOf _location recursiveTerm recursiveCarrier
+ | Internal.SymbolMixfix carrier
+ `Set.member`
+ Internal.mentionedSymbols recursiveTerm ->
+ Left
+ (ExactInductiveRecursiveTermMentionsCarrier
+ (termLocation recursiveTerm))
+ | otherwise -> do
+ context <-
+ first recursiveCarrierContextError
+ (Typed.prepareRecursiveCarrierContext
+ carrier parameters recursiveCarrier)
+ Right
+ (Typed.DirectRecursiveCondition
+ recursiveTerm context)
+ _ ->
+ Left
+ (ExactInductiveRecursiveCarrierOutsideMembership
+ (termLocation formula))
+
+recursiveCarrierContextError
+ :: Typed.RecursiveCarrierContextError
+ -> ExactInductiveError
+recursiveCarrierContextError = \case
+ Typed.RecursiveCarrierWrongArguments location ->
+ ExactInductiveRecursiveCarrierWrongArguments location
+ Typed.RecursiveCarrierUnsupportedContext location ->
+ ExactInductiveUnsupportedRecursiveCarrierContext location
+
+matchesCarrier
+ :: Internal.FunctionSymbol
+ -> [Internal.VarSymbol]
+ -> Internal.Term
+ -> Bool
+matchesCarrier carrier parameters = \case
+ Internal.TermSymbol _location (Internal.SymbolMixfix actual) arguments ->
+ actual == carrier
+ && length arguments == length parameters
+ && and
+ (zipWith
+ (\argument parameter ->
+ argument == Internal.TermVar parameter)
+ arguments
+ parameters)
+ _ -> False
+
+resolveSourceGlobals
+ :: Location
+ -> Internal.Inductive
+ -> Typed.DirectInductive
+ -> Prepare
+ ( Map.Map
+ Internal.Symbol
+ (Typed.SourceGlobal ObjectId)
+ , Map.Map ObjectId CoreType
+ )
+resolveSourceGlobals location internal direct =
+ Except.lift
+ (ExactGlobal.resolveExactSourceGlobals symbols)
+ >>= Except.liftEither
+ . first (exactGlobalError location)
+ where
+ carrier = Internal.SymbolMixfix (Internal.inductiveSymbol internal)
+ symbols =
+ Set.delete carrier (directSymbols direct)
+
+exactGlobalError
+ :: Location
+ -> ExactGlobal.ExactGlobalResolutionError
+ -> ExactInductiveError
+exactGlobalError location = \case
+ ExactGlobal.ExactGlobalNotVisible symbol ->
+ ExactInductiveGlobalNotVisible location symbol
+ ExactGlobal.ExactGlobalAmbiguous symbol ->
+ ExactInductiveGlobalAmbiguous location symbol
+ ExactGlobal.ExactGlobalUnsupported symbol ->
+ ExactInductiveUnsupportedSymbol location symbol
+ ExactGlobal.ExactGlobalContextualUnsupported symbol ->
+ ExactInductiveUnsupportedSymbol location symbol
+ ExactGlobal.ExactGlobalContentInvalid failure ->
+ ExactInductiveGlobalContentInvalid location failure
+
+resolveGuard
+ :: Location
+ -> FrozenCheckedCore ObjectId
+ -> Prepare SemanticFactOccurrenceFingerprint
+resolveGuard location target = do
+ matches <-
+ Except.lift
+ (Declaration.resolveVisibleFactTargetsLowering target)
+ case matches of
+ [] ->
+ Except.throwError (ExactInductiveGuardMissing location)
+ [fingerprint] ->
+ pure fingerprint
+ _ ->
+ Except.throwError (ExactInductiveGuardAmbiguous location)
+
+directSymbols :: Typed.DirectInductive -> Set.Set Internal.Symbol
+directSymbols direct =
+ Internal.mentionedSymbols (Typed.directInductiveDomain direct)
+ <> foldMap clauseSymbols
+ (Typed.directInductiveClauses direct)
+ where
+ clauseSymbols clause =
+ foldMap conditionSymbols
+ (Typed.directClauseConditions clause)
+ <> Internal.mentionedSymbols
+ (Typed.directClauseResult clause)
+ conditionSymbols = \case
+ Typed.DirectSideCondition formula ->
+ Internal.mentionedSymbols formula
+ Typed.DirectRecursiveCondition term context ->
+ Internal.mentionedSymbols term
+ <> Typed.recursiveCarrierContextSymbols context
+
+lowerPreparedExactInductive
+ :: PreparedExactInductive
+ -> Declaration.LoweringDriver
+ (Either
+ Declaration.DeclarationError
+ (Declaration.CheckedDeclaration
+ CheckedExactInductiveAuthorization))
+lowerPreparedExactInductive
+ (PreparedExactInductive
+ _location key identity asserted alias syntax typed guards) =
+ do
+ let facts = Typed.typedInductiveFacts typed
+ monotonicities =
+ Vector.toList
+ (Typed.typedInductiveMonotonicities typed)
+ objects = maybeToList asserted
+ definition <-
+ Declaration.prepareDefinitionEquationSpecLowering
+ objects identity alias
+ preparedMonotonicities <-
+ traverse
+ (\monotonicity -> Except.runExceptT do
+ let factLocation =
+ Typed.typedInductiveMonotonicityLocation
+ monotonicity
+ target =
+ Typed.typedInductiveMonotonicityTarget
+ monotonicity
+ spec <-
+ Except.lift
+ (Declaration.prepareFrozenCandidateSpecLowering
+ objects target SearchIneligible [])
+ >>= Except.liftEither
+ obligation <-
+ Except.lift
+ (Declaration.prepareScopedVampireObligationLowering
+ Vector.empty
+ (embedClosedCore [] target)
+ []
+ []
+ Declaration.VampireImplicitPremises)
+ >>= either
+ (Except.throwError
+ . Declaration.ProofObligationFailedAt
+ factLocation
+ . Declaration.CurrentCandidateVampirePreparationFailed)
+ pure
+ pure
+ ( Declaration.checkedCandidate
+ spec
+ (Declaration.checkedSourceProofPlanning
+ [ Declaration.checkedPlannedVampireRequest
+ factLocation obligation
+ ]
+ [])
+ , (factLocation, obligation)
+ ))
+ monotonicities
+ preparedCandidates <-
+ traverse
+ (\fact -> do
+ prepared <-
+ Declaration.prepareCandidateSpecLowering
+ objects
+ (embedClosedCore []
+ (Typed.typedInductiveFactTarget fact))
+ SearchEligible
+ [markerAlias
+ (Typed.typedInductiveFactMarker fact)]
+ let descriptor =
+ GuardedFoundationRules
+ (guardedRuleSet
+ (Typed.typedInductiveFactRules fact))
+ planning
+ | null monotonicities =
+ Declaration.checkedKernelPlanning
+ descriptor guards
+ | otherwise =
+ Declaration.checkedKernelPlanningWithStaged
+ descriptor
+ guards
+ (if Typed.typedInductiveFactRequiresMonotonicities
+ fact
+ then
+ [ Declaration.plannedEarlierCandidate
+ 1 index
+ | (index, _target) <-
+ zip [0 ..] monotonicities
+ ]
+ else [])
+ pure
+ (fmap
+ (\spec ->
+ Declaration.checkedCandidate spec planning)
+ prepared))
+ facts
+ pure do
+ definitionSpec <- definition
+ monotonicityCandidates <- sequence preparedMonotonicities
+ factCandidates <- sequence preparedCandidates
+ let stages
+ | null monotonicityCandidates =
+ [ Declaration.checkedCandidate definitionSpec
+ (Declaration.checkedDefinitionEquationPlanning identity)
+ :| toList factCandidates
+ ]
+ | otherwise =
+ [ Declaration.checkedCandidate definitionSpec
+ (Declaration.checkedDefinitionEquationPlanning identity)
+ :| []
+ , NonEmpty.fromList (fst <$> monotonicityCandidates)
+ , factCandidates
+ ]
+ pure
+ (Declaration.checkedCompiledDeclaration
+ syntax
+ objects
+ []
+ [semanticGlobalBinding key (GlobalReference identity)]
+ []
+ stages
+ (CheckedExactInductiveAuthorization
+ identity typed guards
+ (snd <$> monotonicityCandidates)))
+ where
+ markerAlias (Raw.Marker name) =
+ semanticName name
+
+authorizeCheckedExactInductive
+ :: CheckedExactInductiveAuthorization
+ -> [NonEmpty Declaration.ReservedCandidate]
+ -> Declaration.Declaration ()
+authorizeCheckedExactInductive
+ (CheckedExactInductiveAuthorization
+ identity typed guards monotonicityObligations) = \case
+ [definitionCandidate :| candidates] -> do
+ unless (null monotonicityObligations)
+ (Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch 3 1))
+ Declaration.authorizeDefinitionEquationCandidate
+ identity definitionCandidate
+ let facts = Typed.typedInductiveFacts typed
+ case NonEmpty.nonEmpty candidates of
+ Just factCandidates
+ | NonEmpty.length factCandidates == NonEmpty.length facts ->
+ sequence_
+ (NonEmpty.zipWith
+ (authorizeFact [])
+ factCandidates
+ facts)
+ _ ->
+ Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch
+ (1 + NonEmpty.length facts)
+ (1 + length candidates))
+ [ definitionCandidate :| []
+ , monotonicityCandidates
+ , factCandidates
+ ]
+ | NonEmpty.length monotonicityCandidates
+ == length monotonicityObligations
+ , NonEmpty.length factCandidates
+ == NonEmpty.length (Typed.typedInductiveFacts typed) -> do
+ obligations <-
+ maybe
+ (Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch
+ 1 0))
+ pure
+ (NonEmpty.nonEmpty monotonicityObligations)
+ Declaration.authorizeDefinitionEquationCandidate
+ identity definitionCandidate
+ Declaration.authorizeVampireCandidateBatch
+ (NonEmpty.zipWith
+ (\candidate (factLocation, obligation) ->
+ (factLocation, candidate, pure obligation))
+ monotonicityCandidates
+ obligations)
+ sequence_
+ (NonEmpty.zipWith
+ (\candidate fact ->
+ authorizeFact
+ (if Typed.typedInductiveFactRequiresMonotonicities
+ fact
+ then NonEmpty.toList monotonicityCandidates
+ else [])
+ candidate
+ fact)
+ factCandidates
+ (Typed.typedInductiveFacts typed))
+ stages ->
+ Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch
+ (if null monotonicityObligations then 1 else 3)
+ (length stages))
+ where
+ authorizeFact monotonicityCandidates candidate fact =
+ Declaration.authorizeKernelConstructionCandidate
+ (GuardedFoundationRules
+ (guardedRuleSet
+ (Typed.typedInductiveFactRules fact)))
+ candidate do
+ traverse_ Declaration.useAuthorizedFact guards
+ traverse_
+ Declaration.useStagedCandidate
+ monotonicityCandidates
+ pure (Typed.typedInductiveFactDerivation fact)
+
+
+encodePreparedInductive
+ :: SemanticGlobalKey
+ -> SemanticName
+ -> Typed.PreparedTypedInductive ObjectId
+ -> ByteString
+encodePreparedInductive key alias typed =
+ encodeCache do
+ putCacheTag 0x04
+ putSemanticGlobalKeyCache key
+ putCoreTypeCache
+ (Typed.typedInductiveCarrierType typed)
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm
+ (Typed.typedInductiveCarrierBody typed))
+ putCacheList putFrozenTerm
+ (Vector.toList
+ (Typed.typedInductiveContextInventory typed))
+ putCacheList
+ (putFrozenTerm
+ . Typed.typedInductiveMonotonicityTarget)
+ (Vector.toList
+ (Typed.typedInductiveMonotonicities typed))
+ putCacheText (semanticNameText alias)
+ putCacheList putFact
+ (toList (Typed.typedInductiveFacts typed))
+ where
+ putFrozenTerm =
+ putCanonicalTermCache putObjectIdCache . frozenCoreTerm
+
+ putFact fact = do
+ let Raw.Marker marker =
+ Typed.typedInductiveFactMarker fact
+ putCacheText marker
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm
+ (Typed.typedInductiveFactTarget fact))
+ putCacheList
+ (putCacheBytes . encodeKernelRuleTag)
+ (toList (Typed.typedInductiveFactRules fact))
+
+firstDuplicate :: Ord value => [value] -> Maybe value
+firstDuplicate =
+ go Set.empty
+ where
+ go _seen [] = Nothing
+ go seen (value : remaining)
+ | value `Set.member` seen = Just value
+ | otherwise =
+ go (Set.insert value seen) remaining
+
+orderedUnique :: Ord value => [value] -> [value]
+orderedUnique =
+ reverse . snd . foldl' step (Set.empty, [])
+ where
+ step (seen, values) value
+ | value `Set.member` seen = (seen, values)
+ | otherwise =
+ (Set.insert value seen, value : values)
+
+termLocation :: Internal.Expr -> Location
+termLocation = Internal.exprLocation
diff --git a/source/Felix/Checking/Exact/Proof.hs b/source/Felix/Checking/Exact/Proof.hs
new file mode 100644
index 0000000..e2149c4
--- /dev/null
+++ b/source/Felix/Checking/Exact/Proof.hs
@@ -0,0 +1,2639 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Exact lowering for the first ordinary theorem/proof fragment.
+--
+-- This module is the trusted owner of structural local-proof composition. Its
+-- private prepared tree controls when assumptions and proved local claims
+-- become available and executes discharges depth-first. The declaration
+-- boundary validates typed tasks and authority; it does not reconstruct this
+-- derivation.
+module Felix.Checking.Exact.Proof
+ ( PreparedExactProof
+ , preparedExactProofSyntaxId
+ , preparedExactProofFirstOmission
+ , prepareExactProof
+ , CheckedExactProofAuthorization
+ , lowerPreparedExactProof
+ , authorizeCheckedExactProof
+ , PreparedFinalPreludeFoundationClaim
+ , prepareFinalPreludeFoundationClaim
+ , CheckedFinalPreludeFoundationAuthorization
+ , lowerPreparedFinalPreludeFoundationClaim
+ , authorizeCheckedFinalPreludeFoundationClaim
+ , ExactProofError(..)
+ , exactProofErrorLocation
+ , renderExactProofError
+ ) where
+
+import Base
+import Felix.Checking.Authority qualified as Authority
+import Felix.Checking.Backend.Problem qualified as Backend
+import Felix.Checking.Core
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Exact qualified as Exact
+import Felix.Checking.Foundation
+import Felix.Checking.Identity
+import Felix.Checking.Kernel.Derivation (foundationFactDerivation)
+import Felix.Checking.Kernel.Proof qualified as KernelProof
+import Felix.Checking.SetConstruction
+import Felix.Checking.Semantic
+import Felix.Cache.Codec
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+
+import Control.Monad.Except (ExceptT)
+import Control.Monad.Except qualified as Except
+import Control.Monad (foldM, unless, when)
+import Control.Monad.State.Strict (StateT)
+import Control.Monad.State.Strict qualified as State
+import Data.ByteString (ByteString)
+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.Vector (Vector)
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+
+
+data ExactProofError
+ = ExactProofUnsupportedClaim !Location
+ | ExactProofUnsupportedStep !Location
+ | ExactProofSetInductionVariableRequired !Location
+ | ExactProofSetInductionVariableNotActive
+ !Location !Raw.VarSymbol
+ | ExactProofSetInductionFocusAmbiguous !Location
+ | ExactProofSetInductionActiveBinderIneligible
+ !Location !Raw.VarSymbol
+ | ExactProofSetInductionBinderConflict
+ !Location !Raw.VarSymbol
+ | ExactProofSetInductionGoalMismatch !Location
+ | ExactProofSetExtensionalityGoalMismatch !Location
+ | ExactProofSetExtensionalityDirectionsUnavailable !Location
+ | ExactProofExpectedUniversalGoal !Location
+ | ExactProofExpectedImplicationGoal !Location
+ | ExactProofGoalStatementMismatch !Location
+ | ExactProofEmptyCaseSplit !Location
+ | ExactProofStructuralCompositionFailed
+ !Location !KernelProof.KernelProofBuildError
+ | ExactProofLocalFunctionBinderMismatch !Location
+ | ExactProofLocalFunctionNameConflict !Location
+ | ExactProofUnknownReference !Location !Raw.Marker
+ | ExactProofElaborationFailed !Exact.ExactCompileError
+ | ExactProofObligationPreparationFailed
+ !Location
+ !(Declaration.VampireObligationPreparationError
+ Exact.ExactLocalId)
+ | ExactProofFoundationLeafRequiresImplicitAuto !Location
+ | ExactProofFoundationLeafTargetMismatch !Location
+ | ExactProofFoundationLeafTargetAmbiguous !Location
+ deriving stock (Show, Eq)
+
+exactProofErrorLocation :: ExactProofError -> Location
+exactProofErrorLocation = \case
+ ExactProofUnsupportedClaim location -> location
+ ExactProofUnsupportedStep location -> location
+ ExactProofSetInductionVariableRequired location -> location
+ ExactProofSetInductionVariableNotActive location _variable ->
+ location
+ ExactProofSetInductionFocusAmbiguous location -> location
+ ExactProofSetInductionActiveBinderIneligible location _variable ->
+ location
+ ExactProofSetInductionBinderConflict location _variable ->
+ location
+ ExactProofSetInductionGoalMismatch location -> location
+ ExactProofSetExtensionalityGoalMismatch location -> location
+ ExactProofSetExtensionalityDirectionsUnavailable location -> location
+ ExactProofExpectedUniversalGoal location -> location
+ ExactProofExpectedImplicationGoal location -> location
+ ExactProofGoalStatementMismatch location -> location
+ ExactProofEmptyCaseSplit location -> location
+ ExactProofStructuralCompositionFailed location _failure -> location
+ ExactProofLocalFunctionBinderMismatch location -> location
+ ExactProofLocalFunctionNameConflict location -> location
+ ExactProofUnknownReference location _marker -> location
+ ExactProofElaborationFailed failure ->
+ Exact.exactCompileErrorLocation failure
+ ExactProofObligationPreparationFailed location _failure -> location
+ ExactProofFoundationLeafRequiresImplicitAuto location -> location
+ ExactProofFoundationLeafTargetMismatch location -> location
+ ExactProofFoundationLeafTargetAmbiguous location -> location
+
+renderExactProofError :: ExactProofError -> Text
+renderExactProofError = \case
+ ExactProofUnsupportedClaim location ->
+ at location <> "this claim is not yet supported by the typed checker"
+ ExactProofUnsupportedStep location ->
+ at location <> "this proof step is not yet supported by the typed checker"
+ ExactProofSetInductionVariableRequired location ->
+ at location <> "exact set induction requires a named set variable"
+ ExactProofSetInductionVariableNotActive location variable ->
+ at location <> "the set-induction variable " <> shown variable
+ <> " is not an eligible exact focus"
+ ExactProofSetInductionFocusAmbiguous location ->
+ at location
+ <> "set induction without an explicit variable has no unique focus"
+ ExactProofSetInductionActiveBinderIneligible location variable ->
+ at location <> "the active binder " <> shown variable
+ <> " is not an eligible set-induction focus"
+ ExactProofSetInductionBinderConflict location variable ->
+ at location <> "the leading set-induction binder " <> shown variable
+ <> " conflicts with an active exact binder"
+ ExactProofSetInductionGoalMismatch location ->
+ at location <> "the set-induction variable does not belong to this goal"
+ ExactProofSetExtensionalityGoalMismatch location ->
+ at location <> "set extensionality requires a set-equality goal"
+ ExactProofSetExtensionalityDirectionsUnavailable location ->
+ at location
+ <> "set extensionality requires both directions as proved local claims"
+ ExactProofExpectedUniversalGoal location ->
+ at location <> "this fix step requires a universal goal"
+ ExactProofExpectedImplicationGoal location ->
+ at location <> "this assume step requires an implication goal"
+ ExactProofGoalStatementMismatch location ->
+ at location <> "the proof step does not match the current goal"
+ ExactProofEmptyCaseSplit location ->
+ at location <> "case analysis requires at least one case"
+ ExactProofStructuralCompositionFailed location failure ->
+ at location <> "invalid structural proof composition: " <> shown failure
+ ExactProofLocalFunctionBinderMismatch location ->
+ at location <> "the function argument must match its domain binder"
+ ExactProofLocalFunctionNameConflict location ->
+ at location <> "the function and argument names must be distinct"
+ ExactProofUnknownReference location marker ->
+ at location <> "the cited fact " <> shown marker <> " is not visible"
+ ExactProofElaborationFailed failure ->
+ Exact.renderExactCompileError failure
+ ExactProofObligationPreparationFailed location failure ->
+ at location <> "the exact proof obligation is invalid: " <> shown failure
+ ExactProofFoundationLeafRequiresImplicitAuto location ->
+ at location
+ <> "a confined foundation claim requires an implicit Auto proof"
+ ExactProofFoundationLeafTargetMismatch location ->
+ at location <> "the claim does not exactly match a foundation axiom"
+ ExactProofFoundationLeafTargetAmbiguous location ->
+ at location <> "the claim matches more than one foundation axiom"
+ where
+ at location = locationToText location <> ": "
+ shown :: Show value => value -> Text
+ shown = Text.pack . show
+
+data ExactLocalOrigin
+ = ExactAssumption
+ | ExactDerivedClaim
+ | ExactLocalDefinition
+ | ExactLocalConstructionExtensional
+ | ExactLocalConstructionEquation
+ deriving stock (Show, Eq, Ord)
+
+data PreparedLocal = PreparedLocal
+ !Backend.LocalPremiseOrdinal
+ !ExactLocalOrigin
+ !(Vector (Exact.ExactLocalId, CoreType))
+ !(ScopedCheckedCore ObjectId)
+
+data PreparedJustification
+ = PreparedAuto
+ | PreparedReferences
+ !(NonEmpty SemanticFactOccurrenceFingerprint)
+ | PreparedLocalOnly
+
+data PreparedDischarge
+ = PreparedVampireDischarge
+ !Location
+ !PreparedJustification
+ !(ScopedCheckedCore ObjectId)
+ !(Declaration.PreparedVampireObligation
+ Exact.ExactLocalId
+ ExactLocalOrigin)
+ | PreparedSetExtensionality
+ !Location
+ !(ScopedCheckedCore ObjectId)
+
+data PreparedCalculationLink = PreparedCalculationLink
+ !(ScopedCheckedCore ObjectId)
+ !PreparedDischarge
+
+-- The private constructor stores every destination with the discharge derived
+-- from its immediately preceding endpoint. Planning and admission can
+-- therefore traverse one immutable sequence without re-associating shapes.
+data PreparedCalculation = PreparedCheckedCalculation
+ !CoreType
+ ![Exact.ExactLocalId]
+ !(Maybe (ScopedCheckedCore ObjectId))
+ !(ScopedCheckedCore ObjectId)
+ !(NonEmpty PreparedCalculationLink)
+ !(ScopedCheckedCore ObjectId)
+
+data PreparedSinceEvidence
+ = PreparedSinceExisting !PreparedLocal
+ | PreparedSinceDischarged !PreparedDischarge !PreparedLocal
+
+data PreparedCase = PreparedCase
+ !(ScopedCheckedCore ObjectId)
+ !PreparedProof
+
+data PreparedCaseAnalysis = PreparedCaseAnalysis
+ !(ScopedCheckedCore ObjectId)
+ !(NonEmpty PreparedCase)
+ !(ScopedCheckedCore ObjectId)
+ !PreparedDischarge
+
+data InitialSetInductionFocus = InitialSetInductionFocus
+ !Raw.VarSymbol
+ !Exact.ExactLocalId
+ !Natural
+
+data InitialSetInductionView = InitialSetInductionView
+ ![InitialSetInductionFocus]
+ !(Vector (Exact.ExactLocalId, CoreType))
+ !(ScopedCheckedCore ObjectId)
+ ![ScopedCheckedCore ObjectId]
+ !(ScopedCheckedCore ObjectId)
+ !(Maybe Raw.VarSymbol)
+
+data SetInductionBoundary
+ = InitialClaimInduction !InitialSetInductionView
+ -- A direct source-statement goal may retain only its leading binder name.
+ -- Recursive proof transformations deliberately discard this hint.
+ | SourceStatementInduction !(Maybe Raw.VarSymbol)
+ | RecursiveProofInduction
+
+data SelectedSetInductionFocus
+ = SelectedInitialSetInduction !InitialSetInductionFocus
+ | SelectedLeadingSetInduction !(Maybe Raw.VarSymbol)
+
+data PreparedSetInductionFocus
+ = PreparedInitialSetInductionFocus
+ !Exact.ExactLocalId
+ !Natural
+ | PreparedLeadingSetInductionFocus
+ !Exact.ExactLocalId
+
+data PreparedSetInduction = PreparedCheckedSetInduction
+ !PreparedSetInductionFocus
+ !(ScopedCheckedCore ObjectId)
+ ![ScopedCheckedCore ObjectId]
+ !(ScopedCheckedCore ObjectId)
+ !(ScopedCheckedCore ObjectId)
+ !(ScopedCheckedCore ObjectId)
+ !PreparedProof
+
+data PreparedProof
+ = PreparedImplicitAuto !PreparedDischarge
+ | PreparedQed !PreparedDischarge
+ | PreparedOmitted
+ !Location
+ !(ScopedCheckedCore ObjectId)
+ | PreparedFix ![Exact.ExactLocalId] !PreparedProof
+ | PreparedAssume
+ !(ScopedCheckedCore ObjectId)
+ !PreparedProof
+ | PreparedTake
+ ![Exact.ExactLocalId]
+ !(ScopedCheckedCore ObjectId)
+ !PreparedDischarge
+ !PreparedProof
+ | PreparedSetInduction !PreparedSetInduction
+ | PreparedHave
+ !(ScopedCheckedCore ObjectId)
+ !PreparedDischarge
+ !PreparedProof
+ | PreparedSuffices
+ !(ScopedCheckedCore ObjectId)
+ !(ScopedCheckedCore ObjectId)
+ !(ScopedCheckedCore ObjectId)
+ !PreparedDischarge
+ !PreparedProof
+ | PreparedCalculate
+ !PreparedCalculation
+ !PreparedProof
+ | PreparedSince
+ !(ScopedCheckedCore ObjectId)
+ !PreparedSinceEvidence
+ !(ScopedCheckedCore ObjectId)
+ !PreparedDischarge
+ !PreparedProof
+ | PreparedSubclaim
+ !(ScopedCheckedCore ObjectId)
+ !PreparedProof
+ !PreparedProof
+ | PreparedDefine
+ !Exact.ExactLocalId
+ !(ScopedCheckedCore ObjectId)
+ !(NonEmpty (ScopedCheckedCore ObjectId))
+ !PreparedProof
+ | PreparedDefineRelational
+ !Exact.ExactLocalId
+ !(ScopedCheckedCore ObjectId)
+ !PreparedDischarge
+ !(NonEmpty (ScopedCheckedCore ObjectId))
+ !PreparedProof
+ | PreparedDefineFunction
+ !Exact.ExactLocalId
+ !(ScopedCheckedCore ObjectId)
+ !(ScopedCheckedCore ObjectId)
+ !PreparedProof
+ | PreparedByCase !PreparedCaseAnalysis
+ | PreparedByContradiction
+ !(ScopedCheckedCore ObjectId)
+ !(ScopedCheckedCore ObjectId)
+ !(ScopedCheckedCore ObjectId)
+ !PreparedProof
+ | PreparedContradiction
+ !(ScopedCheckedCore ObjectId)
+ !(ScopedCheckedCore ObjectId)
+ !PreparedDischarge
+
+data PreparedExactProof = PreparedExactProof
+ !Location
+ !SemanticName
+ !(ScopedCheckedCore ObjectId)
+ !PreparedProof
+ !ProofSyntaxId
+
+data PreparedFinalPreludeFoundationClaim =
+ PreparedFinalPreludeFoundationClaim
+ !Location
+ !SemanticName
+ !(ScopedCheckedCore ObjectId)
+ !FoundationAxiomTag
+ !ProofSyntaxId
+
+preparedExactProofSyntaxId :: PreparedExactProof -> ProofSyntaxId
+preparedExactProofSyntaxId
+ (PreparedExactProof _location _alias _target _proof syntax) =
+ syntax
+
+preparedExactProofFirstOmission :: PreparedExactProof -> Maybe Location
+preparedExactProofFirstOmission
+ (PreparedExactProof _location _alias _target proof _syntax) =
+ preparedProofFirstOmission proof
+
+data PrepareState = PrepareState
+ { prepareNextLocal :: !Natural
+ , prepareNextPremise :: !Natural
+ }
+
+type Prepare =
+ StateT
+ PrepareState
+ (ExceptT ExactProofError (Declaration.LoweringDriver))
+
+prepareExactProof
+ :: Raw.Block
+ -> Maybe Raw.Proof
+ -> Declaration.LoweringDriver
+ (Either ExactProofError PreparedExactProof)
+prepareExactProof block explicitProof =
+ Except.runExceptT
+ (State.evalStateT prepare initialState)
+ where
+ initialState = PrepareState 0 0
+
+ prepare =
+ case block of
+ Raw.BlockClaim
+ _kind location _title (Raw.Marker marker)
+ (Raw.Claim assumptions statement) -> do
+ envelope <-
+ liftDriver
+ (Exact.prepareExactClaimEnvelope assumptions statement)
+ >>= either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ let targetCore = Exact.preparedExactClaimTarget envelope
+ unless (null (scopedCoreContext targetCore))
+ (throwProof
+ (ExactProofUnsupportedClaim location))
+ (context, openedGoal, identities) <-
+ openEnvelopeVariables
+ targetCore
+ (Exact.preparedExactClaimVariables envelope)
+ (Exact.preparedExactClaimContext envelope)
+ (locals, bodyGoal, antecedents) <-
+ openEnvelopeAntecedents
+ context
+ openedGoal
+ (Exact.preparedExactClaimAntecedentCount envelope)
+ initialInduction <-
+ prepareInitialSetInductionView
+ statement
+ context
+ (Exact.preparedExactClaimVariables envelope)
+ identities
+ antecedents
+ bodyGoal
+ bodyProof <-
+ case explicitProof of
+ Nothing ->
+ PreparedImplicitAuto
+ <$> prepareDischarge
+ location
+ context
+ locals
+ bodyGoal
+ Raw.JustificationEmpty
+ Just sourceProof ->
+ prepareProof
+ location
+ context
+ locals
+ (InitialClaimInduction initialInduction)
+ bodyGoal
+ sourceProof
+ let withAssumptions =
+ foldr PreparedAssume bodyProof antecedents
+ proof =
+ case identities of
+ [] -> withAssumptions
+ _ -> PreparedFix identities withAssumptions
+ pure
+ (PreparedExactProof
+ location
+ (semanticName marker)
+ targetCore
+ proof
+ (proofSyntaxId
+ (encodePreparedProof proof)))
+ _ ->
+ throwProof
+ (ExactProofUnsupportedClaim (locate block))
+
+prepareFinalPreludeFoundationClaim
+ :: CheckedFoundation
+ -> Raw.Block
+ -> Maybe Raw.Proof
+ -> Declaration.LoweringDriver
+ (Either ExactProofError PreparedFinalPreludeFoundationClaim)
+prepareFinalPreludeFoundationClaim foundation block explicitProof =
+ Except.runExceptT do
+ case (block, explicitProof) of
+ ( Raw.BlockClaim
+ _kind location _title (Raw.Marker marker)
+ (Raw.Claim assumptions statement)
+ , Nothing
+ ) -> do
+ envelope <-
+ Except.lift
+ (Exact.prepareExactClaimEnvelope
+ assumptions
+ statement)
+ >>= either
+ (Except.throwError
+ . ExactProofElaborationFailed)
+ pure
+ unless
+ ( null (Exact.preparedExactClaimVariables envelope)
+ && Exact.preparedExactClaimAntecedentCount envelope
+ == 0
+ )
+ (Except.throwError
+ (ExactProofFoundationLeafRequiresImplicitAuto
+ location))
+ let target = Exact.preparedExactClaimTarget envelope
+ matches =
+ [ tag
+ | tag <- [minBound .. maxBound]
+ , target == foundationTarget tag
+ ]
+ tag <- case matches of
+ [] ->
+ Except.throwError
+ (ExactProofFoundationLeafTargetMismatch location)
+ [only] ->
+ pure only
+ _ ->
+ Except.throwError
+ (ExactProofFoundationLeafTargetAmbiguous location)
+ pure
+ (PreparedFinalPreludeFoundationClaim
+ location
+ (semanticName marker)
+ target
+ tag
+ (implicitAutoProofSyntaxId target))
+ (Raw.BlockClaim _kind location _title _marker _claim, Just{}) ->
+ Except.throwError
+ (ExactProofFoundationLeafRequiresImplicitAuto location)
+ _ ->
+ Except.throwError
+ (ExactProofUnsupportedClaim (locate block))
+ where
+ foundationTarget tag =
+ embedClosedCore []
+ (mapFrozenGlobals
+ absurd
+ (foundationAxiomFrozen foundation tag))
+
+openEnvelopeVariables
+ :: ScopedCheckedCore ObjectId
+ -> [Raw.VarSymbol]
+ -> Exact.ExactBinderContext
+ -> Prepare
+ ( Exact.ExactBinderContext
+ , ScopedCheckedCore ObjectId
+ , [Exact.ExactLocalId]
+ )
+openEnvelopeVariables target variables preparedContext =
+ case NonEmpty.nonEmpty variables of
+ Nothing ->
+ pure (preparedContext, target, [])
+ Just nonempty -> do
+ (_unannotated, opened, identities) <-
+ openFixedVariables
+ Exact.emptyExactBinderContext
+ target
+ nonempty
+ let expected =
+ reverse
+ (fst <$> toList
+ (Exact.exactBinderContextSupport preparedContext))
+ unless
+ (identities == expected)
+ (impossible
+ "prepared claim annotations do not match opened binders")
+ pure (preparedContext, opened, identities)
+
+openEnvelopeAntecedents
+ :: Exact.ExactBinderContext
+ -> ScopedCheckedCore ObjectId
+ -> Natural
+ -> Prepare
+ ( [PreparedLocal]
+ , ScopedCheckedCore ObjectId
+ , [ScopedCheckedCore ObjectId]
+ )
+openEnvelopeAntecedents context initialGoal initialCount =
+ go [] [] initialGoal initialCount
+ where
+ go locals antecedents goal 0 =
+ pure (locals, goal, antecedents)
+ go locals antecedents goal remaining = do
+ (antecedent, conclusion) <-
+ maybe
+ (impossible
+ "a prepared claim envelope has too few implications")
+ pure
+ (openScopedImplication goal)
+ local <- allocateLocal ExactAssumption context antecedent
+ go
+ (locals <> [local])
+ (antecedents <> [antecedent])
+ conclusion
+ (remaining - 1)
+
+prepareInitialSetInductionView
+ :: Raw.Stmt
+ -> Exact.ExactBinderContext
+ -> [Raw.VarSymbol]
+ -> [Exact.ExactLocalId]
+ -> [ScopedCheckedCore ObjectId]
+ -> ScopedCheckedCore ObjectId
+ -> Prepare InitialSetInductionView
+prepareInitialSetInductionView
+ statement context variables identities antecedents bodyGoal = do
+ unless (length variables == length identities)
+ (impossible
+ "opened claim binders lost their source identity association")
+ foci <- traverse checkedFocus (zip variables identities)
+ let property = foldr implyChecked bodyGoal antecedents
+ support = Exact.exactBinderContextSupport context
+ unless
+ ( scopedCoreContext property
+ == (snd <$> Vector.toList support)
+ )
+ (impossible
+ "initial set-induction property changed its checked context")
+ pure
+ (InitialSetInductionView
+ foci support property antecedents bodyGoal
+ (claimLeadingUniversalName statement))
+ where
+ checkedFocus (variable, identity) = do
+ index <-
+ maybe
+ (impossible
+ "an opened claim binder is absent from its exact context")
+ pure
+ (Exact.exactBinderContextIndex variable context)
+ case Exact.exactBinderContextSupport context
+ Vector.!? (fromIntegral index) of
+ Just (actualIdentity, TySet)
+ | actualIdentity == identity ->
+ pure
+ (InitialSetInductionFocus
+ variable identity index)
+ _ ->
+ impossible
+ "an initial set-induction focus changed identity or type"
+
+ implyChecked antecedent conclusion =
+ fromMaybe
+ (impossible
+ "an exact claim antecedent changed context")
+ (implyScopedCore antecedent conclusion)
+
+claimLeadingUniversalName :: Raw.Stmt -> Maybe Raw.VarSymbol
+claimLeadingUniversalName = \case
+ Raw.StmtFormula
+ (Raw.FormulaQuantified _location Raw.Universally
+ (variable :| _rest) _bound _formula) ->
+ Just variable
+ Raw.SymbolicForall _location (variable :| _rest)
+ _bound _suchThat _statement ->
+ Just variable
+ Raw.StmtQuantPhrase
+ _location
+ (Raw.QuantPhrase Raw.Universally
+ (Raw.NounPhrase _left _noun variables _right _suchThat))
+ _statement ->
+ listToMaybe variables
+ Raw.StmtVerbPhrase
+ (Raw.TermQuantified Raw.Universally _location
+ (Raw.NounPhrase _left _noun variable _right _suchThat)
+ :| [])
+ _verb ->
+ variable
+ Raw.StmtNoun
+ (Raw.TermQuantified Raw.Universally _location
+ (Raw.NounPhrase _left _noun variable _right _suchThat)
+ :| [])
+ _nounPhrase ->
+ variable
+ _statement ->
+ Nothing
+
+prepareProof
+ :: Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> SetInductionBoundary
+ -> ScopedCheckedCore ObjectId
+ -> Raw.Proof
+ -> Prepare PreparedProof
+prepareProof fallback context locals inductionBoundary goal = \case
+ Raw.Omitted location ->
+ pure (PreparedOmitted location goal)
+ Raw.Qed maybeLocation justification ->
+ PreparedQed
+ <$> prepareDischarge
+ (fromMaybe fallback maybeLocation)
+ context
+ locals
+ goal
+ justification
+ Raw.FixSymbolic location variables bound continuation -> do
+ (context', goal', identities) <-
+ openFixedVariables context goal variables
+ case bound of
+ Raw.Unbounded ->
+ PreparedFix identities
+ <$> prepareProof
+ fallback
+ context'
+ locals
+ RecursiveProofInduction
+ goal'
+ continuation
+ _ -> do
+ constraint <-
+ prepareSymbolicBoundConstraints
+ context' variables bound
+ prepareGuardedFix
+ fallback location context' locals goal'
+ identities constraint continuation
+ Raw.FixSuchThat location variables statement continuation -> do
+ (context', goal', identities) <-
+ openFixedVariables context goal variables
+ constraint <-
+ Exact.preparedExactPropositionCore
+ <$> prepareStatement context' statement
+ prepareGuardedFix
+ fallback location context' locals goal'
+ identities constraint continuation
+ Raw.Assume location statement continuation -> do
+ supplied <- prepareStatement context statement
+ when (isNothing (openScopedImplication goal))
+ (throwProof (ExactProofExpectedImplicationGoal location))
+ (assumption, conclusion) <-
+ maybe
+ (throwProof (ExactProofGoalStatementMismatch location))
+ pure
+ (openScopedAssumption
+ (Exact.preparedExactPropositionCore supplied)
+ goal)
+ local <- allocateLocal ExactAssumption context assumption
+ PreparedAssume assumption
+ <$> prepareProof
+ fallback
+ context
+ (locals <> [local])
+ RecursiveProofInduction
+ conclusion
+ continuation
+ Raw.TakeVar location variables bound statement justification continuation -> do
+ prepareSymbolicTake
+ fallback location context locals goal variables bound statement
+ justification continuation
+ Raw.TakeNoun location nounPhrase justification continuation ->
+ prepareNounTake
+ fallback location context locals goal nounPhrase
+ justification continuation
+ Raw.BySetInduction location variable continuation ->
+ prepareSetInduction
+ fallback location context locals inductionBoundary goal
+ variable continuation
+ Raw.Have location Nothing
+ (Raw.SymbolicExists _existential variables bound statement)
+ justification continuation ->
+ prepareSymbolicTake
+ fallback location context locals goal variables bound statement
+ justification continuation
+ Raw.Have location Nothing statement justification continuation -> do
+ claim <-
+ Exact.preparedExactPropositionCore
+ <$> prepareStatement context statement
+ discharge <-
+ prepareDischarge
+ location context locals claim justification
+ local <- allocateLocal ExactDerivedClaim context claim
+ PreparedHave claim discharge
+ <$> prepareProof
+ fallback
+ context
+ (locals <> [local])
+ RecursiveProofInduction
+ goal
+ continuation
+ Raw.Have location (Just sinceStatement)
+ statement justification continuation -> do
+ sinceProposition <-
+ Exact.preparedExactPropositionCore
+ <$> prepareStatement context sinceStatement
+ claim <-
+ Exact.preparedExactPropositionCore
+ <$> prepareStatement context statement
+ (evidence, sinceLocals) <-
+ case find (localMatches sinceProposition) locals of
+ Just existing ->
+ pure (PreparedSinceExisting existing, locals)
+ Nothing -> do
+ discharge <-
+ prepareDischarge
+ location
+ context
+ locals
+ sinceProposition
+ Raw.JustificationLocal
+ local <-
+ allocateLocal
+ ExactDerivedClaim context sinceProposition
+ pure
+ ( PreparedSinceDischarged discharge local
+ , locals <> [local]
+ )
+ claimDischarge <-
+ prepareDischarge
+ location context sinceLocals claim justification
+ claimLocal <-
+ allocateLocal ExactDerivedClaim context claim
+ PreparedSince
+ sinceProposition evidence claim claimDischarge
+ <$> prepareProof
+ fallback
+ context
+ (sinceLocals <> [claimLocal])
+ RecursiveProofInduction
+ goal
+ continuation
+ Raw.Suffices location statement justification continuation -> do
+ reduction <-
+ Exact.preparedExactPropositionCore
+ <$> prepareStatement context statement
+ implication <-
+ maybe
+ (impossible
+ "a checked suffices reduction changed lexical context")
+ pure
+ (implyScopedCore reduction goal)
+ discharge <-
+ prepareDischarge
+ location context locals implication justification
+ PreparedSuffices goal reduction implication discharge
+ <$> prepareProof
+ fallback
+ context
+ locals
+ (SourceStatementInduction
+ (claimLeadingUniversalName statement))
+ reduction
+ continuation
+ Raw.Calc location quantifier calculation continuation -> do
+ prepared <-
+ prepareCalculation
+ location context locals quantifier calculation
+ local <-
+ allocateLocal
+ ExactDerivedClaim
+ context
+ (preparedCalculationResult prepared)
+ PreparedCalculate prepared
+ <$> prepareProof
+ fallback
+ context
+ (locals <> [local])
+ RecursiveProofInduction
+ goal
+ continuation
+ Raw.Subclaim location statement subproof continuation -> do
+ claim <-
+ Exact.preparedExactPropositionCore
+ <$> prepareStatement context statement
+ preparedSubproof <-
+ prepareProof
+ location
+ context
+ locals
+ (SourceStatementInduction
+ (claimLeadingUniversalName statement))
+ claim
+ subproof
+ local <- allocateLocal ExactDerivedClaim context claim
+ PreparedSubclaim claim preparedSubproof
+ <$> prepareProof
+ fallback
+ context
+ (locals <> [local])
+ RecursiveProofInduction
+ goal
+ continuation
+ Raw.Define location variable expression continuation -> do
+ preparedBody <-
+ liftDriver
+ (Exact.prepareExactSetExpression context expression)
+ >>= either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ let body = Exact.preparedExactSetExpressionCore preparedBody
+ identity <- allocateLocalIdentity
+ context' <-
+ either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ (Exact.extendExactBinderContext
+ ((identity, variable) :| [])
+ context)
+ case Exact.preparedExactSetExpressionConstruction preparedBody of
+ Nothing -> do
+ separationCharacteristic <-
+ liftDriver
+ (Declaration.currentFoundationAxiomLowering
+ SeparationCharacteristic)
+ definition <-
+ maybe
+ (impossible
+ "an exact set expression did not form a local definition")
+ pure
+ (scopedSetDefinition separationCharacteristic body)
+ local <-
+ allocateLocal ExactLocalDefinition context' definition
+ PreparedDefine identity body (definition :| [])
+ <$> prepareProof
+ fallback context' (locals <> [local])
+ RecursiveProofInduction
+ (weakenCheckedScopedCore TySet goal)
+ continuation
+ Just (Exact.PreparedUnconditionalSetConstruction construction) -> do
+ characteristics <- prepareConstructionFoundation
+ (extensional, equation) <-
+ maybe
+ (impossible
+ "a checked named construction has no definition views")
+ pure
+ (namedSetConstructionLocalViews
+ characteristics construction)
+ extensionalLocal <-
+ allocateLocal
+ ExactLocalConstructionExtensional context' extensional
+ equationLocal <-
+ allocateLocal
+ ExactLocalConstructionEquation context' equation
+ PreparedDefine identity body (extensional :| [equation])
+ <$> prepareProof
+ fallback context'
+ (locals <> [extensionalLocal, equationLocal])
+ RecursiveProofInduction
+ (weakenCheckedScopedCore TySet goal)
+ continuation
+ Just (Exact.PreparedRelationalSetConstruction construction) -> do
+ characteristics <- prepareConstructionFoundation
+ let functionality =
+ relationalSetConstructionFunctionality construction
+ discharge <-
+ prepareDischarge
+ location context locals functionality
+ Raw.JustificationEmpty
+ (extensional, equation) <-
+ maybe
+ (impossible
+ "a checked relational construction has no admitted definition views")
+ pure
+ (relationalSetConstructionLocalViews
+ characteristics construction functionality)
+ extensionalLocal <-
+ allocateLocal
+ ExactLocalConstructionExtensional context' extensional
+ equationLocal <-
+ allocateLocal
+ ExactLocalConstructionEquation context' equation
+ PreparedDefineRelational
+ identity body discharge (extensional :| [equation])
+ <$> prepareProof
+ fallback context'
+ (locals <> [extensionalLocal, equationLocal])
+ RecursiveProofInduction
+ (weakenCheckedScopedCore TySet goal)
+ continuation
+ Raw.DefineFunction
+ location function argument value bound domain continuation -> do
+ unless (argument == bound)
+ (throwProof
+ (ExactProofLocalFunctionBinderMismatch (locate bound)))
+ when (function == argument)
+ (throwProof
+ (ExactProofLocalFunctionNameConflict (locate function)))
+ argumentIdentity <- allocateLocalIdentity
+ argumentContext <-
+ either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ (Exact.extendExactBinderContext
+ ((argumentIdentity, argument) :| [])
+ context)
+ graph <-
+ liftDriver
+ (Exact.prepareExactLocalFunctionGraph
+ location context argumentContext domain value)
+ >>= either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ functionIdentity <- allocateLocalIdentity
+ functionContext <-
+ either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ (Exact.extendExactBinderContext
+ ((functionIdentity, function) :| [])
+ context)
+ replacementCharacteristic <-
+ liftDriver
+ (Declaration.currentFoundationAxiomLowering
+ ReplacementCharacteristic)
+ definition <-
+ maybe
+ (impossible
+ "a checked replacement graph did not form a local definition")
+ pure
+ (scopedCharacteristicDefinition
+ replacementCharacteristic
+ (Exact.preparedExactLocalFunctionGraphCore graph)
+ ( Exact.preparedExactLocalFunctionGraphDomain graph
+ :| [Exact.preparedExactLocalFunctionGraphMap graph]
+ ))
+ local <-
+ allocateLocal ExactLocalDefinition functionContext definition
+ PreparedDefineFunction
+ functionIdentity
+ (Exact.preparedExactLocalFunctionGraphCore graph)
+ definition
+ <$> prepareProof
+ fallback
+ functionContext
+ (locals <> [local])
+ RecursiveProofInduction
+ (weakenCheckedScopedCore TySet goal)
+ continuation
+ Raw.ByCase location sourceCases ->
+ prepareByCase
+ location context locals goal sourceCases
+ Raw.ByContradiction location continuation -> do
+ let falsum = falsumScopedCore (scopedCoreContext goal)
+ negation <-
+ maybe
+ (structuralFailure
+ location
+ "proof by contradiction requires a proposition goal")
+ pure
+ (negateScopedCore goal)
+ local <- allocateLocal ExactAssumption context negation
+ prepared <-
+ prepareProof
+ location
+ context
+ (locals <> [local])
+ RecursiveProofInduction
+ falsum
+ continuation
+ validateStructuralComposition
+ location [goal, negation, falsum]
+ (\foundation globalType ->
+ KernelProof.validateDoubleNegationComposition
+ foundation globalType goal negation falsum)
+ pure
+ (PreparedByContradiction
+ goal negation falsum prepared)
+ Raw.Contradiction location justification -> do
+ let falsum = falsumScopedCore (scopedCoreContext goal)
+ discharge <-
+ prepareDischarge
+ location
+ context
+ locals
+ falsum
+ justification
+ validateStructuralComposition
+ location [goal, falsum]
+ (\foundation globalType ->
+ KernelProof.validateFalsumEliminationComposition
+ foundation globalType goal falsum)
+ pure (PreparedContradiction goal falsum discharge)
+ proof ->
+ throwProof
+ (ExactProofUnsupportedStep
+ (proofLocation fallback proof))
+ where
+ localMatches proposition
+ (PreparedLocal _ordinal _origin _support local) =
+ local == proposition
+
+prepareSetInduction
+ :: Location
+ -> Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> SetInductionBoundary
+ -> ScopedCheckedCore ObjectId
+ -> Maybe Raw.Term
+ -> Raw.Proof
+ -> Prepare PreparedProof
+prepareSetInduction
+ fallback location context locals boundary goal sourceFocus
+ continuation = do
+ selected <-
+ selectSetInductionFocus
+ location context boundary goal sourceFocus
+ case selected of
+ SelectedInitialSetInduction
+ (InitialSetInductionFocus _variable identity index) -> do
+ (foci, expectedSupport, property, antecedents, childTarget) <-
+ case boundary of
+ InitialClaimInduction
+ (InitialSetInductionView
+ foundFoci support foundProperty
+ foundAntecedents foundTarget _leadingName) ->
+ pure
+ ( foundFoci
+ , support
+ , foundProperty
+ , foundAntecedents
+ , foundTarget
+ )
+ RecursiveProofInduction ->
+ impossible
+ "an initial induction focus escaped its claim boundary"
+ SourceStatementInduction _leadingName ->
+ impossible
+ "an initial induction focus escaped its claim boundary"
+ unless
+ ( Exact.exactBinderContextSupport context == expectedSupport
+ && goal == childTarget
+ && any (sameInitialFocus identity index) foci
+ )
+ (throwProof
+ (ExactProofSetInductionGoalMismatch location))
+ PreparedSetInduction
+ <$> prepareCheckedSetInduction
+ fallback location context locals
+ (PreparedInitialSetInductionFocus identity index)
+ index property antecedents childTarget continuation
+ SelectedLeadingSetInduction sourceName -> do
+ (binderType, property) <-
+ maybe
+ (throwProof
+ (ExactProofSetInductionGoalMismatch location))
+ pure
+ (openScopedForall goal)
+ unless (binderType == TySet)
+ (throwProof
+ (ExactProofSetInductionGoalMismatch location))
+ identity <- allocateLocalIdentity
+ extendedContext <-
+ either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ (case sourceName of
+ Just variable ->
+ Exact.extendExactBinderContext
+ ((identity, variable) :| [])
+ context
+ Nothing ->
+ Exact.extendExactAnonymousBinderContext
+ identity context)
+ let expectedResult = weakenCheckedScopedCore TySet goal
+ prepared <- prepareCheckedSetInduction
+ fallback location extendedContext locals
+ (PreparedLeadingSetInductionFocus identity)
+ 0 property [] property continuation
+ unless
+ (preparedSetInductionResult prepared == expectedResult)
+ (throwProof
+ (ExactProofSetInductionGoalMismatch location))
+ pure (PreparedSetInduction prepared)
+ where
+ sameInitialFocus expectedIdentity expectedIndex
+ (InitialSetInductionFocus _variable identity index) =
+ identity == expectedIdentity && index == expectedIndex
+
+prepareCheckedSetInduction
+ :: Location
+ -> Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> PreparedSetInductionFocus
+ -> Natural
+ -> ScopedCheckedCore ObjectId
+ -> [ScopedCheckedCore ObjectId]
+ -> ScopedCheckedCore ObjectId
+ -> Raw.Proof
+ -> Prepare PreparedSetInduction
+prepareCheckedSetInduction
+ fallback location context locals focus selected property antecedents
+ childTarget continuation = do
+ (_predicate, hypothesis, _step, result) <-
+ maybe
+ (throwProof
+ (ExactProofSetInductionGoalMismatch location))
+ pure
+ (scopedSetInductionInstance selected property)
+ validateStructuralComposition
+ location
+ (property : hypothesis : result : childTarget : antecedents)
+ (\foundation globalType ->
+ KernelProof.validateSetInductionComposition
+ foundation globalType selected property antecedents
+ childTarget hypothesis result)
+ local <- allocateLocal ExactAssumption context hypothesis
+ child <-
+ prepareProof
+ fallback
+ context
+ (locals <> [local])
+ RecursiveProofInduction
+ childTarget
+ continuation
+ pure
+ (PreparedCheckedSetInduction
+ focus property antecedents childTarget hypothesis result child)
+
+preparedSetInductionResult
+ :: PreparedSetInduction
+ -> ScopedCheckedCore ObjectId
+preparedSetInductionResult
+ (PreparedCheckedSetInduction
+ _focus _property _antecedents _target _hypothesis result _child) =
+ result
+
+selectSetInductionFocus
+ :: Location
+ -> Exact.ExactBinderContext
+ -> SetInductionBoundary
+ -> ScopedCheckedCore ObjectId
+ -> Maybe Raw.Term
+ -> Prepare SelectedSetInductionFocus
+selectSetInductionFocus location context boundary goal sourceFocus = do
+ explicit <- traverse simpleVariable sourceFocus
+ let (initialFoci, retainedLeadingName) =
+ case boundary of
+ InitialClaimInduction
+ (InitialSetInductionView
+ foci _support _property _antecedents _target
+ leadingName) ->
+ (foci, leadingName)
+ SourceStatementInduction leadingName ->
+ ([], leadingName)
+ RecursiveProofInduction ->
+ ([], Nothing)
+ leadingAvailable =
+ case openScopedForall goal of
+ Just (TySet, _body) -> True
+ _ -> False
+ case explicit of
+ Just variable ->
+ case find (initialNamed variable) initialFoci of
+ Just focus ->
+ pure (SelectedInitialSetInduction focus)
+ Nothing
+ | leadingAvailable
+ , isJust
+ (Exact.exactBinderContextIndex variable context) ->
+ throwProof
+ (ExactProofSetInductionBinderConflict
+ location variable)
+ | leadingAvailable ->
+ pure
+ (SelectedLeadingSetInduction
+ (Just variable))
+ | isJust
+ (Exact.exactBinderContextIndex variable context) ->
+ throwProof
+ (ExactProofSetInductionActiveBinderIneligible
+ location variable)
+ | otherwise ->
+ throwProof
+ (ExactProofSetInductionVariableNotActive
+ location variable)
+ Nothing ->
+ case
+ ( (SelectedInitialSetInduction <$> initialFoci)
+ <> [ SelectedLeadingSetInduction retainedLeadingName
+ | leadingAvailable
+ ]
+ ) of
+ [only] -> pure only
+ _ ->
+ throwProof
+ (ExactProofSetInductionFocusAmbiguous location)
+ where
+ simpleVariable = \case
+ Raw.TermExpr (Raw.ExprVar variable) ->
+ pure variable
+ _term ->
+ throwProof
+ (ExactProofSetInductionVariableRequired location)
+
+ initialNamed variable
+ (InitialSetInductionFocus candidate _identity _index) =
+ candidate == variable
+
+prepareByCase
+ :: Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> ScopedCheckedCore ObjectId
+ -> [Raw.Case]
+ -> Prepare PreparedProof
+prepareByCase location context locals goal sourceCases = do
+ cases <-
+ maybe
+ (throwProof (ExactProofEmptyCaseSplit location))
+ (traverse prepareCase)
+ (NonEmpty.nonEmpty sourceCases)
+ exhaustive <-
+ foldM disjoin
+ (preparedCaseAssumption (NonEmpty.head cases))
+ (preparedCaseAssumption <$> NonEmpty.tail cases)
+ discharge <-
+ prepareDischarge
+ location context locals exhaustive Raw.JustificationEmpty
+ validateStructuralComposition
+ location
+ (goal : exhaustive : (preparedCaseAssumption <$> toList cases))
+ (\foundation globalType ->
+ KernelProof.validateCaseAnalysisComposition
+ foundation
+ globalType
+ goal
+ (preparedCaseAssumption <$> cases)
+ exhaustive)
+ pure
+ (PreparedByCase
+ (PreparedCaseAnalysis goal cases exhaustive discharge))
+ where
+ prepareCase (Raw.Case statement child) = do
+ assumption <-
+ Exact.preparedExactPropositionCore
+ <$> prepareStatement context statement
+ local <- allocateLocal ExactAssumption context assumption
+ prepared <-
+ prepareProof
+ (locate statement)
+ context
+ (locals <> [local])
+ RecursiveProofInduction
+ goal
+ child
+ pure (PreparedCase assumption prepared)
+
+ disjoin left right =
+ maybe
+ (structuralFailure
+ location
+ "case assumptions changed type or lexical context")
+ pure
+ (disjoinScopedCore left right)
+
+preparedCaseAssumption
+ :: PreparedCase
+ -> ScopedCheckedCore ObjectId
+preparedCaseAssumption (PreparedCase assumption _proof) =
+ assumption
+
+preparedCaseProof :: PreparedCase -> PreparedProof
+preparedCaseProof (PreparedCase _assumption proof) =
+ proof
+
+prepareGuardedFix
+ :: Location
+ -> Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> ScopedCheckedCore ObjectId
+ -> [Exact.ExactLocalId]
+ -> ScopedCheckedCore ObjectId
+ -> Raw.Proof
+ -> Prepare PreparedProof
+prepareGuardedFix
+ fallback location context locals goal identities constraint continuation = do
+ (antecedent, conclusion) <-
+ maybe
+ (throwProof (ExactProofExpectedImplicationGoal location))
+ pure
+ (openScopedImplication goal)
+ unless (constraint == antecedent)
+ (throwProof (ExactProofGoalStatementMismatch location))
+ local <- allocateLocal ExactAssumption context constraint
+ prepared <-
+ prepareProof
+ fallback
+ context
+ (locals <> [local])
+ RecursiveProofInduction
+ conclusion
+ continuation
+ pure (PreparedFix identities (PreparedAssume constraint prepared))
+
+prepareCalculation
+ :: Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> Maybe Raw.CalcQuantifier
+ -> Raw.Calc
+ -> Prepare PreparedCalculation
+prepareCalculation location context locals quantifier calculation = do
+ (identities, calculationContext, calculationGuard) <-
+ prepareCalculationScope context quantifier
+ case calculation of
+ Raw.Equation first destinations -> do
+ firstChecked <- prepareSetEndpoint calculationContext first
+ checkedDestinations <-
+ traverse
+ (\(destination, justification) -> do
+ checked <-
+ prepareSetEndpoint calculationContext destination
+ pure
+ ( locate destination
+ , checked
+ , justification
+ ))
+ destinations
+ finishCalculation
+ location context locals TySet identities calculationGuard
+ firstChecked checkedDestinations
+ Raw.Biconditionals first destinations -> do
+ firstChecked <- preparePropositionEndpoint calculationContext first
+ checkedDestinations <-
+ traverse
+ (\(destination, justification) -> do
+ checked <-
+ preparePropositionEndpoint
+ calculationContext destination
+ pure
+ ( locate destination
+ , checked
+ , justification
+ ))
+ destinations
+ finishCalculation
+ location context locals TyProp identities calculationGuard
+ firstChecked checkedDestinations
+ where
+ prepareSetEndpoint endpointContext expression =
+ Exact.preparedExactSetExpressionCore
+ <$> ( liftDriver
+ (Exact.prepareExactSetExpression
+ endpointContext expression)
+ >>= either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ )
+
+ preparePropositionEndpoint endpointContext formula =
+ Exact.preparedExactPropositionCore
+ <$> prepareStatement endpointContext (Raw.StmtFormula formula)
+
+prepareCalculationScope
+ :: Exact.ExactBinderContext
+ -> Maybe Raw.CalcQuantifier
+ -> Prepare
+ ( [Exact.ExactLocalId]
+ , Exact.ExactBinderContext
+ , Maybe (ScopedCheckedCore ObjectId)
+ )
+prepareCalculationScope context = \case
+ Nothing ->
+ pure ([], context, Nothing)
+ Just (Raw.CalcQuantifier variables bound suchThat) -> do
+ identities <- traverse (const allocateLocalIdentity) variables
+ calculationContext <-
+ either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ (Exact.extendExactBinderContext
+ (NonEmpty.zip identities variables)
+ context)
+ boundGuard <-
+ prepareSymbolicBoundConstraints
+ calculationContext variables bound
+ suchThatGuard <-
+ traverse
+ (fmap Exact.preparedExactPropositionCore
+ . prepareStatement calculationContext)
+ suchThat
+ calculationGuard <-
+ normalizeCalculationGuard
+ (boundGuard : maybeToList suchThatGuard)
+ pure (toList identities, calculationContext, calculationGuard)
+
+normalizeCalculationGuard
+ :: [ScopedCheckedCore ObjectId]
+ -> Prepare (Maybe (ScopedCheckedCore ObjectId))
+normalizeCalculationGuard guards =
+ foldM add Nothing guards
+ where
+ add accumulated constraint
+ | isScopedTruth constraint = pure accumulated
+ | otherwise =
+ case accumulated of
+ Nothing -> pure (Just constraint)
+ Just previous ->
+ Just
+ <$> maybe
+ (impossible
+ "checked calculation guards changed context")
+ pure
+ (conjoinScopedCore previous constraint)
+
+ isScopedTruth proposition =
+ scopedCoreType proposition == TyProp
+ && scopedCoreTerm proposition == CImp CFalsum CFalsum
+
+finishCalculation
+ :: Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> CoreType
+ -> [Exact.ExactLocalId]
+ -> Maybe (ScopedCheckedCore ObjectId)
+ -> ScopedCheckedCore ObjectId
+ -> NonEmpty
+ ( Location
+ , ScopedCheckedCore ObjectId
+ , Raw.Justification
+ )
+ -> Prepare PreparedCalculation
+finishCalculation
+ fallback context locals operandType identities calculationGuard
+ first destinations = do
+ links <- prepareCalculationLinks first destinations
+ let finalEndpoint = preparedCalculationLinkDestination (NonEmpty.last links)
+ resultOpen <-
+ calculationEquality first finalEndpoint
+ result <-
+ closeCalculationProposition identities calculationGuard resultOpen
+ pure
+ (PreparedCheckedCalculation
+ operandType identities calculationGuard first links result)
+ where
+ prepareCalculationLinks previous (destination :| rest) = do
+ (next, firstLink) <- prepareCalculationLink previous destination
+ later <- prepareRemainingCalculationLinks next rest
+ pure (firstLink :| later)
+
+ prepareRemainingCalculationLinks _previous [] =
+ pure []
+ prepareRemainingCalculationLinks previous (destination : rest) = do
+ (next, link) <- prepareCalculationLink previous destination
+ (link :) <$> prepareRemainingCalculationLinks next rest
+
+ prepareCalculationLink previous
+ (destinationLocation, destination, justification) = do
+ linkOpen <- calculationEquality previous destination
+ link <- closeCalculationProposition
+ identities calculationGuard linkOpen
+ discharge <-
+ prepareDischarge
+ (if destinationLocation == Nowhere
+ then fallback
+ else destinationLocation)
+ context
+ locals
+ link
+ justification
+ pure
+ ( destination
+ , PreparedCalculationLink destination discharge
+ )
+
+ calculationEquality left right =
+ maybe
+ (impossible
+ "checked calculation endpoints changed type or context")
+ pure
+ (equalScopedCore left right)
+
+closeCalculationProposition
+ :: [Exact.ExactLocalId]
+ -> Maybe (ScopedCheckedCore ObjectId)
+ -> ScopedCheckedCore ObjectId
+ -> Prepare (ScopedCheckedCore ObjectId)
+closeCalculationProposition identities calculationGuard proposition = do
+ guarded <-
+ case calculationGuard of
+ Nothing -> pure proposition
+ Just constraint ->
+ maybe
+ (impossible
+ "a checked calculation guard changed context")
+ pure
+ (implyScopedCore constraint proposition)
+ pure (closeBinders (length identities) guarded)
+ where
+ closeBinders 0 closed = closed
+ closeBinders remaining open =
+ closeBinders (remaining - 1)
+ (fromMaybe
+ (impossible
+ "a checked calculation lost a quantified binder")
+ (closeScopedForall open))
+
+preparedCalculationResult
+ :: PreparedCalculation
+ -> ScopedCheckedCore ObjectId
+preparedCalculationResult
+ (PreparedCheckedCalculation
+ _operandType _identities _guard _first _links result) =
+ result
+
+preparedCalculationLinkDestination
+ :: PreparedCalculationLink
+ -> ScopedCheckedCore ObjectId
+preparedCalculationLinkDestination
+ (PreparedCalculationLink destination _discharge) =
+ destination
+
+preparedCalculationLinkDischarge
+ :: PreparedCalculationLink
+ -> PreparedDischarge
+preparedCalculationLinkDischarge
+ (PreparedCalculationLink _destination discharge) =
+ discharge
+
+preparedDischargeGoal
+ :: PreparedDischarge
+ -> ScopedCheckedCore ObjectId
+preparedDischargeGoal = \case
+ PreparedVampireDischarge _location _justification goal _obligation ->
+ goal
+ PreparedSetExtensionality _location goal ->
+ goal
+
+prepareSymbolicBoundConstraints
+ :: Exact.ExactBinderContext
+ -> NonEmpty Raw.VarSymbol
+ -> Raw.Bound
+ -> Prepare (ScopedCheckedCore ObjectId)
+prepareSymbolicBoundConstraints context variables bound =
+ Exact.preparedExactPropositionCore
+ <$> ( liftDriver
+ (Exact.prepareExactSymbolicBoundConstraints
+ context variables bound)
+ >>= either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ )
+
+prepareSymbolicTake
+ :: Location
+ -> Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> ScopedCheckedCore ObjectId
+ -> NonEmpty Raw.VarSymbol
+ -> Raw.Bound
+ -> Raw.Stmt
+ -> Raw.Justification
+ -> Raw.Proof
+ -> Prepare PreparedProof
+prepareSymbolicTake
+ fallback location context locals goal variables bound statement
+ justification continuation = do
+ identities <- traverse (const allocateLocalIdentity) variables
+ context' <-
+ either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ (Exact.extendExactBinderContext
+ (NonEmpty.zip identities variables)
+ context)
+ witness <-
+ Exact.preparedExactPropositionCore
+ <$> ( liftDriver
+ (Exact.prepareExactSymbolicWitnessConstraints
+ context' variables bound statement)
+ >>= either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ )
+ prepareTake
+ fallback location context locals goal context'
+ (toList identities) witness justification continuation
+
+prepareNounTake
+ :: Location
+ -> Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> ScopedCheckedCore ObjectId
+ -> Raw.NounPhrase []
+ -> Raw.Justification
+ -> Raw.Proof
+ -> Prepare PreparedProof
+prepareNounTake
+ fallback location context locals goal nounPhrase
+ justification continuation = do
+ (identities, context') <-
+ case nounPhrase of
+ Raw.NounPhrase _left _noun variables _right _suchThat ->
+ case NonEmpty.nonEmpty variables of
+ Just binders -> do
+ identities <-
+ traverse (const allocateLocalIdentity) binders
+ context' <-
+ either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ (Exact.extendExactBinderContext
+ (NonEmpty.zip identities binders)
+ context)
+ pure (toList identities, context')
+ Nothing -> do
+ identity <- allocateLocalIdentity
+ context' <-
+ either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ (Exact.extendExactAnonymousBinderContext
+ identity context)
+ pure ([identity], context')
+ witness <-
+ Exact.preparedExactPropositionCore
+ <$> ( liftDriver
+ (Exact.prepareExactNounWitnessConstraints
+ context' nounPhrase)
+ >>= either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ )
+ prepareTake
+ fallback location context locals goal context'
+ identities witness justification continuation
+
+prepareTake
+ :: Location
+ -> Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> ScopedCheckedCore ObjectId
+ -> Exact.ExactBinderContext
+ -> [Exact.ExactLocalId]
+ -> ScopedCheckedCore ObjectId
+ -> Raw.Justification
+ -> Raw.Proof
+ -> Prepare PreparedProof
+prepareTake
+ fallback location context locals goal witnessContext
+ identities witness justification continuation = do
+ let witnessCount = length identities
+ existence = closeTakenWitnesses witnessCount witness
+ goal' = weakenForTakenWitnesses witnessCount goal
+ discharge <-
+ prepareDischarge
+ location context locals existence justification
+ local <- allocateLocal ExactAssumption witnessContext witness
+ PreparedTake identities witness discharge
+ <$> prepareProof
+ fallback
+ witnessContext
+ (locals <> [local])
+ RecursiveProofInduction
+ goal'
+ continuation
+
+-- The discharged existential and the opened witness premise are the same
+-- checked proposition viewed on opposite sides of existential elimination.
+closeTakenWitnesses
+ :: Int
+ -> ScopedCheckedCore ObjectId
+ -> ScopedCheckedCore ObjectId
+closeTakenWitnesses binderCount = go binderCount
+ where
+ go 0 proposition = proposition
+ go remaining proposition =
+ go (remaining - 1)
+ (fromMaybe
+ (impossible "a taken witness has no checked binder")
+ (closeScopedExists proposition))
+
+weakenForTakenWitnesses
+ :: Int
+ -> ScopedCheckedCore ObjectId
+ -> ScopedCheckedCore ObjectId
+weakenForTakenWitnesses binderCount = go binderCount
+ where
+ go 0 proposition = proposition
+ go remaining proposition =
+ go (remaining - 1)
+ (weakenCheckedScopedCore TySet proposition)
+
+openFixedVariables
+ :: Exact.ExactBinderContext
+ -> ScopedCheckedCore ObjectId
+ -> NonEmpty Raw.VarSymbol
+ -> Prepare
+ ( Exact.ExactBinderContext
+ , ScopedCheckedCore ObjectId
+ , [Exact.ExactLocalId]
+ )
+openFixedVariables initialContext initialGoal variables =
+ foldM openOne
+ (initialContext, initialGoal, [])
+ (toList variables)
+ where
+ openOne (context, goal, identities) variable = do
+ (binderType, body) <-
+ maybe
+ (throwProof
+ (ExactProofExpectedUniversalGoal
+ (locate variable)))
+ pure
+ (openScopedForall goal)
+ unless (binderType == TySet)
+ (throwProof
+ (ExactProofExpectedUniversalGoal
+ (locate variable)))
+ identity <- allocateLocalIdentity
+ context' <-
+ either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+ (Exact.extendExactBinderContext
+ ((identity, variable) :| [])
+ context)
+ pure (context', body, identities <> [identity])
+
+allocateLocalIdentity :: Prepare Exact.ExactLocalId
+allocateLocalIdentity = do
+ state <- State.get
+ State.put
+ state
+ { prepareNextLocal = prepareNextLocal state + 1
+ }
+ pure (Exact.exactLocalId (prepareNextLocal state))
+
+allocateLocal
+ :: ExactLocalOrigin
+ -> Exact.ExactBinderContext
+ -> ScopedCheckedCore ObjectId
+ -> Prepare PreparedLocal
+allocateLocal origin context proposition = do
+ state <- State.get
+ State.put
+ state
+ { prepareNextPremise = prepareNextPremise state + 1
+ }
+ pure
+ (PreparedLocal
+ (Backend.localPremiseOrdinal
+ (prepareNextPremise state))
+ origin
+ (Exact.exactBinderContextSupport context)
+ proposition)
+
+prepareConstructionFoundation
+ :: Prepare SetConstructionFoundation
+prepareConstructionFoundation = do
+ familyUnion <- foundation FamilyUnionCharacteristic
+ separation <- foundation SeparationCharacteristic
+ replacement <- foundation ReplacementCharacteristic
+ setChoose <- foundation SetChooseWitness
+ pure
+ (setConstructionFoundation
+ familyUnion separation replacement setChoose)
+ where
+ foundation tag =
+ liftDriver
+ (Declaration.currentFoundationAxiomLowering tag)
+
+prepareDischarge
+ :: Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> ScopedCheckedCore ObjectId
+ -> Raw.Justification
+ -> Prepare PreparedDischarge
+prepareDischarge location context locals goal justification =
+ prepareDischargeWith
+ (dischargeModeFor goal)
+ [] Nothing location context locals goal justification
+
+-- A contradictory-axioms answer can establish falsum, but never an unrelated
+-- proposition directly. Derive that distinction from the checked target so
+-- every surface proof spelling reaches the same guarded request path.
+dischargeModeFor :: ScopedCheckedCore ObjectId -> DischargeMode
+dischargeModeFor goal
+ | scopedCoreType goal == TyProp
+ , scopedCoreTerm goal == CFalsum =
+ IndirectContradictionDischarge
+ | otherwise =
+ DirectDischarge
+
+data DischargeMode
+ = DirectDischarge
+ | IndirectContradictionDischarge
+
+prepareDischargeWith
+ :: DischargeMode
+ -> [FoundationAxiomTag]
+ -> Maybe Declaration.VampirePremiseSelection
+ -> Location
+ -> Exact.ExactBinderContext
+ -> [PreparedLocal]
+ -> ScopedCheckedCore ObjectId
+ -> Raw.Justification
+ -> Prepare PreparedDischarge
+prepareDischargeWith
+ dischargeMode auxiliaries selectionOverride
+ location context locals goal justification =
+ case justification of
+ Raw.JustificationSetExt -> do
+ (leftToRight, rightToLeft) <-
+ maybe
+ (throwProof
+ (ExactProofSetExtensionalityGoalMismatch location))
+ pure
+ (splitScopedSetEquality goal)
+ unless
+ ( hasDerivedLocal leftToRight
+ && hasDerivedLocal rightToLeft
+ )
+ (throwProof
+ (ExactProofSetExtensionalityDirectionsUnavailable
+ location))
+ pure (PreparedSetExtensionality location goal)
+ _ -> do
+ preparedJustification <-
+ prepareJustification location justification
+ prepared <-
+ liftDriver
+ (prepareObligation
+ (Exact.exactBinderContextSupport context)
+ goal
+ (toScopedPremise <$> locals)
+ auxiliaries
+ (fromMaybe
+ (vampirePremiseSelection preparedJustification)
+ selectionOverride))
+ >>= either
+ (throwProof
+ . ExactProofObligationPreparationFailed location)
+ pure
+ pure
+ (PreparedVampireDischarge
+ location
+ preparedJustification
+ goal
+ prepared)
+ where
+ prepareObligation =
+ case dischargeMode of
+ DirectDischarge ->
+ Declaration.prepareScopedVampireObligationLowering
+ IndirectContradictionDischarge ->
+ Declaration.prepareScopedContradictionObligationLowering
+
+ hasDerivedLocal proposition =
+ any
+ (\case
+ PreparedLocal
+ _ordinal ExactDerivedClaim _support local ->
+ local == proposition
+ PreparedLocal{} ->
+ False)
+ locals
+
+ toScopedPremise
+ (PreparedLocal ordinal origin support proposition) =
+ Declaration.scopedVampirePremise
+ ordinal origin support proposition
+
+prepareJustification
+ :: Location
+ -> Raw.Justification
+ -> Prepare PreparedJustification
+prepareJustification _location Raw.JustificationEmpty =
+ pure PreparedAuto
+prepareJustification location (Raw.JustificationRef markers) = do
+ resolved <- traverse (resolveReference location) (toList markers)
+ let unique = stableUnique resolved
+ case unique of
+ [] ->
+ impossible "a nonempty citation list resolved to no facts"
+ first : rest ->
+ pure (PreparedReferences (first :| rest))
+prepareJustification _location Raw.JustificationLocal =
+ pure PreparedLocalOnly
+prepareJustification location Raw.JustificationSetExt =
+ throwProof (ExactProofUnsupportedStep location)
+
+vampirePremiseSelection
+ :: PreparedJustification
+ -> Declaration.VampirePremiseSelection
+vampirePremiseSelection = \case
+ PreparedAuto ->
+ Declaration.VampireImplicitPremises
+ PreparedReferences fingerprints ->
+ Declaration.VampireExplicitPremises fingerprints
+ PreparedLocalOnly ->
+ Declaration.VampireLocalPremises
+
+resolveReference
+ :: Location
+ -> Raw.Marker
+ -> Prepare SemanticFactOccurrenceFingerprint
+resolveReference location marker@(Raw.Marker name) = do
+ resolved <-
+ liftDriver
+ (Declaration.resolveVisibleFactAliasLowering
+ (semanticName name))
+ maybe
+ (throwProof
+ (ExactProofUnknownReference location marker))
+ pure
+ resolved
+
+prepareStatement
+ :: Exact.ExactBinderContext
+ -> Raw.Stmt
+ -> Prepare Exact.PreparedExactProposition
+prepareStatement context statement =
+ liftDriver
+ (Exact.prepareExactProposition context statement)
+ >>= either
+ (throwProof . ExactProofElaborationFailed)
+ pure
+
+validateStructuralComposition
+ :: Location
+ -> [ScopedCheckedCore ObjectId]
+ -> ( CheckedFoundation
+ -> (ObjectId -> Maybe CoreType)
+ -> Either KernelProof.KernelProofBuildError ()
+ )
+ -> Prepare ()
+validateStructuralComposition location propositions validate = do
+ foundation <-
+ liftDriver Declaration.currentFoundationLowering
+ let identities =
+ Set.toAscList
+ (Set.unions
+ ( canonicalTermGlobals . scopedCoreTerm
+ <$> propositions
+ ))
+ types <-
+ traverse
+ (\identity -> do
+ coreType <-
+ liftDriver
+ (Declaration.objectTypeLowering identity)
+ maybe
+ (impossible
+ "a checked structural proof lost a global object")
+ (\availableType -> pure (identity, availableType))
+ coreType)
+ identities
+ either
+ (throwProof
+ . ExactProofStructuralCompositionFailed location)
+ pure
+ (validate foundation
+ (\identity -> Map.lookup identity (Map.fromList types)))
+
+structuralFailure :: Location -> Text -> Prepare value
+structuralFailure location message =
+ throwProof
+ (ExactProofStructuralCompositionFailed
+ location
+ (KernelProof.ProofStructuralCompositionMismatch message))
+
+data CheckedExactProofAuthorization = CheckedExactProofAuthorization
+ !PreparedProof
+ !Bool
+
+lowerPreparedExactProof
+ :: PreparedExactProof
+ -> Declaration.LoweringDriver
+ (Either
+ Declaration.DeclarationError
+ (Declaration.CheckedDeclaration CheckedExactProofAuthorization))
+lowerPreparedExactProof
+ (PreparedExactProof _location alias target proof syntax) =
+ fmap checked
+ <$> Declaration.prepareCandidateSpecLowering
+ [] target SearchEligible [alias]
+ where
+ checked spec =
+ Declaration.checkedProofDeclaration
+ syntax [] [] [] []
+ [Declaration.checkedCandidate spec planning :| []]
+ (CheckedExactProofAuthorization
+ proof
+ (isJust (preparedProofFirstOmission proof)))
+ where
+ requests = plannedProofRequests proof
+ planning
+ | isJust (preparedProofFirstOmission proof) =
+ Declaration.checkedOmittedPlanning requests []
+ | otherwise =
+ Declaration.checkedSourceProofPlanning requests []
+
+authorizeCheckedExactProof
+ :: CheckedExactProofAuthorization
+ -> [NonEmpty Declaration.ReservedCandidate]
+ -> Declaration.Declaration ()
+authorizeCheckedExactProof
+ (CheckedExactProofAuthorization proof hasOmission) = \case
+ [candidate :| []]
+ | hasOmission ->
+ Declaration.authorizeOmittedCandidate
+ candidate
+ (executePreparedProof proof)
+ | otherwise ->
+ Declaration.authorizeVampireCandidate
+ candidate
+ (executePreparedProof proof)
+ stages ->
+ Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch
+ 1 (length stages))
+
+data CheckedFinalPreludeFoundationAuthorization =
+ CheckedFinalPreludeFoundationAuthorization !FoundationAxiomTag
+
+lowerPreparedFinalPreludeFoundationClaim
+ :: PreparedFinalPreludeFoundationClaim
+ -> Declaration.LoweringDriver
+ (Either
+ Declaration.DeclarationError
+ (Declaration.CheckedDeclaration
+ CheckedFinalPreludeFoundationAuthorization))
+lowerPreparedFinalPreludeFoundationClaim
+ (PreparedFinalPreludeFoundationClaim
+ _location alias target tag syntax) =
+ fmap checked
+ <$> Declaration.prepareCandidateSpecLowering
+ [] target SearchEligible [alias]
+ where
+ checked spec =
+ Declaration.checkedProofDeclaration
+ syntax [] [] [] []
+ [ Declaration.checkedCandidate spec
+ (Declaration.checkedKernelPlanning
+ (Authority.FoundationLeaf tag) [])
+ :| []
+ ]
+ (CheckedFinalPreludeFoundationAuthorization tag)
+
+authorizeCheckedFinalPreludeFoundationClaim
+ :: CheckedFinalPreludeFoundationAuthorization
+ -> [NonEmpty Declaration.ReservedCandidate]
+ -> Declaration.Declaration ()
+authorizeCheckedFinalPreludeFoundationClaim
+ (CheckedFinalPreludeFoundationAuthorization tag) = \case
+ [candidate :| []] ->
+ Declaration.authorizeKernelConstructionCandidate
+ (Authority.FoundationLeaf tag)
+ candidate
+ (pure (foundationFactDerivation tag))
+ stages ->
+ Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch
+ 1 (length stages))
+
+preparedProofFirstOmission :: PreparedProof -> Maybe Location
+preparedProofFirstOmission = \case
+ PreparedImplicitAuto{} -> Nothing
+ PreparedQed{} -> Nothing
+ PreparedOmitted location _goal -> Just location
+ PreparedFix _identities continuation ->
+ preparedProofFirstOmission continuation
+ PreparedAssume _antecedent continuation ->
+ preparedProofFirstOmission continuation
+ PreparedTake _identities _witness _discharge continuation ->
+ preparedProofFirstOmission continuation
+ PreparedSetInduction
+ (PreparedCheckedSetInduction
+ _focus _property _antecedents _target
+ _hypothesis _result child) ->
+ preparedProofFirstOmission child
+ PreparedHave _claim _discharge continuation ->
+ preparedProofFirstOmission continuation
+ PreparedSuffices _goal _reduction _implication _discharge continuation ->
+ preparedProofFirstOmission continuation
+ PreparedCalculate _calculation continuation ->
+ preparedProofFirstOmission continuation
+ PreparedSince _since _evidence _claim _discharge continuation ->
+ preparedProofFirstOmission continuation
+ PreparedSubclaim _claim subproof continuation ->
+ preparedProofFirstOmission subproof
+ <|> preparedProofFirstOmission continuation
+ PreparedDefine _identity _body _definition continuation ->
+ preparedProofFirstOmission continuation
+ PreparedDefineRelational
+ _identity _body _functionality _definitions continuation ->
+ preparedProofFirstOmission continuation
+ PreparedDefineFunction _identity _graph _definition continuation ->
+ preparedProofFirstOmission continuation
+ PreparedByCase (PreparedCaseAnalysis _goal cases _exhaustive _discharge) ->
+ foldr
+ ((<|>) . preparedProofFirstOmission . preparedCaseProof)
+ Nothing
+ cases
+ PreparedByContradiction _goal _negation _falsum child ->
+ preparedProofFirstOmission child
+ PreparedContradiction{} -> Nothing
+
+plannedProofRequests
+ :: PreparedProof
+ -> [Declaration.CheckedPlannedVampireRequest]
+plannedProofRequests = \case
+ PreparedImplicitAuto discharge -> plannedDischargeRequests discharge
+ PreparedQed discharge -> plannedDischargeRequests discharge
+ PreparedOmitted{} -> []
+ PreparedFix _identities continuation ->
+ plannedProofRequests continuation
+ PreparedAssume _antecedent continuation ->
+ plannedProofRequests continuation
+ PreparedTake _identities _witness discharge continuation ->
+ plannedDischargeRequests discharge <> plannedProofRequests continuation
+ PreparedSetInduction
+ (PreparedCheckedSetInduction
+ _focus _property _antecedents _target
+ _hypothesis _result child) ->
+ plannedProofRequests child
+ PreparedHave _claim discharge continuation ->
+ plannedDischargeRequests discharge <> plannedProofRequests continuation
+ PreparedSuffices _goal _reduction _implication discharge continuation ->
+ plannedDischargeRequests discharge <> plannedProofRequests continuation
+ PreparedCalculate calculation continuation ->
+ plannedCalculationRequests calculation
+ <> plannedProofRequests continuation
+ PreparedSince _since evidence _claim discharge continuation ->
+ plannedSinceEvidenceRequests evidence
+ <> plannedDischargeRequests discharge
+ <> plannedProofRequests continuation
+ PreparedSubclaim _claim subproof continuation ->
+ plannedProofRequests subproof <> plannedProofRequests continuation
+ PreparedDefine _identity _body _definition continuation ->
+ plannedProofRequests continuation
+ PreparedDefineRelational
+ _identity _body functionality _definitions continuation ->
+ plannedDischargeRequests functionality
+ <> plannedProofRequests continuation
+ PreparedDefineFunction _identity _graph _definition continuation ->
+ plannedProofRequests continuation
+ PreparedByCase
+ (PreparedCaseAnalysis _goal cases _exhaustive discharge) ->
+ concatMap
+ (plannedProofRequests . preparedCaseProof)
+ (toList cases)
+ <> plannedDischargeRequests discharge
+ PreparedByContradiction _goal _negation _falsum child ->
+ plannedProofRequests child
+ PreparedContradiction _goal _falsum discharge ->
+ plannedDischargeRequests discharge
+
+plannedDischargeRequests
+ :: PreparedDischarge
+ -> [Declaration.CheckedPlannedVampireRequest]
+plannedDischargeRequests = \case
+ PreparedVampireDischarge location _justification _goal obligation ->
+ [Declaration.checkedPlannedVampireRequest location obligation]
+ PreparedSetExtensionality{} -> []
+
+plannedCalculationRequests
+ :: PreparedCalculation
+ -> [Declaration.CheckedPlannedVampireRequest]
+plannedCalculationRequests
+ (PreparedCheckedCalculation
+ _operandType _identities _guard _first links _result) =
+ concatMap
+ (plannedDischargeRequests . preparedCalculationLinkDischarge)
+ (toList links)
+
+plannedSinceEvidenceRequests
+ :: PreparedSinceEvidence
+ -> [Declaration.CheckedPlannedVampireRequest]
+plannedSinceEvidenceRequests = \case
+ PreparedSinceExisting{} -> []
+ PreparedSinceDischarged discharge _local ->
+ plannedDischargeRequests discharge
+
+executePreparedProof
+ :: PreparedProof
+ -> Declaration.CandidateProof ()
+executePreparedProof = \case
+ PreparedImplicitAuto discharge ->
+ executeDischarge discharge
+ PreparedQed discharge ->
+ executeDischarge discharge
+ PreparedOmitted _location _goal ->
+ Declaration.recordOmittedUse
+ PreparedFix _identities continuation ->
+ executePreparedProof continuation
+ PreparedAssume _antecedent continuation ->
+ executePreparedProof continuation
+ PreparedTake _identities _witness discharge continuation -> do
+ executeDischarge discharge
+ executePreparedProof continuation
+ PreparedSetInduction
+ (PreparedCheckedSetInduction
+ _focus _property _antecedents _target
+ _hypothesis _result child) ->
+ executePreparedProof child
+ PreparedHave _claim discharge continuation -> do
+ executeDischarge discharge
+ executePreparedProof continuation
+ PreparedSuffices goal reduction implication discharge continuation -> do
+ executeDischarge discharge
+ executePreparedProof continuation
+ unless
+ (implyScopedCore reduction goal == Just implication)
+ (impossible "a prepared suffices implication diverged")
+ PreparedCalculate calculation continuation -> do
+ executePreparedCalculation calculation
+ executePreparedProof continuation
+ PreparedSince sinceProposition evidence _claim discharge continuation -> do
+ executeSinceEvidence sinceProposition evidence
+ executeDischarge discharge
+ executePreparedProof continuation
+ PreparedSubclaim _claim subproof continuation -> do
+ executePreparedProof subproof
+ executePreparedProof continuation
+ PreparedDefine _identity _body _definition continuation ->
+ executePreparedProof continuation
+ PreparedDefineRelational
+ _identity _body functionality _definitions continuation -> do
+ executeDischarge functionality
+ executePreparedProof continuation
+ PreparedDefineFunction _identity _graph _definition continuation ->
+ executePreparedProof continuation
+ PreparedByCase
+ (PreparedCaseAnalysis _goal cases _exhaustive discharge) -> do
+ traverse_ (executePreparedProof . preparedCaseProof) cases
+ executeDischarge discharge
+ PreparedByContradiction _goal _negation _falsum child ->
+ executePreparedProof child
+ PreparedContradiction _goal _falsum discharge ->
+ executeDischarge discharge
+
+executeDischarge
+ :: PreparedDischarge
+ -> Declaration.CandidateProof ()
+executeDischarge
+ (PreparedVampireDischarge
+ location _justification _goal obligation) =
+ Declaration.locateProofObligation location
+ (Declaration.acceptPreparedVampireObligation obligation)
+executeDischarge PreparedSetExtensionality{} =
+ pure ()
+
+executePreparedCalculation
+ :: PreparedCalculation
+ -> Declaration.CandidateProof ()
+executePreparedCalculation
+ (PreparedCheckedCalculation
+ _operandType _identities _guard _first links _result) =
+ traverse_
+ (executeDischarge . preparedCalculationLinkDischarge)
+ links
+
+executeSinceEvidence
+ :: ScopedCheckedCore ObjectId
+ -> PreparedSinceEvidence
+ -> Declaration.CandidateProof ()
+executeSinceEvidence proposition = \case
+ PreparedSinceExisting local ->
+ unless (preparedLocalProposition local == proposition)
+ (impossible "a structural since premise diverged")
+ PreparedSinceDischarged discharge local -> do
+ executeDischarge discharge
+ unless
+ ( preparedDischargeGoal discharge == proposition
+ && preparedLocalProposition local == proposition
+ )
+ (impossible "a discharged since premise diverged")
+
+preparedLocalProposition
+ :: PreparedLocal
+ -> ScopedCheckedCore ObjectId
+preparedLocalProposition
+ (PreparedLocal _ordinal _origin _support proposition) =
+ proposition
+
+encodePreparedProof :: PreparedProof -> ByteString
+encodePreparedProof =
+ encodeCache . putPreparedProof
+
+putPreparedProof :: PreparedProof -> CachePut
+putPreparedProof = \case
+ PreparedImplicitAuto discharge -> do
+ putCacheTag 0x00
+ putPreparedDischarge discharge
+ PreparedQed discharge -> do
+ putCacheTag 0x01
+ putPreparedDischarge discharge
+ PreparedOmitted _location goal -> do
+ putCacheTag 0x06
+ putScopedProposition goal
+ PreparedFix identities continuation -> do
+ putCacheTag 0x02
+ putCacheList
+ (putCacheNatural . Exact.exactLocalIdValue)
+ identities
+ putPreparedProof continuation
+ PreparedAssume antecedent continuation -> do
+ putCacheTag 0x03
+ putScopedProposition antecedent
+ putPreparedProof continuation
+ PreparedTake identities witness discharge continuation -> do
+ putCacheTag 0x08
+ putCacheList
+ (putCacheNatural . Exact.exactLocalIdValue)
+ identities
+ putScopedProposition witness
+ putPreparedDischarge discharge
+ putPreparedProof continuation
+ PreparedSetInduction
+ (PreparedCheckedSetInduction
+ focus property antecedents target hypothesis result child) -> do
+ putCacheTag 0x07
+ putPreparedSetInductionFocus focus
+ putScopedProposition property
+ putCacheList putScopedProposition antecedents
+ putScopedProposition target
+ putScopedProposition hypothesis
+ putScopedProposition result
+ putPreparedProof child
+ PreparedHave claim discharge continuation -> do
+ putCacheTag 0x04
+ putScopedProposition claim
+ putPreparedDischarge discharge
+ putPreparedProof continuation
+ PreparedSuffices goal reduction implication discharge continuation -> do
+ putCacheTag 0x0c
+ putScopedProposition goal
+ putScopedProposition reduction
+ putScopedProposition implication
+ putPreparedDischarge discharge
+ putPreparedProof continuation
+ PreparedCalculate calculation continuation -> do
+ putCacheTag 0x0d
+ putPreparedCalculation calculation
+ putPreparedProof continuation
+ PreparedSince sinceProposition evidence claim discharge continuation -> do
+ putCacheTag 0x0e
+ putScopedProposition sinceProposition
+ putPreparedSinceEvidence evidence
+ putScopedProposition claim
+ putPreparedDischarge discharge
+ putPreparedProof continuation
+ PreparedSubclaim claim subproof continuation -> do
+ putCacheTag 0x05
+ putScopedProposition claim
+ putPreparedProof subproof
+ putPreparedProof continuation
+ PreparedDefine identity body definitions continuation -> do
+ putCacheTag 0x09
+ putCacheNatural (Exact.exactLocalIdValue identity)
+ putScopedTerm body
+ putCacheList putScopedProposition (toList definitions)
+ putPreparedProof continuation
+ PreparedDefineRelational
+ identity body functionality definitions continuation -> do
+ putCacheTag 0x11
+ putCacheNatural (Exact.exactLocalIdValue identity)
+ putScopedTerm body
+ putPreparedDischarge functionality
+ putCacheList putScopedProposition (toList definitions)
+ putPreparedProof continuation
+ PreparedByCase caseAnalysis -> do
+ putCacheTag 0x0f
+ putPreparedCaseAnalysis caseAnalysis
+ PreparedByContradiction goal negation falsum child -> do
+ putCacheTag 0x10
+ putScopedProposition goal
+ putScopedProposition negation
+ putScopedProposition falsum
+ putPreparedProof child
+ PreparedContradiction goal falsum discharge -> do
+ putCacheTag 0x0a
+ putScopedProposition goal
+ putScopedProposition falsum
+ putPreparedDischarge discharge
+ PreparedDefineFunction identity graph definition continuation -> do
+ putCacheTag 0x0b
+ putCacheNatural (Exact.exactLocalIdValue identity)
+ putScopedTerm graph
+ putScopedProposition definition
+ putPreparedProof continuation
+
+putPreparedSetInductionFocus
+ :: PreparedSetInductionFocus
+ -> CachePut
+putPreparedSetInductionFocus = \case
+ PreparedInitialSetInductionFocus identity index -> do
+ putCacheTag 0x00
+ putCacheNatural (Exact.exactLocalIdValue identity)
+ putCacheNatural index
+ PreparedLeadingSetInductionFocus identity -> do
+ putCacheTag 0x01
+ putCacheNatural (Exact.exactLocalIdValue identity)
+
+putPreparedDischarge :: PreparedDischarge -> CachePut
+putPreparedDischarge
+ (PreparedVampireDischarge
+ _location justification goal _obligation) = do
+ putPreparedDischargeSyntax justification goal
+putPreparedDischarge
+ (PreparedSetExtensionality _location goal) = do
+ putCacheTag 0x03
+ putScopedProposition goal
+
+putPreparedDischargeSyntax
+ :: PreparedJustification
+ -> ScopedCheckedCore ObjectId
+ -> CachePut
+putPreparedDischargeSyntax justification goal = do
+ putPreparedJustification justification
+ putScopedProposition goal
+
+putPreparedCalculation :: PreparedCalculation -> CachePut
+putPreparedCalculation
+ (PreparedCheckedCalculation
+ operandType identities calculationGuard first links result) = do
+ putCoreTypeCache operandType
+ putCacheList
+ (putCacheNatural . Exact.exactLocalIdValue)
+ identities
+ putCacheMaybe putScopedProposition calculationGuard
+ putCacheList putScopedTerm
+ (first : (preparedCalculationLinkDestination <$> toList links))
+ putCacheList putPreparedDischarge
+ (preparedCalculationLinkDischarge <$> toList links)
+ putScopedProposition result
+
+putPreparedSinceEvidence :: PreparedSinceEvidence -> CachePut
+putPreparedSinceEvidence = \case
+ PreparedSinceExisting local -> do
+ putCacheTag 0x00
+ putPreparedLocalEvidence local
+ PreparedSinceDischarged discharge local -> do
+ putCacheTag 0x01
+ putPreparedDischarge discharge
+ putPreparedLocalEvidence local
+
+putPreparedCaseAnalysis :: PreparedCaseAnalysis -> CachePut
+putPreparedCaseAnalysis
+ (PreparedCaseAnalysis goal cases exhaustive discharge) = do
+ putScopedProposition goal
+ putCacheList putPreparedCase (toList cases)
+ putScopedProposition exhaustive
+ putPreparedDischarge discharge
+
+putPreparedCase :: PreparedCase -> CachePut
+putPreparedCase (PreparedCase assumption proof) = do
+ putScopedProposition assumption
+ putPreparedProof proof
+
+putPreparedLocalEvidence :: PreparedLocal -> CachePut
+putPreparedLocalEvidence
+ (PreparedLocal ordinal _origin support proposition) = do
+ putCacheNatural (Backend.localPremiseOrdinalValue ordinal)
+ putCacheList
+ (\(identity, coreType) -> do
+ putCacheNatural (Exact.exactLocalIdValue identity)
+ putCoreTypeCache coreType)
+ (Vector.toList support)
+ putScopedProposition proposition
+
+implicitAutoProofSyntaxId
+ :: ScopedCheckedCore ObjectId
+ -> ProofSyntaxId
+implicitAutoProofSyntaxId goal =
+ proofSyntaxId
+ (encodeCache do
+ putCacheTag 0x00
+ putPreparedDischargeSyntax PreparedAuto goal)
+
+putPreparedJustification :: PreparedJustification -> CachePut
+putPreparedJustification = \case
+ PreparedAuto ->
+ putCacheTag 0x00
+ PreparedReferences fingerprints -> do
+ putCacheTag 0x01
+ putCacheList
+ putSemanticFactOccurrenceFingerprintCache
+ (toList fingerprints)
+ PreparedLocalOnly ->
+ putCacheTag 0x02
+
+putScopedProposition
+ :: ScopedCheckedCore ObjectId
+ -> CachePut
+putScopedProposition proposition = do
+ putCacheList putCoreTypeCache
+ (scopedCoreContext proposition)
+ putCanonicalTermCache putObjectIdCache
+ (scopedCoreTerm proposition)
+
+putScopedTerm
+ :: ScopedCheckedCore ObjectId
+ -> CachePut
+putScopedTerm term = do
+ putCacheList putCoreTypeCache
+ (scopedCoreContext term)
+ putCoreTypeCache (scopedCoreType term)
+ putCanonicalTermCache putObjectIdCache
+ (scopedCoreTerm term)
+
+proofLocation :: Location -> Raw.Proof -> Location
+proofLocation fallback = \case
+ Raw.Omitted location -> location
+ Raw.Qed maybeLocation _justification ->
+ fromMaybe fallback maybeLocation
+ Raw.Contradiction location _justification -> location
+ Raw.ByCase location _cases -> location
+ Raw.ByContradiction location _proof -> location
+ Raw.BySetInduction location _term _proof -> location
+ Raw.ByOrdInduction location _proof -> location
+ Raw.Assume location _statement _proof -> location
+ Raw.FixSymbolic location _variables _bound _proof -> location
+ Raw.FixSuchThat location _variables _statement _proof -> location
+ Raw.Calc location _quantifier _calculation _proof -> location
+ Raw.TakeVar location _variables _bound _statement _justification _proof ->
+ location
+ Raw.TakeNoun location _noun _justification _proof -> location
+ Raw.Have location _since _statement _justification _proof -> location
+ Raw.Suffices location _statement _justification _proof -> location
+ Raw.Subclaim location _statement _subproof _proof -> location
+ Raw.Define location _variable _expression _proof -> location
+ Raw.DefineFunction location _function _argument _value _bound _domain _proof ->
+ location
+ Raw.DefineFunctionLocal
+ location _function _argument _value _bound _target _rules _proof ->
+ location
+
+throwProof :: ExactProofError -> Prepare value
+throwProof =
+ State.lift . Except.throwError
+
+liftDriver
+ :: Declaration.LoweringDriver value
+ -> Prepare value
+liftDriver =
+ State.lift . Except.lift
+
+stableUnique :: Ord value => [value] -> [value]
+stableUnique =
+ reverse . snd
+ . foldl'
+ (\(seen, reversed) value ->
+ if value `Set.member` seen
+ then (seen, reversed)
+ else
+ ( Set.insert value seen
+ , value : reversed
+ ))
+ (Set.empty, [])
diff --git a/source/Felix/Checking/Exact/Vocabulary.hs b/source/Felix/Checking/Exact/Vocabulary.hs
new file mode 100644
index 0000000..a712bf1
--- /dev/null
+++ b/source/Felix/Checking/Exact/Vocabulary.hs
@@ -0,0 +1,218 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Semantic classification shared by the exact source compilers.
+module Felix.Checking.Exact.Vocabulary
+ ( FixedSemanticMeaning(..)
+ , fixedSemanticMeaning
+ , lowerFixedEqualityPredicate
+ , ExactSymbolClass(..)
+ , classifyExactSymbol
+ , FixedSetTermDispatch(..)
+ , dispatchFixedSetTerm
+ ) where
+
+import Base hiding (Empty)
+import Felix.Checking.Core
+import Felix.Checking.Semantic
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Internal qualified as Internal
+import Felix.Syntax.Lexicon qualified as Lexicon
+
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Map.Strict qualified as Map
+
+
+data FixedSemanticMeaning
+ = FixedEquality
+ | FixedDisequality
+ | FixedIntrinsic !CoreIntrinsicTag
+ | FixedNegatedIntrinsic !CoreIntrinsicTag
+ deriving stock (Show, Eq)
+
+fixedSemanticMeaning
+ :: SemanticGlobalKey
+ -> Maybe FixedSemanticMeaning
+fixedSemanticMeaning key =
+ Map.lookup key fixedSemanticVocabulary
+
+-- This inventory owns every exact source form that bypasses global lookup.
+fixedSemanticVocabulary
+ :: Map.Map SemanticGlobalKey FixedSemanticMeaning
+fixedSemanticVocabulary =
+ Map.fromList
+ [ ( relationKey Raw.EqSymbol
+ , FixedEquality
+ )
+ , ( SemanticRightAdjective
+ (Raw.lexicalItemPattern
+ Lexicon.builtinEqualityRightAdjective)
+ , FixedEquality
+ )
+ , ( verbKey Lexicon.builtinEqualityVerb
+ , FixedEquality
+ )
+ , ( relationKey Raw.ElementSymbol
+ , FixedIntrinsic Member
+ )
+ , ( relationKey Raw.NotElementSymbol
+ , FixedNegatedIntrinsic Member
+ )
+ , ( relationKey Raw.NeqSymbol
+ , FixedDisequality
+ )
+ , ( nounKey Lexicon.builtinElementNoun
+ , FixedIntrinsic Member
+ )
+ , ( expressionKey
+ (Raw.TokenCons (Raw.Command "emptyset") Raw.End)
+ , FixedIntrinsic Empty
+ )
+ , ( expressionKey (Raw.mixfixPattern Raw.UnionsSymbol)
+ , FixedIntrinsic FamilyUnion
+ )
+ , ( expressionKey (unaryCommandPattern "pow")
+ , FixedIntrinsic PowerSet
+ )
+ , ( expressionKey (unaryCommandPattern "cumul")
+ , FixedIntrinsic UnivOf
+ )
+ , ( expressionKey (Raw.mixfixPattern Raw.UpairSymbol)
+ , FixedIntrinsic PairSet
+ )
+ ]
+ where
+ relationKey relation =
+ SemanticRelation
+ (Raw.relationSymbolToken relation)
+ (Raw.relationSymbolParameterArity relation)
+ nounKey item =
+ let patterns = Raw.lexicalItemSgPlPattern item
+ in SemanticNoun (Raw.sg patterns) (Raw.pl patterns)
+ verbKey item =
+ let patterns = Raw.lexicalItemSgPlPattern item
+ in SemanticVerb (Raw.sg patterns) (Raw.pl patterns)
+ expressionKey = SemanticExpressionFunction
+
+-- | Lower the fixed proposition meanings shared by raw exact elaboration and
+-- the reusable internal-formula path. Membership deliberately retains its
+-- carrier-aware source lowering and is not handled here.
+lowerFixedEqualityPredicate
+ :: FixedSemanticMeaning
+ -> [CanonicalTerm global]
+ -> Maybe (CanonicalTerm global)
+lowerFixedEqualityPredicate meaning arguments =
+ case (meaning, arguments) of
+ (FixedEquality, [left, right]) ->
+ Just (CEq TySet left right)
+ (FixedDisequality, [left, right]) ->
+ Just (CImp (CEq TySet left right) CFalsum)
+ _ ->
+ Nothing
+
+unaryCommandPattern :: Text -> Raw.Pattern
+unaryCommandPattern command =
+ Raw.TokenCons (Raw.Command command)
+ (Raw.TokenCons Raw.InvisibleBraceL
+ (Raw.HoleCons
+ (Raw.TokenCons Raw.InvisibleBraceR Raw.End)))
+
+data ExactSymbolClass
+ = ExactClosedLiteral
+ | ExactFixedPrimitive !FixedSemanticMeaning
+ | ExactSourceGlobal !(NonEmpty SemanticGlobalKey)
+ | ExactUnsupportedSymbol
+ deriving stock (Show, Eq)
+
+classifyExactSymbol :: Internal.Symbol -> ExactSymbolClass
+classifyExactSymbol symbol =
+ case symbol of
+ Internal.SymbolInteger{} ->
+ ExactClosedLiteral
+ _ ->
+ case NonEmpty.nonEmpty (semanticKeys symbol) of
+ Nothing ->
+ ExactUnsupportedSymbol
+ Just keys ->
+ case firstFixed keys of
+ Just meaning ->
+ ExactFixedPrimitive meaning
+ Nothing ->
+ ExactSourceGlobal keys
+ where
+ firstFixed =
+ foldr
+ (\key found -> fixedSemanticMeaning key <|> found)
+ Nothing
+
+semanticKeys :: Internal.Symbol -> [SemanticGlobalKey]
+semanticKeys = \case
+ Internal.SymbolMixfix symbol ->
+ [SemanticExpressionFunction (Raw.mixfixPattern symbol)]
+ Internal.SymbolFun item ->
+ let patterns = Raw.lexicalItemSgPlPattern item
+ in [SemanticFunctionPhrase (Raw.sg patterns) (Raw.pl patterns)]
+ Internal.SymbolPredicate predicate ->
+ case predicate of
+ Internal.PredicateAdj item ->
+ [ SemanticLeftAdjective (Raw.lexicalItemPattern item)
+ , SemanticRightAdjective (Raw.lexicalItemPattern item)
+ ]
+ Internal.PredicateVerb item ->
+ let patterns = Raw.lexicalItemSgPlPattern item
+ in [SemanticVerb (Raw.sg patterns) (Raw.pl patterns)]
+ Internal.PredicateNoun item ->
+ let patterns = Raw.lexicalItemSgPlPattern item
+ in [SemanticNoun (Raw.sg patterns) (Raw.pl patterns)]
+ Internal.PredicateRelation relation ->
+ [ SemanticRelation
+ (Raw.relationSymbolToken relation)
+ (Raw.relationSymbolParameterArity relation)
+ ]
+ Internal.PredicateSymbol{} -> []
+ Internal.PredicateNounStruct{} -> []
+ Internal.SymbolInteger{} -> []
+
+-- | Result of interpreting a symbol already classified by the fixed exact
+-- vocabulary as a set-valued term.
+data FixedSetTermDispatch global
+ = NotFixedSetTerm
+ | LoweredFixedSetTerm !(CanonicalTerm global)
+ | RejectedFixedSetTerm
+ deriving stock (Show, Eq)
+
+-- | Interpret every fixed symbol that can occur in the reusable internal-term
+-- lowering. Fixed relations are handled by formula lowering.
+dispatchFixedSetTerm
+ :: Internal.Symbol
+ -> [CanonicalTerm global]
+ -> FixedSetTermDispatch global
+dispatchFixedSetTerm symbol arguments =
+ case classifyExactSymbol symbol of
+ ExactFixedPrimitive meaning ->
+ case meaning of
+ FixedIntrinsic intrinsic ->
+ applyIntrinsic
+ (CIntrinsic intrinsic)
+ (coreIntrinsicType intrinsic)
+ arguments
+ FixedNegatedIntrinsic _intrinsic ->
+ RejectedFixedSetTerm
+ FixedEquality ->
+ RejectedFixedSetTerm
+ FixedDisequality ->
+ RejectedFixedSetTerm
+ _ ->
+ NotFixedSetTerm
+ where
+ applyIntrinsic term coreType remaining =
+ case (coreType, remaining) of
+ (TySet, []) ->
+ LoweredFixedSetTerm term
+ (TyArrow TySet resultType, argument : rest) ->
+ applyIntrinsic
+ (CApp term argument)
+ resultType
+ rest
+ _ ->
+ RejectedFixedSetTerm
diff --git a/source/Felix/Checking/FinalPrelude.hs b/source/Felix/Checking/FinalPrelude.hs
new file mode 100644
index 0000000..9cc43f4
--- /dev/null
+++ b/source/Felix/Checking/FinalPrelude.hs
@@ -0,0 +1,1266 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE RankNTypes #-}
+
+-- | Authority-confined construction of the inert final-prelude candidate.
+module Felix.Checking.FinalPrelude
+ ( FinalPreludeCandidate
+ , finalPreludeParsed
+ , finalPreludeSyntax
+ , finalPreludeSemantic
+ , finalPreludePrefix
+ , finalPreludeObjects
+ , PreludePublicRole(..)
+ , expectedFinalPreludePublicRoles
+ , FinalPreludeRoleTarget(..)
+ , finalPreludePublicRole
+ , FinalPreludeValidationError(..)
+ , FinalPreludeFailure(..)
+ , FinalPreludeBuildResult(..)
+ , buildFinalPreludeCandidate
+ , buildParsedFinalPreludeCandidate
+ , validateOmegaFactInventory
+ ) where
+
+import Base hiding (Empty)
+import Felix.Checking.Authority qualified as Authority
+import Felix.Checking.Core
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Exact qualified as Exact
+import Felix.Checking.Exact.Proof qualified as ExactProof
+import Felix.Checking.Foundation
+import Felix.Checking.Identity
+import Felix.Checking.SetConstruction
+import Felix.Checking.Semantic
+import Felix.Checking.Semantic qualified as Semantic
+import Felix.Module
+import Felix.Cache.Codec (CacheDigest)
+import Felix.Parse
+import Felix.Prelude qualified as Prelude
+import Felix.Source (ImportRef)
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Interface
+import Felix.Syntax.Lexicon qualified as Lexicon
+
+import Control.Monad (unless)
+import Data.Bifunctor (first)
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+
+
+data FinalPreludeCandidate = FinalPreludeCandidate
+ !Prelude.ReservedParsedPrelude
+ !ModuleSyntaxInterface
+ !SemanticInterface
+ !Declaration.PendingModulePrefix
+ !CheckedObjectClosure
+ !(Map.Map PreludePublicRole FinalPreludeRoleTarget)
+
+finalPreludeParsed
+ :: FinalPreludeCandidate
+ -> Prelude.ReservedParsedPrelude
+finalPreludeParsed
+ (FinalPreludeCandidate parsed _syntax _semantic _prefix _objects
+ _roles) =
+ parsed
+
+finalPreludeSyntax
+ :: FinalPreludeCandidate
+ -> ModuleSyntaxInterface
+finalPreludeSyntax
+ (FinalPreludeCandidate _parsed syntax _semantic _prefix _objects
+ _roles) =
+ syntax
+
+finalPreludeSemantic
+ :: FinalPreludeCandidate
+ -> SemanticInterface
+finalPreludeSemantic
+ (FinalPreludeCandidate _parsed _syntax semantic _prefix _objects
+ _roles) =
+ semantic
+
+finalPreludePrefix
+ :: FinalPreludeCandidate
+ -> Declaration.PendingModulePrefix
+finalPreludePrefix
+ (FinalPreludeCandidate _parsed _syntax _semantic prefix _objects
+ _roles) =
+ prefix
+
+finalPreludeObjects
+ :: FinalPreludeCandidate
+ -> CheckedObjectClosure
+finalPreludeObjects
+ (FinalPreludeCandidate _parsed _syntax _semantic _prefix objects
+ _roles) =
+ objects
+
+data FinalPreludeRoleTarget
+ = FinalPreludeObjectRole !ObjectId
+ | FinalPreludeTheoremRole !TheoremRef
+ deriving stock (Show, Eq, Ord)
+
+-- | Stable public roles exported by the packaged final prelude.
+data PreludePublicRole
+ = PreludeInfinityTheorem
+ | PreludeOmegaObject
+ | PreludeOmegaDefiningEquation
+ | PreludeNaturalsAlias
+ | PreludeNaturalsInductiveTheorem
+ | PreludeNaturalsMinimalTheorem
+ deriving stock (Show, Eq, Ord, Enum, Bounded)
+
+expectedFinalPreludePublicRoles :: Set PreludePublicRole
+expectedFinalPreludePublicRoles =
+ Set.fromList [minBound .. maxBound]
+
+finalPreludePublicRole
+ :: FinalPreludeCandidate
+ -> PreludePublicRole
+ -> Maybe FinalPreludeRoleTarget
+finalPreludePublicRole
+ (FinalPreludeCandidate _parsed _syntax _semantic _prefix _objects
+ roles) role =
+ Map.lookup role roles
+
+data FinalPreludeValidationError
+ = FinalPreludePackagedInputMismatch
+ | FinalPreludeSemanticEnvironmentMismatch
+ | FinalPreludeDeclarationAssociationMismatch
+ | FinalPreludeUnexpectedObject !ObjectId
+ | FinalPreludeDeclarationMissing !Text
+ | FinalPreludeDeclarationDuplicate !Text
+ | FinalPreludeDeclarationShapeMismatch !Text
+ | FinalPreludeDefinitionContentMismatch !Text
+ | FinalPreludeFactContentMismatch !Text
+ | FinalPreludeValidationInventoryMismatch !DeclarationSlot
+ | FinalPreludeAuthorityMismatch !DeclarationSlot
+ | FinalPreludeBaseStructureMismatch
+ | FinalPreludePublicRoleMismatch !PreludePublicRole
+ deriving stock (Show, Eq)
+
+data FinalPreludeFailure
+ = FinalPreludeWrongOwner !ModuleName
+ | FinalPreludeHasImports ![ImportRef]
+ | FinalPreludeHasSyntaxImports ![SyntaxInterfaceId]
+ | FinalPreludeUnsupportedBlock !Location
+ | FinalPreludeUnmatchedProof !Location
+ | FinalPreludeExactDeclarationFailed !Exact.ExactCompileError
+ | FinalPreludeExactProofFailed !ExactProof.ExactProofError
+ | FinalPreludeOmittedProof !Location
+ | FinalPreludeDeclarationFailed !Declaration.DeclarationError
+ | FinalPreludeSealFailed !SemanticInterfaceError
+ | FinalPreludeValidationFailed !FinalPreludeValidationError
+ deriving stock (Show, Eq)
+
+data FinalPreludeBuildResult
+ = FinalPreludeSourceLoadFailed !Prelude.PreludeLoadError
+ | FinalPreludeSourceParseFailed !Prelude.PreludeParseError
+ | FinalPreludeBuilt !FinalPreludeCandidate
+ | FinalPreludeBuildFailed
+ !FinalPreludeFailure
+ !Declaration.PendingModulePrefix
+ | FinalPreludeBuildOpenFailed !Declaration.DriverOpenError
+
+data PlannedPreludeDeclaration
+ = PlannedPreludeBinding
+ !(Declaration.PlannedDeclaration
+ Exact.CheckedExactBindingAuthorization)
+ | PlannedPreludeFoundation
+ !(Declaration.PlannedDeclaration
+ ExactProof.CheckedFinalPreludeFoundationAuthorization)
+ | PlannedPreludeProof
+ !(Declaration.PlannedDeclaration
+ ExactProof.CheckedExactProofAuthorization)
+ | PlannedPreludeBase
+ !(Declaration.PlannedDeclaration ())
+
+data PreludePlanningFailure
+ = PreludePlanningAction !FinalPreludeFailure
+ | PreludePlanningDeclaration !Declaration.DeclarationError
+
+data PreludeModulePlan = PreludeModulePlan
+ ![PlannedPreludeDeclaration]
+ !(Maybe PreludePlanningFailure)
+
+buildFinalPreludeCandidate
+ :: CheckedFoundation
+ -> Declaration.VampireResolver
+ -> IO FinalPreludeBuildResult
+buildFinalPreludeCandidate foundation resolver =
+ Prelude.loadReservedPreludeSourceInput >>= \case
+ Left failure ->
+ pure (FinalPreludeSourceLoadFailed failure)
+ Right source ->
+ Prelude.parseReservedPreludeSource source >>= \case
+ Left failure ->
+ pure (FinalPreludeSourceParseFailed failure)
+ Right parsed ->
+ buildParsedFinalPreludeCandidate
+ foundation parsed resolver
+
+-- The production acquisition path establishes packaged provenance before
+-- passing its already parsed source here. The explicit parsed seam avoids a
+-- second load and parse on a cache miss.
+buildParsedFinalPreludeCandidate
+ :: CheckedFoundation
+ -> Prelude.ReservedParsedPrelude
+ -> Declaration.VampireResolver
+ -> IO FinalPreludeBuildResult
+buildParsedFinalPreludeCandidate foundation parsed resolver =
+ case validatePackagedPreludeInput parsed syntax of
+ Left failure -> do
+ case emptyPrefix of
+ Left prefixFailure ->
+ pure
+ (FinalPreludeBuildOpenFailed
+ (Declaration.DriverInitialPrefixError
+ prefixFailure))
+ Right prefix ->
+ pure (FinalPreludeBuildFailed failure prefix)
+ Right () -> do
+ outcome <-
+ Declaration.runModuleDriver
+ foundation
+ preludeModuleName
+ []
+ resolver
+ -- The confined builder never consults stored validation.
+ Declaration.FreshValidation
+ do
+ PreludeModulePlan declarations terminal <-
+ Declaration.runProspectiveLoweringDriver
+ (planBlocks [] 0 blocks)
+ traverse_ admitPreludeDeclaration declarations
+ traverse_ failPreludePlanning terminal
+ pure case outcome of
+ Left failure ->
+ FinalPreludeBuildOpenFailed failure
+ Right (Declaration.DriverFailed failure prefix) ->
+ FinalPreludeBuildFailed
+ (case failure of
+ Declaration.DriverDeclarationFailed err ->
+ FinalPreludeDeclarationFailed err
+ Declaration.DriverActionFailed err ->
+ err)
+ prefix
+ Right (Declaration.DriverSealFailed failure prefix) ->
+ FinalPreludeBuildFailed
+ (FinalPreludeSealFailed failure)
+ prefix
+ Right (Declaration.DriverSucceeded
+ () semantic prefix objects) ->
+ case validateFinalPrelude
+ foundation parsed semantic prefix objects of
+ Left failure ->
+ FinalPreludeBuildFailed
+ (FinalPreludeValidationFailed failure)
+ prefix
+ Right roles ->
+ FinalPreludeBuilt
+ (FinalPreludeCandidate
+ parsed syntax semantic prefix objects roles)
+ where
+ identified = Prelude.reservedParsedPreludeModule parsed
+ blocks = identifiedParsedModuleBlocks identified
+ occurrences = identifiedParsedModuleSyntaxOccurrences identified
+ syntax = identifiedParsedModuleSyntaxInterface identified
+
+ emptyPrefix =
+ Declaration.emptyPendingModulePrefix
+ <$> initialPrefixContextId
+ (theoryId foundation)
+ preludeModuleName
+ []
+
+ planBlocks completed _blockIndex [] = do
+ planBaseStructure >>= \case
+ Left failure ->
+ pure
+ (PreludeModulePlan
+ (reverse completed)
+ (Just failure))
+ Right base ->
+ pure
+ (PreludeModulePlan
+ (reverse (base : completed))
+ Nothing)
+ planBlocks completed blockIndex (block : remaining) =
+ case block of
+ Raw.BlockClaim{} ->
+ case remaining of
+ Raw.BlockProof _location proof _end : rest -> do
+ continue completed (blockIndex + 2) rest
+ =<< planOrdinaryProof block (Just proof)
+ _ -> do
+ continue completed (blockIndex + 1) remaining
+ =<< planImplicitClaim block
+ Raw.BlockProof location _proof _end ->
+ pure
+ (PreludeModulePlan
+ (reverse completed)
+ (Just
+ (PreludePlanningAction
+ (FinalPreludeUnmatchedProof location))))
+ Raw.BlockAbbr{} -> do
+ continue completed (blockIndex + 1) remaining
+ =<< planBinding blockIndex block
+ Raw.BlockDefn{} -> do
+ continue completed (blockIndex + 1) remaining
+ =<< planBinding blockIndex block
+ _ ->
+ pure
+ (PreludeModulePlan
+ (reverse completed)
+ (Just
+ (PreludePlanningAction
+ (FinalPreludeUnsupportedBlock
+ (locate block)))))
+ where
+ continue accumulated nextIndex rest = \case
+ Left failure ->
+ pure
+ (PreludeModulePlan
+ (reverse accumulated)
+ (Just failure))
+ Right declaration ->
+ planBlocks (declaration : accumulated) nextIndex rest
+
+ planBaseStructure = do
+ slot <- Declaration.nextDeclarationSlotLowering
+ theory <- Declaration.currentTheoryLowering
+ let seed =
+ opaqueDeclarationSeed
+ (declarationSlotModule slot)
+ (declarationSlotOrdinal slot)
+ StructureDeclaration
+ (generatedObjectSlot 0)
+ coreType = TyArrow TySet TySet
+ content = OpaqueObjectContent theory seed coreType
+ identity = opaqueObjectId theory seed coreType
+ asserted = assertedObject identity content
+ structurePhrase =
+ semanticStructurePhrase Lexicon._Onesorted
+ operation =
+ semanticStructureOperation Raw.CarrierSymbol identity
+ descriptor <-
+ either
+ (pure
+ . Left
+ . PreludePlanningDeclaration
+ . Declaration.DeclarationEnvironmentFailed)
+ (pure . Right)
+ (semanticStructureDescriptor
+ structurePhrase Nothing [] [operation])
+ case descriptor of
+ Left failure -> pure (Left failure)
+ Right checkedDescriptor ->
+ planPreludeChecked PlannedPreludeBase
+ (Declaration.checkedCompiledDeclaration
+ (declarationSyntaxId
+ "felix-final-prelude-base-structure-v1")
+ [asserted] [] [] [checkedDescriptor] [] ())
+
+ planBinding blockIndex block = do
+ prepared <-
+ Exact.prepareExactDeclaration
+ block
+ [ parsedSyntaxOccurrenceEntry occurrence
+ | occurrence <- occurrences
+ , parsedSyntaxOccurrenceBlockIndex occurrence == blockIndex
+ ]
+ case prepared of
+ Left failure ->
+ pure
+ (Left
+ (PreludePlanningAction
+ (FinalPreludeExactDeclarationFailed failure)))
+ Right declaration -> do
+ Exact.lowerPreparedExactBinding declaration >>= \case
+ Left failure -> planningDeclarationFailure failure
+ Right checked ->
+ planPreludeChecked PlannedPreludeBinding checked
+
+ planImplicitClaim block = do
+ foundationClaim <-
+ ExactProof.prepareFinalPreludeFoundationClaim
+ foundation block Nothing
+ case foundationClaim of
+ Right claim -> do
+ ExactProof.lowerPreparedFinalPreludeFoundationClaim claim
+ >>= \case
+ Left failure -> planningDeclarationFailure failure
+ Right checked ->
+ planPreludeChecked PlannedPreludeFoundation checked
+ Left ExactProof.ExactProofFoundationLeafTargetMismatch{} ->
+ planOrdinaryProof block Nothing
+ Left ExactProof.ExactProofFoundationLeafRequiresImplicitAuto{} ->
+ planOrdinaryProof block Nothing
+ Left failure ->
+ pure
+ (Left
+ (PreludePlanningAction
+ (FinalPreludeExactProofFailed failure)))
+
+ planOrdinaryProof block explicitProof = do
+ prepared <-
+ ExactProof.prepareExactProof block explicitProof
+ case prepared of
+ Left failure ->
+ pure
+ (Left
+ (PreludePlanningAction
+ (FinalPreludeExactProofFailed failure)))
+ Right proof
+ | Just location <-
+ ExactProof.preparedExactProofFirstOmission proof ->
+ pure
+ (Left
+ (PreludePlanningAction
+ (FinalPreludeOmittedProof location)))
+ | otherwise ->
+ ExactProof.lowerPreparedExactProof proof >>= \case
+ Left failure -> planningDeclarationFailure failure
+ Right checked ->
+ planPreludeChecked PlannedPreludeProof checked
+
+ planPreludeChecked
+ :: forall body.
+ (Declaration.PlannedDeclaration body
+ -> PlannedPreludeDeclaration)
+ -> Declaration.CheckedDeclaration body
+ -> Declaration.LoweringDriver
+ (Either PreludePlanningFailure PlannedPreludeDeclaration)
+ planPreludeChecked constructor checked =
+ Declaration.planCheckedDeclaration checked >>= \case
+ Left failure -> planningDeclarationFailure failure
+ Right planned -> pure (Right (constructor planned))
+
+ planningDeclarationFailure =
+ pure . Left . PreludePlanningDeclaration
+
+ admitPreludeDeclaration = \case
+ PlannedPreludeBinding planned ->
+ void
+ (Declaration.admitPlannedCheckedDeclaration
+ planned Exact.authorizeCheckedExactBinding)
+ PlannedPreludeFoundation planned ->
+ void
+ (Declaration.admitPlannedCheckedDeclaration planned
+ ExactProof.authorizeCheckedFinalPreludeFoundationClaim)
+ PlannedPreludeProof planned ->
+ void
+ (Declaration.admitPlannedCheckedDeclaration
+ planned ExactProof.authorizeCheckedExactProof)
+ PlannedPreludeBase planned ->
+ void
+ (Declaration.admitPlannedCheckedDeclaration planned
+ (\() stages ->
+ unless
+ (null stages)
+ (Declaration.failDeclaration
+ (Declaration.CheckedAuthorizationCandidateShapeMismatch
+ 0 (length stages)))))
+
+ failPreludePlanning = \case
+ PreludePlanningAction failure ->
+ Declaration.failModuleDriver failure
+ PreludePlanningDeclaration failure ->
+ Declaration.failDeclarationDriver failure
+
+data PreludeDeclaration = PreludeDeclaration
+ !Raw.Block
+ !Declaration.CommittedDeclarationBatch
+
+data PreludeDefinitionKind
+ = PreludeDefinition
+ | PreludeAbbreviation
+
+data PreludeDefinitionView = PreludeDefinitionView
+ !ObjectId
+ !SemanticGlobalTarget
+
+validateFinalPrelude
+ :: CheckedFoundation
+ -> Prelude.ReservedParsedPrelude
+ -> SemanticInterface
+ -> Declaration.PendingModulePrefix
+ -> CheckedObjectClosure
+ -> Either
+ FinalPreludeValidationError
+ (Map.Map PreludePublicRole FinalPreludeRoleTarget)
+validateFinalPrelude foundation parsed semantic prefix objects = do
+ (declarations, baseStructure) <-
+ associatePreludeDeclarations parsed prefix
+ validateConfinedAuthority
+ foundation semantic objects declarations baseStructure
+ resolveAndValidatePublicRoles foundation objects declarations
+
+resolveAndValidatePublicRoles
+ :: CheckedFoundation
+ -> CheckedObjectClosure
+ -> [PreludeDeclaration]
+ -> Either
+ FinalPreludeValidationError
+ (Map.Map PreludePublicRole FinalPreludeRoleTarget)
+resolveAndValidatePublicRoles foundation objects declarations = do
+ successor <-
+ expectDefinition
+ foundation objects declarations
+ "prelude_successor"
+ PreludeDefinition
+ (TyArrow TySet TySet)
+ expectedSuccessorBody
+ let successorId = definitionViewObject successor
+
+ inductive <-
+ expectDefinition
+ foundation objects declarations
+ "prelude_inductive"
+ PreludeDefinition
+ (TyArrow TySet TyProp)
+ (expectedInductiveBody successorId)
+ let inductiveId = definitionViewObject inductive
+
+ u0 <-
+ expectDefinition
+ foundation objects declarations
+ "prelude_u0"
+ PreludeDefinition
+ TySet
+ (applyIntrinsic UnivOf (CIntrinsic Empty))
+ let u0Id = definitionViewObject u0
+
+ let omegaBody = expectedOmegaBody u0Id inductiveId
+ omega <-
+ expectDefinition
+ foundation objects declarations
+ "prelude_omega"
+ PreludeDefinition
+ TySet
+ omegaBody
+ let omegaId = definitionViewObject omega
+
+ omegaConstruction <-
+ expectedOmegaConstruction objects omegaBody
+ omegaDerived <-
+ maybe
+ (Left
+ (FinalPreludeFactContentMismatch
+ "prelude_omega"))
+ Right
+ (namedSetConstructionObjectFact
+ (checkedFoundationSetConstruction foundation)
+ omegaId
+ omegaConstruction)
+
+ naturals <-
+ expectDefinition
+ foundation objects declarations
+ "prelude_naturals"
+ PreludeAbbreviation
+ TySet
+ (CGlobal omegaId)
+ case definitionViewTarget naturals of
+ TransparentExpansion{} -> pure ()
+ _ ->
+ Left
+ (FinalPreludeDefinitionContentMismatch
+ "prelude_naturals")
+
+ (infinity, infinityTarget) <-
+ expectClaim declarations "prelude_infinity"
+ validateInfinityTarget objects inductiveId infinityTarget
+ omegaDeclaration <- findDeclaration "prelude_omega" declarations
+ omegaEquation <-
+ validateOmegaDeclaration
+ objects
+ omegaId
+ omegaBody
+ omegaDerived
+ omegaDeclaration
+ let inductiveOmega =
+ CApp (CGlobal inductiveId) (CGlobal omegaId)
+ minimalOmega = expectedMinimality inductiveId omegaId
+ naturalsInductive <-
+ fst
+ <$> expectClaimTarget
+ declarations
+ "prelude_naturals_inductive"
+ inductiveOmega
+ naturalsMinimal <-
+ fst
+ <$> expectClaimTarget
+ declarations
+ "prelude_naturals_minimal"
+ minimalOmega
+
+ let roles = Map.fromList
+ [ ( PreludeInfinityTheorem
+ , FinalPreludeTheoremRole infinity
+ )
+ , ( PreludeOmegaObject
+ , FinalPreludeObjectRole omegaId
+ )
+ , ( PreludeOmegaDefiningEquation
+ , FinalPreludeTheoremRole omegaEquation
+ )
+ , ( PreludeNaturalsAlias
+ , FinalPreludeObjectRole omegaId
+ )
+ , ( PreludeNaturalsInductiveTheorem
+ , FinalPreludeTheoremRole naturalsInductive
+ )
+ , ( PreludeNaturalsMinimalTheorem
+ , FinalPreludeTheoremRole naturalsMinimal
+ )
+ ]
+ unless
+ (Map.keysSet roles == expectedFinalPreludePublicRoles)
+ (Left
+ (FinalPreludePublicRoleMismatch
+ PreludeInfinityTheorem))
+ pure roles
+
+expectedOmegaConstruction
+ :: CheckedObjectClosure
+ -> CanonicalTerm ObjectId
+ -> Either
+ FinalPreludeValidationError
+ (NamedSetConstruction ObjectId)
+expectedOmegaConstruction objects = \case
+ CApp (CApp (CIntrinsic Sep) bound) (CLam TySet predicate) -> do
+ checkedBound <- checked [] bound
+ checkedPredicate <- checked [TySet] predicate
+ maybe
+ (Left
+ (FinalPreludeFactContentMismatch
+ "prelude_omega"))
+ Right
+ (checkedSeparationConstruction
+ (`lookupCheckedObjectType` objects)
+ checkedBound
+ checkedPredicate)
+ _ ->
+ Left
+ (FinalPreludeFactContentMismatch
+ "prelude_omega")
+ where
+ checked context term =
+ first
+ (const
+ (FinalPreludeFactContentMismatch
+ "prelude_omega"))
+ (checkScopedCanonicalCore
+ (`lookupCheckedObjectType` objects)
+ context
+ term)
+
+validateOmegaDeclaration
+ :: CheckedObjectClosure
+ -> ObjectId
+ -> CanonicalTerm ObjectId
+ -> NamedSetConstructionFact
+ -> PreludeDeclaration
+ -> Either FinalPreludeValidationError TheoremRef
+validateOmegaDeclaration objects omegaId omegaBody derived declaration = do
+ let batch = declarationBatch declaration
+ delta = Declaration.committedBatchDelta batch
+ omegaContent <- case lookupCheckedObjectContent omegaId objects of
+ Just content@TransparentObjectContent{} -> pure content
+ _ ->
+ Left
+ (FinalPreludeFactContentMismatch
+ "prelude_omega")
+ unless
+ ( declarationDeltaObjects delta == [omegaId]
+ && case Declaration.committedBatchObjects batch of
+ [asserted] ->
+ assertedObjectId asserted == omegaId
+ && assertedObjectContent asserted == omegaContent
+ _ -> False
+ && null (Declaration.committedBatchProofValidations batch)
+ )
+ (Left
+ (FinalPreludeFactContentMismatch
+ "prelude_omega"))
+ certificates <-
+ maybe
+ (Left
+ (FinalPreludeFactContentMismatch
+ "prelude_omega"))
+ (Right . declarationValidationRecordCertificates)
+ (Declaration.committedBatchDeclarationValidation batch)
+ validateOmegaFactInventory
+ omegaId
+ omegaBody
+ (namedSetConstructionFactProposition derived)
+ (namedSetConstructionFactDescriptor derived)
+ (declarationDeltaFacts delta)
+ (declarationDeltaAliases delta)
+ (Declaration.committedBatchPropositions batch)
+ certificates
+
+-- | Purpose-specific audit of the distinguished Omega definition. It is
+-- deliberately not a general declaration manifest: the confined prelude has
+-- exactly one declaration whose public role requires this two-fact shape.
+-- The explicit arguments also provide a narrow pure seam for corruption
+-- regression tests.
+validateOmegaFactInventory
+ :: ObjectId
+ -> CanonicalTerm ObjectId
+ -> FrozenCheckedCore ObjectId
+ -> CacheDigest
+ -> [SemanticFactOccurrence]
+ -> [SemanticAlias]
+ -> [CheckedPropositionContent]
+ -> [Authority.ValidationCertificate]
+ -> Either FinalPreludeValidationError TheoremRef
+validateOmegaFactInventory
+ omegaId omegaBody expectedExtensional expectedDescriptor
+ facts aliases propositions certificates = do
+ (equationOccurrence, extensionalOccurrence) <-
+ case facts of
+ [equation, extensional] -> Right (equation, extensional)
+ _ -> mismatch
+ (equationCertificate, extensionalCertificate) <-
+ case certificates of
+ [equation, extensional] -> Right (equation, extensional)
+ _ -> mismatch
+ case aliases of
+ [alias]
+ | semanticAliasName alias == semanticName "prelude_omega"
+ , semanticAliasTarget alias
+ == semanticFactFingerprint equationOccurrence ->
+ pure ()
+ _ -> mismatch
+ equation <- propositionFor equationOccurrence
+ extensional <- propositionFor extensionalOccurrence
+ unless
+ ( length propositions == 2
+ && semanticFactSearchEligibility equationOccurrence
+ == SearchIneligible
+ && frozenCoreTerm (checkedPropositionTerm equation)
+ == CEq TySet (CGlobal omegaId) omegaBody
+ && Authority.validationTarget equationCertificate
+ == semanticFactAuthority equationOccurrence
+ && Authority.validationDirectAuthorization equationCertificate
+ == Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation omegaId)
+ && semanticFactSearchEligibility extensionalOccurrence
+ == SearchEligible
+ && checkedPropositionTerm extensional == expectedExtensional
+ && Authority.validationTarget extensionalCertificate
+ == semanticFactAuthority extensionalOccurrence
+ && Authority.validationDirectAuthorization extensionalCertificate
+ == Authority.CheckedKernelConstruction
+ (Authority.CheckedSetConstructionExtensionality
+ omegaId expectedDescriptor)
+ )
+ mismatch
+ pure
+ (Authority.factAuthorityTheorem
+ (semanticFactAuthority equationOccurrence))
+ where
+ mismatch =
+ Left
+ (FinalPreludeFactContentMismatch
+ "prelude_omega")
+
+ propositionFor occurrence =
+ case List.filter
+ ((== semanticFactProposition occurrence)
+ . checkedPropositionId)
+ propositions of
+ [proposition] -> Right proposition
+ _ -> mismatch
+
+validatePackagedPreludeInput
+ :: Prelude.ReservedParsedPrelude
+ -> ModuleSyntaxInterface
+ -> Either FinalPreludeFailure ()
+validatePackagedPreludeInput parsed syntax
+ | freshModuleInputOwner input /= preludeModuleName =
+ Left (FinalPreludeWrongOwner (freshModuleInputOwner input))
+ | not (null (freshModuleInputImports input)) =
+ Left (FinalPreludeHasImports (freshModuleInputImports input))
+ | not (null (moduleSyntaxDirectInputs syntax)) =
+ Left
+ (FinalPreludeHasSyntaxImports
+ (moduleSyntaxDirectInputs syntax))
+ | otherwise = do
+ unless
+ ( freshModuleInputBinding input == FreshReservedSource
+ && freshModuleInputLocationPath input
+ == Prelude.preludeDiagnosticLabel
+ && freshModuleInputSyntaxInterface input == syntax
+ && identifiedParsedModuleSyntaxInterface identified == syntax
+ )
+ (Left
+ (FinalPreludeValidationFailed
+ FinalPreludePackagedInputMismatch))
+ where
+ input = Prelude.reservedParsedPreludeInput parsed
+ identified = Prelude.reservedParsedPreludeModule parsed
+
+associatePreludeDeclarations
+ :: Prelude.ReservedParsedPrelude
+ -> Declaration.PendingModulePrefix
+ -> Either
+ FinalPreludeValidationError
+ ([PreludeDeclaration], Declaration.CommittedDeclarationBatch)
+associatePreludeDeclarations parsed prefix = do
+ case List.splitAt (length sourceDeclarations) batches of
+ (sourceBatches, [baseStructure])
+ | length sourceBatches == length sourceDeclarations ->
+ pure
+ ( zipWith PreludeDeclaration
+ sourceDeclarations sourceBatches
+ , baseStructure
+ )
+ _ -> Left FinalPreludeDeclarationAssociationMismatch
+ where
+ sourceDeclarations =
+ [ block
+ | block <-
+ identifiedParsedModuleBlocks
+ (Prelude.reservedParsedPreludeModule parsed)
+ , case block of
+ Raw.BlockProof{} -> False
+ _ -> True
+ ]
+ batches = Declaration.pendingModulePrefixBatches prefix
+
+validateConfinedAuthority
+ :: CheckedFoundation
+ -> SemanticInterface
+ -> CheckedObjectClosure
+ -> [PreludeDeclaration]
+ -> Declaration.CommittedDeclarationBatch
+ -> Either FinalPreludeValidationError ()
+validateConfinedAuthority
+ foundation semantic objects declarations baseStructure = do
+ unless
+ ( semanticInterfaceOwner semantic == preludeModuleName
+ && null (semanticInterfaceDirectInputs semantic)
+ )
+ (Left FinalPreludeSemanticEnvironmentMismatch)
+ traverse_ requireTransparent
+ [ identity
+ | declaration <- declarations
+ , identity <-
+ declarationDeltaObjects
+ (Declaration.committedBatchDelta
+ (declarationBatch declaration))
+ ]
+ traverse_ (validateDeclarationAuthority foundation objects) declarations
+ validateBaseStructure foundation objects baseStructure
+ where
+ requireTransparent identity =
+ case lookupCheckedObjectContent identity objects of
+ Just TransparentObjectContent{} -> pure ()
+ _ -> Left (FinalPreludeUnexpectedObject identity)
+
+validateBaseStructure
+ :: CheckedFoundation
+ -> CheckedObjectClosure
+ -> Declaration.CommittedDeclarationBatch
+ -> Either FinalPreludeValidationError ()
+validateBaseStructure foundation objects batch = do
+ let slot = Declaration.committedBatchSlot batch
+ delta = Declaration.committedBatchDelta batch
+ environment = declarationDeltaEnvironment delta
+ structurePhrase = semanticStructurePhrase Lexicon._Onesorted
+ expectedSeed =
+ opaqueDeclarationSeed
+ (declarationSlotModule slot)
+ (declarationSlotOrdinal slot)
+ StructureDeclaration
+ (generatedObjectSlot 0)
+ expectedType = TyArrow TySet TySet
+ expectedObject = opaqueObjectId (theoryId foundation) expectedSeed expectedType
+ expectedContent =
+ OpaqueObjectContent
+ (theoryId foundation)
+ expectedSeed
+ expectedType
+ descriptor <-
+ case semanticEnvironmentStructures environment of
+ [single] -> Right single
+ _ -> Left FinalPreludeBaseStructureMismatch
+ expectedDescriptor <-
+ first (const FinalPreludeBaseStructureMismatch)
+ (semanticStructureDescriptor
+ structurePhrase
+ Nothing
+ []
+ [semanticStructureOperation Raw.CarrierSymbol expectedObject])
+ unless
+ ( declarationSlotModule slot == preludeModuleName
+ && descriptor == expectedDescriptor
+ && null (semanticEnvironmentBindings environment)
+ && declarationDeltaObjects delta == [expectedObject]
+ && null (declarationDeltaFacts delta)
+ && null (declarationDeltaAliases delta)
+ && null (declarationDeltaPropositions delta)
+ && Declaration.committedBatchObjects batch
+ == [assertedObject expectedObject expectedContent]
+ && null (Declaration.committedBatchPropositions batch)
+ && null (Declaration.committedBatchProofValidations batch)
+ && maybe
+ False
+ (null . declarationValidationRecordCertificates)
+ (Declaration.committedBatchDeclarationValidation batch)
+ && lookupCheckedObjectContent expectedObject objects
+ == Just expectedContent
+ )
+ (Left FinalPreludeBaseStructureMismatch)
+
+validateDeclarationAuthority
+ :: CheckedFoundation
+ -> CheckedObjectClosure
+ -> PreludeDeclaration
+ -> Either FinalPreludeValidationError ()
+validateDeclarationAuthority foundation objects declaration = do
+ unless
+ (fmap Authority.validationTarget certificates
+ == fmap semanticFactAuthority facts)
+ (Left (FinalPreludeValidationInventoryMismatch slot))
+ traverse_ validateOne (zip facts certificates)
+ where
+ batch = declarationBatch declaration
+ delta = Declaration.committedBatchDelta batch
+ slot = Declaration.committedBatchSlot batch
+ facts = declarationDeltaFacts delta
+ certificates =
+ ( Semantic.proofValidationRecordCertificate
+ <$> Declaration.committedBatchProofValidations batch
+ )
+ <> maybe
+ []
+ Semantic.declarationValidationRecordCertificates
+ (Declaration.committedBatchDeclarationValidation batch)
+
+ validateOne (occurrence, certificate) = do
+ unless
+ (Authority.factAuthoritySafety
+ (semanticFactAuthority occurrence)
+ == Authority.cleanAuthoritySafety)
+ (Left (FinalPreludeAuthorityMismatch slot))
+ proposition <-
+ maybe
+ (Left (FinalPreludeValidationInventoryMismatch slot))
+ Right
+ (List.find
+ ((== semanticFactProposition occurrence)
+ . checkedPropositionId)
+ (Declaration.committedBatchPropositions batch))
+ let target = frozenCoreTerm (checkedPropositionTerm proposition)
+ case Authority.validationDirectAuthorization certificate of
+ Authority.CheckedKernelConstruction
+ (Authority.FoundationLeaf tag) -> do
+ let expected =
+ frozenCoreTerm
+ (mapFrozenGlobals absurd
+ (foundationAxiomFrozen foundation tag))
+ unless (target == expected)
+ (Left (FinalPreludeAuthorityMismatch slot))
+ Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation identity) ->
+ case lookupCheckedObjectContent identity objects of
+ Just (TransparentObjectContent _theory coreType body) ->
+ unless
+ (target == CEq coreType (CGlobal identity) body)
+ (Left (FinalPreludeAuthorityMismatch slot))
+ _ ->
+ Left (FinalPreludeAuthorityMismatch slot)
+ Authority.CheckedKernelConstruction
+ (Authority.CheckedSetConstructionExtensionality
+ identity _descriptor) ->
+ case lookupCheckedObjectContent identity objects of
+ Just TransparentObjectContent{}
+ | semanticFactSearchEligibility occurrence
+ == SearchEligible ->
+ pure ()
+ _ ->
+ Left (FinalPreludeAuthorityMismatch slot)
+ Authority.CheckedSourceProof requests ->
+ unless
+ ( not (null requests)
+ && case declarationBlock declaration of
+ Raw.BlockClaim{} -> True
+ _ -> False
+ )
+ (Left (FinalPreludeAuthorityMismatch slot))
+ _ ->
+ Left (FinalPreludeAuthorityMismatch slot)
+
+expectDefinition
+ :: CheckedFoundation
+ -> CheckedObjectClosure
+ -> [PreludeDeclaration]
+ -> Text
+ -> PreludeDefinitionKind
+ -> CoreType
+ -> CanonicalTerm ObjectId
+ -> Either FinalPreludeValidationError PreludeDefinitionView
+expectDefinition foundation objects declarations marker kind coreType body = do
+ declaration <- findDeclaration marker declarations
+ let delta =
+ Declaration.committedBatchDelta
+ (declarationBatch declaration)
+ unless
+ (case (kind, declarationBlock declaration) of
+ (PreludeDefinition, Raw.BlockDefn{}) -> True
+ (PreludeAbbreviation, Raw.BlockAbbr{}) -> True
+ _ -> False)
+ (Left (FinalPreludeDeclarationShapeMismatch marker))
+ binding <-
+ case semanticEnvironmentBindings
+ (declarationDeltaEnvironment delta) of
+ [single] -> Right single
+ _ -> Left (FinalPreludeDeclarationShapeMismatch marker)
+ let target = semanticGlobalBindingTarget binding
+ identity = semanticGlobalTargetObject target
+ unless
+ (case (kind, target) of
+ (PreludeDefinition, GlobalReference{}) -> True
+ (PreludeAbbreviation, TransparentExpansion{}) -> True
+ _ -> False)
+ (Left (FinalPreludeDefinitionContentMismatch marker))
+ content <-
+ maybe
+ (Left (FinalPreludeDefinitionContentMismatch marker))
+ Right
+ (lookupCheckedObjectContent identity objects)
+ let expected =
+ TransparentObjectContent
+ (theoryId foundation)
+ coreType
+ body
+ unless (content == expected)
+ (Left (FinalPreludeDefinitionContentMismatch marker))
+ pure (PreludeDefinitionView identity target)
+
+expectClaim
+ :: [PreludeDeclaration]
+ -> Text
+ -> Either
+ FinalPreludeValidationError
+ (TheoremRef, CanonicalTerm ObjectId)
+expectClaim declarations marker = do
+ declaration <- findDeclaration marker declarations
+ unless
+ (case declarationBlock declaration of
+ Raw.BlockClaim{} -> True
+ _ -> False)
+ (Left (FinalPreludeDeclarationShapeMismatch marker))
+ expectFact declarations marker
+
+expectClaimTarget
+ :: [PreludeDeclaration]
+ -> Text
+ -> CanonicalTerm ObjectId
+ -> Either
+ FinalPreludeValidationError
+ (TheoremRef, CanonicalTerm ObjectId)
+expectClaimTarget declarations marker expected = do
+ result@(_theorem, actual) <- expectClaim declarations marker
+ unless
+ (actual == expected)
+ (Left (FinalPreludeFactContentMismatch marker))
+ pure result
+
+expectFact
+ :: [PreludeDeclaration]
+ -> Text
+ -> Either
+ FinalPreludeValidationError
+ (TheoremRef, CanonicalTerm ObjectId)
+expectFact declarations marker = do
+ declaration <- findDeclaration marker declarations
+ unless
+ (length
+ (declarationDeltaFacts
+ (Declaration.committedBatchDelta
+ (declarationBatch declaration))) == 1)
+ (Left (FinalPreludeFactContentMismatch marker))
+ expectAliasedFact declaration marker
+
+expectAliasedFact
+ :: PreludeDeclaration
+ -> Text
+ -> Either
+ FinalPreludeValidationError
+ (TheoremRef, CanonicalTerm ObjectId)
+expectAliasedFact declaration marker = do
+ let batch = declarationBatch declaration
+ delta = Declaration.committedBatchDelta batch
+ expectedAlias = semanticName marker
+ fingerprint <- case declarationDeltaAliases delta of
+ [alias]
+ | semanticAliasName alias == expectedAlias ->
+ Right (semanticAliasTarget alias)
+ _ -> Left (FinalPreludeFactContentMismatch marker)
+ occurrence <- case List.filter
+ ((== fingerprint) . semanticFactFingerprint)
+ (declarationDeltaFacts delta) of
+ [single] -> Right single
+ _ -> Left (FinalPreludeFactContentMismatch marker)
+ proposition <-
+ maybe
+ (Left (FinalPreludeFactContentMismatch marker))
+ Right
+ (List.find
+ ((== semanticFactProposition occurrence)
+ . checkedPropositionId)
+ (Declaration.committedBatchPropositions batch))
+ pure
+ ( Authority.factAuthorityTheorem
+ (semanticFactAuthority occurrence)
+ , frozenCoreTerm (checkedPropositionTerm proposition)
+ )
+
+validateInfinityTarget
+ :: CheckedObjectClosure
+ -> ObjectId
+ -> CanonicalTerm ObjectId
+ -> Either FinalPreludeValidationError ()
+validateInfinityTarget objects inductive = \case
+ CApp (CGlobal predicate) (CGlobal witness)
+ | predicate == inductive ->
+ case lookupCheckedObjectContent witness objects of
+ Just TransparentObjectContent{} -> pure ()
+ _ ->
+ Left
+ (FinalPreludeFactContentMismatch
+ "prelude_infinity")
+ _ ->
+ Left
+ (FinalPreludeFactContentMismatch
+ "prelude_infinity")
+
+findDeclaration
+ :: Text
+ -> [PreludeDeclaration]
+ -> Either FinalPreludeValidationError PreludeDeclaration
+findDeclaration marker declarations =
+ case List.filter ((== marker) . declarationMarker) declarations of
+ [] -> Left (FinalPreludeDeclarationMissing marker)
+ [single] -> Right single
+ _ -> Left (FinalPreludeDeclarationDuplicate marker)
+
+declarationBlock :: PreludeDeclaration -> Raw.Block
+declarationBlock (PreludeDeclaration block _batch) =
+ block
+
+declarationBatch
+ :: PreludeDeclaration
+ -> Declaration.CommittedDeclarationBatch
+declarationBatch (PreludeDeclaration _block batch) =
+ batch
+
+declarationMarker :: PreludeDeclaration -> Text
+declarationMarker =
+ fromMaybe "<unmarked>" . blockMarkerText . declarationBlock
+
+definitionViewObject :: PreludeDefinitionView -> ObjectId
+definitionViewObject (PreludeDefinitionView identity _target) =
+ identity
+
+definitionViewTarget
+ :: PreludeDefinitionView
+ -> SemanticGlobalTarget
+definitionViewTarget (PreludeDefinitionView _identity target) =
+ target
+
+blockMarkerText :: Raw.Block -> Maybe Text
+blockMarkerText = fmap (\(Raw.Marker marker) -> marker) . \case
+ Raw.BlockAxiom _location _title marker _axiom -> Just marker
+ Raw.BlockClaim _kind _location _title marker _claim -> Just marker
+ Raw.BlockDefn _location _title marker _definition -> Just marker
+ Raw.BlockAbbr _location _title marker _abbreviation -> Just marker
+ Raw.BlockData _location _title marker _datatype -> Just marker
+ Raw.BlockInductive _location _title marker _inductive -> Just marker
+ Raw.BlockSig _location _title marker _assumptions _signature -> Just marker
+ Raw.BlockStruct _location _title marker _structure -> Just marker
+ Raw.BlockProof{} -> Nothing
+
+expectedSuccessorBody :: CanonicalTerm ObjectId
+expectedSuccessorBody =
+ CLam TySet
+ (canonicalSetInsert (CBound 0) (CBound 0))
+
+expectedInductiveBody :: ObjectId -> CanonicalTerm ObjectId
+expectedInductiveBody successor =
+ CLam TySet
+ (logicalAnd
+ (memberTerm (CIntrinsic Empty) (CBound 0))
+ (CForall TySet
+ (CImp
+ (memberTerm (CBound 0) (CBound 1))
+ (memberTerm
+ (CApp (CGlobal successor) (CBound 0))
+ (CBound 1)))))
+
+expectedOmegaBody
+ :: ObjectId
+ -> ObjectId
+ -> CanonicalTerm ObjectId
+expectedOmegaBody u0 inductive =
+ CApp
+ (CApp (CIntrinsic Sep) (CGlobal u0))
+ (CLam TySet
+ (CForall TySet
+ (CImp
+ (CApp (CGlobal inductive) (CBound 0))
+ (memberTerm (CBound 1) (CBound 0)))))
+
+expectedMinimality
+ :: ObjectId
+ -> ObjectId
+ -> CanonicalTerm ObjectId
+expectedMinimality inductive omega =
+ CForall TySet
+ (CImp
+ (CApp (CGlobal inductive) (CBound 0))
+ (CForall TySet
+ (CImp
+ (memberTerm (CBound 0) (CGlobal omega))
+ (memberTerm (CBound 0) (CBound 1)))))
+
+applyIntrinsic
+ :: CoreIntrinsicTag
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+applyIntrinsic intrinsic argument =
+ CApp (CIntrinsic intrinsic) argument
+
+applyIntrinsic2
+ :: CoreIntrinsicTag
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+applyIntrinsic2 intrinsic firstArgument secondArgument =
+ CApp
+ (CApp (CIntrinsic intrinsic) firstArgument)
+ secondArgument
+
+memberTerm
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+memberTerm =
+ applyIntrinsic2 Member
+
+logicalAnd
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+logicalAnd left right =
+ CImp
+ (CImp left (CImp right CFalsum))
+ CFalsum
diff --git a/source/Felix/Checking/Foundation.hs b/source/Felix/Checking/Foundation.hs
new file mode 100644
index 0000000..5852901
--- /dev/null
+++ b/source/Felix/Checking/Foundation.hs
@@ -0,0 +1,1037 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | The one compiled monomorphic HOL/HOTG foundation.
+--
+-- Constructing 'CheckedFoundation' requires exact manifest coverage, closed
+-- well-typed schemas, and structural backend classification. This gate
+-- certifies manifest conformance only; it is not a consistency proof.
+module Felix.Checking.Foundation
+ ( FoundationAxiomTag(..)
+ , KernelRuleTag(..)
+ , KernelRuleSignature(..)
+ , FoundationRuleInput(..)
+ , FoundationBackendClass(..)
+ , FofExclusion(..)
+ , FoundationAxiomInput(..)
+ , FoundationManifestError(..)
+ , FoundationManifestAudit
+ , auditFoundationManifest
+ , compiledFoundationIntrinsicRows
+ , compiledFoundationRuleRows
+ , compiledFoundationAxiomRows
+ , CheckedFoundation
+ , checkedFoundation
+ , foundationAxiomProposition
+ , foundationAxiomFrozen
+ , foundationAxiomBackendClass
+ , foundationRuleSignature
+ , foundationAxiomDependencies
+ , classifyCanonicalFofStructure
+ , classifyFrozenCore
+ ) where
+
+import Base hiding (Empty)
+import Felix.Checking.Core
+
+import Control.Monad (unless)
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Numeric.Natural (Natural)
+
+
+-- | The complete axiom inventory. Ordinary implication, quantifier, equality,
+-- and falsum rules are dedicated kernel operations rather than manifest rows.
+data FoundationAxiomTag
+ = EmptyCharacteristic
+ | PairSetCharacteristic
+ | FamilyUnionCharacteristic
+ | PowerSetCharacteristic
+ | SeparationCharacteristic
+ | ReplacementCharacteristic
+ | SetChooseWitness
+ | SetExtensionality
+ | SetInduction
+ | PropositionalExtensionality
+ | DoubleNegationElim
+ | UnivOfContains
+ | UnivOfTransitive
+ | UnivOfFamilyUnionClosed
+ | UnivOfPowerSetClosed
+ | UnivOfReplacementClosed
+ | UnivOfMinimal
+ deriving stock (Show, Eq, Ord, Enum, Bounded)
+
+-- | The complete guarded-rule inventory.
+data KernelRuleTag
+ = SetLfpBound
+ | SetLfpLeast
+ | SetLfpFixed
+ | SetLfpInduct
+ deriving stock (Show, Eq, Ord, Enum, Bounded)
+
+data KernelRuleSignature = KernelRuleSignature
+ ![CoreType]
+ !Natural
+ deriving stock (Show, Eq)
+
+data FoundationRuleInput = FoundationRuleInput
+ !KernelRuleTag
+ !KernelRuleSignature
+ deriving stock (Show, Eq)
+
+data FofExclusion
+ = HigherOrderBinder !CoreType
+ | HigherOrderEquality !CoreType
+ | HigherOrderLambda
+ | HigherOrderIntrinsic !CoreIntrinsicTag
+ deriving stock (Show, Eq, Ord)
+
+data FoundationBackendClass
+ = FoundationFofProjectable
+ | FoundationRequiresTh0 !(NonEmpty FofExclusion)
+ deriving stock (Show, Eq)
+
+-- | One proposed manifest row. This is deliberately not an authority-bearing
+-- fact; only the fixed compiled rows can produce 'CheckedFoundation'.
+data FoundationAxiomInput = FoundationAxiomInput
+ !FoundationAxiomTag
+ !(CoreSyntax Void Natural)
+ !FoundationBackendClass
+
+data FoundationManifestError
+ = MissingFoundationIntrinsic !CoreIntrinsicTag
+ | DuplicateFoundationIntrinsic !CoreIntrinsicTag
+ | FoundationIntrinsicTypeMismatch
+ !CoreIntrinsicTag
+ !CoreType
+ !CoreType
+ | MissingFoundationRule !KernelRuleTag
+ | DuplicateFoundationRule !KernelRuleTag
+ | FoundationRuleSignatureMismatch
+ !KernelRuleTag
+ !KernelRuleSignature
+ !KernelRuleSignature
+ | MissingFoundationAxiom !FoundationAxiomTag
+ | DuplicateFoundationAxiom !FoundationAxiomTag
+ | FoundationAxiomIllTyped
+ !FoundationAxiomTag
+ !CoreCheckError
+ | FoundationAxiomFreezeFailed
+ !FoundationAxiomTag
+ !FreezeError
+ | FoundationAxiomStatementMismatch
+ !FoundationAxiomTag
+ | FoundationAxiomBackendClassMismatch
+ !FoundationAxiomTag
+ !FoundationBackendClass
+ !FoundationBackendClass
+ deriving stock (Show, Eq)
+
+data CheckedFoundationAxiom = CheckedFoundationAxiom
+ !(ClosedCheckedProposition Void)
+ !(FrozenCheckedCore Void)
+ !FoundationBackendClass
+
+newtype FoundationManifestAudit = FoundationManifestAudit
+ ( Map FoundationAxiomTag CheckedFoundationAxiom
+ , Map KernelRuleTag KernelRuleSignature
+ )
+
+data CheckedFoundation = CheckedFoundation
+ !(Map FoundationAxiomTag CheckedFoundationAxiom)
+ !(Map KernelRuleTag KernelRuleSignature)
+
+
+compiledFoundationIntrinsicRows
+ :: [(CoreIntrinsicTag, CoreType)]
+compiledFoundationIntrinsicRows =
+ [ (Member, TySet `TyArrow` (TySet `TyArrow` TyProp))
+ , (Empty, TySet)
+ , (PairSet, TySet `TyArrow` (TySet `TyArrow` TySet))
+ , (FamilyUnion, TySet `TyArrow` TySet)
+ , (PowerSet, TySet `TyArrow` TySet)
+ , ( Sep
+ , TySet
+ `TyArrow`
+ ((TySet `TyArrow` TyProp) `TyArrow` TySet)
+ )
+ , ( Repl
+ , TySet
+ `TyArrow`
+ ((TySet `TyArrow` TySet) `TyArrow` TySet)
+ )
+ , (SetChoose, (TySet `TyArrow` TyProp) `TyArrow` TySet)
+ , (UnivOf, TySet `TyArrow` TySet)
+ , ( ISetLfp
+ , TySet
+ `TyArrow`
+ ((TySet `TyArrow` TySet) `TyArrow` TySet)
+ )
+ ]
+
+compiledFoundationRuleRows :: [FoundationRuleInput]
+compiledFoundationRuleRows =
+ [ FoundationRuleInput
+ tag
+ (expectedKernelRuleSignature tag)
+ | tag <- allKernelRuleTags
+ ]
+
+compiledFoundationAxiomRows :: [FoundationAxiomInput]
+compiledFoundationAxiomRows =
+ [ FoundationAxiomInput
+ tag
+ (foundationAxiomSyntax tag)
+ (expectedFoundationBackendClass tag)
+ | tag <- allFoundationAxiomTags
+ ]
+
+checkedFoundation
+ :: Either
+ (NonEmpty FoundationManifestError)
+ CheckedFoundation
+checkedFoundation = do
+ FoundationManifestAudit (axioms, rules) <-
+ auditFoundationManifest
+ compiledFoundationIntrinsicRows
+ compiledFoundationRuleRows
+ compiledFoundationAxiomRows
+ pure (CheckedFoundation axioms rules)
+
+-- | Audit arbitrary proposed rows without granting foundation authority.
+-- This is also the pure mutation boundary used by conformance tests.
+auditFoundationManifest
+ :: [(CoreIntrinsicTag, CoreType)]
+ -> [FoundationRuleInput]
+ -> [FoundationAxiomInput]
+ -> Either
+ (NonEmpty FoundationManifestError)
+ FoundationManifestAudit
+auditFoundationManifest intrinsicRows ruleRows axiomRows =
+ case
+ intrinsicErrors
+ <> ruleErrors
+ <> axiomCoverageErrors
+ <> axiomErrors of
+ [] ->
+ case checkedRows of
+ Left errors ->
+ Left errors
+ Right rows ->
+ Right
+ (FoundationManifestAudit
+ ( Map.fromList rows
+ , Map.fromList
+ [ (tag, signature)
+ | FoundationRuleInput
+ tag
+ signature <-
+ ruleRows
+ ]
+ ))
+ firstError : remainingErrors ->
+ Left (firstError :| remainingErrors)
+ where
+ intrinsicErrors =
+ coverageErrors
+ MissingFoundationIntrinsic
+ DuplicateFoundationIntrinsic
+ allCoreIntrinsicTags
+ (fst <$> intrinsicRows)
+ <> [ FoundationIntrinsicTypeMismatch
+ tag
+ (coreIntrinsicType tag)
+ actual
+ | (tag, actual) <- intrinsicRows
+ , actual /= coreIntrinsicType tag
+ ]
+ axiomCoverageErrors =
+ coverageErrors
+ MissingFoundationAxiom
+ DuplicateFoundationAxiom
+ allFoundationAxiomTags
+ [ tag
+ | FoundationAxiomInput tag _syntax _backendClass <-
+ axiomRows
+ ]
+ ruleErrors =
+ coverageErrors
+ MissingFoundationRule
+ DuplicateFoundationRule
+ allKernelRuleTags
+ [ tag
+ | FoundationRuleInput tag _signature <-
+ ruleRows
+ ]
+ <> [ FoundationRuleSignatureMismatch
+ tag
+ (expectedKernelRuleSignature tag)
+ actual
+ | FoundationRuleInput tag actual <-
+ ruleRows
+ , actual /= expectedKernelRuleSignature tag
+ ]
+ checkedRows =
+ traverse checkAxiomRow axiomRows
+ axiomErrors =
+ case checkedRows of
+ Left errors ->
+ toList errors
+ Right _rows ->
+ []
+
+checkAxiomRow
+ :: FoundationAxiomInput
+ -> Either
+ (NonEmpty FoundationManifestError)
+ (FoundationAxiomTag, CheckedFoundationAxiom)
+checkAxiomRow
+ (FoundationAxiomInput
+ tag
+ syntax
+ declaredBackendClass) = do
+ proposition <-
+ firstOne (FoundationAxiomIllTyped tag)
+ (checkClosedProposition absurd syntax)
+ frozen <-
+ firstOne (FoundationAxiomFreezeFailed tag)
+ (freezeClosed
+ (checkedPropositionCore proposition))
+ expectedFrozen <-
+ expectedFoundationAxiom tag
+ unless
+ (frozen == expectedFrozen)
+ (Left
+ (FoundationAxiomStatementMismatch tag :| []))
+ let actualBackendClass =
+ classifyFrozenCore frozen
+ unless
+ (declaredBackendClass == actualBackendClass)
+ (Left
+ (FoundationAxiomBackendClassMismatch
+ tag
+ declaredBackendClass
+ actualBackendClass
+ :| []))
+ pure
+ ( tag
+ , CheckedFoundationAxiom
+ proposition
+ frozen
+ actualBackendClass
+ )
+
+expectedFoundationAxiom
+ :: FoundationAxiomTag
+ -> Either
+ (NonEmpty FoundationManifestError)
+ (FrozenCheckedCore Void)
+expectedFoundationAxiom tag = do
+ proposition <-
+ firstOne (FoundationAxiomIllTyped tag)
+ (checkClosedProposition
+ absurd
+ (foundationAxiomSyntax tag))
+ firstOne (FoundationAxiomFreezeFailed tag)
+ (freezeClosed
+ (checkedPropositionCore proposition))
+
+foundationAxiomProposition
+ :: CheckedFoundation
+ -> FoundationAxiomTag
+ -> ClosedCheckedProposition Void
+foundationAxiomProposition foundation tag =
+ case lookupFoundationAxiom foundation tag of
+ CheckedFoundationAxiom proposition _frozen _backendClass ->
+ proposition
+
+foundationAxiomFrozen
+ :: CheckedFoundation
+ -> FoundationAxiomTag
+ -> FrozenCheckedCore Void
+foundationAxiomFrozen foundation tag =
+ case lookupFoundationAxiom foundation tag of
+ CheckedFoundationAxiom _proposition frozen _backendClass ->
+ frozen
+
+foundationAxiomBackendClass
+ :: CheckedFoundation
+ -> FoundationAxiomTag
+ -> FoundationBackendClass
+foundationAxiomBackendClass foundation tag =
+ case lookupFoundationAxiom foundation tag of
+ CheckedFoundationAxiom _proposition _frozen backendClass ->
+ backendClass
+
+lookupFoundationAxiom
+ :: CheckedFoundation
+ -> FoundationAxiomTag
+ -> CheckedFoundationAxiom
+lookupFoundationAxiom
+ (CheckedFoundation axioms _rules)
+ tag =
+ case Map.lookup tag axioms of
+ Just axiom ->
+ axiom
+ Nothing ->
+ impossible
+ "checked foundation omitted a validated axiom tag"
+
+foundationRuleSignature
+ :: CheckedFoundation
+ -> KernelRuleTag
+ -> KernelRuleSignature
+foundationRuleSignature
+ (CheckedFoundation _axioms rules)
+ tag =
+ case Map.lookup tag rules of
+ Just signature ->
+ signature
+ Nothing ->
+ impossible
+ "checked foundation omitted a validated kernel rule"
+
+
+classifyFrozenCore
+ :: FrozenCheckedCore global
+ -> FoundationBackendClass
+classifyFrozenCore =
+ classifyCanonicalFofStructure
+ . frozenCoreTerm
+
+classifyCanonicalFofStructure
+ :: CanonicalTerm global
+ -> FoundationBackendClass
+classifyCanonicalFofStructure term =
+ case Set.toAscList
+ (termFofExclusions
+ term) of
+ [] ->
+ FoundationFofProjectable
+ firstExclusion : remainingExclusions ->
+ FoundationRequiresTh0
+ (firstExclusion :| remainingExclusions)
+
+foundationAxiomDependencies
+ :: CanonicalTerm global
+ -> Set FoundationAxiomTag
+foundationAxiomDependencies = \case
+ CBound{} ->
+ mempty
+ CGlobal{} ->
+ mempty
+ CIntrinsic intrinsic ->
+ intrinsicFoundationAxioms intrinsic
+ COpaqueInteger{} ->
+ mempty
+ CApp function argument ->
+ foundationAxiomDependencies function
+ <> foundationAxiomDependencies argument
+ CLam _binderType body ->
+ foundationAxiomDependencies body
+ CFalsum ->
+ mempty
+ CImp premise conclusion ->
+ foundationAxiomDependencies premise
+ <> foundationAxiomDependencies conclusion
+ CEq _operandType left right ->
+ foundationAxiomDependencies left
+ <> foundationAxiomDependencies right
+ CForall _binderType body ->
+ foundationAxiomDependencies body
+
+intrinsicFoundationAxioms
+ :: CoreIntrinsicTag
+ -> Set FoundationAxiomTag
+intrinsicFoundationAxioms = Set.fromList . \case
+ Member ->
+ []
+ Empty ->
+ [EmptyCharacteristic]
+ PairSet ->
+ [PairSetCharacteristic]
+ FamilyUnion ->
+ [FamilyUnionCharacteristic]
+ PowerSet ->
+ [PowerSetCharacteristic]
+ Sep ->
+ [SeparationCharacteristic]
+ Repl ->
+ [ReplacementCharacteristic]
+ SetChoose ->
+ [SetChooseWitness]
+ UnivOf ->
+ []
+ ISetLfp ->
+ []
+
+termFofExclusions
+ :: CanonicalTerm global
+ -> Set FofExclusion
+termFofExclusions = \case
+ CBound{} ->
+ mempty
+ CGlobal{} ->
+ mempty
+ CIntrinsic intrinsic
+ | intrinsic `elem`
+ [Sep, Repl, SetChoose, ISetLfp] ->
+ Set.singleton
+ (HigherOrderIntrinsic intrinsic)
+ | otherwise ->
+ mempty
+ COpaqueInteger{} ->
+ mempty
+ CApp function argument ->
+ termFofExclusions function
+ <> termFofExclusions argument
+ CLam _binderType body ->
+ Set.insert HigherOrderLambda
+ (termFofExclusions body)
+ CFalsum ->
+ mempty
+ CImp premise conclusion ->
+ termFofExclusions premise
+ <> termFofExclusions conclusion
+ CEq operandType left right ->
+ (case operandType of
+ TyArrow{} ->
+ Set.singleton
+ (HigherOrderEquality operandType)
+ _ ->
+ mempty)
+ <> termFofExclusions left
+ <> termFofExclusions right
+ CForall binderType body ->
+ (case binderType of
+ TySet ->
+ mempty
+ _ ->
+ Set.singleton
+ (HigherOrderBinder binderType))
+ <> termFofExclusions body
+
+
+allCoreIntrinsicTags :: [CoreIntrinsicTag]
+allCoreIntrinsicTags =
+ [minBound .. maxBound]
+
+allFoundationAxiomTags :: [FoundationAxiomTag]
+allFoundationAxiomTags =
+ [minBound .. maxBound]
+
+allKernelRuleTags :: [KernelRuleTag]
+allKernelRuleTags =
+ [minBound .. maxBound]
+
+expectedKernelRuleSignature
+ :: KernelRuleTag
+ -> KernelRuleSignature
+expectedKernelRuleSignature = \case
+ SetLfpBound ->
+ KernelRuleSignature
+ [TySet, TySet `TyArrow` TySet]
+ 0
+ SetLfpLeast ->
+ KernelRuleSignature
+ [TySet, TySet `TyArrow` TySet, TySet]
+ 2
+ SetLfpFixed ->
+ KernelRuleSignature
+ [TySet, TySet `TyArrow` TySet]
+ 1
+ SetLfpInduct ->
+ KernelRuleSignature
+ [ TySet
+ , TySet `TyArrow` TySet
+ , TySet `TyArrow` TyProp
+ , TySet
+ ]
+ 3
+
+coverageErrors
+ :: Ord tag
+ => (tag -> error)
+ -> (tag -> error)
+ -> [tag]
+ -> [tag]
+ -> [error]
+coverageErrors missing duplicate expected actual =
+ [ missing tag
+ | tag <- expected
+ , occurrenceCount tag == 0
+ ]
+ <> [ duplicate tag
+ | tag <- expected
+ , occurrenceCount tag > 1
+ ]
+ where
+ occurrenceCount tag =
+ length (List.filter (== tag) actual)
+
+firstOne
+ :: (error -> FoundationManifestError)
+ -> Either error value
+ -> Either (NonEmpty FoundationManifestError) value
+firstOne wrap =
+ either
+ (Left . (:| []) . wrap)
+ Right
+
+
+type FoundationSyntax = CoreSyntax Void Natural
+
+foundationAxiomSyntax
+ :: FoundationAxiomTag
+ -> FoundationSyntax
+foundationAxiomSyntax = \case
+ EmptyCharacteristic ->
+ foralls
+ [(x, TySet)]
+ (iff
+ (member (var x) emptySet)
+ coreFalsum)
+ PairSetCharacteristic ->
+ foralls
+ [(a, TySet), (b, TySet), (x, TySet)]
+ (iff
+ (member
+ (var x)
+ (pairSet (var a) (var b)))
+ (orP
+ (eqSet (var x) (var a))
+ (eqSet (var x) (var b))))
+ FamilyUnionCharacteristic ->
+ foralls
+ [(a, TySet), (x, TySet)]
+ (iff
+ (member
+ (var x)
+ (familyUnion (var a)))
+ (exists
+ b
+ TySet
+ (andP
+ (member (var b) (var a))
+ (member (var x) (var b)))))
+ PowerSetCharacteristic ->
+ foralls
+ [(a, TySet), (b, TySet)]
+ (iff
+ (member
+ (var b)
+ (powerSet (var a)))
+ (subset (var b) (var a)))
+ SeparationCharacteristic ->
+ foralls
+ [ (a, TySet)
+ , (p, TySet `TyArrow` TyProp)
+ , (x, TySet)
+ ]
+ (iff
+ (member
+ (var x)
+ (separation (var a) (var p)))
+ (andP
+ (member (var x) (var a))
+ (apply (var p) (var x))))
+ ReplacementCharacteristic ->
+ foralls
+ [ (a, TySet)
+ , (f, TySet `TyArrow` TySet)
+ , (y, TySet)
+ ]
+ (iff
+ (member
+ (var y)
+ (replacement (var a) (var f)))
+ (exists
+ x
+ TySet
+ (andP
+ (member (var x) (var a))
+ (eqSet
+ (var y)
+ (apply (var f) (var x))))))
+ SetChooseWitness ->
+ foralls
+ [ (p, TySet `TyArrow` TyProp)
+ , (x, TySet)
+ ]
+ (implies
+ (apply (var p) (var x))
+ (apply
+ (var p)
+ (setChoose (var p))))
+ SetExtensionality ->
+ foralls
+ [(a, TySet), (b, TySet)]
+ (implies
+ (subset (var a) (var b))
+ (implies
+ (subset (var b) (var a))
+ (eqSet (var a) (var b))))
+ SetInduction ->
+ forallOne
+ p
+ (TySet `TyArrow` TyProp)
+ (implies
+ (forallOne
+ a
+ TySet
+ (implies
+ (forallOne
+ x
+ TySet
+ (implies
+ (member (var x) (var a))
+ (apply (var p) (var x))))
+ (apply (var p) (var a))))
+ (forallOne
+ a
+ TySet
+ (apply (var p) (var a))))
+ PropositionalExtensionality ->
+ foralls
+ [(p, TyProp), (q, TyProp)]
+ (implies
+ (implies (var p) (var q))
+ (implies
+ (implies (var q) (var p))
+ (coreEquality
+ TyProp
+ (var p)
+ (var q))))
+ DoubleNegationElim ->
+ forallOne
+ p
+ TyProp
+ (implies
+ (notP (notP (var p)))
+ (var p))
+ UnivOfContains ->
+ forallOne
+ n
+ TySet
+ (member
+ (var n)
+ (univOf (var n)))
+ UnivOfTransitive ->
+ forallOne
+ n
+ TySet
+ (transitive (univOf (var n)))
+ UnivOfFamilyUnionClosed ->
+ forallOne
+ n
+ TySet
+ (familyUnionClosed
+ (univOf (var n)))
+ UnivOfPowerSetClosed ->
+ forallOne
+ n
+ TySet
+ (powerSetClosed
+ (univOf (var n)))
+ UnivOfReplacementClosed ->
+ forallOne
+ n
+ TySet
+ (replacementClosed
+ (univOf (var n)))
+ UnivOfMinimal ->
+ foralls
+ [(n, TySet), (u, TySet)]
+ (implies
+ (member (var n) (var u))
+ (implies
+ (transitive (var u))
+ (implies
+ (familyUnionClosed (var u))
+ (implies
+ (powerSetClosed (var u))
+ (implies
+ (replacementClosed (var u))
+ (subset
+ (univOf (var n))
+ (var u)))))))
+
+expectedFoundationBackendClass
+ :: FoundationAxiomTag
+ -> FoundationBackendClass
+expectedFoundationBackendClass = \case
+ EmptyCharacteristic ->
+ FoundationFofProjectable
+ PairSetCharacteristic ->
+ FoundationFofProjectable
+ FamilyUnionCharacteristic ->
+ FoundationFofProjectable
+ PowerSetCharacteristic ->
+ FoundationFofProjectable
+ SeparationCharacteristic ->
+ requiresTh0
+ (HigherOrderBinder
+ (TySet `TyArrow` TyProp))
+ [HigherOrderIntrinsic Sep]
+ ReplacementCharacteristic ->
+ requiresTh0
+ (HigherOrderBinder
+ (TySet `TyArrow` TySet))
+ [HigherOrderIntrinsic Repl]
+ SetChooseWitness ->
+ requiresTh0
+ (HigherOrderBinder
+ (TySet `TyArrow` TyProp))
+ [HigherOrderIntrinsic SetChoose]
+ SetExtensionality ->
+ FoundationFofProjectable
+ SetInduction ->
+ requiresTh0
+ (HigherOrderBinder
+ (TySet `TyArrow` TyProp))
+ []
+ PropositionalExtensionality ->
+ requiresTh0
+ (HigherOrderBinder TyProp)
+ []
+ DoubleNegationElim ->
+ requiresTh0
+ (HigherOrderBinder TyProp)
+ []
+ UnivOfContains ->
+ FoundationFofProjectable
+ UnivOfTransitive ->
+ FoundationFofProjectable
+ UnivOfFamilyUnionClosed ->
+ FoundationFofProjectable
+ UnivOfPowerSetClosed ->
+ FoundationFofProjectable
+ UnivOfReplacementClosed ->
+ requiresTh0
+ (HigherOrderBinder
+ (TySet `TyArrow` TySet))
+ [HigherOrderIntrinsic Repl]
+ UnivOfMinimal ->
+ requiresTh0
+ (HigherOrderBinder
+ (TySet `TyArrow` TySet))
+ [HigherOrderIntrinsic Repl]
+ where
+ requiresTh0 firstExclusion remainingExclusions =
+ FoundationRequiresTh0
+ (firstExclusion :| remainingExclusions)
+
+
+x, y, a, b, p, q, f, n, u :: Natural
+x = 0
+y = 1
+a = 2
+b = 3
+p = 4
+q = 5
+f = 6
+n = 7
+u = 8
+
+var :: Natural -> FoundationSyntax
+var = coreLocal
+
+apply
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+apply = coreApply
+
+apply2
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+apply2 function firstArgument secondArgument =
+ apply
+ (apply function firstArgument)
+ secondArgument
+
+member
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+member =
+ apply2 (coreIntrinsic Member)
+
+emptySet :: FoundationSyntax
+emptySet =
+ coreIntrinsic Empty
+
+pairSet
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+pairSet =
+ apply2 (coreIntrinsic PairSet)
+
+familyUnion :: FoundationSyntax -> FoundationSyntax
+familyUnion =
+ apply (coreIntrinsic FamilyUnion)
+
+powerSet :: FoundationSyntax -> FoundationSyntax
+powerSet =
+ apply (coreIntrinsic PowerSet)
+
+separation
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+separation =
+ apply2 (coreIntrinsic Sep)
+
+replacement
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+replacement =
+ apply2 (coreIntrinsic Repl)
+
+setChoose :: FoundationSyntax -> FoundationSyntax
+setChoose =
+ apply (coreIntrinsic SetChoose)
+
+univOf :: FoundationSyntax -> FoundationSyntax
+univOf =
+ apply (coreIntrinsic UnivOf)
+
+implies
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+implies =
+ coreImplication
+
+notP :: FoundationSyntax -> FoundationSyntax
+notP proposition =
+ implies proposition coreFalsum
+
+andP
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+andP left right =
+ notP
+ (implies left (notP right))
+
+orP
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+orP left right =
+ implies (notP left) right
+
+iff
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+iff =
+ coreEquality TyProp
+
+eqSet
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+eqSet =
+ coreEquality TySet
+
+forallOne
+ :: Natural
+ -> CoreType
+ -> FoundationSyntax
+ -> FoundationSyntax
+forallOne local binderType =
+ coreForall binderType local
+
+foralls
+ :: [(Natural, CoreType)]
+ -> FoundationSyntax
+ -> FoundationSyntax
+foralls binders body =
+ foldr
+ (uncurry forallOne)
+ body
+ binders
+
+exists
+ :: Natural
+ -> CoreType
+ -> FoundationSyntax
+ -> FoundationSyntax
+exists local binderType body =
+ notP
+ (forallOne
+ local
+ binderType
+ (notP body))
+
+subset
+ :: FoundationSyntax
+ -> FoundationSyntax
+ -> FoundationSyntax
+subset left right =
+ forallOne
+ x
+ TySet
+ (implies
+ (member (var x) left)
+ (member (var x) right))
+
+transitive :: FoundationSyntax -> FoundationSyntax
+transitive universe =
+ forallOne
+ a
+ TySet
+ (implies
+ (member (var a) universe)
+ (subset (var a) universe))
+
+familyUnionClosed :: FoundationSyntax -> FoundationSyntax
+familyUnionClosed universe =
+ forallOne
+ a
+ TySet
+ (implies
+ (member (var a) universe)
+ (member
+ (familyUnion (var a))
+ universe))
+
+powerSetClosed :: FoundationSyntax -> FoundationSyntax
+powerSetClosed universe =
+ forallOne
+ a
+ TySet
+ (implies
+ (member (var a) universe)
+ (member
+ (powerSet (var a))
+ universe))
+
+replacementClosed :: FoundationSyntax -> FoundationSyntax
+replacementClosed universe =
+ foralls
+ [ (a, TySet)
+ , (f, TySet `TyArrow` TySet)
+ ]
+ (implies
+ (member (var a) universe)
+ (implies
+ (forallOne
+ x
+ TySet
+ (implies
+ (member (var x) (var a))
+ (member
+ (apply (var f) (var x))
+ universe)))
+ (member
+ (replacement (var a) (var f))
+ universe)))
diff --git a/source/Felix/Checking/Identity.hs b/source/Felix/Checking/Identity.hs
new file mode 100644
index 0000000..67347a3
--- /dev/null
+++ b/source/Felix/Checking/Identity.hs
@@ -0,0 +1,1023 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Content-addressed identities for checked mathematical content.
+module Felix.Checking.Identity
+ ( TheoryId
+ , theoryId
+ , theoryIdDigest
+ , encodeFoundationManifest
+ , foundationManifestTags
+ , encodeKernelRuleTag
+ , ObjectFamily(..)
+ , ObjectId
+ , objectId
+ , objectIdFamily
+ , objectIdDigest
+ , encodeObjectId
+ , OpaqueDeclarationSeed
+ , opaqueDeclarationSeed
+ , opaqueDeclarationSeedDigest
+ , ObjectContent(..)
+ , objectContentTheory
+ , objectContentType
+ , intrinsicObjectId
+ , transparentObjectId
+ , opaqueObjectId
+ , AssertedObject
+ , assertedObject
+ , assertedObjectId
+ , assertedObjectContent
+ , CheckedObjectClosure
+ , checkedObjectClosureTheory
+ , checkedObjectIds
+ , lookupCheckedObjectType
+ , lookupCheckedObjectContent
+ , validateObjectClosure
+ , extendObjectClosure
+ , ObjectValidationError(..)
+ , PropositionId
+ , propositionIdDigest
+ , CheckedPropositionContent
+ , checkedPropositionId
+ , checkedPropositionTerm
+ , validatePropositionContent
+ , validateAssertedPropositionContent
+ , propositionIdOf
+ , PropositionValidationError(..)
+ , TheoremRef
+ , theoremRef
+ , theoremRefTheory
+ , theoremRefProposition
+ , encodeTheoremRef
+ , TheoremId
+ , theoremId
+ , theoremIdDigest
+ , putTheoryIdCache
+ , getTheoryIdCache
+ , putObjectIdCache
+ , getObjectIdCache
+ , putObjectContentCache
+ , getObjectContentCache
+ , putPropositionIdCache
+ , getPropositionIdCache
+ , putTheoremRefCache
+ , getTheoremRefCache
+ ) where
+
+import Base
+import Felix.Checking.Core
+import Felix.Checking.Foundation
+import Felix.Cache.Codec
+import Felix.Math.Codec
+import Felix.Module
+
+import Control.DeepSeq (NFData)
+import Control.Monad.State.Strict
+import Data.Bifunctor (first)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as ByteString
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Word (Word8)
+
+
+newtype TheoryId =
+ TheoryId MathematicalDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+theoryId :: CheckedFoundation -> TheoryId
+theoryId foundation =
+ TheoryId
+ (codecInvariant
+ (hashCanonicalFields
+ "felix-theory"
+ [encodeFoundationManifest foundation]))
+
+theoryIdDigest :: TheoryId -> MathematicalDigest
+theoryIdDigest (TheoryId digest) =
+ digest
+
+-- | Canonical ordered intrinsic/rule/axiom manifest. Backend classification is
+-- intentionally absent.
+encodeFoundationManifest :: CheckedFoundation -> ByteString
+encodeFoundationManifest foundation =
+ codecInvariant
+ (encodeSequence
+ [ codecInvariant (encodeSequence intrinsicRows)
+ , codecInvariant (encodeSequence ruleRows)
+ , codecInvariant (encodeSequence axiomRows)
+ ])
+ where
+ (intrinsicTags, ruleTags, axiomTags) =
+ foundationManifestTags
+
+ intrinsicRows =
+ [ encodeCoreIntrinsicTag tag
+ <> encodeFrame (encodeCoreType (coreIntrinsicType tag))
+ | tag <- intrinsicTags
+ ]
+
+ ruleRows =
+ [ encodeKernelRuleTag tag
+ <> encodeFrame
+ (codecInvariant
+ (encodeSequence
+ (encodeCoreType <$> inputTypes)))
+ <> encodeFrame (encodeNatural binderCount)
+ | tag <- ruleTags
+ , let KernelRuleSignature inputTypes binderCount =
+ foundationRuleSignature foundation tag
+ ]
+
+ axiomRows =
+ [ encodeFoundationAxiomTag tag
+ <> encodeFrame
+ (encodeCanonicalTerm
+ absurd
+ (frozenCoreTerm
+ (foundationAxiomFrozen foundation tag)))
+ | tag <- axiomTags
+ ]
+
+-- | Exhaustive foundation inventories in their stable encoded-tag order.
+foundationManifestTags
+ :: ( [CoreIntrinsicTag]
+ , [KernelRuleTag]
+ , [FoundationAxiomTag]
+ )
+foundationManifestTags =
+ ( stableTagOrder
+ "core intrinsic"
+ encodeCoreIntrinsicTag
+ allCoreIntrinsicTags
+ , stableTagOrder
+ "kernel rule"
+ encodeKernelRuleTag
+ allKernelRuleTags
+ , stableTagOrder
+ "foundation axiom"
+ encodeFoundationAxiomTag
+ allFoundationAxiomTags
+ )
+
+
+data ObjectFamily
+ = IntrinsicObject
+ | TransparentObject
+ | OpaqueObject
+ deriving stock (Show, Eq, Ord, Enum, Bounded, Generic)
+ deriving anyclass (NFData)
+
+data ObjectId = ObjectId
+ !ObjectFamily
+ !MathematicalDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+objectId
+ :: ObjectFamily
+ -> MathematicalDigest
+ -> ObjectId
+objectId =
+ ObjectId
+
+objectIdFamily :: ObjectId -> ObjectFamily
+objectIdFamily (ObjectId family _digest) =
+ family
+
+objectIdDigest :: ObjectId -> MathematicalDigest
+objectIdDigest (ObjectId _family digest) =
+ digest
+
+encodeObjectId :: ObjectId -> ByteString
+encodeObjectId (ObjectId family digest) =
+ ByteString.singleton (objectFamilyTag family)
+ <> mathematicalDigestBytes digest
+
+
+newtype OpaqueDeclarationSeed =
+ OpaqueDeclarationSeed MathematicalDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+opaqueDeclarationSeed
+ :: ModuleName
+ -> LocalDeclarationOrdinal
+ -> DeclarationFamilyTag
+ -> GeneratedObjectSlot
+ -> OpaqueDeclarationSeed
+opaqueDeclarationSeed
+ owner
+ declarationOrdinal
+ family
+ generatedSlot =
+ OpaqueDeclarationSeed
+ (codecInvariant
+ (hashCanonicalFields
+ "felix-opaque-declaration-v1"
+ [ encodeModuleName owner
+ , encodeNatural
+ (localDeclarationOrdinalValue
+ declarationOrdinal)
+ , encodeDeclarationFamilyTag family
+ , encodeNatural
+ (generatedObjectSlotValue
+ generatedSlot)
+ ]))
+
+opaqueDeclarationSeedDigest
+ :: OpaqueDeclarationSeed
+ -> MathematicalDigest
+opaqueDeclarationSeedDigest
+ (OpaqueDeclarationSeed digest) =
+ digest
+
+
+data ObjectContent
+ = IntrinsicObjectContent
+ !TheoryId
+ !CoreIntrinsicTag
+ !CoreType
+ | TransparentObjectContent
+ !TheoryId
+ !CoreType
+ !(CanonicalTerm ObjectId)
+ | OpaqueObjectContent
+ !TheoryId
+ !OpaqueDeclarationSeed
+ !CoreType
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+objectContentTheory :: ObjectContent -> TheoryId
+objectContentTheory = \case
+ IntrinsicObjectContent identity _tag _coreType ->
+ identity
+ TransparentObjectContent identity _coreType _body ->
+ identity
+ OpaqueObjectContent identity _seed _coreType ->
+ identity
+
+objectContentType :: ObjectContent -> CoreType
+objectContentType = \case
+ IntrinsicObjectContent _identity _tag coreType ->
+ coreType
+ TransparentObjectContent _identity coreType _body ->
+ coreType
+ OpaqueObjectContent _identity _seed coreType ->
+ coreType
+
+intrinsicObjectId
+ :: TheoryId
+ -> CoreIntrinsicTag
+ -> CoreType
+ -> ObjectId
+intrinsicObjectId identity tag coreType =
+ ObjectId
+ IntrinsicObject
+ (codecInvariant
+ (hashCanonicalFields
+ "felix-intrinsic-object-v1"
+ [ mathematicalDigestBytes
+ (theoryIdDigest identity)
+ , encodeCoreIntrinsicTag tag
+ , encodeCoreType coreType
+ ]))
+
+transparentObjectId
+ :: TheoryId
+ -> CoreType
+ -> CanonicalTerm ObjectId
+ -> ObjectId
+transparentObjectId identity coreType body =
+ ObjectId
+ TransparentObject
+ (codecInvariant
+ (hashCanonicalFields
+ "felix-transparent-object-v1"
+ [ mathematicalDigestBytes
+ (theoryIdDigest identity)
+ , encodeCoreType coreType
+ , encodeCanonicalTerm encodeObjectId body
+ ]))
+
+opaqueObjectId
+ :: TheoryId
+ -> OpaqueDeclarationSeed
+ -> CoreType
+ -> ObjectId
+opaqueObjectId identity seed coreType =
+ ObjectId
+ OpaqueObject
+ (codecInvariant
+ (hashCanonicalFields
+ "felix-opaque-object-v1"
+ [ mathematicalDigestBytes
+ (theoryIdDigest identity)
+ , mathematicalDigestBytes
+ (opaqueDeclarationSeedDigest seed)
+ , encodeCoreType coreType
+ ]))
+
+
+data AssertedObject = AssertedObject
+ !ObjectId
+ !ObjectContent
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+assertedObject :: ObjectId -> ObjectContent -> AssertedObject
+assertedObject =
+ AssertedObject
+
+assertedObjectId :: AssertedObject -> ObjectId
+assertedObjectId (AssertedObject identity _content) =
+ identity
+
+assertedObjectContent :: AssertedObject -> ObjectContent
+assertedObjectContent (AssertedObject _identity content) =
+ content
+
+data CheckedObject = CheckedObject
+ !ObjectContent
+ !CoreType
+
+data CheckedObjectClosure = CheckedObjectClosure
+ !TheoryId
+ !(Map ObjectId CheckedObject)
+
+checkedObjectClosureTheory :: CheckedObjectClosure -> TheoryId
+checkedObjectClosureTheory (CheckedObjectClosure identity _objects) =
+ identity
+
+checkedObjectIds :: CheckedObjectClosure -> Set ObjectId
+checkedObjectIds (CheckedObjectClosure _identity objects) =
+ Map.keysSet objects
+
+lookupCheckedObjectType
+ :: ObjectId
+ -> CheckedObjectClosure
+ -> Maybe CoreType
+lookupCheckedObjectType identity
+ (CheckedObjectClosure _theory objects) =
+ checkedObjectType
+ <$> Map.lookup identity objects
+
+lookupCheckedObjectContent
+ :: ObjectId
+ -> CheckedObjectClosure
+ -> Maybe ObjectContent
+lookupCheckedObjectContent identity
+ (CheckedObjectClosure _theory objects) =
+ checkedObjectContent
+ <$> Map.lookup identity objects
+
+checkedObjectType :: CheckedObject -> CoreType
+checkedObjectType (CheckedObject _content coreType) =
+ coreType
+
+checkedObjectContent :: CheckedObject -> ObjectContent
+checkedObjectContent (CheckedObject content _coreType) =
+ content
+
+
+data ObjectValidationError
+ = DuplicateAssertedObjectId !ObjectId
+ | ObjectContentTheoryMismatch
+ !ObjectId
+ !TheoryId
+ !TheoryId
+ | ObjectContentFamilyMismatch
+ !ObjectId
+ !ObjectFamily
+ !ObjectFamily
+ | IntrinsicObjectTypeMismatch
+ !ObjectId
+ !CoreIntrinsicTag
+ !CoreType
+ !CoreType
+ | TransparentObjectReferenceMissing
+ !ObjectId
+ !ObjectId
+ | TransparentObjectCycle !(NonEmpty ObjectId)
+ | TransparentObjectCoreCheckError
+ !ObjectId
+ !CoreCheckError
+ | TransparentObjectTypeMismatch
+ !ObjectId
+ !CoreType
+ !CoreType
+ | ObjectIdPayloadMismatch
+ !ObjectId
+ !ObjectId
+ deriving stock (Show, Eq)
+
+data ObjectValidationState = ObjectValidationState
+ { validationStack :: ![ObjectId]
+ , validatedObjects :: !(Map ObjectId CheckedObject)
+ }
+
+validateObjectClosure
+ :: TheoryId
+ -> [AssertedObject]
+ -> Either ObjectValidationError CheckedObjectClosure
+validateObjectClosure expectedTheory asserted = do
+ inventory <- buildObjectInventory asserted
+ finalState <-
+ execStateT
+ (traverse_ (validateOneObject expectedTheory inventory)
+ (Map.keys inventory))
+ (ObjectValidationState [] Map.empty)
+ pure
+ (CheckedObjectClosure
+ expectedTheory
+ (validatedObjects finalState))
+
+-- | Validate one declaration's new objects against an already checked
+-- closure. The existing closure is returned unchanged for an empty batch.
+extendObjectClosure
+ :: CheckedObjectClosure
+ -> [AssertedObject]
+ -> Either ObjectValidationError CheckedObjectClosure
+extendObjectClosure closure [] =
+ Right closure
+extendObjectClosure
+ (CheckedObjectClosure expectedTheory existing)
+ asserted = do
+ additions <- buildObjectInventory asserted
+ traverse_
+ (\identity ->
+ when
+ (Map.member identity existing)
+ (Left (DuplicateAssertedObjectId identity)))
+ (Map.keys additions)
+ let existingInventory =
+ checkedObjectContent <$> existing
+ inventory =
+ Map.union additions existingInventory
+ finalState <-
+ execStateT
+ (traverse_
+ (validateOneObject expectedTheory inventory)
+ (Map.keys additions))
+ (ObjectValidationState [] existing)
+ pure
+ (CheckedObjectClosure
+ expectedTheory
+ (validatedObjects finalState))
+
+buildObjectInventory
+ :: [AssertedObject]
+ -> Either ObjectValidationError (Map ObjectId ObjectContent)
+buildObjectInventory =
+ foldM insertOne Map.empty
+ where
+ insertOne inventory (AssertedObject identity content)
+ | Map.member identity inventory =
+ Left (DuplicateAssertedObjectId identity)
+ | otherwise =
+ Right (Map.insert identity content inventory)
+
+validateOneObject
+ :: TheoryId
+ -> Map ObjectId ObjectContent
+ -> ObjectId
+ -> StateT
+ ObjectValidationState
+ (Either ObjectValidationError)
+ ()
+validateOneObject expectedTheory inventory identity = do
+ alreadyValidated <-
+ gets (Map.member identity . validatedObjects)
+ unless alreadyValidated do
+ stack <- gets validationStack
+ when (identity `elem` stack) do
+ lift
+ (Left
+ (TransparentObjectCycle
+ (cyclePath identity stack)))
+ content <-
+ case Map.lookup identity inventory of
+ Nothing ->
+ impossible
+ "object validation root is absent from its inventory"
+ Just found ->
+ pure found
+ unless
+ (objectContentTheory content == expectedTheory)
+ (lift
+ (Left
+ (ObjectContentTheoryMismatch
+ identity
+ expectedTheory
+ (objectContentTheory content))))
+ let expectedFamily =
+ objectContentFamily content
+ suppliedFamily =
+ objectIdFamily identity
+ unless
+ (suppliedFamily == expectedFamily)
+ (lift
+ (Left
+ (ObjectContentFamilyMismatch
+ identity
+ expectedFamily
+ suppliedFamily)))
+ modify'
+ (\validationState ->
+ validationState
+ { validationStack =
+ identity
+ : validationStack validationState
+ })
+ checked <- case content of
+ IntrinsicObjectContent
+ theory
+ tag
+ suppliedType -> do
+ let expectedType =
+ coreIntrinsicType tag
+ unless
+ (suppliedType == expectedType)
+ (lift
+ (Left
+ (IntrinsicObjectTypeMismatch
+ identity
+ tag
+ expectedType
+ suppliedType)))
+ verifyObjectId
+ identity
+ (intrinsicObjectId
+ theory
+ tag
+ suppliedType)
+ pure
+ (CheckedObject content suppliedType)
+ TransparentObjectContent
+ theory
+ suppliedType
+ body -> do
+ traverse_
+ (validateDependency expectedTheory inventory identity)
+ (Set.toAscList (canonicalTermGlobals body))
+ resolvedObjects <-
+ gets validatedObjects
+ checkedBody <-
+ lift
+ (first
+ (TransparentObjectCoreCheckError
+ identity)
+ (checkCanonicalCore
+ (\reference ->
+ checkedObjectType
+ <$> Map.lookup
+ reference
+ resolvedObjects)
+ body))
+ let inferredType =
+ frozenCoreType checkedBody
+ unless
+ (inferredType == suppliedType)
+ (lift
+ (Left
+ (TransparentObjectTypeMismatch
+ identity
+ suppliedType
+ inferredType)))
+ verifyObjectId
+ identity
+ (transparentObjectId
+ theory
+ suppliedType
+ body)
+ pure
+ (CheckedObject content suppliedType)
+ OpaqueObjectContent
+ theory
+ seed
+ suppliedType -> do
+ verifyObjectId
+ identity
+ (opaqueObjectId
+ theory
+ seed
+ suppliedType)
+ pure
+ (CheckedObject content suppliedType)
+ modify'
+ (\validationState ->
+ validationState
+ { validationStack =
+ dropCurrent
+ identity
+ (validationStack validationState)
+ , validatedObjects =
+ Map.insert
+ identity
+ checked
+ (validatedObjects validationState)
+ })
+ where
+ verifyObjectId supplied computed =
+ unless
+ (supplied == computed)
+ (lift
+ (Left
+ (ObjectIdPayloadMismatch
+ supplied
+ computed)))
+
+validateDependency
+ :: TheoryId
+ -> Map ObjectId ObjectContent
+ -> ObjectId
+ -> ObjectId
+ -> StateT
+ ObjectValidationState
+ (Either ObjectValidationError)
+ ()
+validateDependency expectedTheory inventory parent dependency =
+ case Map.lookup dependency inventory of
+ Nothing ->
+ lift
+ (Left
+ (TransparentObjectReferenceMissing
+ parent
+ dependency))
+ Just _ ->
+ validateOneObject
+ expectedTheory
+ inventory
+ dependency
+
+cyclePath :: ObjectId -> [ObjectId] -> NonEmpty ObjectId
+cyclePath repeated stack =
+ case break (== repeated) stack of
+ (between, _repeated : _outer) ->
+ repeated :| (reverse between <> [repeated])
+ _ ->
+ impossible "repeated object is absent from validation stack"
+
+dropCurrent :: ObjectId -> [ObjectId] -> [ObjectId]
+dropCurrent expected = \case
+ current : rest
+ | current == expected ->
+ rest
+ _ ->
+ impossible "object validation stack is inconsistent"
+
+objectContentFamily :: ObjectContent -> ObjectFamily
+objectContentFamily = \case
+ IntrinsicObjectContent{} ->
+ IntrinsicObject
+ TransparentObjectContent{} ->
+ TransparentObject
+ OpaqueObjectContent{} ->
+ OpaqueObject
+
+newtype PropositionId =
+ PropositionId MathematicalDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+propositionIdDigest :: PropositionId -> MathematicalDigest
+propositionIdDigest (PropositionId digest) =
+ digest
+
+data CheckedPropositionContent = CheckedPropositionContent
+ !PropositionId
+ !(FrozenCheckedCore ObjectId)
+ deriving stock (Generic)
+ deriving anyclass (NFData)
+
+checkedPropositionId
+ :: CheckedPropositionContent
+ -> PropositionId
+checkedPropositionId
+ (CheckedPropositionContent identity _term) =
+ identity
+
+checkedPropositionTerm
+ :: CheckedPropositionContent
+ -> FrozenCheckedCore ObjectId
+checkedPropositionTerm
+ (CheckedPropositionContent _identity term) =
+ term
+
+data PropositionValidationError
+ = PropositionObjectMissing !ObjectId
+ | PropositionCoreCheckError !CoreCheckError
+ | PropositionIsNotProp !CoreType
+ | PropositionIdPayloadMismatch
+ !PropositionId
+ !PropositionId
+ deriving stock (Show, Eq)
+
+validatePropositionContent
+ :: CheckedObjectClosure
+ -> CanonicalTerm ObjectId
+ -> Either
+ PropositionValidationError
+ CheckedPropositionContent
+validatePropositionContent closure term = do
+ traverse_
+ (\identity ->
+ unless
+ (isJust
+ (lookupCheckedObjectType identity closure))
+ (Left (PropositionObjectMissing identity)))
+ (Set.toAscList (canonicalTermGlobals term))
+ checked <-
+ first PropositionCoreCheckError
+ (checkCanonicalCore
+ (\identity ->
+ lookupCheckedObjectType identity closure)
+ term)
+ unless
+ (frozenCoreType checked == TyProp)
+ (Left
+ (PropositionIsNotProp
+ (frozenCoreType checked)))
+ let identity =
+ propositionIdOf term
+ pure
+ (CheckedPropositionContent
+ identity
+ checked)
+
+validateAssertedPropositionContent
+ :: CheckedObjectClosure
+ -> PropositionId
+ -> CanonicalTerm ObjectId
+ -> Either
+ PropositionValidationError
+ CheckedPropositionContent
+validateAssertedPropositionContent closure supplied term = do
+ checked <-
+ validatePropositionContent closure term
+ let computed =
+ checkedPropositionId checked
+ unless
+ (supplied == computed)
+ (Left
+ (PropositionIdPayloadMismatch
+ supplied
+ computed))
+ pure checked
+
+propositionIdOf
+ :: CanonicalTerm ObjectId
+ -> PropositionId
+propositionIdOf term =
+ PropositionId
+ (codecInvariant
+ (hashCanonicalFields
+ "felix-proposition-v1"
+ [encodeCanonicalTerm encodeObjectId term]))
+
+
+data TheoremRef = TheoremRef
+ !TheoryId
+ !PropositionId
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+theoremRef :: TheoryId -> PropositionId -> TheoremRef
+theoremRef =
+ TheoremRef
+
+theoremRefTheory :: TheoremRef -> TheoryId
+theoremRefTheory (TheoremRef identity _proposition) =
+ identity
+
+theoremRefProposition :: TheoremRef -> PropositionId
+theoremRefProposition (TheoremRef _identity proposition) =
+ proposition
+
+encodeTheoremRef :: TheoremRef -> ByteString
+encodeTheoremRef (TheoremRef identity proposition) =
+ encodeFrame
+ (mathematicalDigestBytes
+ (theoryIdDigest identity))
+ <> encodeFrame
+ (mathematicalDigestBytes
+ (propositionIdDigest proposition))
+
+newtype TheoremId =
+ TheoremId MathematicalDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+theoremId :: TheoremRef -> TheoremId
+theoremId reference =
+ TheoremId
+ (codecInvariant
+ (hashCanonicalFields
+ "felix-theorem"
+ [encodeTheoremRef reference]))
+
+theoremIdDigest :: TheoremId -> MathematicalDigest
+theoremIdDigest (TheoremId digest) =
+ digest
+
+
+putTheoryIdCache :: TheoryId -> CachePut
+putTheoryIdCache =
+ putMathematicalDigestCache . theoryIdDigest
+
+getTheoryIdCache :: CacheGet TheoryId
+getTheoryIdCache =
+ TheoryId <$> getMathematicalDigestCache
+
+putObjectIdCache :: ObjectId -> CachePut
+putObjectIdCache (ObjectId family digest) = do
+ putCacheTag (objectFamilyTag family)
+ putMathematicalDigestCache digest
+
+getObjectIdCache :: CacheGet ObjectId
+getObjectIdCache = do
+ family <- getCacheTag >>= \case
+ 0x00 ->
+ pure IntrinsicObject
+ 0x01 ->
+ pure TransparentObject
+ 0x02 ->
+ pure OpaqueObject
+ tag ->
+ fail ("unknown cache object-family tag " <> show tag)
+ ObjectId family <$> getMathematicalDigestCache
+
+putOpaqueDeclarationSeedCache
+ :: OpaqueDeclarationSeed
+ -> CachePut
+putOpaqueDeclarationSeedCache =
+ putMathematicalDigestCache
+ . opaqueDeclarationSeedDigest
+
+getOpaqueDeclarationSeedCache
+ :: CacheGet OpaqueDeclarationSeed
+getOpaqueDeclarationSeedCache =
+ OpaqueDeclarationSeed
+ <$> getMathematicalDigestCache
+
+putObjectContentCache :: ObjectContent -> CachePut
+putObjectContentCache = \case
+ IntrinsicObjectContent identity tag coreType -> do
+ putCacheTag 0x00
+ putTheoryIdCache identity
+ putCoreIntrinsicTagCache tag
+ putCoreTypeCache coreType
+ TransparentObjectContent identity coreType body -> do
+ putCacheTag 0x01
+ putTheoryIdCache identity
+ putCoreTypeCache coreType
+ putCanonicalTermCache putObjectIdCache body
+ OpaqueObjectContent identity seed coreType -> do
+ putCacheTag 0x02
+ putTheoryIdCache identity
+ putOpaqueDeclarationSeedCache seed
+ putCoreTypeCache coreType
+
+getObjectContentCache :: CacheGet ObjectContent
+getObjectContentCache =
+ getCacheTag >>= \case
+ 0x00 ->
+ IntrinsicObjectContent
+ <$> getTheoryIdCache
+ <*> getCoreIntrinsicTagCache
+ <*> getCoreTypeCache
+ 0x01 ->
+ TransparentObjectContent
+ <$> getTheoryIdCache
+ <*> getCoreTypeCache
+ <*> getCanonicalTermCache getObjectIdCache
+ 0x02 ->
+ OpaqueObjectContent
+ <$> getTheoryIdCache
+ <*> getOpaqueDeclarationSeedCache
+ <*> getCoreTypeCache
+ tag ->
+ fail ("unknown cache object-content tag " <> show tag)
+
+putPropositionIdCache :: PropositionId -> CachePut
+putPropositionIdCache =
+ putMathematicalDigestCache . propositionIdDigest
+
+getPropositionIdCache :: CacheGet PropositionId
+getPropositionIdCache =
+ PropositionId <$> getMathematicalDigestCache
+
+putTheoremRefCache :: TheoremRef -> CachePut
+putTheoremRefCache (TheoremRef identity proposition) = do
+ putTheoryIdCache identity
+ putPropositionIdCache proposition
+
+getTheoremRefCache :: CacheGet TheoremRef
+getTheoremRefCache =
+ TheoremRef
+ <$> getTheoryIdCache
+ <*> getPropositionIdCache
+
+
+objectFamilyTag :: ObjectFamily -> Word8
+objectFamilyTag = \case
+ IntrinsicObject ->
+ 0x00
+ TransparentObject ->
+ 0x01
+ OpaqueObject ->
+ 0x02
+
+encodeKernelRuleTag :: KernelRuleTag -> ByteString
+encodeKernelRuleTag =
+ ByteString.singleton . \case
+ SetLfpBound ->
+ 0x00
+ SetLfpLeast ->
+ 0x01
+ SetLfpFixed ->
+ 0x02
+ SetLfpInduct ->
+ 0x03
+
+encodeFoundationAxiomTag :: FoundationAxiomTag -> ByteString
+encodeFoundationAxiomTag =
+ ByteString.singleton . \case
+ EmptyCharacteristic ->
+ 0x00
+ PairSetCharacteristic ->
+ 0x01
+ FamilyUnionCharacteristic ->
+ 0x02
+ PowerSetCharacteristic ->
+ 0x03
+ SeparationCharacteristic ->
+ 0x04
+ ReplacementCharacteristic ->
+ 0x05
+ SetChooseWitness ->
+ 0x06
+ SetExtensionality ->
+ 0x07
+ SetInduction ->
+ 0x08
+ PropositionalExtensionality ->
+ 0x09
+ DoubleNegationElim ->
+ 0x0a
+ UnivOfContains ->
+ 0x0b
+ UnivOfTransitive ->
+ 0x0c
+ UnivOfFamilyUnionClosed ->
+ 0x0d
+ UnivOfPowerSetClosed ->
+ 0x0e
+ UnivOfReplacementClosed ->
+ 0x0f
+ UnivOfMinimal ->
+ 0x10
+
+allCoreIntrinsicTags :: [CoreIntrinsicTag]
+allCoreIntrinsicTags =
+ [minBound .. maxBound]
+
+allKernelRuleTags :: [KernelRuleTag]
+allKernelRuleTags =
+ [minBound .. maxBound]
+
+allFoundationAxiomTags :: [FoundationAxiomTag]
+allFoundationAxiomTags =
+ [minBound .. maxBound]
+
+stableTagOrder
+ :: Show tag
+ => String
+ -> (tag -> ByteString)
+ -> [tag]
+ -> [tag]
+stableTagOrder description encodeTag tags
+ | Set.size encodedTags == length tags =
+ List.sortOn encodeTag tags
+ | otherwise =
+ impossible
+ ("duplicate stable "
+ <> description
+ <> " tag in "
+ <> show tags)
+ where
+ encodedTags =
+ Set.fromList (encodeTag <$> tags)
+
+codecInvariant
+ :: Either MathematicalCodecError value
+ -> value
+codecInvariant =
+ either
+ (impossible . ("canonical codec invariant: " <>) . show)
+ id
diff --git a/source/Felix/Checking/Kernel/Derivation.hs b/source/Felix/Checking/Kernel/Derivation.hs
new file mode 100644
index 0000000..e83b376
--- /dev/null
+++ b/source/Felix/Checking/Kernel/Derivation.hs
@@ -0,0 +1,1264 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Private in-memory proof trees and independent kernel replay.
+module Felix.Checking.Kernel.Derivation
+ ( ImportIx
+ , importIx
+ , importIxValue
+ , HypothesisIx
+ , hypothesisIx
+ , DerivationImportJudgment
+ , derivationImportJudgment
+ , derivationImportStatement
+ , KernelDerivation
+ , mapKernelDerivationGlobals
+ , importedFactDerivation
+ , localHypothesisDerivation
+ , foundationFactDerivation
+ , implicationEliminationDerivation
+ , forallEliminationDerivation
+ , falsumEliminationDerivation
+ , implicationIntroductionDerivation
+ , forallIntroductionDerivation
+ , ConversionPlan
+ , conversionPlan
+ , conversionPlanBudget
+ , ConversionPlanError(..)
+ , convertJudgmentDerivation
+ , equalityReflexivityDerivation
+ , scopedEqualityReflexivityDerivation
+ , equalityCongruenceApplicationDerivation
+ , equalityCongruenceLambdaDerivation
+ , equalityModusPonensDerivation
+ , setLfpBoundDerivation
+ , setLfpLeastDerivation
+ , setLfpFixedDerivation
+ , setLfpInductDerivation
+ , weakenDerivationHypotheses
+ , KernelReplayLimits
+ , kernelReplayLimits
+ , defaultKernelReplayLimits
+ , KernelReplayLimitError(..)
+ , ReplayedKernelDerivation
+ , replayKernelDerivation
+ , replayedKernelTarget
+ , replayedKernelImportUses
+ , replayedKernelFoundationUses
+ , replayedKernelRuleUses
+ , replayedKernelNodeCount
+ , replayedKernelMaximumDepth
+ , DerivationImportError(..)
+ , KernelReplayError(..)
+ ) where
+
+import Base
+import Felix.Checking.Core
+import Felix.Checking.Foundation
+import Felix.Checking.Kernel.Semantics qualified as Semantics
+import Felix.Checking.Kernel.SetLfp qualified as SetLfp
+
+import Control.Monad (unless)
+import Data.Bifunctor (first)
+import Data.Set qualified as Set
+import Data.Vector (Vector)
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+
+
+newtype ImportIx = ImportIx Natural
+ deriving stock (Show, Eq, Ord)
+
+importIx :: Natural -> ImportIx
+importIx = ImportIx
+
+importIxValue :: ImportIx -> Natural
+importIxValue (ImportIx index) =
+ index
+
+newtype HypothesisIx = HypothesisIx Natural
+ deriving stock (Show, Eq, Ord)
+
+hypothesisIx :: Natural -> HypothesisIx
+hypothesisIx = HypothesisIx
+
+newtype DerivationImportJudgment global =
+ DerivationImportJudgment
+ (FrozenCheckedCore global)
+ deriving stock (Eq)
+
+data DerivationImportError =
+ DerivationImportIsNotProposition !CoreType
+ deriving stock (Show, Eq)
+
+derivationImportJudgment
+ :: FrozenCheckedCore global
+ -> Either
+ DerivationImportError
+ (DerivationImportJudgment global)
+derivationImportJudgment statement
+ | frozenCoreType statement == TyProp =
+ Right (DerivationImportJudgment statement)
+ | otherwise =
+ Left
+ (DerivationImportIsNotProposition
+ (frozenCoreType statement))
+
+derivationImportStatement
+ :: DerivationImportJudgment global
+ -> FrozenCheckedCore global
+derivationImportStatement
+ (DerivationImportJudgment statement) =
+ statement
+
+data KernelDerivation global
+ = UseImportedFact !ImportIx
+ | UseLocalHypothesis !HypothesisIx
+ | UseFoundationFact !FoundationAxiomTag
+ | ImplicationElimination
+ !(KernelDerivation global)
+ !(KernelDerivation global)
+ | ForallElimination
+ !(KernelDerivation global)
+ !(ScopedCheckedCore global)
+ | FalsumElimination
+ !(KernelDerivation global)
+ !(ScopedCheckedCore global)
+ | ImplicationIntroduction
+ !(ScopedCheckedCore global)
+ !(KernelDerivation global)
+ | ForallIntroduction
+ !CoreType
+ !(KernelDerivation global)
+ | ConvertJudgment
+ !(KernelDerivation global)
+ !(ScopedCheckedCore global)
+ !ConversionPlan
+ | EqualityReflexivity
+ !(ScopedCheckedCore global)
+ | EqualityCongruenceApplication
+ !(KernelDerivation global)
+ !(KernelDerivation global)
+ | EqualityCongruenceLambda
+ !CoreType
+ !(KernelDerivation global)
+ | EqualityModusPonens
+ !(KernelDerivation global)
+ !(KernelDerivation global)
+ | ApplySetLfpBound
+ !(ScopedCheckedCore global)
+ !(ScopedCheckedCore global)
+ | ApplySetLfpLeast
+ !(ScopedCheckedCore global)
+ !(ScopedCheckedCore global)
+ !(ScopedCheckedCore global)
+ !(KernelDerivation global)
+ !(KernelDerivation global)
+ | ApplySetLfpFixed
+ !(ScopedCheckedCore global)
+ !(ScopedCheckedCore global)
+ !(KernelDerivation global)
+ | ApplySetLfpInduct
+ !(ScopedCheckedCore global)
+ !(ScopedCheckedCore global)
+ !(ScopedCheckedCore global)
+ !(ScopedCheckedCore global)
+ !(KernelDerivation global)
+ !(KernelDerivation global)
+ !(KernelDerivation global)
+ deriving stock (Eq)
+
+mapKernelDerivationGlobals
+ :: (left -> right)
+ -> KernelDerivation left
+ -> KernelDerivation right
+mapKernelDerivationGlobals transform = go
+ where
+ scoped = mapScopedGlobals transform
+ go = \case
+ UseImportedFact index ->
+ UseImportedFact index
+ UseLocalHypothesis index ->
+ UseLocalHypothesis index
+ UseFoundationFact tag ->
+ UseFoundationFact tag
+ ImplicationElimination premise implication ->
+ ImplicationElimination (go premise) (go implication)
+ ForallElimination proof argument ->
+ ForallElimination (go proof) (scoped argument)
+ FalsumElimination proof target ->
+ FalsumElimination (go proof) (scoped target)
+ ImplicationIntroduction premise proof ->
+ ImplicationIntroduction (scoped premise) (go proof)
+ ForallIntroduction binderType proof ->
+ ForallIntroduction binderType (go proof)
+ ConvertJudgment proof target plan ->
+ ConvertJudgment (go proof) (scoped target) plan
+ EqualityReflexivity term ->
+ EqualityReflexivity (scoped term)
+ EqualityCongruenceApplication function argument ->
+ EqualityCongruenceApplication (go function) (go argument)
+ EqualityCongruenceLambda binderType proof ->
+ EqualityCongruenceLambda binderType (go proof)
+ EqualityModusPonens equality proof ->
+ EqualityModusPonens (go equality) (go proof)
+ ApplySetLfpBound domain operator ->
+ ApplySetLfpBound (scoped domain) (scoped operator)
+ ApplySetLfpLeast domain operator candidate bounded closed ->
+ ApplySetLfpLeast
+ (scoped domain)
+ (scoped operator)
+ (scoped candidate)
+ (go bounded)
+ (go closed)
+ ApplySetLfpFixed domain operator monotone ->
+ ApplySetLfpFixed
+ (scoped domain)
+ (scoped operator)
+ (go monotone)
+ ApplySetLfpInduct domain operator predicate element
+ monotone member closed ->
+ ApplySetLfpInduct
+ (scoped domain)
+ (scoped operator)
+ (scoped predicate)
+ (scoped element)
+ (go monotone)
+ (go member)
+ (go closed)
+
+importedFactDerivation
+ :: ImportIx
+ -> KernelDerivation global
+importedFactDerivation =
+ UseImportedFact
+
+localHypothesisDerivation
+ :: HypothesisIx
+ -> KernelDerivation global
+localHypothesisDerivation =
+ UseLocalHypothesis
+
+foundationFactDerivation
+ :: FoundationAxiomTag
+ -> KernelDerivation global
+foundationFactDerivation =
+ UseFoundationFact
+
+implicationEliminationDerivation
+ :: KernelDerivation global
+ -> KernelDerivation global
+ -> KernelDerivation global
+implicationEliminationDerivation =
+ ImplicationElimination
+
+forallEliminationDerivation
+ :: KernelDerivation global
+ -> ScopedCheckedCore global
+ -> KernelDerivation global
+forallEliminationDerivation =
+ ForallElimination
+
+falsumEliminationDerivation
+ :: KernelDerivation global
+ -> ScopedCheckedCore global
+ -> KernelDerivation global
+falsumEliminationDerivation =
+ FalsumElimination
+
+implicationIntroductionDerivation
+ :: ScopedCheckedCore global
+ -> KernelDerivation global
+ -> KernelDerivation global
+implicationIntroductionDerivation =
+ ImplicationIntroduction
+
+forallIntroductionDerivation
+ :: CoreType
+ -> KernelDerivation global
+ -> KernelDerivation global
+forallIntroductionDerivation =
+ ForallIntroduction
+
+newtype ConversionPlan = ConversionPlan Natural
+ deriving stock (Show, Eq, Ord)
+
+data ConversionPlanError =
+ ConversionPlanExceedsLimit !Natural
+ deriving stock (Show, Eq)
+
+conversionPlanLimit :: Natural
+conversionPlanLimit =
+ 100000
+
+conversionPlan
+ :: Natural
+ -> Either ConversionPlanError ConversionPlan
+conversionPlan budget
+ | budget <= conversionPlanLimit =
+ Right (ConversionPlan budget)
+ | otherwise =
+ Left (ConversionPlanExceedsLimit budget)
+
+conversionPlanBudget :: ConversionPlan -> Natural
+conversionPlanBudget (ConversionPlan budget) =
+ budget
+
+convertJudgmentDerivation
+ :: KernelDerivation global
+ -> ScopedCheckedCore global
+ -> ConversionPlan
+ -> KernelDerivation global
+convertJudgmentDerivation =
+ ConvertJudgment
+
+equalityReflexivityDerivation
+ :: FrozenCheckedCore global
+ -> KernelDerivation global
+equalityReflexivityDerivation =
+ EqualityReflexivity . embedClosedCore []
+
+scopedEqualityReflexivityDerivation
+ :: ScopedCheckedCore global
+ -> KernelDerivation global
+scopedEqualityReflexivityDerivation =
+ EqualityReflexivity
+
+equalityCongruenceApplicationDerivation
+ :: KernelDerivation global
+ -> KernelDerivation global
+ -> KernelDerivation global
+equalityCongruenceApplicationDerivation =
+ EqualityCongruenceApplication
+
+equalityCongruenceLambdaDerivation
+ :: CoreType
+ -> KernelDerivation global
+ -> KernelDerivation global
+equalityCongruenceLambdaDerivation =
+ EqualityCongruenceLambda
+
+equalityModusPonensDerivation
+ :: KernelDerivation global
+ -> KernelDerivation global
+ -> KernelDerivation global
+equalityModusPonensDerivation =
+ EqualityModusPonens
+
+setLfpBoundDerivation
+ :: ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> KernelDerivation global
+setLfpBoundDerivation =
+ ApplySetLfpBound
+
+setLfpLeastDerivation
+ :: ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> KernelDerivation global
+ -> KernelDerivation global
+ -> KernelDerivation global
+setLfpLeastDerivation =
+ ApplySetLfpLeast
+
+setLfpFixedDerivation
+ :: ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> KernelDerivation global
+ -> KernelDerivation global
+setLfpFixedDerivation =
+ ApplySetLfpFixed
+
+setLfpInductDerivation
+ :: ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> KernelDerivation global
+ -> KernelDerivation global
+ -> KernelDerivation global
+ -> KernelDerivation global
+setLfpInductDerivation =
+ ApplySetLfpInduct
+
+-- | Add hypotheses outside a derivation while preserving hypotheses introduced
+-- by implication nodes inside it.
+weakenDerivationHypotheses
+ :: Natural
+ -> KernelDerivation global
+ -> KernelDerivation global
+weakenDerivationHypotheses amount =
+ shift 0
+ where
+ shift cutoff = \case
+ UseImportedFact index ->
+ UseImportedFact index
+ UseLocalHypothesis (HypothesisIx index) ->
+ UseLocalHypothesis
+ (HypothesisIx
+ (if index >= cutoff
+ then index + amount
+ else index))
+ UseFoundationFact tag ->
+ UseFoundationFact tag
+ ImplicationElimination implication premise ->
+ ImplicationElimination
+ (shift cutoff implication)
+ (shift cutoff premise)
+ ForallElimination quantified argument ->
+ ForallElimination
+ (shift cutoff quantified)
+ argument
+ FalsumElimination falsum target ->
+ FalsumElimination
+ (shift cutoff falsum)
+ target
+ ImplicationIntroduction premise body ->
+ ImplicationIntroduction
+ premise
+ (shift (cutoff + 1) body)
+ ForallIntroduction binderType body ->
+ ForallIntroduction
+ binderType
+ (shift cutoff body)
+ ConvertJudgment source target plan ->
+ ConvertJudgment
+ (shift cutoff source)
+ target
+ plan
+ EqualityReflexivity operand ->
+ EqualityReflexivity operand
+ EqualityCongruenceApplication function argument ->
+ EqualityCongruenceApplication
+ (shift cutoff function)
+ (shift cutoff argument)
+ EqualityCongruenceLambda binderType body ->
+ EqualityCongruenceLambda
+ binderType
+ (shift cutoff body)
+ EqualityModusPonens equality premise ->
+ EqualityModusPonens
+ (shift cutoff equality)
+ (shift cutoff premise)
+ ApplySetLfpBound domain operator ->
+ ApplySetLfpBound domain operator
+ ApplySetLfpLeast
+ domain operator candidate
+ closed bounded ->
+ ApplySetLfpLeast
+ domain
+ operator
+ candidate
+ (shift cutoff closed)
+ (shift cutoff bounded)
+ ApplySetLfpFixed domain operator monotone ->
+ ApplySetLfpFixed
+ domain
+ operator
+ (shift cutoff monotone)
+ ApplySetLfpInduct
+ domain operator predicate element
+ monotone member closure ->
+ ApplySetLfpInduct
+ domain
+ operator
+ predicate
+ element
+ (shift cutoff monotone)
+ (shift cutoff member)
+ (shift cutoff closure)
+
+data KernelReplayLimits = KernelReplayLimits
+ !Natural
+ !Natural
+ deriving stock (Show, Eq)
+
+data KernelReplayLimitError
+ = KernelReplayNodeLimitIsZero
+ | KernelReplayNodeLimitTooLarge !Natural
+ | KernelReplayDepthLimitTooLarge !Natural
+ deriving stock (Show, Eq)
+
+maximumKernelReplayNodes :: Natural
+maximumKernelReplayNodes =
+ 1000000
+
+maximumKernelReplayDepth :: Natural
+maximumKernelReplayDepth =
+ 4096
+
+kernelReplayLimits
+ :: Natural
+ -> Natural
+ -> Either KernelReplayLimitError KernelReplayLimits
+kernelReplayLimits nodeLimit depthLimit
+ | nodeLimit == 0 =
+ Left KernelReplayNodeLimitIsZero
+ | nodeLimit > maximumKernelReplayNodes =
+ Left (KernelReplayNodeLimitTooLarge nodeLimit)
+ | depthLimit > maximumKernelReplayDepth =
+ Left (KernelReplayDepthLimitTooLarge depthLimit)
+ | otherwise =
+ Right
+ (KernelReplayLimits
+ nodeLimit
+ depthLimit)
+
+defaultKernelReplayLimits :: KernelReplayLimits
+defaultKernelReplayLimits =
+ KernelReplayLimits
+ 200000
+ 2048
+
+data ReplayedKernelDerivation global =
+ ReplayedKernelDerivation
+ !(FrozenCheckedCore global)
+ !(Set ImportIx)
+ !(Set FoundationAxiomTag)
+ !(Set KernelRuleTag)
+ !Natural
+ !Natural
+ deriving stock (Eq)
+
+replayedKernelTarget
+ :: ReplayedKernelDerivation global
+ -> FrozenCheckedCore global
+replayedKernelTarget
+ (ReplayedKernelDerivation
+ target
+ _importUses
+ _foundationUses
+ _ruleUses
+ _nodeCount
+ _maximumDepth) =
+ target
+
+replayedKernelImportUses
+ :: ReplayedKernelDerivation global
+ -> Set ImportIx
+replayedKernelImportUses
+ (ReplayedKernelDerivation
+ _target
+ importUses
+ _foundationUses
+ _ruleUses
+ _nodeCount
+ _maximumDepth) =
+ importUses
+
+replayedKernelFoundationUses
+ :: ReplayedKernelDerivation global
+ -> Set FoundationAxiomTag
+replayedKernelFoundationUses
+ (ReplayedKernelDerivation
+ _target
+ _importUses
+ foundationUses
+ _ruleUses
+ _nodeCount
+ _maximumDepth) =
+ foundationUses
+
+replayedKernelRuleUses
+ :: ReplayedKernelDerivation global
+ -> Set KernelRuleTag
+replayedKernelRuleUses
+ (ReplayedKernelDerivation
+ _target
+ _importUses
+ _foundationUses
+ ruleUses
+ _nodeCount
+ _maximumDepth) =
+ ruleUses
+
+replayedKernelNodeCount
+ :: ReplayedKernelDerivation global
+ -> Natural
+replayedKernelNodeCount
+ (ReplayedKernelDerivation
+ _target
+ _importUses
+ _foundationUses
+ _ruleUses
+ nodeCount
+ _maximumDepth) =
+ nodeCount
+
+replayedKernelMaximumDepth
+ :: ReplayedKernelDerivation global
+ -> Natural
+replayedKernelMaximumDepth
+ (ReplayedKernelDerivation
+ _target
+ _importUses
+ _foundationUses
+ _ruleUses
+ _nodeCount
+ maximumDepth) =
+ maximumDepth
+
+data ReplayStep global = ReplayStep
+ !(ScopedCheckedCore global)
+ !(Set ImportIx)
+ !(Set FoundationAxiomTag)
+ !(Set KernelRuleTag)
+ !Natural
+ !Natural
+
+data KernelReplayError
+ = KernelReplaySemanticsError
+ !Semantics.KernelSemanticsError
+ | KernelReplaySetLfpRuleError
+ !SetLfp.SetLfpRuleError
+ | KernelReplayImportOutOfBounds !ImportIx
+ | KernelReplayHypothesisOutOfBounds !HypothesisIx
+ | KernelReplayStoredContextMismatch
+ ![CoreType]
+ ![CoreType]
+ | KernelReplayStoredTermIllTyped !CoreCheckError
+ | KernelReplayStoredTypeMismatch
+ !CoreType
+ !CoreType
+ | KernelReplayWeakeningError !CoreCheckError
+ | KernelReplayNodeLimitExceeded !Natural
+ | KernelReplayDepthLimitExceeded !Natural
+ | KernelReplayRootRemainedOpen
+ | KernelReplayTargetMismatch
+ deriving stock (Show, Eq)
+
+replayKernelDerivation
+ :: Eq global
+ => CheckedFoundation
+ -> KernelReplayLimits
+ -> (global -> Maybe CoreType)
+ -> Vector (DerivationImportJudgment global)
+ -> FrozenCheckedCore global
+ -> KernelDerivation global
+ -> Either
+ KernelReplayError
+ (ReplayedKernelDerivation global)
+replayKernelDerivation
+ checkedFoundationValue
+ limits
+ globalType
+ imports
+ expectedTarget
+ derivation =
+ checkedFoundationValue `seq` do
+ expectedTarget' <-
+ recheckClosed expectedTarget
+ ReplayStep
+ synthesized
+ importUses
+ foundationUses
+ ruleUses
+ nodeCount
+ maximumDepth <-
+ replay [] [] 0 derivation
+ closed <-
+ maybe
+ (Left KernelReplayRootRemainedOpen)
+ Right
+ (closeScopedCore synthesized)
+ unless
+ (closed == expectedTarget')
+ (Left KernelReplayTargetMismatch)
+ pure
+ (ReplayedKernelDerivation
+ closed
+ importUses
+ foundationUses
+ ruleUses
+ nodeCount
+ maximumDepth)
+ where
+ replay context hypotheses depth derivationNode
+ | depth > replayDepthLimit =
+ Left
+ (KernelReplayDepthLimitExceeded
+ replayDepthLimit)
+ | otherwise = do
+ step <-
+ replayWithin
+ context
+ hypotheses
+ depth
+ derivationNode
+ if stepNodeCount step > replayNodeLimit
+ then
+ Left
+ (KernelReplayNodeLimitExceeded
+ replayNodeLimit)
+ else
+ Right step
+
+ replayWithin context hypotheses depth = \case
+ UseImportedFact index -> do
+ judgment <-
+ lookupImport index
+ statement <-
+ recheckStored
+ context
+ (embedClosedCore context
+ (derivationImportStatement
+ judgment))
+ pure
+ (leaf
+ depth
+ statement
+ (Set.singleton index)
+ mempty)
+ UseLocalHypothesis index -> do
+ hypothesis <-
+ lookupHypothesis index hypotheses
+ checkedHypothesis <-
+ recheckStored context hypothesis
+ pure
+ (leaf
+ depth
+ checkedHypothesis
+ mempty
+ mempty)
+ UseFoundationFact tag -> do
+ statement <-
+ recheckStored
+ context
+ (embedClosedCore context
+ (mapFrozenGlobals
+ absurd
+ (foundationAxiomFrozen
+ checkedFoundationValue
+ tag)))
+ pure
+ (leaf
+ depth
+ statement
+ mempty
+ (Set.singleton tag))
+ ImplicationElimination implication premise -> do
+ implicationStep <-
+ replay context hypotheses (depth + 1) implication
+ premiseStep <-
+ replay context hypotheses (depth + 1) premise
+ combine2 depth
+ (Semantics.implicationElimination
+ globalType
+ (stepValue implicationStep)
+ (stepValue premiseStep))
+ implicationStep
+ premiseStep
+ ForallElimination quantified argument -> do
+ argument' <-
+ recheckStored context argument
+ quantifiedStep <-
+ replay context hypotheses (depth + 1) quantified
+ combine1 depth
+ (Semantics.forallElimination
+ globalType
+ (stepValue quantifiedStep)
+ argument')
+ quantifiedStep
+ FalsumElimination falsum target -> do
+ target' <-
+ recheckStored context target
+ falsumStep <-
+ replay context hypotheses (depth + 1) falsum
+ combine1 depth
+ (Semantics.falsumElimination
+ globalType
+ (stepValue falsumStep)
+ target')
+ falsumStep
+ ImplicationIntroduction premise body -> do
+ premise' <-
+ recheckStored context premise
+ bodyStep <-
+ replay
+ context
+ (premise' : hypotheses)
+ (depth + 1)
+ body
+ combine1 depth
+ (Semantics.implicationIntroduction
+ globalType
+ premise'
+ (stepValue bodyStep))
+ bodyStep
+ ForallIntroduction binderType body -> do
+ weakenedHypotheses <-
+ traverse
+ (first KernelReplayWeakeningError
+ . weakenScopedCore
+ globalType
+ binderType)
+ hypotheses
+ bodyStep <-
+ replay
+ (binderType : context)
+ weakenedHypotheses
+ (depth + 1)
+ body
+ combine1 depth
+ (Semantics.forallIntroduction
+ globalType
+ binderType
+ (stepValue bodyStep))
+ bodyStep
+ ConvertJudgment source target plan -> do
+ target' <-
+ recheckStored context target
+ sourceStep <-
+ replay
+ context
+ hypotheses
+ (depth + 1)
+ source
+ combine1 depth
+ (Semantics.convertJudgment
+ globalType
+ (conversionPlanBudget plan)
+ (stepValue sourceStep)
+ target')
+ sourceStep
+ EqualityReflexivity operand -> do
+ operand' <-
+ recheckStored context operand
+ value <-
+ first KernelReplaySemanticsError
+ (Semantics.equalityReflexivity
+ globalType
+ operand')
+ pure (leaf depth value mempty mempty)
+ EqualityCongruenceApplication
+ functionEquality
+ argumentEquality -> do
+ functionStep <-
+ replay
+ context
+ hypotheses
+ (depth + 1)
+ functionEquality
+ argumentStep <-
+ replay
+ context
+ hypotheses
+ (depth + 1)
+ argumentEquality
+ combine2 depth
+ (Semantics.equalityCongruenceApplication
+ globalType
+ (stepValue functionStep)
+ (stepValue argumentStep))
+ functionStep
+ argumentStep
+ EqualityCongruenceLambda binderType bodyEquality -> do
+ weakenedHypotheses <-
+ traverse
+ (first KernelReplayWeakeningError
+ . weakenScopedCore
+ globalType
+ binderType)
+ hypotheses
+ bodyStep <-
+ replay
+ (binderType : context)
+ weakenedHypotheses
+ (depth + 1)
+ bodyEquality
+ combine1 depth
+ (Semantics.equalityCongruenceLambda
+ globalType
+ binderType
+ (stepValue bodyStep))
+ bodyStep
+ EqualityModusPonens equality premise -> do
+ equalityStep <-
+ replay context hypotheses (depth + 1) equality
+ premiseStep <-
+ replay context hypotheses (depth + 1) premise
+ combine2 depth
+ (Semantics.equalityModusPonens
+ globalType
+ (stepValue equalityStep)
+ (stepValue premiseStep))
+ equalityStep
+ premiseStep
+ ApplySetLfpBound domain operator -> do
+ domain' <-
+ recheckStored context domain
+ operator' <-
+ recheckStored context operator
+ value <-
+ first KernelReplaySetLfpRuleError
+ (SetLfp.setLfpBound
+ checkedFoundationValue
+ globalType
+ domain'
+ operator')
+ pure
+ (ruleLeaf
+ depth
+ SetLfpBound
+ value)
+ ApplySetLfpLeast
+ domain
+ operator
+ candidate
+ closedPremise
+ boundedPremise -> do
+ domain' <-
+ recheckStored context domain
+ operator' <-
+ recheckStored context operator
+ candidate' <-
+ recheckStored context candidate
+ closedStep <-
+ replay
+ context
+ hypotheses
+ (depth + 1)
+ closedPremise
+ boundedStep <-
+ replay
+ context
+ hypotheses
+ (depth + 1)
+ boundedPremise
+ combineRule depth SetLfpLeast
+ (SetLfp.setLfpLeast
+ checkedFoundationValue
+ globalType
+ domain'
+ operator'
+ candidate'
+ (stepValue closedStep)
+ (stepValue boundedStep))
+ [closedStep, boundedStep]
+ ApplySetLfpFixed domain operator monotonePremise -> do
+ domain' <-
+ recheckStored context domain
+ operator' <-
+ recheckStored context operator
+ monotoneStep <-
+ replay
+ context
+ hypotheses
+ (depth + 1)
+ monotonePremise
+ combineRule depth SetLfpFixed
+ (SetLfp.setLfpFixed
+ checkedFoundationValue
+ globalType
+ domain'
+ operator'
+ (stepValue monotoneStep))
+ [monotoneStep]
+ ApplySetLfpInduct
+ domain
+ operator
+ predicate
+ element
+ monotonePremise
+ memberPremise
+ closurePremise -> do
+ domain' <-
+ recheckStored context domain
+ operator' <-
+ recheckStored context operator
+ predicate' <-
+ recheckStored context predicate
+ element' <-
+ recheckStored context element
+ monotoneStep <-
+ replay
+ context
+ hypotheses
+ (depth + 1)
+ monotonePremise
+ memberStep <-
+ replay
+ context
+ hypotheses
+ (depth + 1)
+ memberPremise
+ closureStep <-
+ replay
+ context
+ hypotheses
+ (depth + 1)
+ closurePremise
+ combineRule depth SetLfpInduct
+ (SetLfp.setLfpInduct
+ checkedFoundationValue
+ globalType
+ domain'
+ operator'
+ predicate'
+ element'
+ (stepValue monotoneStep)
+ (stepValue memberStep)
+ (stepValue closureStep))
+ [ monotoneStep
+ , memberStep
+ , closureStep
+ ]
+
+ lookupImport index@(ImportIx naturalIndex)
+ | naturalIndex
+ > fromIntegral (maxBound :: Int) =
+ Left
+ (KernelReplayImportOutOfBounds
+ index)
+ | otherwise =
+ maybe
+ (Left
+ (KernelReplayImportOutOfBounds
+ index))
+ Right
+ (imports
+ Vector.!?
+ (fromIntegral naturalIndex))
+
+ lookupHypothesis
+ index@(HypothesisIx naturalIndex)
+ hypotheses =
+ maybe
+ (Left
+ (KernelReplayHypothesisOutOfBounds
+ index))
+ Right
+ (atNatural naturalIndex hypotheses)
+
+ requireContext expected value
+ | scopedCoreContext value == expected =
+ Right ()
+ | otherwise =
+ Left
+ (KernelReplayStoredContextMismatch
+ expected
+ (scopedCoreContext value))
+
+ recheckStored expectedContext stored = do
+ requireContext expectedContext stored
+ checked <-
+ first KernelReplayStoredTermIllTyped
+ (checkScopedCanonicalCore
+ globalType
+ expectedContext
+ (scopedCoreTerm stored))
+ unless
+ (scopedCoreType checked
+ == scopedCoreType stored)
+ (Left
+ (KernelReplayStoredTypeMismatch
+ (scopedCoreType stored)
+ (scopedCoreType checked)))
+ pure checked
+
+ recheckClosed stored = do
+ checked <-
+ first KernelReplayStoredTermIllTyped
+ (checkCanonicalCore
+ globalType
+ (frozenCoreTerm stored))
+ unless
+ (frozenCoreType checked
+ == frozenCoreType stored)
+ (Left
+ (KernelReplayStoredTypeMismatch
+ (frozenCoreType stored)
+ (frozenCoreType checked)))
+ pure checked
+
+ replayNodeLimit =
+ case limits of
+ KernelReplayLimits nodeLimit _depthLimit ->
+ nodeLimit
+
+ replayDepthLimit =
+ case limits of
+ KernelReplayLimits _nodeLimit depthLimit ->
+ depthLimit
+
+leaf
+ :: Natural
+ -> ScopedCheckedCore global
+ -> Set ImportIx
+ -> Set FoundationAxiomTag
+ -> ReplayStep global
+leaf depth value importUses foundationUses =
+ ReplayStep
+ value
+ importUses
+ foundationUses
+ mempty
+ 1
+ depth
+
+ruleLeaf
+ :: Natural
+ -> KernelRuleTag
+ -> ScopedCheckedCore global
+ -> ReplayStep global
+ruleLeaf depth tag value =
+ ReplayStep
+ value
+ mempty
+ mempty
+ (Set.singleton tag)
+ 1
+ depth
+
+stepValue :: ReplayStep global -> ScopedCheckedCore global
+stepValue
+ (ReplayStep
+ value
+ _importUses
+ _foundationUses
+ _ruleUses
+ _nodeCount
+ _maximumDepth) =
+ value
+
+combine1
+ :: Natural
+ -> Either
+ Semantics.KernelSemanticsError
+ (ScopedCheckedCore global)
+ -> ReplayStep global
+ -> Either KernelReplayError (ReplayStep global)
+combine1 depth synthesized child = do
+ value <-
+ first KernelReplaySemanticsError synthesized
+ pure
+ (ReplayStep
+ value
+ (stepImportUses child)
+ (stepFoundationUses child)
+ (stepRuleUses child)
+ (1 + stepNodeCount child)
+ (max depth
+ (stepMaximumDepth child)))
+
+combine2
+ :: Natural
+ -> Either
+ Semantics.KernelSemanticsError
+ (ScopedCheckedCore global)
+ -> ReplayStep global
+ -> ReplayStep global
+ -> Either KernelReplayError (ReplayStep global)
+combine2 depth synthesized left right = do
+ value <-
+ first KernelReplaySemanticsError synthesized
+ pure
+ (ReplayStep
+ value
+ (stepImportUses left
+ <> stepImportUses right)
+ (stepFoundationUses left
+ <> stepFoundationUses right)
+ (stepRuleUses left
+ <> stepRuleUses right)
+ (1
+ + stepNodeCount left
+ + stepNodeCount right)
+ (maximum
+ [ depth
+ , stepMaximumDepth left
+ , stepMaximumDepth right
+ ]))
+
+combineRule
+ :: Natural
+ -> KernelRuleTag
+ -> Either
+ SetLfp.SetLfpRuleError
+ (ScopedCheckedCore global)
+ -> [ReplayStep global]
+ -> Either KernelReplayError (ReplayStep global)
+combineRule depth tag synthesized children = do
+ value <-
+ first KernelReplaySetLfpRuleError synthesized
+ pure
+ (ReplayStep
+ value
+ (foldMap stepImportUses children)
+ (foldMap stepFoundationUses children)
+ (Set.insert tag
+ (foldMap stepRuleUses children))
+ (1 + sum (stepNodeCount <$> children))
+ (maximum
+ (depth
+ : (stepMaximumDepth <$> children))))
+
+stepImportUses
+ :: ReplayStep global
+ -> Set ImportIx
+stepImportUses
+ (ReplayStep
+ _value
+ importUses
+ _foundationUses
+ _ruleUses
+ _nodeCount
+ _maximumDepth) =
+ importUses
+
+stepFoundationUses
+ :: ReplayStep global
+ -> Set FoundationAxiomTag
+stepFoundationUses
+ (ReplayStep
+ _value
+ _importUses
+ foundationUses
+ _ruleUses
+ _nodeCount
+ _maximumDepth) =
+ foundationUses
+
+stepRuleUses
+ :: ReplayStep global
+ -> Set KernelRuleTag
+stepRuleUses
+ (ReplayStep
+ _value
+ _importUses
+ _foundationUses
+ ruleUses
+ _nodeCount
+ _maximumDepth) =
+ ruleUses
+
+stepNodeCount :: ReplayStep global -> Natural
+stepNodeCount
+ (ReplayStep
+ _value
+ _importUses
+ _foundationUses
+ _ruleUses
+ nodeCount
+ _maximumDepth) =
+ nodeCount
+
+stepMaximumDepth :: ReplayStep global -> Natural
+stepMaximumDepth
+ (ReplayStep
+ _value
+ _importUses
+ _foundationUses
+ _ruleUses
+ _nodeCount
+ maximumDepth) =
+ maximumDepth
+
+atNatural :: Natural -> [a] -> Maybe a
+atNatural _index [] =
+ Nothing
+atNatural 0 (value : _rest) =
+ Just value
+atNatural index (_value : rest) =
+ atNatural (index - 1) rest
diff --git a/source/Felix/Checking/Kernel/Proof.hs b/source/Felix/Checking/Kernel/Proof.hs
new file mode 100644
index 0000000..d4ccedc
--- /dev/null
+++ b/source/Felix/Checking/Kernel/Proof.hs
@@ -0,0 +1,1443 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Small proof-producing natural-deduction combinators. Every resulting tree
+-- is still replayed independently before it can authorize a fact.
+module Felix.Checking.Kernel.Proof
+ ( ProofContext
+ , rootProofContext
+ , proofContextTypes
+ , scopedTerm
+ , BuiltProof
+ , builtProofStatement
+ , builtProofDerivation
+ , importedProof
+ , foundationProof
+ , hypothesisProof
+ , implicationEliminationProof
+ , implicationIntroductionProof
+ , forallEliminationProof
+ , forallIntroductionProof
+ , falsumEliminationProof
+ , equalityReflexivityProof
+ , equalityReverseProof
+ , equalityCongruenceApplicationProof
+ , equalityModusPonensProof
+ , conversionProof
+ , doubleNegationEliminationProof
+ , conjunctionTerm
+ , conjunctionIntroductionProof
+ , conjunctionLeftProof
+ , conjunctionRightProof
+ , disjunctionTerm
+ , disjunctionLeftProof
+ , disjunctionRightProof
+ , disjunctionEliminationProof
+ , validateCaseAnalysisComposition
+ , validateDoubleNegationComposition
+ , validateFalsumEliminationComposition
+ , validateSetInductionComposition
+ , existentialTerm
+ , existentialIntroductionProof
+ , existentialEliminationProof
+ , setLfpBoundProof
+ , setLfpFixedProof
+ , setLfpInductProof
+ , KernelProofBuildError(..)
+ ) where
+
+import Base
+import Felix.Checking.Core
+import Felix.Checking.Foundation
+import Felix.Checking.Kernel.Derivation
+import Felix.Checking.Kernel.Semantics qualified as Semantics
+import Felix.Checking.Kernel.SetLfp qualified as SetLfp
+
+import Control.Monad (unless)
+import Data.Bifunctor (first)
+import Data.List qualified as List
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Text qualified as Text
+import Numeric.Natural (Natural)
+
+
+data ProofContext global = ProofContext
+ !CheckedFoundation
+ !(global -> Maybe CoreType)
+ ![CoreType]
+ ![ScopedCheckedCore global]
+
+rootProofContext
+ :: CheckedFoundation
+ -> (global -> Maybe CoreType)
+ -> ProofContext global
+rootProofContext foundation globalType =
+ ProofContext foundation globalType [] []
+
+proofContextTypes
+ :: ProofContext global
+ -> [CoreType]
+proofContextTypes
+ (ProofContext
+ _foundation
+ _globalType
+ context
+ _hypotheses) =
+ context
+
+data BuiltProof global = BuiltProof
+ !(ScopedCheckedCore global)
+ !(KernelDerivation global)
+
+builtProofStatement
+ :: BuiltProof global
+ -> ScopedCheckedCore global
+builtProofStatement
+ (BuiltProof statement _derivation) =
+ statement
+
+builtProofDerivation
+ :: BuiltProof global
+ -> KernelDerivation global
+builtProofDerivation
+ (BuiltProof _statement derivation) =
+ derivation
+
+data KernelProofBuildError
+ = ProofTermIllTyped !CoreCheckError
+ | ProofSemanticsFailed
+ !Semantics.KernelSemanticsError
+ | ProofFoundationArgumentMismatch
+ !FoundationAxiomTag
+ | ProofHypothesisNotFound
+ | ProofExpectedEquality
+ | ProofExpectedUnaryBinder
+ | ProofSetLfpRuleFailed !Text
+ | ProofConversionPlanFailed !Text
+ | ProofStructuralCompositionMismatch !Text
+ deriving stock (Show, Eq)
+
+scopedTerm
+ :: ProofContext global
+ -> CanonicalTerm global
+ -> Either
+ KernelProofBuildError
+ (ScopedCheckedCore global)
+scopedTerm
+ (ProofContext
+ _foundation
+ globalType
+ context
+ _hypotheses) =
+ first ProofTermIllTyped
+ . checkScopedCanonicalCore
+ globalType
+ context
+
+importedProof
+ :: ProofContext global
+ -> ImportIx
+ -> FrozenCheckedCore global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+importedProof context index statement =
+ pure
+ (BuiltProof
+ (embedClosedCore
+ (proofContextTypes context)
+ statement)
+ (importedFactDerivation index))
+
+foundationProof
+ :: ProofContext global
+ -> FoundationAxiomTag
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+foundationProof
+ context@(ProofContext
+ foundation
+ _globalType
+ _types
+ _hypotheses)
+ tag =
+ pure
+ (BuiltProof
+ (embedClosedCore
+ (proofContextTypes context)
+ (mapFrozenGlobals
+ absurd
+ (foundationAxiomFrozen
+ foundation
+ tag)))
+ (foundationFactDerivation tag))
+
+hypothesisProof
+ :: Eq global
+ => ProofContext global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+hypothesisProof
+ (ProofContext
+ _foundation
+ _globalType
+ _context
+ hypotheses)
+ statement =
+ case List.findIndex (== statement) hypotheses of
+ Nothing ->
+ Left ProofHypothesisNotFound
+ Just index ->
+ pure
+ (BuiltProof
+ statement
+ (localHypothesisDerivation
+ (hypothesisIx
+ (fromIntegral index))))
+
+implicationEliminationProof
+ :: Eq global
+ => ProofContext global
+ -> BuiltProof global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+implicationEliminationProof
+ (ProofContext
+ _foundation
+ globalType
+ _context
+ _hypotheses)
+ implication
+ premise = do
+ conclusion <-
+ first ProofSemanticsFailed
+ (Semantics.implicationElimination
+ globalType
+ (builtProofStatement implication)
+ (builtProofStatement premise))
+ pure
+ (BuiltProof
+ conclusion
+ (implicationEliminationDerivation
+ (builtProofDerivation implication)
+ (builtProofDerivation premise)))
+
+implicationIntroductionProof
+ :: ProofContext global
+ -> ScopedCheckedCore global
+ -> ( ProofContext global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+ )
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+implicationIntroductionProof
+ (ProofContext
+ foundation
+ globalType
+ types
+ hypotheses)
+ premise
+ buildBody = do
+ let extended =
+ ProofContext
+ foundation
+ globalType
+ types
+ (premise : hypotheses)
+ premiseProof =
+ BuiltProof
+ premise
+ (localHypothesisDerivation
+ (hypothesisIx 0))
+ body <-
+ buildBody extended premiseProof
+ conclusion <-
+ first ProofSemanticsFailed
+ (Semantics.implicationIntroduction
+ globalType
+ premise
+ (builtProofStatement body))
+ pure
+ (BuiltProof
+ conclusion
+ (implicationIntroductionDerivation
+ premise
+ (builtProofDerivation body)))
+
+forallEliminationProof
+ :: ProofContext global
+ -> BuiltProof global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+forallEliminationProof
+ (ProofContext
+ _foundation
+ globalType
+ _types
+ _hypotheses)
+ quantified
+ argument = do
+ conclusion <-
+ first ProofSemanticsFailed
+ (Semantics.forallElimination
+ globalType
+ (builtProofStatement quantified)
+ argument)
+ pure
+ (BuiltProof
+ conclusion
+ (forallEliminationDerivation
+ (builtProofDerivation quantified)
+ argument))
+
+forallIntroductionProof
+ :: ProofContext global
+ -> CoreType
+ -> ( ProofContext global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+ )
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+forallIntroductionProof
+ (ProofContext
+ foundation
+ globalType
+ types
+ hypotheses)
+ binderType
+ buildBody = do
+ weakenedHypotheses <-
+ traverse
+ (first ProofTermIllTyped
+ . weakenScopedCore
+ globalType
+ binderType)
+ hypotheses
+ let extended =
+ ProofContext
+ foundation
+ globalType
+ (binderType : types)
+ weakenedHypotheses
+ variable <-
+ scopedTerm extended (CBound 0)
+ body <-
+ buildBody extended variable
+ conclusion <-
+ first ProofSemanticsFailed
+ (Semantics.forallIntroduction
+ globalType
+ binderType
+ (builtProofStatement body))
+ pure
+ (BuiltProof
+ conclusion
+ (forallIntroductionDerivation
+ binderType
+ (builtProofDerivation body)))
+
+falsumEliminationProof
+ :: ProofContext global
+ -> BuiltProof global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+falsumEliminationProof
+ (ProofContext
+ _foundation
+ globalType
+ _types
+ _hypotheses)
+ falsum
+ target = do
+ conclusion <-
+ first ProofSemanticsFailed
+ (Semantics.falsumElimination
+ globalType
+ (builtProofStatement falsum)
+ target)
+ pure
+ (BuiltProof
+ conclusion
+ (falsumEliminationDerivation
+ (builtProofDerivation falsum)
+ target))
+
+equalityReflexivityProof
+ :: ProofContext global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+equalityReflexivityProof
+ (ProofContext
+ _foundation
+ globalType
+ _types
+ _hypotheses)
+ operand = do
+ equality <-
+ first ProofSemanticsFailed
+ (Semantics.equalityReflexivity
+ globalType
+ operand)
+ pure
+ (BuiltProof
+ equality
+ (scopedEqualityReflexivityDerivation
+ operand))
+
+equalityCongruenceApplicationProof
+ :: ProofContext global
+ -> BuiltProof global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+equalityCongruenceApplicationProof
+ (ProofContext
+ _foundation
+ globalType
+ _types
+ _hypotheses)
+ functionEquality
+ argumentEquality = do
+ equality <-
+ first ProofSemanticsFailed
+ (Semantics.equalityCongruenceApplication
+ globalType
+ (builtProofStatement functionEquality)
+ (builtProofStatement argumentEquality))
+ pure
+ (BuiltProof
+ equality
+ (equalityCongruenceApplicationDerivation
+ (builtProofDerivation
+ functionEquality)
+ (builtProofDerivation
+ argumentEquality)))
+
+equalityModusPonensProof
+ :: Eq global
+ => ProofContext global
+ -> BuiltProof global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+equalityModusPonensProof
+ (ProofContext
+ _foundation
+ globalType
+ _types
+ _hypotheses)
+ equality
+ premise = do
+ conclusion <-
+ first ProofSemanticsFailed
+ (Semantics.equalityModusPonens
+ globalType
+ (builtProofStatement equality)
+ (builtProofStatement premise))
+ pure
+ (BuiltProof
+ conclusion
+ (equalityModusPonensDerivation
+ (builtProofDerivation equality)
+ (builtProofDerivation premise)))
+
+conversionProof
+ :: Eq global
+ => ProofContext global
+ -> BuiltProof global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+conversionProof
+ (ProofContext
+ _foundation
+ globalType
+ _types
+ _hypotheses)
+ source
+ target = do
+ plan <-
+ first (ProofConversionPlanFailed . Text.pack . show)
+ (conversionPlan 100000)
+ result <-
+ first ProofSemanticsFailed
+ (Semantics.convertJudgment
+ globalType
+ (conversionPlanBudget plan)
+ (builtProofStatement source)
+ target)
+ pure
+ (BuiltProof
+ result
+ (convertJudgmentDerivation
+ (builtProofDerivation source)
+ target
+ plan))
+
+equalityReverseProof
+ :: Eq global
+ => ProofContext global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+equalityReverseProof context equality = do
+ (operandType, left, right) <-
+ equalityParts
+ (builtProofStatement equality)
+ leftOperand <-
+ scopedTerm context left
+ weakenedLeft <-
+ first ProofTermIllTyped
+ (weakenScopedCore
+ (contextGlobalType context)
+ operandType
+ leftOperand)
+ function <-
+ scopedTerm context
+ (CLam operandType
+ (CEq
+ operandType
+ (CBound 0)
+ (scopedCoreTerm
+ weakenedLeft)))
+ functionReflexivity <-
+ equalityReflexivityProof
+ context
+ function
+ appliedEquality <-
+ equalityCongruenceApplicationProof
+ context
+ functionReflexivity
+ equality
+ leftReflexivity <-
+ equalityReflexivityProof
+ context
+ leftOperand
+ appliedLeft <-
+ scopedTerm context
+ (CApp
+ (scopedCoreTerm function)
+ left)
+ appliedLeftReflexivity <-
+ conversionProof
+ context
+ leftReflexivity
+ appliedLeft
+ reversedApplication <-
+ equalityModusPonensProof
+ context
+ appliedEquality
+ appliedLeftReflexivity
+ expected <-
+ scopedTerm context
+ (CEq operandType right left)
+ conversionProof
+ context
+ reversedApplication
+ expected
+
+doubleNegationEliminationProof
+ :: Eq global
+ => ProofContext global
+ -> ScopedCheckedCore global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+doubleNegationEliminationProof
+ context
+ target
+ doubleNegation = do
+ axiom <-
+ foundationProof
+ context
+ DoubleNegationElim
+ instanceProof <-
+ forallEliminationProof
+ context
+ axiom
+ target
+ implicationEliminationProof
+ context
+ instanceProof
+ doubleNegation
+
+conjunctionTerm
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+conjunctionTerm left right =
+ CImp
+ (CImp left
+ (CImp right CFalsum))
+ CFalsum
+
+conjunctionIntroductionProof
+ :: Eq global
+ => ProofContext global
+ -> BuiltProof global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+conjunctionIntroductionProof
+ context
+ left
+ right = do
+ let leftTerm =
+ scopedCoreTerm
+ (builtProofStatement left)
+ rightTerm =
+ scopedCoreTerm
+ (builtProofStatement right)
+ refuter <-
+ scopedTerm context
+ (CImp leftTerm
+ (CImp rightTerm CFalsum))
+ implicationIntroductionProof
+ context
+ refuter
+ (\extended refuterProof -> do
+ firstApplication <-
+ implicationEliminationProof
+ extended
+ refuterProof
+ (weakenForHypothesis left)
+ implicationEliminationProof
+ extended
+ firstApplication
+ (weakenForHypothesis right))
+
+conjunctionLeftProof
+ :: Eq global
+ => ProofContext global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+conjunctionLeftProof context left right conjunction = do
+ leftProposition <-
+ scopedTerm context left
+ notLeft <-
+ scopedTerm context
+ (CImp left CFalsum)
+ doubleNegation <-
+ implicationIntroductionProof
+ context
+ notLeft
+ (\withNotLeft _notLeftProof -> do
+ leftAtRefuter <-
+ scopedTerm withNotLeft left
+ refuterProof <-
+ implicationIntroductionProof
+ withNotLeft
+ leftAtRefuter
+ (\withLeft leftProof -> do
+ rightProposition <-
+ scopedTerm withLeft right
+ implicationIntroductionProof
+ withLeft
+ rightProposition
+ (\withBoth _rightProof -> do
+ notLeftCurrent <-
+ hypothesisProof
+ withBoth
+ notLeft
+ implicationEliminationProof
+ withBoth
+ notLeftCurrent
+ (weakenForHypothesis
+ leftProof)))
+ implicationEliminationProof
+ withNotLeft
+ (weakenForHypothesis conjunction)
+ refuterProof)
+ doubleNegationEliminationProof
+ context
+ leftProposition
+ doubleNegation
+
+conjunctionRightProof
+ :: Eq global
+ => ProofContext global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+conjunctionRightProof context left right conjunction = do
+ rightProposition <-
+ scopedTerm context right
+ notRight <-
+ scopedTerm context
+ (CImp right CFalsum)
+ doubleNegation <-
+ implicationIntroductionProof
+ context
+ notRight
+ (\withNotRight _notRightProof -> do
+ leftAtRefuter <-
+ scopedTerm withNotRight left
+ refuterProof <-
+ implicationIntroductionProof
+ withNotRight
+ leftAtRefuter
+ (\withLeft _leftProof -> do
+ rightAtLeft <-
+ scopedTerm
+ withLeft
+ right
+ implicationIntroductionProof
+ withLeft
+ rightAtLeft
+ (\withBoth rightProof -> do
+ notRightCurrent <-
+ hypothesisProof
+ withBoth
+ notRight
+ implicationEliminationProof
+ withBoth
+ notRightCurrent
+ rightProof))
+ implicationEliminationProof
+ withNotRight
+ (weakenForHypothesis conjunction)
+ refuterProof)
+ doubleNegationEliminationProof
+ context
+ rightProposition
+ doubleNegation
+
+disjunctionTerm
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+disjunctionTerm left right =
+ CImp
+ (CImp left CFalsum)
+ right
+
+disjunctionLeftProof
+ :: Eq global
+ => ProofContext global
+ -> CanonicalTerm global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+disjunctionLeftProof context right leftProof = do
+ notLeft <-
+ scopedTerm context
+ (CImp
+ (scopedCoreTerm
+ (builtProofStatement leftProof))
+ CFalsum)
+ rightTarget <-
+ scopedTerm context right
+ implicationIntroductionProof
+ context
+ notLeft
+ (\extended notLeftProof -> do
+ falsum <-
+ implicationEliminationProof
+ extended
+ notLeftProof
+ (weakenForHypothesis
+ leftProof)
+ falsumEliminationProof
+ extended
+ falsum
+ rightTarget)
+
+disjunctionRightProof
+ :: ProofContext global
+ -> CanonicalTerm global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+disjunctionRightProof context left rightProof = do
+ notLeft <-
+ scopedTerm context
+ (CImp left CFalsum)
+ implicationIntroductionProof
+ context
+ notLeft
+ (\_extended _notLeftProof ->
+ pure
+ (weakenForHypothesis
+ rightProof))
+
+disjunctionEliminationProof
+ :: Eq global
+ => ProofContext global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+ -> BuiltProof global
+ -> ScopedCheckedCore global
+ -> ( ProofContext global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+ )
+ -> ( ProofContext global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+ )
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+disjunctionEliminationProof
+ context
+ left
+ right
+ disjunction
+ result
+ leftCase
+ rightCase = do
+ leftProposition <-
+ scopedTerm context left
+ rightProposition <-
+ scopedTerm context right
+ leftImplication <-
+ implicationIntroductionProof
+ context
+ leftProposition
+ leftCase
+ rightImplication <-
+ implicationIntroductionProof
+ context
+ rightProposition
+ rightCase
+ notResult <-
+ scopedTerm context
+ (CImp
+ (scopedCoreTerm result)
+ CFalsum)
+ doubleNegation <-
+ implicationIntroductionProof
+ context
+ notResult
+ (\extended notResultProof -> do
+ leftAtNotResult <-
+ scopedTerm extended left
+ notLeftProof <-
+ implicationIntroductionProof
+ extended
+ leftAtNotResult
+ (\withLeft leftProof -> do
+ resultProof <-
+ implicationEliminationProof
+ withLeft
+ (weakenForHypothesis
+ (weakenForHypothesis
+ leftImplication))
+ leftProof
+ implicationEliminationProof
+ withLeft
+ (weakenForHypothesis
+ notResultProof)
+ resultProof)
+ rightProof <-
+ implicationEliminationProof
+ extended
+ (weakenForHypothesis
+ disjunction)
+ notLeftProof
+ resultProof <-
+ implicationEliminationProof
+ extended
+ (weakenForHypothesis
+ rightImplication)
+ rightProof
+ implicationEliminationProof
+ extended
+ notResultProof
+ resultProof)
+ doubleNegationEliminationProof
+ context
+ result
+ doubleNegation
+
+-- | Validate the one structural rule used by exact source case analysis.
+-- The branch proofs and the exhaustive disjunction are represented here by
+-- exact hypotheses; the kernel combinators must derive the owned goal from
+-- precisely those propositions. No derived proof escapes this check.
+validateCaseAnalysisComposition
+ :: Eq global
+ => CheckedFoundation
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> NonEmpty (ScopedCheckedCore global)
+ -> ScopedCheckedCore global
+ -> Either KernelProofBuildError ()
+validateCaseAnalysisComposition
+ foundation globalType goal cases exhaustive = do
+ validatePropositionContext "case goal" lexicalContext goal
+ traverse_
+ (validatePropositionContext "case assumption" lexicalContext)
+ cases
+ validatePropositionContext
+ "case exhaustiveness target" lexicalContext exhaustive
+ let expectedExhaustive =
+ foldl1
+ (\left right ->
+ CImp
+ (CImp left CFalsum)
+ right)
+ (scopedCoreTerm <$> cases)
+ unless (scopedCoreTerm exhaustive == expectedExhaustive)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "case exhaustiveness is not the source-ordered disjunction"))
+ branchImplications <-
+ traverse
+ (checkedImplication lexicalContext goal)
+ cases
+ let context =
+ ProofContext
+ foundation
+ globalType
+ lexicalContext
+ (exhaustive : toList branchImplications)
+ exhaustiveProof <- hypothesisProof context exhaustive
+ result <-
+ eliminateCases
+ context goal cases exhaustiveProof
+ unless (builtProofStatement result == goal)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "case elimination did not derive the owned goal"))
+ where
+ lexicalContext = scopedCoreContext goal
+
+ checkedImplication expectedContext conclusion antecedent =
+ case implyScopedCore antecedent conclusion of
+ Just implication
+ | scopedCoreContext implication == expectedContext ->
+ pure implication
+ _ ->
+ Left
+ (ProofStructuralCompositionMismatch
+ "case branch implication changed context")
+
+ eliminateCases context result (only :| []) caseProof = do
+ branchImplication <-
+ scopedTerm context
+ (CImp
+ (scopedCoreTerm only)
+ (scopedCoreTerm result))
+ >>= hypothesisProof context
+ implicationEliminationProof context branchImplication caseProof
+ eliminateCases context result (firstCase :| rest) disjunctionProof = do
+ let allCases = firstCase :| rest
+ leftCases = NonEmpty.fromList (NonEmpty.init allCases)
+ rightCase = NonEmpty.last allCases
+ leftTerm =
+ foldl1 disjunctionTerm
+ (scopedCoreTerm <$> leftCases)
+ disjunctionEliminationProof
+ context
+ leftTerm
+ (scopedCoreTerm rightCase)
+ disjunctionProof
+ result
+ (\extended leftProof ->
+ eliminateCases extended result leftCases leftProof)
+ (\extended rightProof -> do
+ branchImplication <-
+ scopedTerm extended
+ (CImp
+ (scopedCoreTerm rightCase)
+ (scopedCoreTerm result))
+ >>= hypothesisProof extended
+ implicationEliminationProof
+ extended branchImplication rightProof)
+
+-- | Validate the exact classical closing step for a proof by contradiction.
+-- The only classical input is the confined 'DoubleNegationElim' foundation
+-- row already consumed by 'doubleNegationEliminationProof'.
+validateDoubleNegationComposition
+ :: Eq global
+ => CheckedFoundation
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either KernelProofBuildError ()
+validateDoubleNegationComposition
+ foundation globalType goal negation falsum = do
+ let lexicalContext = scopedCoreContext goal
+ validatePropositionContext "contradiction goal" lexicalContext goal
+ validatePropositionContext
+ "contradiction negation" lexicalContext negation
+ validatePropositionContext "contradiction falsum" lexicalContext falsum
+ unless (scopedCoreTerm falsum == CFalsum)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "proof by contradiction did not target falsum"))
+ expectedNegation <-
+ checkedNegation lexicalContext goal
+ unless (negation == expectedNegation)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "proof by contradiction did not own the exact negated goal"))
+ doubleNegation <-
+ checkedNegation lexicalContext negation
+ let context =
+ ProofContext
+ foundation globalType lexicalContext [doubleNegation]
+ hypothesis <- hypothesisProof context doubleNegation
+ result <- doubleNegationEliminationProof context goal hypothesis
+ unless (builtProofStatement result == goal)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "double-negation elimination did not derive the owned goal"))
+
+-- | Validate the exact ex-falso closing step used after a terminal indirect
+-- contradiction discharge.
+validateFalsumEliminationComposition
+ :: Eq global
+ => CheckedFoundation
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either KernelProofBuildError ()
+validateFalsumEliminationComposition foundation globalType goal falsum = do
+ let lexicalContext = scopedCoreContext goal
+ validatePropositionContext "contradiction goal" lexicalContext goal
+ validatePropositionContext "contradiction falsum" lexicalContext falsum
+ unless (scopedCoreTerm falsum == CFalsum)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "falsum elimination did not receive falsum"))
+ let context =
+ ProofContext foundation globalType lexicalContext [falsum]
+ hypothesis <- hypothesisProof context falsum
+ result <- falsumEliminationProof context hypothesis goal
+ unless (builtProofStatement result == goal)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "falsum elimination did not derive the owned goal"))
+
+-- | Validate the exact structural instance used by source set induction.
+-- The admitted child is represented by its generalized step proposition;
+-- the checked foundation row must specialize to that exact premise and the
+-- owned binder-level result. No induction principle becomes an ATP premise.
+validateSetInductionComposition
+ :: Eq global
+ => CheckedFoundation
+ -> (global -> Maybe CoreType)
+ -> Natural
+ -> ScopedCheckedCore global
+ -> [ScopedCheckedCore global]
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either KernelProofBuildError ()
+validateSetInductionComposition
+ foundation globalType selected property antecedents childTarget
+ hypothesis result = do
+ let lexicalContext = scopedCoreContext property
+ traverse_
+ (validatePropositionContext
+ "set-induction antecedent" lexicalContext)
+ antecedents
+ validatePropositionContext
+ "set-induction property" lexicalContext property
+ validatePropositionContext
+ "set-induction child target" lexicalContext childTarget
+ validatePropositionContext
+ "set-induction hypothesis" lexicalContext hypothesis
+ validatePropositionContext
+ "set-induction result" lexicalContext result
+ expectedProperty <-
+ foldrM implyChecked childTarget antecedents
+ unless (property == expectedProperty)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "set-induction property does not own the child target and guards"))
+ (predicate, expectedHypothesis, step, expectedResult) <-
+ maybe
+ (Left
+ (ProofStructuralCompositionMismatch
+ "set-induction focus is not a set-valued ambient binder"))
+ pure
+ (scopedSetInductionInstance selected property)
+ unless (hypothesis == expectedHypothesis)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "set-induction hypothesis does not match the owned property"))
+ unless (result == expectedResult)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "set-induction result does not close the owned property"))
+ let context =
+ ProofContext foundation globalType lexicalContext [step]
+ stepProof <- hypothesisProof context step
+ axiom <- foundationProof context SetInduction
+ instanceProof <- forallEliminationProof context axiom predicate
+ expectedInstance <-
+ maybe
+ (Left
+ (ProofStructuralCompositionMismatch
+ "set-induction instance changed lexical context"))
+ pure
+ (implyScopedCore step result)
+ convertedInstance <-
+ conversionProof context instanceProof expectedInstance
+ resultProof <-
+ implicationEliminationProof context convertedInstance stepProof
+ unless (builtProofStatement resultProof == result)
+ (Left
+ (ProofStructuralCompositionMismatch
+ "set-induction foundation instance did not derive the owned result"))
+ where
+ implyChecked antecedent conclusion =
+ maybe
+ (Left
+ (ProofStructuralCompositionMismatch
+ "set-induction guard changed lexical context"))
+ pure
+ (implyScopedCore antecedent conclusion)
+
+validatePropositionContext
+ :: Text
+ -> [CoreType]
+ -> ScopedCheckedCore global
+ -> Either KernelProofBuildError ()
+validatePropositionContext label expected proposition =
+ unless
+ ( scopedCoreType proposition == TyProp
+ && scopedCoreContext proposition == expected
+ )
+ (Left
+ (ProofStructuralCompositionMismatch
+ (label <> " has the wrong type or lexical context")))
+
+checkedNegation
+ :: [CoreType]
+ -> ScopedCheckedCore global
+ -> Either KernelProofBuildError (ScopedCheckedCore global)
+checkedNegation expectedContext proposition =
+ case negateScopedCore proposition of
+ Just negation
+ | scopedCoreContext negation == expectedContext ->
+ pure negation
+ _ ->
+ Left
+ (ProofStructuralCompositionMismatch
+ "classical negation changed context")
+
+existentialTerm
+ :: CoreType
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+existentialTerm binderType body =
+ CImp
+ (CForall binderType
+ (CImp body CFalsum))
+ CFalsum
+
+existentialIntroductionProof
+ :: Eq global
+ => ProofContext global
+ -> CoreType
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+existentialIntroductionProof
+ context
+ binderType
+ bodyUnderBinder
+ witness
+ bodyAtWitness = do
+ unless
+ (proofContextTypes context
+ == drop 1
+ (scopedCoreContext
+ bodyUnderBinder))
+ (Left ProofExpectedUnaryBinder)
+ universalNegation <-
+ scopedTerm context
+ (CForall binderType
+ (CImp
+ (scopedCoreTerm
+ bodyUnderBinder)
+ CFalsum))
+ implicationIntroductionProof
+ context
+ universalNegation
+ (\extended universalProof -> do
+ negatedBody <-
+ forallEliminationProof
+ extended
+ universalProof
+ witness
+ implicationEliminationProof
+ extended
+ negatedBody
+ (weakenForHypothesis
+ bodyAtWitness))
+
+existentialEliminationProof
+ :: Eq global
+ => ProofContext global
+ -> CoreType
+ -> ScopedCheckedCore global
+ -> BuiltProof global
+ -> ScopedCheckedCore global
+ -> ( ProofContext global
+ -> ScopedCheckedCore global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+ )
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+existentialEliminationProof
+ context
+ binderType
+ bodyUnderBinder
+ existential
+ result
+ bodyCase = do
+ notResult <-
+ scopedTerm context
+ (CImp
+ (scopedCoreTerm result)
+ CFalsum)
+ notResultUnderBinder <-
+ first ProofTermIllTyped
+ (weakenScopedCore
+ (contextGlobalType context)
+ binderType
+ notResult)
+ doubleNegation <-
+ implicationIntroductionProof
+ context
+ notResult
+ (\withNotResult _notResultProof -> do
+ universalNegation <-
+ forallIntroductionProof
+ withNotResult
+ binderType
+ (\withBinder variable -> do
+ let body =
+ bodyUnderBinder
+ implicationIntroductionProof
+ withBinder
+ body
+ (\withBody bodyProof -> do
+ resultProof <-
+ bodyCase
+ withBody
+ variable
+ bodyProof
+ notResultCurrent <-
+ hypothesisProof
+ withBody
+ notResultUnderBinder
+ implicationEliminationProof
+ withBody
+ notResultCurrent
+ resultProof))
+ implicationEliminationProof
+ withNotResult
+ (weakenForHypothesis
+ existential)
+ universalNegation)
+ doubleNegationEliminationProof
+ context
+ result
+ doubleNegation
+
+setLfpBoundProof
+ :: ProofContext global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+setLfpBoundProof
+ (ProofContext
+ foundation
+ globalType
+ _types
+ _hypotheses)
+ domain
+ operator = do
+ result <-
+ first (ProofSetLfpRuleFailed . Text.pack . show)
+ (SetLfp.setLfpBound
+ foundation
+ globalType
+ domain
+ operator)
+ pure
+ (BuiltProof
+ result
+ (setLfpBoundDerivation
+ domain
+ operator))
+
+setLfpFixedProof
+ :: Eq global
+ => ProofContext global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+setLfpFixedProof
+ (ProofContext
+ foundation
+ globalType
+ _types
+ _hypotheses)
+ domain
+ operator
+ monotone = do
+ result <-
+ first (ProofSetLfpRuleFailed . Text.pack . show)
+ (SetLfp.setLfpFixed
+ foundation
+ globalType
+ domain
+ operator
+ (builtProofStatement
+ monotone))
+ pure
+ (BuiltProof
+ result
+ (setLfpFixedDerivation
+ domain
+ operator
+ (builtProofDerivation
+ monotone)))
+
+setLfpInductProof
+ :: Eq global
+ => ProofContext global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> BuiltProof global
+ -> BuiltProof global
+ -> BuiltProof global
+ -> Either
+ KernelProofBuildError
+ (BuiltProof global)
+setLfpInductProof
+ (ProofContext
+ foundation
+ globalType
+ _types
+ _hypotheses)
+ domain
+ operator
+ predicate
+ element
+ monotone
+ member
+ closure = do
+ result <-
+ first (ProofSetLfpRuleFailed . Text.pack . show)
+ (SetLfp.setLfpInduct
+ foundation
+ globalType
+ domain
+ operator
+ predicate
+ element
+ (builtProofStatement
+ monotone)
+ (builtProofStatement
+ member)
+ (builtProofStatement
+ closure))
+ pure
+ (BuiltProof
+ result
+ (setLfpInductDerivation
+ domain
+ operator
+ predicate
+ element
+ (builtProofDerivation
+ monotone)
+ (builtProofDerivation
+ member)
+ (builtProofDerivation
+ closure)))
+
+contextGlobalType
+ :: ProofContext global
+ -> (global -> Maybe CoreType)
+contextGlobalType
+ (ProofContext
+ _foundation
+ globalType
+ _types
+ _hypotheses) =
+ globalType
+
+equalityParts
+ :: ScopedCheckedCore global
+ -> Either
+ KernelProofBuildError
+ ( CoreType
+ , CanonicalTerm global
+ , CanonicalTerm global
+ )
+equalityParts equality =
+ case scopedCoreTerm equality of
+ CEq operandType left right ->
+ Right (operandType, left, right)
+ _ ->
+ Left ProofExpectedEquality
+
+weakenForHypothesis
+ :: BuiltProof global
+ -> BuiltProof global
+weakenForHypothesis
+ (BuiltProof statement derivation) =
+ BuiltProof
+ statement
+ (weakenDerivationHypotheses 1 derivation)
diff --git a/source/Felix/Checking/Kernel/Semantics.hs b/source/Felix/Checking/Kernel/Semantics.hs
new file mode 100644
index 0000000..f9cfcfa
--- /dev/null
+++ b/source/Felix/Checking/Kernel/Semantics.hs
@@ -0,0 +1,436 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Checked logical inference over scoped canonical HOL terms.
+module Felix.Checking.Kernel.Semantics
+ ( implicationElimination
+ , implicationIntroduction
+ , forallElimination
+ , forallIntroduction
+ , falsumElimination
+ , equalityReflexivity
+ , equalityCongruenceApplication
+ , equalityCongruenceLambda
+ , equalityModusPonens
+ , convertJudgment
+ , KernelSemanticsError(..)
+ ) where
+
+import Base
+import Felix.Checking.Core
+
+import Control.Monad (unless)
+import Data.Bifunctor (first)
+import Numeric.Natural (Natural)
+
+
+data KernelSemanticsError
+ = KernelContextMismatch
+ ![CoreType]
+ ![CoreType]
+ | KernelBinderContextMismatch
+ !CoreType
+ ![CoreType]
+ | KernelExpectedProposition !CoreType
+ | KernelExpectedImplication
+ | KernelImplicationPremiseMismatch
+ | KernelExpectedForall
+ | KernelForallArgumentTypeMismatch
+ !CoreType
+ !CoreType
+ | KernelExpectedFalsum
+ | KernelExpectedEquality
+ | KernelEqualityOperandTypeMismatch
+ !CoreType
+ !CoreType
+ | KernelExpectedFunctionEquality !CoreType
+ | KernelEqualityPremiseMismatch
+ | KernelConversionBudgetExhausted
+ | KernelConversionMismatch
+ | KernelConclusionIllTyped !CoreCheckError
+ deriving stock (Show, Eq)
+
+implicationElimination
+ :: Eq global
+ => (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+implicationElimination globalType implication premise = do
+ requireSameContext implication premise
+ requireProposition implication
+ requireProposition premise
+ case scopedCoreTerm implication of
+ CImp expected conclusion
+ | scopedCoreTerm premise == expected ->
+ checkConclusion
+ globalType
+ (scopedCoreContext implication)
+ conclusion
+ | otherwise ->
+ Left KernelImplicationPremiseMismatch
+ _ ->
+ Left KernelExpectedImplication
+
+implicationIntroduction
+ :: (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+implicationIntroduction globalType premise conclusion = do
+ requireSameContext premise conclusion
+ requireProposition premise
+ requireProposition conclusion
+ checkConclusion
+ globalType
+ (scopedCoreContext premise)
+ (CImp
+ (scopedCoreTerm premise)
+ (scopedCoreTerm conclusion))
+
+forallElimination
+ :: (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+forallElimination globalType quantified argument = do
+ requireSameContext quantified argument
+ requireProposition quantified
+ case scopedCoreTerm quantified of
+ CForall binderType body
+ | scopedCoreType argument == binderType ->
+ checkConclusion
+ globalType
+ (scopedCoreContext quantified)
+ (instantiateCanonical
+ (scopedCoreTerm argument)
+ body)
+ | otherwise ->
+ Left
+ (KernelForallArgumentTypeMismatch
+ binderType
+ (scopedCoreType argument))
+ _ ->
+ Left KernelExpectedForall
+
+forallIntroduction
+ :: (global -> Maybe CoreType)
+ -> CoreType
+ -> ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+forallIntroduction globalType binderType body = do
+ requireProposition body
+ outerContext <-
+ case scopedCoreContext body of
+ actualBinder : context
+ | actualBinder == binderType ->
+ Right context
+ | otherwise ->
+ Left
+ (KernelBinderContextMismatch
+ binderType
+ (scopedCoreContext body))
+ [] ->
+ Left
+ (KernelBinderContextMismatch
+ binderType
+ [])
+ checkConclusion
+ globalType
+ outerContext
+ (CForall
+ binderType
+ (scopedCoreTerm body))
+
+falsumElimination
+ :: (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+falsumElimination _globalType falsum target = do
+ requireSameContext falsum target
+ requireProposition falsum
+ requireProposition target
+ case scopedCoreTerm falsum of
+ CFalsum ->
+ Right target
+ _ ->
+ Left KernelExpectedFalsum
+
+equalityReflexivity
+ :: (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+equalityReflexivity globalType operand =
+ checkConclusion
+ globalType
+ (scopedCoreContext operand)
+ (CEq
+ (scopedCoreType operand)
+ (scopedCoreTerm operand)
+ (scopedCoreTerm operand))
+
+equalityCongruenceApplication
+ :: (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+equalityCongruenceApplication
+ globalType
+ functionEquality
+ argumentEquality = do
+ requireSameContext functionEquality argumentEquality
+ (functionType, leftFunction, rightFunction) <-
+ equalityOperands functionEquality
+ (argumentType, leftArgument, rightArgument) <-
+ equalityOperands argumentEquality
+ case functionType of
+ TyArrow expectedArgument resultType
+ | expectedArgument == argumentType ->
+ checkConclusion
+ globalType
+ (scopedCoreContext functionEquality)
+ (CEq
+ resultType
+ (CApp leftFunction leftArgument)
+ (CApp rightFunction rightArgument))
+ | otherwise ->
+ Left
+ (KernelEqualityOperandTypeMismatch
+ expectedArgument
+ argumentType)
+ other ->
+ Left (KernelExpectedFunctionEquality other)
+
+equalityCongruenceLambda
+ :: (global -> Maybe CoreType)
+ -> CoreType
+ -> ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+equalityCongruenceLambda
+ globalType
+ binderType
+ bodyEquality = do
+ (bodyType, leftBody, rightBody) <-
+ equalityOperands bodyEquality
+ outerContext <-
+ case scopedCoreContext bodyEquality of
+ actualBinder : context
+ | actualBinder == binderType ->
+ Right context
+ | otherwise ->
+ Left
+ (KernelBinderContextMismatch
+ binderType
+ (scopedCoreContext bodyEquality))
+ [] ->
+ Left
+ (KernelBinderContextMismatch
+ binderType
+ [])
+ checkConclusion
+ globalType
+ outerContext
+ (CEq
+ (TyArrow binderType bodyType)
+ (CLam binderType leftBody)
+ (CLam binderType rightBody))
+
+equalityModusPonens
+ :: Eq global
+ => (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+equalityModusPonens globalType propositionEquality premise = do
+ requireSameContext propositionEquality premise
+ requireProposition premise
+ (operandType, left, right) <-
+ equalityOperands propositionEquality
+ unless
+ (operandType == TyProp)
+ (Left
+ (KernelEqualityOperandTypeMismatch
+ TyProp
+ operandType))
+ unless
+ (left == scopedCoreTerm premise)
+ (Left KernelEqualityPremiseMismatch)
+ checkConclusion
+ globalType
+ (scopedCoreContext premise)
+ right
+
+-- | Recheck a displayed proposition and independently establish beta
+-- conversion within the supplied contraction budget.
+convertJudgment
+ :: Eq global
+ => (global -> Maybe CoreType)
+ -> Natural
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+convertJudgment globalType budget source target = do
+ requireSameContext source target
+ requireProposition source
+ requireProposition target
+ sourceNormal <-
+ normalizeCanonical budget
+ (scopedCoreTerm source)
+ targetNormal <-
+ normalizeCanonical budget
+ (scopedCoreTerm target)
+ unless
+ (sourceNormal == targetNormal)
+ (Left KernelConversionMismatch)
+ checkConclusion
+ globalType
+ (scopedCoreContext target)
+ (scopedCoreTerm target)
+
+requireSameContext
+ :: ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either KernelSemanticsError ()
+requireSameContext left right
+ | scopedCoreContext left
+ == scopedCoreContext right =
+ Right ()
+ | otherwise =
+ Left
+ (KernelContextMismatch
+ (scopedCoreContext left)
+ (scopedCoreContext right))
+
+requireProposition
+ :: ScopedCheckedCore global
+ -> Either KernelSemanticsError ()
+requireProposition value
+ | scopedCoreType value == TyProp =
+ Right ()
+ | otherwise =
+ Left
+ (KernelExpectedProposition
+ (scopedCoreType value))
+
+equalityOperands
+ :: ScopedCheckedCore global
+ -> Either
+ KernelSemanticsError
+ (CoreType, CanonicalTerm global, CanonicalTerm global)
+equalityOperands equality = do
+ requireProposition equality
+ case scopedCoreTerm equality of
+ CEq operandType left right ->
+ Right (operandType, left, right)
+ _ ->
+ Left KernelExpectedEquality
+
+checkConclusion
+ :: (global -> Maybe CoreType)
+ -> [CoreType]
+ -> CanonicalTerm global
+ -> Either
+ KernelSemanticsError
+ (ScopedCheckedCore global)
+checkConclusion globalType context =
+ first KernelConclusionIllTyped
+ . checkScopedCanonicalCore globalType context
+
+normalizeCanonical
+ :: Natural
+ -> CanonicalTerm global
+ -> Either
+ KernelSemanticsError
+ (CanonicalTerm global)
+normalizeCanonical budget term =
+ fst <$> normalize budget term
+ where
+ normalize remaining = \case
+ CBound index ->
+ pure (CBound index, remaining)
+ CGlobal global ->
+ pure (CGlobal global, remaining)
+ CIntrinsic intrinsic ->
+ pure (CIntrinsic intrinsic, remaining)
+ COpaqueInteger integer ->
+ pure (COpaqueInteger integer, remaining)
+ CApp function argument -> do
+ (functionNormal, afterFunction) <-
+ normalize remaining function
+ case functionNormal of
+ CLam _binderType body -> do
+ afterContraction <-
+ consumeReduction afterFunction
+ normalize
+ afterContraction
+ (instantiateCanonical
+ argument
+ body)
+ _ -> do
+ (argumentNormal, afterArgument) <-
+ normalize afterFunction argument
+ pure
+ ( CApp
+ functionNormal
+ argumentNormal
+ , afterArgument
+ )
+ CLam binderType body -> do
+ (bodyNormal, remaining') <-
+ normalize remaining body
+ pure
+ (CLam binderType bodyNormal, remaining')
+ CFalsum ->
+ pure (CFalsum, remaining)
+ CImp premise conclusion -> do
+ (premiseNormal, afterPremise) <-
+ normalize remaining premise
+ (conclusionNormal, afterConclusion) <-
+ normalize afterPremise conclusion
+ pure
+ ( CImp premiseNormal conclusionNormal
+ , afterConclusion
+ )
+ CEq operandType left right -> do
+ (leftNormal, afterLeft) <-
+ normalize remaining left
+ (rightNormal, afterRight) <-
+ normalize afterLeft right
+ pure
+ ( CEq
+ operandType
+ leftNormal
+ rightNormal
+ , afterRight
+ )
+ CForall binderType body -> do
+ (bodyNormal, remaining') <-
+ normalize remaining body
+ pure
+ (CForall binderType bodyNormal, remaining')
+
+ consumeReduction 0 =
+ Left KernelConversionBudgetExhausted
+ consumeReduction remaining =
+ Right (remaining - 1)
diff --git a/source/Felix/Checking/Kernel/SetLfp.hs b/source/Felix/Checking/Kernel/SetLfp.hs
new file mode 100644
index 0000000..19f6714
--- /dev/null
+++ b/source/Felix/Checking/Kernel/SetLfp.hs
@@ -0,0 +1,762 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | The four checked rules for the bounded set-valued least fixed point.
+module Felix.Checking.Kernel.SetLfp
+ ( setLfpBound
+ , setLfpLeast
+ , setLfpFixed
+ , setLfpInduct
+ , setLfpTerm
+ , memberProposition
+ , subsetProposition
+ , boundedMonoProposition
+ , inductionClosureProposition
+ , SetLfpRuleError(..)
+ ) where
+
+import Base
+import Felix.Checking.Core
+import Felix.Checking.Foundation
+
+import Data.Bifunctor (first)
+import Numeric.Natural (Natural)
+
+
+data SetLfpRuleError
+ = SetLfpRuleSignatureMismatch
+ !KernelRuleTag
+ !KernelRuleSignature
+ !KernelRuleSignature
+ | SetLfpRuleContextMismatch
+ !KernelRuleTag
+ ![CoreType]
+ ![CoreType]
+ | SetLfpRuleArgumentTypeMismatch
+ !KernelRuleTag
+ !Natural
+ !CoreType
+ !CoreType
+ | SetLfpRulePremiseIsNotProposition
+ !KernelRuleTag
+ !Natural
+ !CoreType
+ | SetLfpRulePremiseMismatch
+ !KernelRuleTag
+ !Natural
+ | SetLfpRuleConstructionIllTyped
+ !KernelRuleTag
+ !CoreCheckError
+ deriving stock (Show, Eq)
+
+setLfpBound
+ :: CheckedFoundation
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+setLfpBound foundation globalType domain operator = do
+ validateApplication
+ foundation
+ SetLfpBound
+ [domain, operator]
+ []
+ fixedPoint <-
+ setLfpTermFor
+ SetLfpBound
+ globalType
+ domain
+ operator
+ subsetFor
+ SetLfpBound
+ globalType
+ fixedPoint
+ domain
+
+setLfpLeast
+ :: Eq global
+ => CheckedFoundation
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+setLfpLeast
+ foundation
+ globalType
+ domain
+ operator
+ candidate
+ closedPremise
+ boundedPremise = do
+ validateApplication
+ foundation
+ SetLfpLeast
+ [domain, operator, candidate]
+ [closedPremise, boundedPremise]
+ operatorCandidate <-
+ applyFor
+ SetLfpLeast
+ globalType
+ operator
+ candidate
+ expectedClosed <-
+ subsetFor
+ SetLfpLeast
+ globalType
+ operatorCandidate
+ candidate
+ expectedBounded <-
+ subsetFor
+ SetLfpLeast
+ globalType
+ candidate
+ domain
+ requirePremise
+ SetLfpLeast
+ 0
+ expectedClosed
+ closedPremise
+ requirePremise
+ SetLfpLeast
+ 1
+ expectedBounded
+ boundedPremise
+ fixedPoint <-
+ setLfpTermFor
+ SetLfpLeast
+ globalType
+ domain
+ operator
+ subsetFor
+ SetLfpLeast
+ globalType
+ fixedPoint
+ candidate
+
+setLfpFixed
+ :: Eq global
+ => CheckedFoundation
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+setLfpFixed
+ foundation
+ globalType
+ domain
+ operator
+ monotonePremise = do
+ validateApplication
+ foundation
+ SetLfpFixed
+ [domain, operator]
+ [monotonePremise]
+ expectedMonotone <-
+ boundedMonoFor
+ SetLfpFixed
+ globalType
+ domain
+ operator
+ requirePremise
+ SetLfpFixed
+ 0
+ expectedMonotone
+ monotonePremise
+ fixedPoint <-
+ setLfpTermFor
+ SetLfpFixed
+ globalType
+ domain
+ operator
+ unfolded <-
+ applyFor
+ SetLfpFixed
+ globalType
+ operator
+ fixedPoint
+ checkedFor
+ SetLfpFixed
+ globalType
+ (scopedCoreContext domain)
+ (CEq
+ TySet
+ (scopedCoreTerm fixedPoint)
+ (scopedCoreTerm unfolded))
+
+setLfpInduct
+ :: Eq global
+ => CheckedFoundation
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+setLfpInduct
+ foundation
+ globalType
+ domain
+ operator
+ predicate
+ element
+ monotonePremise
+ memberPremise
+ closurePremise = do
+ validateApplication
+ foundation
+ SetLfpInduct
+ [domain, operator, predicate, element]
+ [monotonePremise, memberPremise, closurePremise]
+ expectedMonotone <-
+ boundedMonoFor
+ SetLfpInduct
+ globalType
+ domain
+ operator
+ requirePremise
+ SetLfpInduct
+ 0
+ expectedMonotone
+ monotonePremise
+ fixedPoint <-
+ setLfpTermFor
+ SetLfpInduct
+ globalType
+ domain
+ operator
+ expectedMember <-
+ memberFor
+ SetLfpInduct
+ globalType
+ element
+ fixedPoint
+ requirePremise
+ SetLfpInduct
+ 1
+ expectedMember
+ memberPremise
+ expectedClosure <-
+ inductionClosureFor
+ SetLfpInduct
+ globalType
+ fixedPoint
+ operator
+ predicate
+ requirePremise
+ SetLfpInduct
+ 2
+ expectedClosure
+ closurePremise
+ applyFor
+ SetLfpInduct
+ globalType
+ predicate
+ element
+
+setLfpTerm
+ :: (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+setLfpTerm =
+ setLfpTermFor SetLfpBound
+
+memberProposition
+ :: (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+memberProposition =
+ memberFor SetLfpInduct
+
+subsetProposition
+ :: (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+subsetProposition =
+ subsetFor SetLfpBound
+
+boundedMonoProposition
+ :: (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+boundedMonoProposition =
+ boundedMonoFor SetLfpFixed
+
+inductionClosureProposition
+ :: (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+inductionClosureProposition globalType domain operator predicate = do
+ fixedPoint <-
+ setLfpTermFor
+ SetLfpInduct
+ globalType
+ domain
+ operator
+ inductionClosureFor
+ SetLfpInduct
+ globalType
+ fixedPoint
+ operator
+ predicate
+
+validateApplication
+ :: CheckedFoundation
+ -> KernelRuleTag
+ -> [ScopedCheckedCore global]
+ -> [ScopedCheckedCore global]
+ -> Either SetLfpRuleError ()
+validateApplication foundation tag arguments premises = do
+ let expected =
+ expectedSignature tag
+ actual =
+ foundationRuleSignature foundation tag
+ if actual == expected
+ then pure ()
+ else
+ Left
+ (SetLfpRuleSignatureMismatch
+ tag
+ expected
+ actual)
+ case arguments of
+ [] ->
+ pure ()
+ firstArgument : remainingArguments -> do
+ traverse_
+ (requireContext tag firstArgument)
+ remainingArguments
+ traverse_
+ (requireContext tag firstArgument)
+ premises
+ traverse_
+ (uncurry
+ (requireArgumentType tag))
+ (zip [0 ..] arguments)
+ traverse_
+ (uncurry
+ (requireProposition tag))
+ (zip [0 ..] premises)
+
+expectedSignature :: KernelRuleTag -> KernelRuleSignature
+expectedSignature = \case
+ SetLfpBound ->
+ KernelRuleSignature
+ [TySet, TySet `TyArrow` TySet]
+ 0
+ SetLfpLeast ->
+ KernelRuleSignature
+ [TySet, TySet `TyArrow` TySet, TySet]
+ 2
+ SetLfpFixed ->
+ KernelRuleSignature
+ [TySet, TySet `TyArrow` TySet]
+ 1
+ SetLfpInduct ->
+ KernelRuleSignature
+ [ TySet
+ , TySet `TyArrow` TySet
+ , TySet `TyArrow` TyProp
+ , TySet
+ ]
+ 3
+
+expectedArgumentTypes
+ :: KernelRuleTag
+ -> [CoreType]
+expectedArgumentTypes tag =
+ case expectedSignature tag of
+ KernelRuleSignature argumentTypes _premiseCount ->
+ argumentTypes
+
+requireContext
+ :: KernelRuleTag
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either SetLfpRuleError ()
+requireContext tag expected actual
+ | scopedCoreContext expected
+ == scopedCoreContext actual =
+ Right ()
+ | otherwise =
+ Left
+ (SetLfpRuleContextMismatch
+ tag
+ (scopedCoreContext expected)
+ (scopedCoreContext actual))
+
+requireArgumentType
+ :: KernelRuleTag
+ -> Natural
+ -> ScopedCheckedCore global
+ -> Either SetLfpRuleError ()
+requireArgumentType tag index argument =
+ case atNatural index (expectedArgumentTypes tag) of
+ Nothing ->
+ impossible
+ "fixed-point rule argument inventory is inconsistent"
+ Just expected
+ | scopedCoreType argument == expected ->
+ Right ()
+ | otherwise ->
+ Left
+ (SetLfpRuleArgumentTypeMismatch
+ tag
+ index
+ expected
+ (scopedCoreType argument))
+
+requireProposition
+ :: KernelRuleTag
+ -> Natural
+ -> ScopedCheckedCore global
+ -> Either SetLfpRuleError ()
+requireProposition tag index premise
+ | scopedCoreType premise == TyProp =
+ Right ()
+ | otherwise =
+ Left
+ (SetLfpRulePremiseIsNotProposition
+ tag
+ index
+ (scopedCoreType premise))
+
+requirePremise
+ :: Eq global
+ => KernelRuleTag
+ -> Natural
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either SetLfpRuleError ()
+requirePremise tag index expected actual
+ | expected == actual =
+ Right ()
+ | otherwise =
+ Left
+ (SetLfpRulePremiseMismatch
+ tag
+ index)
+
+setLfpTermFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+setLfpTermFor tag globalType domain operator =
+ checkedFor tag globalType
+ (scopedCoreContext domain)
+ (CApp
+ (CApp
+ (CIntrinsic ISetLfp)
+ (scopedCoreTerm domain))
+ (scopedCoreTerm operator))
+
+applyFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+applyFor tag globalType function argument = do
+ requireContext tag function argument
+ checkedFor tag globalType
+ (scopedCoreContext function)
+ (CApp
+ (scopedCoreTerm function)
+ (scopedCoreTerm argument))
+
+memberFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+memberFor tag globalType element set = do
+ requireContext tag element set
+ checkedFor tag globalType
+ (scopedCoreContext element)
+ (CApp
+ (CApp
+ (CIntrinsic Member)
+ (scopedCoreTerm element))
+ (scopedCoreTerm set))
+
+subsetFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+subsetFor tag globalType left right = do
+ requireContext tag left right
+ left' <- weakenFor tag globalType TySet left
+ right' <- weakenFor tag globalType TySet right
+ element <-
+ checkedFor tag globalType
+ (TySet : scopedCoreContext left)
+ (CBound 0)
+ inLeft <-
+ memberFor tag globalType element left'
+ inRight <-
+ memberFor tag globalType element right'
+ checkedFor tag globalType
+ (scopedCoreContext left)
+ (CForall
+ TySet
+ (CImp
+ (scopedCoreTerm inLeft)
+ (scopedCoreTerm inRight)))
+
+boundedMonoFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+boundedMonoFor tag globalType domain operator = do
+ requireContext tag domain operator
+ operatorDomain <-
+ applyFor tag globalType operator domain
+ bounded <-
+ subsetFor tag globalType operatorDomain domain
+
+ domainX <-
+ weakenFor tag globalType TySet domain
+ operatorX <-
+ weakenFor tag globalType TySet operator
+ domainXY <-
+ weakenFor tag globalType TySet domainX
+ operatorXY <-
+ weakenFor tag globalType TySet operatorX
+ let xyContext =
+ TySet : TySet : scopedCoreContext domain
+ x <-
+ checkedFor tag globalType
+ xyContext
+ (CBound 1)
+ y <-
+ checkedFor tag globalType
+ xyContext
+ (CBound 0)
+ xSubsetY <-
+ subsetFor tag globalType x y
+ ySubsetDomain <-
+ subsetFor tag globalType y domainXY
+ antecedent <-
+ conjunctionFor
+ tag
+ globalType
+ xSubsetY
+ ySubsetDomain
+ operatorXValue <-
+ applyFor tag globalType operatorXY x
+ operatorYValue <-
+ applyFor tag globalType operatorXY y
+ imageSubset <-
+ subsetFor
+ tag
+ globalType
+ operatorXValue
+ operatorYValue
+ monotoneBody <-
+ implicationFor
+ tag
+ globalType
+ antecedent
+ imageSubset
+ quantifiedY <-
+ closeForallFor tag globalType TySet monotoneBody
+ quantifiedXY <-
+ closeForallFor tag globalType TySet quantifiedY
+ conjunctionFor
+ tag
+ globalType
+ bounded
+ quantifiedXY
+
+inductionClosureFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+inductionClosureFor
+ tag
+ globalType
+ fixedPoint
+ operator
+ predicate = do
+ fixedPoint' <-
+ weakenFor tag globalType TySet fixedPoint
+ operator' <-
+ weakenFor tag globalType TySet operator
+ predicate' <-
+ weakenFor tag globalType TySet predicate
+ element <-
+ checkedFor tag globalType
+ (TySet : scopedCoreContext fixedPoint)
+ (CBound 0)
+ separated <-
+ checkedFor tag globalType
+ (scopedCoreContext fixedPoint')
+ (CApp
+ (CApp
+ (CIntrinsic Sep)
+ (scopedCoreTerm fixedPoint'))
+ (scopedCoreTerm predicate'))
+ unfolded <-
+ applyFor tag globalType operator' separated
+ memberUnfolded <-
+ memberFor tag globalType element unfolded
+ predicateElement <-
+ applyFor tag globalType predicate' element
+ body <-
+ implicationFor
+ tag
+ globalType
+ memberUnfolded
+ predicateElement
+ closeForallFor tag globalType TySet body
+
+conjunctionFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+conjunctionFor tag globalType left right = do
+ requireContext tag left right
+ checkedFor tag globalType
+ (scopedCoreContext left)
+ (CImp
+ (CImp
+ (scopedCoreTerm left)
+ (CImp
+ (scopedCoreTerm right)
+ CFalsum))
+ CFalsum)
+
+implicationFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+implicationFor tag globalType premise conclusion = do
+ requireContext tag premise conclusion
+ checkedFor tag globalType
+ (scopedCoreContext premise)
+ (CImp
+ (scopedCoreTerm premise)
+ (scopedCoreTerm conclusion))
+
+closeForallFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> CoreType
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+closeForallFor tag globalType binderType body =
+ case scopedCoreContext body of
+ actualBinder : outerContext
+ | actualBinder == binderType ->
+ checkedFor tag globalType
+ outerContext
+ (CForall
+ binderType
+ (scopedCoreTerm body))
+ _ ->
+ Left
+ (SetLfpRuleContextMismatch
+ tag
+ (binderType : drop 1
+ (scopedCoreContext body))
+ (scopedCoreContext body))
+
+weakenFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> CoreType
+ -> ScopedCheckedCore global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+weakenFor tag globalType binderType =
+ first
+ (SetLfpRuleConstructionIllTyped tag)
+ . weakenScopedCore
+ globalType
+ binderType
+
+checkedFor
+ :: KernelRuleTag
+ -> (global -> Maybe CoreType)
+ -> [CoreType]
+ -> CanonicalTerm global
+ -> Either
+ SetLfpRuleError
+ (ScopedCheckedCore global)
+checkedFor tag globalType context =
+ first
+ (SetLfpRuleConstructionIllTyped tag)
+ . checkScopedCanonicalCore
+ globalType
+ context
+
+atNatural :: Natural -> [a] -> Maybe a
+atNatural _index [] =
+ Nothing
+atNatural 0 (value : _rest) =
+ Just value
+atNatural index (_value : rest) =
+ atNatural (index - 1) rest
diff --git a/source/Felix/Checking/Materialization.hs b/source/Felix/Checking/Materialization.hs
new file mode 100644
index 0000000..51944a5
--- /dev/null
+++ b/source/Felix/Checking/Materialization.hs
@@ -0,0 +1,288 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Pure applicability checks for later trusted materialization.
+--
+-- Successful checks are inert. Fresh completion and validated loading or
+-- sealing own the runtime authority that may publish a fact.
+module Felix.Checking.Materialization
+ ( CandidateValidation
+ , candidateProofValidation
+ , candidateDeclarationValidation
+ , checkDeclarationValidationRecord
+ , checkCandidateValidation
+ , checkImportedMembership
+ , checkImportedOccurrence
+ , MaterializationError(..)
+ ) where
+
+import Base
+import Felix.Checking.Authority
+import Felix.Checking.Identity
+import Felix.Checking.Semantic
+
+import Control.Monad (unless)
+import Numeric.Natural (Natural)
+
+
+data CandidateValidation
+ = CandidateProofValidation
+ !ProofValidationRecord
+ !ProofSyntaxId
+ | CandidateDeclarationValidation
+ !DeclarationValidationRecord
+ !DeclarationSyntaxId
+ ![ObjectId]
+ ![TheoremId]
+ !Natural
+ deriving stock (Show, Eq)
+
+candidateProofValidation
+ :: ProofValidationRecord
+ -> ProofSyntaxId
+ -> CandidateValidation
+candidateProofValidation =
+ CandidateProofValidation
+
+candidateDeclarationValidation
+ :: DeclarationValidationRecord
+ -> DeclarationSyntaxId
+ -> [ObjectId]
+ -> [TheoremId]
+ -> Natural
+ -> CandidateValidation
+candidateDeclarationValidation =
+ CandidateDeclarationValidation
+
+checkDeclarationValidationRecord
+ :: PrefixContextId
+ -> DeclarationSyntaxId
+ -> [ObjectId]
+ -> [TheoremId]
+ -> DeclarationValidationRecord
+ -> Either MaterializationError ()
+checkDeclarationValidationRecord prefix syntax objects theorems record = do
+ let suppliedKey =
+ declarationValidationRecordKey record
+ certificates =
+ declarationValidationRecordCertificates record
+ expectedKey =
+ declarationValidationKey
+ syntax prefix objects theorems
+ unless
+ (suppliedKey == expectedKey)
+ (Left
+ (DeclarationValidationKeyMismatch
+ suppliedKey expectedKey))
+ let certificateTheorems =
+ theoremId
+ . factAuthorityTheorem
+ . validationTarget
+ <$> certificates
+ unless
+ (certificateTheorems == theorems)
+ (Left
+ (DeclarationCertificateTargetsMismatch
+ theorems certificateTheorems))
+
+data MaterializationError
+ = CandidateValidationIndexMissing !Natural
+ | ProofValidationKeyMismatch
+ !ProofValidationKey
+ !ProofValidationKey
+ | DeclarationValidationKeyMismatch
+ !DeclarationValidationKey
+ !DeclarationValidationKey
+ | DeclarationCertificateTargetsMismatch
+ ![TheoremId]
+ ![TheoremId]
+ | CandidateCertificateTargetMismatch
+ !FactAuthority
+ !FactAuthority
+ | CandidateDirectAuthorizationMismatch
+ !DirectAuthorization
+ !DirectAuthorization
+ | CandidateTheoryMismatch !TheoryId !TheoryId
+ | CandidatePropositionMismatch
+ !PropositionId
+ !PropositionId
+ | ImportedOccurrenceMissing
+ !SemanticFactOccurrenceFingerprint
+ | ImportedAuthorityMismatch
+ !FactAuthority
+ !FactAuthority
+ | ImportedTheoryMismatch !TheoryId !TheoryId
+ | ImportedPropositionMismatch
+ !PropositionId
+ !PropositionId
+ deriving stock (Show, Eq)
+
+
+-- | Check whether inert validation data applies to one exact candidate.
+--
+-- Success deliberately returns no builder authorization. A fresh trusted
+-- completion or compatible validated-store hit must perform this check before
+-- its owner mints runtime authority.
+checkCandidateValidation
+ :: TheoryId
+ -> PrefixContextId
+ -> CheckedPropositionContent
+ -> FactAuthority
+ -> DirectAuthorization
+ -> CandidateValidation
+ -> Either MaterializationError ()
+checkCandidateValidation
+ theory prefix proposition expectedAuthority
+ expectedDirect validation = do
+ certificate <-
+ candidateCertificate
+ prefix expectedAuthority validation
+ validateCertificateTarget
+ theory proposition expectedAuthority expectedDirect certificate
+
+candidateCertificate
+ :: PrefixContextId
+ -> FactAuthority
+ -> CandidateValidation
+ -> Either MaterializationError ValidationCertificate
+candidateCertificate prefix authority = \case
+ CandidateProofValidation record syntax -> do
+ let suppliedKey =
+ proofValidationRecordKey record
+ certificate =
+ proofValidationRecordCertificate record
+ expectedKey =
+ proofValidationKey
+ (theoremId
+ (factAuthorityTheorem authority))
+ syntax
+ prefix
+ unless
+ (suppliedKey == expectedKey)
+ (Left
+ (ProofValidationKeyMismatch
+ suppliedKey expectedKey))
+ pure certificate
+ CandidateDeclarationValidation
+ record syntax objects theorems ordinal -> do
+ checkDeclarationValidationRecord
+ prefix syntax objects theorems record
+ maybe
+ (Left (CandidateValidationIndexMissing ordinal))
+ Right
+ (nthNatural ordinal
+ (declarationValidationRecordCertificates record))
+
+validateCertificateTarget
+ :: TheoryId
+ -> CheckedPropositionContent
+ -> FactAuthority
+ -> DirectAuthorization
+ -> ValidationCertificate
+ -> Either MaterializationError ()
+validateCertificateTarget
+ theory proposition expectedAuthority expectedDirect certificate = do
+ let actualAuthority =
+ validationTarget certificate
+ actualDirect =
+ validationDirectAuthorization certificate
+ reference =
+ factAuthorityTheorem actualAuthority
+ unless
+ (actualAuthority == expectedAuthority)
+ (Left
+ (CandidateCertificateTargetMismatch
+ expectedAuthority actualAuthority))
+ unless
+ (actualDirect == expectedDirect)
+ (Left
+ (CandidateDirectAuthorizationMismatch
+ expectedDirect actualDirect))
+ unless
+ (theoremRefTheory reference == theory)
+ (Left
+ (CandidateTheoryMismatch
+ theory
+ (theoremRefTheory reference)))
+ unless
+ ( theoremRefProposition reference
+ == checkedPropositionId proposition
+ )
+ (Left
+ (CandidatePropositionMismatch
+ (checkedPropositionId proposition)
+ (theoremRefProposition reference)))
+
+
+-- | Validate exact membership in inert canonical interface data.
+--
+-- Logical sealing or atomic cached installation must additionally supply the
+-- opaque runtime evidence before an importer receives builder authority.
+checkImportedMembership
+ :: TheoryId
+ -> SemanticInterface
+ -> SemanticFactOccurrenceFingerprint
+ -> CheckedPropositionContent
+ -> FactAuthority
+ -> Either MaterializationError SemanticFactOccurrence
+checkImportedMembership
+ theory interface fingerprint proposition expectedAuthority = do
+ occurrence <-
+ maybe
+ (Left (ImportedOccurrenceMissing fingerprint))
+ Right
+ (find
+ ((== fingerprint) . semanticFactFingerprint)
+ (concatMap
+ declarationDeltaFacts
+ (semanticInterfaceDeclarations interface)))
+ checkImportedOccurrence
+ theory fingerprint occurrence proposition expectedAuthority
+
+checkImportedOccurrence
+ :: TheoryId
+ -> SemanticFactOccurrenceFingerprint
+ -> SemanticFactOccurrence
+ -> CheckedPropositionContent
+ -> FactAuthority
+ -> Either MaterializationError SemanticFactOccurrence
+checkImportedOccurrence
+ theory fingerprint occurrence proposition expectedAuthority = do
+ unless
+ (semanticFactFingerprint occurrence == fingerprint)
+ (Left (ImportedOccurrenceMissing fingerprint))
+ let actualAuthority =
+ semanticFactAuthority occurrence
+ reference =
+ factAuthorityTheorem actualAuthority
+ unless
+ (actualAuthority == expectedAuthority)
+ (Left
+ (ImportedAuthorityMismatch
+ expectedAuthority actualAuthority))
+ unless
+ (theoremRefTheory reference == theory)
+ (Left
+ (ImportedTheoryMismatch
+ theory
+ (theoremRefTheory reference)))
+ unless
+ (theoremRefProposition reference
+ == checkedPropositionId proposition)
+ (Left
+ (ImportedPropositionMismatch
+ (checkedPropositionId proposition)
+ (theoremRefProposition reference)))
+ pure occurrence
+
+
+nthNatural :: Natural -> [value] -> Maybe value
+nthNatural ordinal =
+ go ordinal
+ where
+ go _ [] =
+ Nothing
+ go 0 (value : _) =
+ Just value
+ go remaining (_ : rest) =
+ go (remaining - 1) rest
diff --git a/source/Felix/Checking/Module.hs b/source/Felix/Checking/Module.hs
new file mode 100644
index 0000000..88c8fef
--- /dev/null
+++ b/source/Felix/Checking/Module.hs
@@ -0,0 +1,986 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE RankNTypes #-}
+
+-- | Explicit inputs and sealed outputs for the typed module driver.
+module Felix.Checking.Module
+ ( LiveModuleBinding(..)
+ , IdentifiedModuleInput
+ , identifiedPhysicalModule
+ , identifiedReservedPrelude
+ , identifiedModuleOwner
+ , identifiedModuleBinding
+ , identifiedModuleParsed
+ , TypedSourceDeclaration
+ , typedSourceDeclarationBlockIndex
+ , typedSourceDeclarationHead
+ , typedSourceDeclarationProof
+ , typedSourceDeclarations
+ , SealedTypedModule
+ , sealedTypedModuleOwner
+ , sealedTypedModuleSyntax
+ , sealedTypedModuleSemantic
+ , sealedTypedModuleEvidence
+ , CachedTypedModuleError(..)
+ , renderCachedTypedModuleError
+ , cachedSealedTypedModule
+ , sealedTypedModulePrefix
+ , FinalPreludeSession
+ , ModuleRootAcquisition(..)
+ , finalPreludeSource
+ , finalPreludeInput
+ , finalPreludeModule
+ , finalPreludeAcquisition
+ , FinalPreludeReadiness
+ , finalPreludeReadiness
+ , BootstrapPreludeFixture
+ , bootstrapPreludeInput
+ , bootstrapPreludeModule
+ , bootstrapPreludeReadiness
+ , fixtureFinalPreludeReadinessFromSealed
+ , BootstrapError(..)
+ , buildBootstrapPreludeFixture
+ , FinalPreludeReadinessError(..)
+ , acquireFinalPreludeSession
+ , TypedModuleInput
+ , TypedModuleInputError(..)
+ , renderTypedModuleInputError
+ , typedModuleInput
+ , TypedPathError(..)
+ , TypedModuleFailure(..)
+ , typedModuleFailureLocation
+ , renderTypedModuleFailure
+ , TypedModuleResult(..)
+ , runTypedModule
+ ) where
+
+import Base
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Exact qualified as Exact
+import Felix.Checking.Exact.Datatype qualified as ExactDatatype
+import Felix.Checking.Exact.Inductive qualified as ExactInductive
+import Felix.Checking.Exact.Proof qualified as ExactProof
+import Felix.Checking.FinalPrelude qualified as FinalPrelude
+import Felix.Checking.Foundation
+import Felix.Checking.Identity
+import Felix.Checking.Semantic
+import Felix.Module
+import Felix.Parse
+import Felix.Prelude qualified as Prelude
+import Felix.Source
+import Felix.Store qualified as Store
+import Felix.Report.Location
+import Felix.Syntax.Interface
+import Felix.Syntax.Abstract qualified as Raw
+
+import Control.Monad (unless)
+import Data.Bifunctor (first)
+import Data.Text qualified as Text
+
+
+data LiveModuleBinding
+ = PhysicalModuleBinding !ResolvedSource
+ | ReservedModuleBinding !FileId !FilePath
+ deriving stock (Show, Eq)
+
+data IdentifiedModuleInput = IdentifiedModuleInput
+ !ModuleName
+ !LiveModuleBinding
+ !IdentifiedParsedModule
+
+identifiedPhysicalModule :: ParsedModule -> IdentifiedModuleInput
+identifiedPhysicalModule parsed =
+ IdentifiedModuleInput
+ (moduleName (parsedModuleAddress parsed))
+ (PhysicalModuleBinding (parsedModuleResolved parsed))
+ (parsedModuleIdentified parsed)
+
+identifiedReservedPrelude
+ :: Prelude.ReservedParsedPrelude
+ -> IdentifiedModuleInput
+identifiedReservedPrelude reserved =
+ IdentifiedModuleInput
+ (freshModuleInputOwner input)
+ (ReservedModuleBinding
+ (freshModuleInputFileId input)
+ (freshModuleInputLocationPath input))
+ (Prelude.reservedParsedPreludeModule reserved)
+ where
+ input = Prelude.reservedParsedPreludeInput reserved
+
+identifiedModuleOwner :: IdentifiedModuleInput -> ModuleName
+identifiedModuleOwner (IdentifiedModuleInput owner _binding _parsed) =
+ owner
+
+identifiedModuleBinding
+ :: IdentifiedModuleInput
+ -> LiveModuleBinding
+identifiedModuleBinding
+ (IdentifiedModuleInput _owner binding _parsed) =
+ binding
+
+identifiedModuleParsed
+ :: IdentifiedModuleInput
+ -> IdentifiedParsedModule
+identifiedModuleParsed
+ (IdentifiedModuleInput _owner _binding parsed) =
+ parsed
+
+
+-- | One source declaration as consumed by the typed module driver.
+--
+-- A claim and its immediately following proof occupy one declaration slot.
+-- The head block index remains the syntax-occurrence association index.
+data TypedSourceDeclaration = TypedSourceDeclaration
+ !Int
+ !Raw.Block
+ !(Maybe Raw.Proof)
+
+typedSourceDeclarationBlockIndex :: TypedSourceDeclaration -> Int
+typedSourceDeclarationBlockIndex
+ (TypedSourceDeclaration blockIndex _head _proof) =
+ blockIndex
+
+typedSourceDeclarationHead :: TypedSourceDeclaration -> Raw.Block
+typedSourceDeclarationHead
+ (TypedSourceDeclaration _blockIndex headBlock _proof) =
+ headBlock
+
+typedSourceDeclarationProof
+ :: TypedSourceDeclaration
+ -> Maybe Raw.Proof
+typedSourceDeclarationProof
+ (TypedSourceDeclaration _blockIndex _head proof) =
+ proof
+
+typedSourceDeclarations
+ :: IdentifiedParsedModule
+ -> [TypedSourceDeclaration]
+typedSourceDeclarations parsed =
+ [ declaration
+ | item <- typedSourceItems (identifiedParsedModuleBlocks parsed)
+ , Just declaration <- [sourceItemDeclaration item]
+ ]
+
+data TypedSourceItem
+ = TypedSourceDeclarationItem !TypedSourceDeclaration
+ | TypedUnmatchedSourceProof !Location
+
+sourceItemDeclaration
+ :: TypedSourceItem
+ -> Maybe TypedSourceDeclaration
+sourceItemDeclaration = \case
+ TypedSourceDeclarationItem declaration -> Just declaration
+ TypedUnmatchedSourceProof{} -> Nothing
+
+typedSourceItems :: [Raw.Block] -> [TypedSourceItem]
+typedSourceItems = go 0
+ where
+ go _blockIndex [] = []
+ go blockIndex (block@Raw.BlockClaim{} : remaining) =
+ case remaining of
+ Raw.BlockProof _location proof _end : rest ->
+ TypedSourceDeclarationItem
+ (TypedSourceDeclaration blockIndex block (Just proof))
+ : go (blockIndex + 2) rest
+ _ ->
+ TypedSourceDeclarationItem
+ (TypedSourceDeclaration blockIndex block Nothing)
+ : go (blockIndex + 1) remaining
+ go blockIndex (Raw.BlockProof location _proof _end : remaining) =
+ TypedUnmatchedSourceProof location
+ : go (blockIndex + 1) remaining
+ go blockIndex (block : remaining) =
+ TypedSourceDeclarationItem
+ (TypedSourceDeclaration blockIndex block Nothing)
+ : go (blockIndex + 1) remaining
+
+
+data SealedTypedModule = SealedTypedModule
+ !ModuleName
+ !ModuleSyntaxInterface
+ !SemanticInterface
+ !Declaration.PendingModulePrefix
+ !Declaration.ImportedModuleEvidence
+
+sealedTypedModuleOwner :: SealedTypedModule -> ModuleName
+sealedTypedModuleOwner (SealedTypedModule owner _syntax _semantic _prefix _evidence) =
+ owner
+
+sealedTypedModuleSyntax
+ :: SealedTypedModule
+ -> ModuleSyntaxInterface
+sealedTypedModuleSyntax
+ (SealedTypedModule _owner syntax _semantic _prefix _evidence) =
+ syntax
+
+sealedTypedModuleSemantic
+ :: SealedTypedModule
+ -> SemanticInterface
+sealedTypedModuleSemantic
+ (SealedTypedModule _owner _syntax semantic _prefix _evidence) =
+ semantic
+
+sealedTypedModulePrefix
+ :: SealedTypedModule
+ -> Declaration.PendingModulePrefix
+sealedTypedModulePrefix
+ (SealedTypedModule _owner _syntax _semantic prefix _evidence) =
+ prefix
+
+sealedTypedModuleEvidence
+ :: SealedTypedModule
+ -> Declaration.ImportedModuleEvidence
+sealedTypedModuleEvidence
+ (SealedTypedModule _owner _syntax _semantic _prefix evidence) =
+ evidence
+
+data CachedTypedModuleError
+ = CachedTypedModuleEvidenceError !Declaration.DeclarationError
+ deriving stock (Show, Eq)
+
+renderCachedTypedModuleError :: CachedTypedModuleError -> Text
+renderCachedTypedModuleError = \case
+ CachedTypedModuleEvidenceError failure ->
+ Declaration.renderDeclarationError failure
+
+cachedSealedTypedModule
+ :: CheckedFoundation
+ -> [SealedTypedModule]
+ -> Store.CachedModuleInstallation
+ -> Either CachedTypedModuleError SealedTypedModule
+cachedSealedTypedModule
+ foundation parents installation = do
+ evidence <-
+ first CachedTypedModuleEvidenceError
+ (Declaration.validateImportedModuleEvidence
+ (theoryId foundation)
+ (sealedTypedModuleEvidence <$> parents)
+ semantic
+ objects
+ propositions)
+ pure
+ (SealedTypedModule
+ owner
+ syntax
+ semantic
+ (Declaration.emptyPendingModulePrefix
+ (Store.cachedInstallationFinalPrefix installation))
+ evidence)
+ where
+ syntax = Store.cachedInstallationSyntax installation
+ semantic = Store.cachedInstallationSemantic installation
+ owner = semanticInterfaceOwner semantic
+ objects = Store.cachedInstallationObjects installation
+ propositions = Store.cachedInstallationPropositions installation
+
+
+data ModuleRootAcquisition
+ = ModuleRootHit
+ | ModuleRootMiss
+ deriving stock (Show, Eq)
+
+data FinalPreludeSession = FinalPreludeSession
+ !Prelude.ReservedPreludeSourceInput
+ !IdentifiedModuleInput
+ !SealedTypedModule
+ !ModuleRootAcquisition
+
+finalPreludeSource
+ :: FinalPreludeSession
+ -> Prelude.ReservedPreludeSourceInput
+finalPreludeSource
+ (FinalPreludeSession source _input _module _acquisition) =
+ source
+
+finalPreludeInput
+ :: FinalPreludeSession
+ -> IdentifiedModuleInput
+finalPreludeInput (FinalPreludeSession _source input _module _acquisition) =
+ input
+
+finalPreludeModule
+ :: FinalPreludeSession
+ -> SealedTypedModule
+finalPreludeModule (FinalPreludeSession _source _input sealed _acquisition) =
+ sealed
+
+finalPreludeAcquisition
+ :: FinalPreludeSession
+ -> ModuleRootAcquisition
+finalPreludeAcquisition
+ (FinalPreludeSession _source _input _sealed acquisition) =
+ acquisition
+
+newtype FinalPreludeReadiness = FinalPreludeReadiness
+ SealedTypedModule
+
+finalPreludeReadiness
+ :: FinalPreludeSession
+ -> FinalPreludeReadiness
+finalPreludeReadiness =
+ FinalPreludeReadiness . finalPreludeModule
+
+-- | Test-only empty-prelude fixture.
+--
+-- It is exported for focused exact-compiler tests. Production acquisition is
+-- exclusively 'acquireFinalPreludeSession'.
+newtype BootstrapPreludeFixture = BootstrapPreludeFixture
+ FinalPreludeSession
+
+bootstrapPreludeInput
+ :: BootstrapPreludeFixture
+ -> IdentifiedModuleInput
+bootstrapPreludeInput (BootstrapPreludeFixture fixture) =
+ finalPreludeInput fixture
+
+bootstrapPreludeModule
+ :: BootstrapPreludeFixture
+ -> SealedTypedModule
+bootstrapPreludeModule (BootstrapPreludeFixture fixture) =
+ finalPreludeModule fixture
+
+-- | Test-only seam for the empty bootstrap compiler input.
+bootstrapPreludeReadiness
+ :: BootstrapPreludeFixture
+ -> FinalPreludeReadiness
+bootstrapPreludeReadiness =
+ FinalPreludeReadiness . bootstrapPreludeModule
+
+-- | Test-only seam for exercising a synthetic distinguished prelude.
+fixtureFinalPreludeReadinessFromSealed
+ :: SealedTypedModule
+ -> FinalPreludeReadiness
+fixtureFinalPreludeReadinessFromSealed =
+ FinalPreludeReadiness
+
+data BootstrapError
+ = BootstrapParseFailed !Prelude.PreludeParseError
+ | BootstrapDriverOpenFailed !Declaration.DriverOpenError
+ | BootstrapDeclarationFailed !Declaration.DeclarationError
+ | BootstrapSealFailed !SemanticInterfaceError
+ deriving stock (Show)
+
+-- | Construct the empty distinguished prelude used only by compiler fixtures.
+buildBootstrapPreludeFixture
+ :: CheckedFoundation
+ -> Declaration.VampireResolver
+ -> IO (Either BootstrapError BootstrapPreludeFixture)
+buildBootstrapPreludeFixture foundation resolver = do
+ parsedResult <-
+ Prelude.parseReservedPreludeSource
+ Prelude.emptyBootstrapSourceInput
+ case parsedResult of
+ Left err ->
+ pure (Left (BootstrapParseFailed err))
+ Right reserved -> do
+ let input = identifiedReservedPrelude reserved
+ syntax =
+ identifiedParsedModuleSyntaxInterface
+ (identifiedModuleParsed input)
+ driver <-
+ Declaration.runModuleDriver
+ foundation
+ preludeModuleName
+ []
+ resolver
+ Declaration.FreshValidation
+ emptyDriver
+ pure case driver of
+ Left err ->
+ Left (BootstrapDriverOpenFailed err)
+ Right (Declaration.DriverSucceeded
+ () semantic prefix _closure) ->
+ Right
+ (BootstrapPreludeFixture
+ (FinalPreludeSession
+ Prelude.emptyBootstrapSourceInput
+ input
+ (SealedTypedModule
+ preludeModuleName
+ syntax
+ semantic
+ prefix
+ (Declaration.freshImportedModuleEvidence
+ []
+ semantic
+ prefix))
+ ModuleRootMiss))
+ Right
+ (Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed err)
+ _prefix) ->
+ Left (BootstrapDeclarationFailed err)
+ Right (Declaration.DriverSealFailed err _prefix) ->
+ Left (BootstrapSealFailed err)
+ where
+ emptyDriver :: Declaration.ModuleDriver Void ()
+ emptyDriver = pure ()
+
+
+data FinalPreludeReadinessError
+ = FinalPreludeReadinessSourceLoadFailed !Prelude.PreludeLoadError
+ | FinalPreludeReadinessSourceParseFailed !Prelude.PreludeParseError
+ | FinalPreludeReadinessBuildFailed !FinalPrelude.FinalPreludeFailure
+ | FinalPreludeReadinessBuildOpenFailed !Declaration.DriverOpenError
+ | FinalPreludeReadinessArtifactKeyFailed !ModuleArtifactKeyError
+ | FinalPreludeReadinessStoreFailed !Store.StoreFailure
+ | FinalPreludeReadinessCachedModuleFailed !CachedTypedModuleError
+ | FinalPreludeReadinessAcknowledgementMismatch
+ !ModuleArtifactResult
+ !ModuleArtifactResult
+ deriving stock (Show)
+
+-- | Acquire the exact packaged final prelude through its ordinary module root.
+--
+-- Loading and parsing happen once before the root lookup. A hit is validated
+-- and materialized through the generic cached-module boundary; a miss reuses
+-- that parsed input for confined construction and atomic publication.
+acquireFinalPreludeSession
+ :: Store.StoreMemo
+ -> Store.Store
+ -> CheckedFoundation
+ -> Declaration.VampireResolver
+ -> IO (Either FinalPreludeReadinessError FinalPreludeSession)
+acquireFinalPreludeSession memo store foundation resolver =
+ Prelude.loadReservedPreludeSourceInput >>= \case
+ Left failure ->
+ pure (Left (FinalPreludeReadinessSourceLoadFailed failure))
+ Right source ->
+ Prelude.parseReservedPreludeSource source >>= \case
+ Left failure ->
+ pure (Left (FinalPreludeReadinessSourceParseFailed failure))
+ Right parsed ->
+ acquire source parsed
+ where
+ acquire source parsed =
+ case moduleArtifactKey
+ preludeModuleName
+ parsedId
+ []
+ (theoryId foundation) of
+ Left failure ->
+ pure (Left (FinalPreludeReadinessArtifactKeyFailed failure))
+ Right key -> do
+ Store.loadCachedModuleInstallation
+ memo store key (moduleSyntaxAssertedId syntax) >>= \case
+ Left failure ->
+ pure (Left (FinalPreludeReadinessStoreFailed failure))
+ Right (Just installation) ->
+ pure do
+ sealed <- first FinalPreludeReadinessCachedModuleFailed
+ (cachedSealedTypedModule
+ foundation [] installation)
+ pure
+ (FinalPreludeSession
+ source
+ identified sealed ModuleRootHit)
+ Right Nothing ->
+ build source parsed key
+ where
+ identified = identifiedReservedPrelude parsed
+ parsedId = identifiedParsedModuleId (identifiedModuleParsed identified)
+ syntax = identifiedParsedModuleSyntaxInterface
+ (identifiedModuleParsed identified)
+
+ build source parsed key =
+ FinalPrelude.buildParsedFinalPreludeCandidate
+ foundation parsed resolver >>= \case
+ FinalPrelude.FinalPreludeBuildFailed failure _prefix ->
+ pure (Left (FinalPreludeReadinessBuildFailed failure))
+ FinalPrelude.FinalPreludeBuildOpenFailed failure ->
+ pure (Left (FinalPreludeReadinessBuildOpenFailed failure))
+ FinalPrelude.FinalPreludeBuilt candidate ->
+ publish source key candidate
+ FinalPrelude.FinalPreludeSourceLoadFailed failure ->
+ pure (Left (FinalPreludeReadinessSourceLoadFailed failure))
+ FinalPrelude.FinalPreludeSourceParseFailed failure ->
+ pure (Left (FinalPreludeReadinessSourceParseFailed failure))
+
+ publish source key candidate = do
+ let parsed = FinalPrelude.finalPreludeParsed candidate
+ identified = identifiedReservedPrelude parsed
+ syntax = FinalPrelude.finalPreludeSyntax candidate
+ semantic = FinalPrelude.finalPreludeSemantic candidate
+ prefix = FinalPrelude.finalPreludePrefix candidate
+ artifact =
+ moduleArtifactResult
+ key
+ (moduleSyntaxAssertedId syntax)
+ (semanticInterfaceAssertedId semantic)
+ Store.writeSealedModule
+ store
+ prefix
+ [syntax]
+ [semantic]
+ artifact >>= \case
+ Left failure ->
+ pure (Left (FinalPreludeReadinessStoreFailed failure))
+ Right acknowledged
+ | acknowledged /= artifact ->
+ pure
+ (Left
+ (FinalPreludeReadinessAcknowledgementMismatch
+ artifact
+ acknowledged))
+ | otherwise ->
+ pure
+ (Right
+ (FinalPreludeSession
+ source
+ identified
+ (SealedTypedModule
+ preludeModuleName
+ syntax
+ semantic
+ prefix
+ (Declaration.freshImportedModuleEvidence
+ []
+ semantic
+ prefix))
+ ModuleRootMiss))
+
+
+data TypedModuleInput = TypedModuleInput
+ !ModuleName
+ !IdentifiedModuleInput
+ !ModuleSyntaxInterface
+ !CheckedFoundation
+ !FinalPreludeReadiness
+ ![SealedTypedModule]
+ !Declaration.VampireResolver
+ !Declaration.ValidationRun
+
+data TypedModuleInputError
+ = TypedDirectModuleMismatch ![ModuleName] ![ModuleName]
+ | TypedSyntaxInputMismatch ![SyntaxInterfaceId] ![SyntaxInterfaceId]
+ deriving stock (Show, Eq)
+
+renderTypedModuleInputError :: TypedModuleInputError -> Text
+renderTypedModuleInputError = \case
+ TypedDirectModuleMismatch expected actual ->
+ "direct module inputs differ: expected " <> shown expected
+ <> ", found " <> shown actual
+ TypedSyntaxInputMismatch expected actual ->
+ "direct syntax inputs differ: expected " <> shown expected
+ <> ", found " <> shown actual
+ where
+ shown :: Show value => value -> Text
+ shown = Text.pack . show
+
+typedModuleInput
+ :: CheckedFoundation
+ -> FinalPreludeReadiness
+ -> Declaration.VampireResolver
+ -> Declaration.ValidationRun
+ -> ParsedModule
+ -> [SealedTypedModule]
+ -> Either TypedModuleInputError TypedModuleInput
+typedModuleInput
+ foundation readiness resolver validationRun parsed direct = do
+ unless (expectedOwners == actualOwners)
+ (Left
+ (TypedDirectModuleMismatch
+ expectedOwners
+ actualOwners))
+ unless (expectedSyntax == actualSyntax)
+ (Left
+ (TypedSyntaxInputMismatch
+ expectedSyntax
+ actualSyntax))
+ pure
+ (TypedModuleInput
+ owner
+ identified
+ syntax
+ foundation
+ readiness
+ direct
+ resolver
+ validationRun)
+ where
+ identified = identifiedPhysicalModule parsed
+ owner = identifiedModuleOwner identified
+ syntax = identifiedParsedModuleSyntaxInterface (identifiedModuleParsed identified)
+ expectedOwners =
+ moduleName
+ <$> nubOrd
+ (parsedImportedAddress
+ <$> parsedModuleImports parsed)
+ actualOwners = sealedTypedModuleOwner <$> direct
+ expectedSyntax =
+ nubOrd
+ ( moduleSyntaxAssertedId
+ (sealedTypedModuleSyntax prelude)
+ : ( moduleSyntaxAssertedId
+ . sealedTypedModuleSyntax
+ <$> direct
+ )
+ )
+ actualSyntax = moduleSyntaxDirectInputs syntax
+ FinalPreludeReadiness prelude = readiness
+
+data TypedPathError
+ = TypedExactCompileFailed !Exact.ExactCompileError
+ | TypedExactDatatypeFailed !ExactDatatype.ExactDatatypeError
+ | TypedExactInductiveFailed !ExactInductive.ExactInductiveError
+ | TypedExactProofFailed !ExactProof.ExactProofError
+ | TypedUnmatchedProof !Location
+ deriving stock (Show, Eq)
+
+data TypedModuleFailure
+ = TypedDeclarationFailed !Declaration.DeclarationError
+ | TypedActionFailed !TypedPathError
+ | TypedSealFailed !SemanticInterfaceError
+ deriving stock (Show, Eq)
+
+typedModuleFailureLocation :: TypedModuleFailure -> Maybe Location
+typedModuleFailureLocation = \case
+ TypedActionFailed (TypedExactCompileFailed failure) ->
+ Just (Exact.exactCompileErrorLocation failure)
+ TypedActionFailed (TypedExactDatatypeFailed failure) ->
+ Just (ExactDatatype.exactDatatypeErrorLocation failure)
+ TypedActionFailed (TypedExactInductiveFailed failure) ->
+ Just (ExactInductive.exactInductiveErrorLocation failure)
+ TypedActionFailed (TypedExactProofFailed failure) ->
+ Just (ExactProof.exactProofErrorLocation failure)
+ TypedActionFailed (TypedUnmatchedProof location) ->
+ Just location
+ TypedDeclarationFailed failure ->
+ Declaration.declarationErrorLocation failure
+ TypedSealFailed{} ->
+ Nothing
+
+renderTypedModuleFailure :: TypedModuleFailure -> Text
+renderTypedModuleFailure = \case
+ TypedDeclarationFailed failure ->
+ Declaration.renderDeclarationError failure
+ TypedActionFailed (TypedExactCompileFailed failure) ->
+ Exact.renderExactCompileError failure
+ TypedActionFailed (TypedExactDatatypeFailed failure) ->
+ ExactDatatype.renderExactDatatypeError failure
+ TypedActionFailed (TypedExactInductiveFailed failure) ->
+ ExactInductive.renderExactInductiveError failure
+ TypedActionFailed (TypedExactProofFailed failure) ->
+ ExactProof.renderExactProofError failure
+ TypedActionFailed (TypedUnmatchedProof location) ->
+ locationToText location
+ <> ": this proof does not follow a claim"
+ TypedSealFailed failure ->
+ renderSemanticInterfaceError failure
+
+data TypedModuleResult
+ = TypedModuleOpenFailed !Declaration.DriverOpenError
+ | TypedModuleSucceeded !SealedTypedModule
+ | TypedModuleFailed
+ !TypedModuleFailure
+ !Declaration.PendingModulePrefix
+
+data PlannedTypedDeclaration
+ = PlannedBinding
+ !(Declaration.PlannedDeclaration
+ Exact.CheckedExactBindingAuthorization)
+ | PlannedSourceAxiom
+ !(Declaration.PlannedDeclaration ())
+ | PlannedInductive
+ !(Declaration.PlannedDeclaration
+ ExactInductive.CheckedExactInductiveAuthorization)
+ | PlannedDatatype
+ !(Declaration.PlannedDeclaration
+ ExactDatatype.CheckedExactDatatypeAuthorization)
+ | PlannedStructure
+ !(Declaration.PlannedDeclaration
+ Exact.CheckedExactStructureAuthorization)
+ | PlannedProof
+ !(Declaration.PlannedDeclaration
+ ExactProof.CheckedExactProofAuthorization)
+
+data TypedPlanningFailure
+ = TypedPlanningPathFailure !TypedPathError
+ | TypedPlanningDeclarationFailure !Declaration.DeclarationError
+
+data TypedModulePlan = TypedModulePlan
+ ![PlannedTypedDeclaration]
+ !(Maybe TypedPlanningFailure)
+
+runTypedModule :: TypedModuleInput -> IO TypedModuleResult
+runTypedModule
+ (TypedModuleInput
+ owner identified syntax foundation readiness direct resolver
+ validationRun) = do
+ let FinalPreludeReadiness prelude = readiness
+ action = do
+ traverse_
+ (Declaration.importSealedModuleDriver
+ . sealedTypedModuleEvidence)
+ effectiveDirect
+ TypedModulePlan planned terminal <-
+ Declaration.runProspectiveLoweringDriver
+ (planSourceItems []
+ (typedSourceItems
+ (identifiedParsedModuleBlocks
+ (identifiedModuleParsed identified))))
+ traverse_ admitPlannedDeclaration planned
+ traverse_ failPlanning terminal
+ semanticDirect =
+ semanticInterfaceAssertedId
+ (sealedTypedModuleSemantic prelude)
+ : ( semanticInterfaceAssertedId
+ . sealedTypedModuleSemantic
+ <$> direct
+ )
+ effectiveDirect =
+ prelude : direct
+ occurrences =
+ identifiedParsedModuleSyntaxOccurrences
+ (identifiedModuleParsed identified)
+ planSourceItems completed [] =
+ pure (TypedModulePlan (reverse completed) Nothing)
+ planSourceItems completed (item : remaining) =
+ case item of
+ TypedUnmatchedSourceProof location ->
+ pure
+ (TypedModulePlan
+ (reverse completed)
+ (Just
+ (TypedPlanningPathFailure
+ (TypedUnmatchedProof location))))
+ TypedSourceDeclarationItem declaration -> do
+ planDeclaration declaration >>= \case
+ Left failure ->
+ pure
+ (TypedModulePlan
+ (reverse completed)
+ (Just failure))
+ Right planned ->
+ planSourceItems (planned : completed) remaining
+
+ planDeclaration sourceDeclaration =
+ case block of
+ Raw.BlockClaim{} ->
+ planClaim block explicitProof
+ Raw.BlockProof{} ->
+ impossible
+ "typed declaration association retained a proof head"
+ Raw.BlockSig{} ->
+ planSelected block
+ Raw.BlockAbbr{} ->
+ planSelected block
+ Raw.BlockDefn{} ->
+ planSelected block
+ Raw.BlockAxiom{} ->
+ planSourceAxiom block
+ Raw.BlockInductive{} ->
+ planInductive block
+ Raw.BlockData{} ->
+ planDatatype block
+ Raw.BlockStruct{} ->
+ planStructure block
+ where
+ blockIndex =
+ typedSourceDeclarationBlockIndex sourceDeclaration
+ block = typedSourceDeclarationHead sourceDeclaration
+ explicitProof =
+ typedSourceDeclarationProof sourceDeclaration
+
+ planSelected selected = do
+ prepared <-
+ Exact.prepareExactDeclaration
+ selected
+ [ parsedSyntaxOccurrenceEntry occurrence
+ | occurrence <- occurrences
+ , parsedSyntaxOccurrenceBlockIndex occurrence
+ == blockIndex
+ ]
+ case prepared of
+ Left failure ->
+ pure
+ (Left
+ (TypedPlanningPathFailure
+ (TypedExactCompileFailed failure)))
+ Right declaration -> do
+ Exact.lowerPreparedExactBinding declaration >>= \case
+ Left failure -> planningDeclarationFailure failure
+ Right checked ->
+ planChecked PlannedBinding checked
+
+ planSourceAxiom selected = do
+ prepared <- Exact.prepareExactSourceAxiom selected
+ case prepared of
+ Left failure ->
+ pure
+ (Left
+ (TypedPlanningPathFailure
+ (TypedExactCompileFailed failure)))
+ Right axiom -> do
+ Exact.lowerPreparedExactSourceAxiom axiom >>= \case
+ Left failure -> planningDeclarationFailure failure
+ Right checked ->
+ planChecked PlannedSourceAxiom checked
+
+ planInductive selected = do
+ prepared <-
+ ExactInductive.prepareExactInductive
+ foundation
+ selected
+ [ parsedSyntaxOccurrenceEntry occurrence
+ | occurrence <- occurrences
+ , parsedSyntaxOccurrenceBlockIndex occurrence
+ == blockIndex
+ ]
+ case prepared of
+ Left failure ->
+ pure
+ (Left
+ (TypedPlanningPathFailure
+ (TypedExactInductiveFailed failure)))
+ Right inductive -> do
+ ExactInductive.lowerPreparedExactInductive inductive
+ >>= \case
+ Left failure ->
+ planningDeclarationFailure failure
+ Right checked ->
+ planChecked PlannedInductive checked
+
+ planDatatype selected = do
+ prepared <-
+ ExactDatatype.prepareExactDatatype
+ selected
+ [ ( parsedSyntaxOccurrenceLocation occurrence
+ , parsedSyntaxOccurrenceMarker occurrence
+ , parsedSyntaxOccurrenceEntry occurrence
+ )
+ | occurrence <- occurrences
+ , parsedSyntaxOccurrenceBlockIndex occurrence
+ == blockIndex
+ ]
+ case prepared of
+ Left failure ->
+ pure
+ (Left
+ (TypedPlanningPathFailure
+ (TypedExactDatatypeFailed failure)))
+ Right datatype -> do
+ ExactDatatype.lowerPreparedExactDatatype datatype
+ >>= \case
+ Left failure ->
+ planningDeclarationFailure failure
+ Right checked ->
+ planChecked PlannedDatatype checked
+
+ planStructure selected = do
+ prepared <-
+ Exact.prepareExactStructure
+ selected
+ [ parsedSyntaxOccurrenceEntry occurrence
+ | occurrence <- occurrences
+ , parsedSyntaxOccurrenceBlockIndex occurrence
+ == blockIndex
+ ]
+ case prepared of
+ Left failure ->
+ pure
+ (Left
+ (TypedPlanningPathFailure
+ (TypedExactCompileFailed failure)))
+ Right structure -> do
+ Exact.lowerPreparedExactStructure structure >>= \case
+ Left failure -> planningDeclarationFailure failure
+ Right checked ->
+ planChecked PlannedStructure checked
+
+ planClaim selected selectedProof = do
+ prepared <-
+ ExactProof.prepareExactProof selected selectedProof
+ case prepared of
+ Left failure ->
+ pure
+ (Left
+ (TypedPlanningPathFailure
+ (TypedExactProofFailed failure)))
+ Right proof -> do
+ ExactProof.lowerPreparedExactProof proof >>= \case
+ Left failure -> planningDeclarationFailure failure
+ Right checked ->
+ planChecked PlannedProof checked
+
+ planChecked
+ :: forall body.
+ (Declaration.PlannedDeclaration body
+ -> PlannedTypedDeclaration)
+ -> Declaration.CheckedDeclaration body
+ -> Declaration.LoweringDriver
+ (Either TypedPlanningFailure PlannedTypedDeclaration)
+ planChecked constructor checked =
+ Declaration.planCheckedDeclaration checked >>= \case
+ Left failure -> planningDeclarationFailure failure
+ Right planned -> pure (Right (constructor planned))
+
+ planningDeclarationFailure =
+ pure . Left . TypedPlanningDeclarationFailure
+
+ admitPlannedDeclaration = \case
+ PlannedBinding planned ->
+ void
+ (Declaration.admitPlannedCheckedDeclaration
+ planned Exact.authorizeCheckedExactBinding)
+ PlannedSourceAxiom planned ->
+ void
+ (Declaration.admitPlannedCheckedDeclaration
+ planned Exact.authorizeCheckedExactSourceAxiom)
+ PlannedInductive planned ->
+ void
+ (Declaration.admitPlannedCheckedDeclaration
+ planned
+ ExactInductive.authorizeCheckedExactInductive)
+ PlannedDatatype planned ->
+ void
+ (Declaration.admitPlannedCheckedDeclaration
+ planned ExactDatatype.authorizeCheckedExactDatatype)
+ PlannedStructure planned ->
+ void
+ (Declaration.admitPlannedCheckedDeclaration
+ planned Exact.authorizeCheckedExactStructure)
+ PlannedProof planned ->
+ void
+ (Declaration.admitPlannedCheckedDeclaration
+ planned ExactProof.authorizeCheckedExactProof)
+
+ failPlanning = \case
+ TypedPlanningPathFailure failure ->
+ Declaration.failModuleDriver failure
+ TypedPlanningDeclarationFailure failure ->
+ Declaration.failDeclarationDriver failure
+ result <-
+ Declaration.runModuleDriver
+ foundation
+ owner
+ semanticDirect
+ resolver
+ validationRun
+ action
+ pure case result of
+ Left err ->
+ TypedModuleOpenFailed err
+ Right (Declaration.DriverSucceeded () semantic prefix _closure) ->
+ TypedModuleSucceeded
+ (SealedTypedModule
+ owner
+ syntax
+ semantic
+ prefix
+ (Declaration.freshImportedModuleEvidence
+ (sealedTypedModuleEvidence <$> effectiveDirect)
+ semantic
+ prefix))
+ Right
+ (Declaration.DriverFailed failure prefix) ->
+ TypedModuleFailed
+ (case failure of
+ Declaration.DriverDeclarationFailed err ->
+ TypedDeclarationFailed err
+ Declaration.DriverActionFailed err ->
+ TypedActionFailed err)
+ prefix
+ Right (Declaration.DriverSealFailed err prefix) ->
+ TypedModuleFailed (TypedSealFailed err) prefix
diff --git a/source/Felix/Checking/Semantic.hs b/source/Felix/Checking/Semantic.hs
new file mode 100644
index 0000000..c9442f3
--- /dev/null
+++ b/source/Felix/Checking/Semantic.hs
@@ -0,0 +1,1759 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Canonical semantic declaration, prefix, interface, and validation keys.
+module Felix.Checking.Semantic
+ ( DeclarationSlot
+ , declarationSlot
+ , declarationSlotModule
+ , declarationSlotOrdinal
+ , FactSlot
+ , factSlot
+ , factSlotModule
+ , factSlotOrdinal
+ , SemanticName
+ , semanticName
+ , semanticNameText
+ , FactSearchEligibility(..)
+ , SemanticFactOccurrence
+ , semanticFactOccurrence
+ , semanticFactSlot
+ , semanticFactProposition
+ , semanticFactAuthority
+ , semanticFactSearchEligibility
+ , semanticFactFingerprint
+ , SemanticFactOccurrenceFingerprint
+ , semanticFactOccurrenceFingerprint
+ , semanticFactFingerprintDigest
+ , SemanticAlias
+ , semanticAlias
+ , semanticAliasName
+ , semanticAliasTarget
+ , SemanticGlobalKey(..)
+ , semanticGlobalKeyFromLexicalEntry
+ , semanticGlobalKeyType
+ , SemanticGlobalTarget(..)
+ , semanticGlobalTargetObject
+ , semanticGlobalTargetRequirements
+ , SemanticGlobalBinding
+ , semanticGlobalBinding
+ , semanticGlobalBindingKey
+ , semanticGlobalBindingTarget
+ , SemanticGlobalTargetError(..)
+ , validateSemanticGlobalBindingTarget
+ , SemanticEnvironmentDelta
+ , emptySemanticEnvironmentDelta
+ , semanticEnvironmentDelta
+ , semanticEnvironmentWithStructures
+ , semanticEnvironmentBindings
+ , semanticEnvironmentStructures
+ , SemanticStructurePhrase
+ , semanticStructurePhrase
+ , semanticStructurePhraseSingular
+ , semanticStructurePhrasePlural
+ , semanticStructurePhraseMarker
+ , SemanticStructureOperation
+ , semanticStructureOperation
+ , semanticStructureOperationSymbol
+ , semanticStructureOperationObject
+ , SemanticStructureDescriptor
+ , semanticStructureDescriptor
+ , semanticStructureDescriptorPhrase
+ , semanticStructureDescriptorPredicate
+ , semanticStructureDescriptorParents
+ , semanticStructureDescriptorOperations
+ , SemanticEnvironmentError(..)
+ , DeclarationInterfaceDelta
+ , declarationInterfaceDelta
+ , declarationDeltaSlot
+ , declarationDeltaFacts
+ , declarationDeltaAliases
+ , declarationDeltaObjects
+ , declarationDeltaPropositions
+ , declarationDeltaEnvironment
+ , DeclarationInterfaceError(..)
+ , SemanticInterfaceId
+ , semanticInterfaceIdDigest
+ , SemanticInterface
+ , semanticInterface
+ , semanticInterfaceOwner
+ , semanticInterfaceDirectInputs
+ , semanticInterfaceDeclarations
+ , semanticInterfaceAssertedId
+ , SemanticInterfaceError(..)
+ , validateSemanticInterface
+ , renderSemanticInterfaceError
+ , PrefixContextId
+ , initialPrefixContextId
+ , PrefixContextError(..)
+ , nextPrefixContextId
+ , prefixContextIdDigest
+ , ProofSyntaxId
+ , proofSyntaxId
+ , DeclarationSyntaxId
+ , declarationSyntaxId
+ , ProofValidationKey
+ , proofValidationKey
+ , proofValidationKeyDigest
+ , ProofValidationRecord
+ , proofValidationRecord
+ , proofValidationRecordKey
+ , proofValidationRecordCertificate
+ , DeclarationValidationKey
+ , declarationValidationKey
+ , declarationValidationKeyDigest
+ , DeclarationValidationRecord
+ , declarationValidationRecord
+ , declarationValidationRecordKey
+ , declarationValidationRecordCertificates
+ , ModuleArtifactKey
+ , moduleArtifactKey
+ , moduleArtifactKeyOwner
+ , moduleArtifactKeyDirectSemanticInputs
+ , moduleArtifactKeyTheory
+ , ModuleArtifactKeyError(..)
+ , ModuleArtifactId
+ , moduleArtifactId
+ , moduleArtifactIdDigest
+ , ModuleArtifactResult
+ , moduleArtifactResult
+ , moduleArtifactResultId
+ , moduleArtifactResultSyntax
+ , moduleArtifactResultSemantic
+ , putModuleArtifactKeyCache
+ , getModuleArtifactKeyCache
+ , putModuleArtifactIdCache
+ , getModuleArtifactIdCache
+ , putModuleArtifactResultCache
+ , getModuleArtifactResultCache
+ , putSemanticFactOccurrenceFingerprintCache
+ , getSemanticFactOccurrenceFingerprintCache
+ , putSemanticFactOccurrenceCache
+ , getSemanticFactOccurrenceCache
+ , putSemanticEnvironmentDeltaCache
+ , getSemanticEnvironmentDeltaCache
+ , putSemanticGlobalKeyCache
+ , getSemanticGlobalKeyCache
+ , putDeclarationInterfaceDeltaCache
+ , getDeclarationInterfaceDeltaCache
+ , putSemanticInterfaceCache
+ , getSemanticInterfaceCache
+ , putSemanticInterfaceIdCache
+ , getSemanticInterfaceIdCache
+ , putPrefixContextIdCache
+ , getPrefixContextIdCache
+ , putProofValidationRecordCache
+ , getProofValidationRecordCache
+ , putDeclarationValidationRecordCache
+ , getDeclarationValidationRecordCache
+ ) where
+
+import Base
+import Felix.Checking.Authority
+import Felix.Checking.Core
+import Felix.Checking.Identity
+import Felix.Cache.Codec
+import Felix.Math.Codec
+import Felix.Module
+import Felix.Parsed.Identity
+import Felix.Source
+import Felix.Syntax.Interface
+import Felix.Syntax.Abstract
+
+import Control.DeepSeq (NFData)
+import Control.Monad (unless, when)
+import Data.ByteString (ByteString)
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Numeric.Natural (Natural)
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+
+
+data DeclarationSlot = DeclarationSlot
+ !ModuleName
+ !LocalDeclarationOrdinal
+ deriving stock (Show, Eq, Ord)
+
+declarationSlot
+ :: ModuleName
+ -> LocalDeclarationOrdinal
+ -> DeclarationSlot
+declarationSlot =
+ DeclarationSlot
+
+declarationSlotModule :: DeclarationSlot -> ModuleName
+declarationSlotModule (DeclarationSlot owner _) =
+ owner
+
+declarationSlotOrdinal
+ :: DeclarationSlot
+ -> LocalDeclarationOrdinal
+declarationSlotOrdinal (DeclarationSlot _ ordinal) =
+ ordinal
+
+data FactSlot = FactSlot
+ !ModuleName
+ !LocalFactOrdinal
+ deriving stock (Show, Eq, Ord)
+
+factSlot :: ModuleName -> LocalFactOrdinal -> FactSlot
+factSlot =
+ FactSlot
+
+factSlotModule :: FactSlot -> ModuleName
+factSlotModule (FactSlot owner _) =
+ owner
+
+factSlotOrdinal :: FactSlot -> LocalFactOrdinal
+factSlotOrdinal (FactSlot _ ordinal) =
+ ordinal
+
+newtype SemanticName =
+ SemanticName Text
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+semanticName :: Text -> SemanticName
+semanticName =
+ SemanticName
+
+semanticNameText :: SemanticName -> Text
+semanticNameText (SemanticName name) =
+ name
+
+data FactSearchEligibility
+ = SearchEligible
+ | SearchIneligible
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+newtype SemanticFactOccurrenceFingerprint =
+ SemanticFactOccurrenceFingerprint CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+semanticFactOccurrenceFingerprint
+ :: FactSlot
+ -> FactAuthority
+ -> SemanticFactOccurrenceFingerprint
+semanticFactOccurrenceFingerprint slot authority =
+ SemanticFactOccurrenceFingerprint
+ (hashCacheFields
+ "felix-semantic-fact-occurrence-v1"
+ [ encodeCache (putFactSlotCache slot)
+ , encodeCache (putFactAuthorityCache authority)
+ ])
+
+semanticFactFingerprintDigest
+ :: SemanticFactOccurrenceFingerprint
+ -> CacheDigest
+semanticFactFingerprintDigest
+ (SemanticFactOccurrenceFingerprint digest) =
+ digest
+
+data SemanticFactOccurrence = SemanticFactOccurrence
+ !FactSlot
+ !FactAuthority
+ !FactSearchEligibility
+ deriving stock (Show, Eq, Ord, Generic)
+
+semanticFactOccurrence
+ :: FactSlot
+ -> FactAuthority
+ -> FactSearchEligibility
+ -> SemanticFactOccurrence
+semanticFactOccurrence =
+ SemanticFactOccurrence
+
+semanticFactSlot :: SemanticFactOccurrence -> FactSlot
+semanticFactSlot
+ (SemanticFactOccurrence slot _ _) =
+ slot
+
+semanticFactProposition
+ :: SemanticFactOccurrence
+ -> PropositionId
+semanticFactProposition
+ (SemanticFactOccurrence _ authority _) =
+ theoremRefProposition
+ (factAuthorityTheorem authority)
+
+semanticFactAuthority
+ :: SemanticFactOccurrence
+ -> FactAuthority
+semanticFactAuthority
+ (SemanticFactOccurrence _ authority _) =
+ authority
+
+semanticFactSearchEligibility
+ :: SemanticFactOccurrence
+ -> FactSearchEligibility
+semanticFactSearchEligibility
+ (SemanticFactOccurrence _ _ eligibility) =
+ eligibility
+
+semanticFactFingerprint
+ :: SemanticFactOccurrence
+ -> SemanticFactOccurrenceFingerprint
+semanticFactFingerprint
+ (SemanticFactOccurrence slot authority _) =
+ semanticFactOccurrenceFingerprint slot authority
+
+data SemanticAlias = SemanticAlias
+ !SemanticName
+ !SemanticFactOccurrenceFingerprint
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+semanticAlias
+ :: SemanticName
+ -> SemanticFactOccurrenceFingerprint
+ -> SemanticAlias
+semanticAlias =
+ SemanticAlias
+
+semanticAliasName :: SemanticAlias -> SemanticName
+semanticAliasName (SemanticAlias name _) =
+ name
+
+semanticAliasTarget
+ :: SemanticAlias
+ -> SemanticFactOccurrenceFingerprint
+semanticAliasTarget (SemanticAlias _ target) =
+ target
+
+
+-- | Exact source-level name used to resolve a declared monomorphic object.
+-- Presentation markers and expression fixity are deliberately absent.
+data SemanticGlobalKey
+ = SemanticLeftAdjective !Pattern
+ | SemanticRightAdjective !Pattern
+ | SemanticFunctionPhrase !Pattern !Pattern
+ | SemanticNoun !Pattern !Pattern
+ | SemanticVerb !Pattern !Pattern
+ | SemanticRelation !Token !ParameterArity
+ | SemanticExpressionFunction !Pattern
+ | SemanticPrefixPredicate !Text !Natural
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+semanticGlobalKeyType :: SemanticGlobalKey -> Maybe CoreType
+semanticGlobalKeyType = \case
+ SemanticLeftAdjective pat ->
+ Just (setArguments (1 + patternArity pat) TyProp)
+ SemanticRightAdjective pat ->
+ Just (setArguments (1 + patternArity pat) TyProp)
+ SemanticFunctionPhrase singular plural
+ | patternArity singular == patternArity plural ->
+ Just (setArguments (patternArity singular) TySet)
+ | otherwise -> Nothing
+ SemanticNoun singular plural
+ | patternArity singular == patternArity plural ->
+ Just (setArguments (1 + patternArity singular) TyProp)
+ | otherwise -> Nothing
+ SemanticVerb singular plural
+ | patternArity singular == patternArity plural ->
+ Just (setArguments (1 + patternArity singular) TyProp)
+ | otherwise -> Nothing
+ SemanticRelation _token arity ->
+ Just
+ (setArguments
+ (2 + parameterArityValue arity)
+ TyProp)
+ SemanticExpressionFunction pat ->
+ Just (setArguments (patternArity pat) TySet)
+ SemanticPrefixPredicate _command arity ->
+ Just (setArguments arity TyProp)
+ where
+ patternArity :: Pattern -> Natural
+ patternArity = \case
+ End -> 0
+ HoleCons rest -> 1 + patternArity rest
+ TokenCons _token rest -> patternArity rest
+
+ setArguments :: Natural -> CoreType -> CoreType
+ setArguments argumentCount result =
+ foldr
+ (const (TyArrow TySet))
+ result
+ [1 .. argumentCount]
+
+semanticGlobalKeyFromLexicalEntry
+ :: CanonicalLexicalEntry
+ -> Maybe SemanticGlobalKey
+semanticGlobalKeyFromLexicalEntry = \case
+ CanonicalLeftAdjective pat _marker ->
+ Just (SemanticLeftAdjective pat)
+ CanonicalRightAdjective pat _marker ->
+ Just (SemanticRightAdjective pat)
+ CanonicalFunctionPhrase singular plural _marker ->
+ Just (SemanticFunctionPhrase singular plural)
+ CanonicalNoun singular plural _marker ->
+ Just (SemanticNoun singular plural)
+ CanonicalVerb singular plural _marker ->
+ Just (SemanticVerb singular plural)
+ CanonicalRelation token arity _marker ->
+ Just (SemanticRelation token arity)
+ CanonicalExpressionFunction pat _marker _fixity ->
+ Just (SemanticExpressionFunction pat)
+ CanonicalPrefixPredicate command arity _marker ->
+ Just (SemanticPrefixPredicate command arity)
+ CanonicalStructureNoun{} ->
+ Nothing
+ CanonicalStructureOperation{} ->
+ Nothing
+
+data SemanticGlobalTarget
+ = GlobalReference !ObjectId
+ | TransparentExpansion !ObjectId
+ | ContextualTransparentExpansion
+ !ObjectId
+ !(Map.Map StructSymbol ObjectId)
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+semanticGlobalTargetObject :: SemanticGlobalTarget -> ObjectId
+semanticGlobalTargetObject = \case
+ GlobalReference identity -> identity
+ TransparentExpansion identity -> identity
+ ContextualTransparentExpansion identity _requirements -> identity
+
+semanticGlobalTargetRequirements
+ :: SemanticGlobalTarget
+ -> Map.Map StructSymbol ObjectId
+semanticGlobalTargetRequirements = \case
+ GlobalReference{} -> Map.empty
+ TransparentExpansion{} -> Map.empty
+ ContextualTransparentExpansion _identity requirements -> requirements
+
+data SemanticGlobalBinding = SemanticGlobalBinding
+ !SemanticGlobalKey
+ !SemanticGlobalTarget
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+semanticGlobalBinding
+ :: SemanticGlobalKey
+ -> SemanticGlobalTarget
+ -> SemanticGlobalBinding
+semanticGlobalBinding =
+ SemanticGlobalBinding
+
+semanticGlobalBindingKey
+ :: SemanticGlobalBinding
+ -> SemanticGlobalKey
+semanticGlobalBindingKey (SemanticGlobalBinding key _target) =
+ key
+
+semanticGlobalBindingTarget
+ :: SemanticGlobalBinding
+ -> SemanticGlobalTarget
+semanticGlobalBindingTarget (SemanticGlobalBinding _key target) =
+ target
+
+data SemanticGlobalTargetError
+ = SemanticGlobalKeyHasInconsistentArity !SemanticGlobalKey
+ | SemanticGlobalTargetMissing !ObjectId
+ | SemanticGlobalTargetIsIntrinsic !ObjectId
+ | SemanticGlobalTargetTypeMismatch !ObjectId !CoreType !CoreType
+ | SemanticGlobalExpansionNotTransparent !ObjectId
+ | SemanticGlobalContextualRequirementsEmpty !ObjectId
+ | SemanticGlobalContextualRequirementMissing !StructSymbol !ObjectId
+ | SemanticGlobalContextualRequirementIsIntrinsic !StructSymbol !ObjectId
+ | SemanticGlobalContextualRequirementTypeMismatch
+ !StructSymbol !ObjectId !CoreType !CoreType
+ | SemanticGlobalContextualRequirementNotProvided
+ !StructSymbol !ObjectId
+ | SemanticGlobalContextualRequirementNotReferenced !StructSymbol !ObjectId
+ deriving stock (Show, Eq)
+
+validateSemanticGlobalBindingTarget
+ :: Set.Set (StructSymbol, ObjectId)
+ -> CheckedObjectClosure
+ -> SemanticGlobalBinding
+ -> Either SemanticGlobalTargetError ()
+validateSemanticGlobalBindingTarget operationBindings closure binding = do
+ expected <-
+ maybe
+ (Left (SemanticGlobalKeyHasInconsistentArity key))
+ Right
+ (semanticGlobalKeyType key)
+ content <-
+ maybe
+ (Left (SemanticGlobalTargetMissing identity))
+ Right
+ (lookupCheckedObjectContent identity closure)
+ when
+ (objectIdFamily identity == IntrinsicObject)
+ (Left (SemanticGlobalTargetIsIntrinsic identity))
+ let targetExpected =
+ case target of
+ ContextualTransparentExpansion{} ->
+ TyArrow TySet expected
+ _ -> expected
+ actual = objectContentType content
+ unless
+ (actual == targetExpected)
+ (Left
+ (SemanticGlobalTargetTypeMismatch
+ identity targetExpected actual))
+ case target of
+ GlobalReference{} -> pure ()
+ TransparentExpansion{} ->
+ validateTransparent content
+ ContextualTransparentExpansion _ requirements -> do
+ validateTransparent content
+ when
+ (Map.null requirements)
+ (Left (SemanticGlobalContextualRequirementsEmpty identity))
+ traverse_ (validateRequirement content) (Map.toAscList requirements)
+ where
+ key = semanticGlobalBindingKey binding
+ target = semanticGlobalBindingTarget binding
+ identity = semanticGlobalTargetObject target
+
+ validateTransparent = \case
+ TransparentObjectContent{} -> pure ()
+ _ -> Left (SemanticGlobalExpansionNotTransparent identity)
+
+ validateRequirement content (symbol, object) = do
+ operationContent <-
+ maybe
+ (Left
+ (SemanticGlobalContextualRequirementMissing
+ symbol object))
+ Right
+ (lookupCheckedObjectContent object closure)
+ when
+ (objectIdFamily object == IntrinsicObject)
+ (Left
+ (SemanticGlobalContextualRequirementIsIntrinsic
+ symbol object))
+ let expectedOperation = TyArrow TySet TySet
+ actualOperation = objectContentType operationContent
+ unless
+ (actualOperation == expectedOperation)
+ (Left
+ (SemanticGlobalContextualRequirementTypeMismatch
+ symbol object expectedOperation actualOperation))
+ unless
+ ((symbol, object) `Set.member` operationBindings)
+ (Left
+ (SemanticGlobalContextualRequirementNotProvided
+ symbol object))
+ case content of
+ TransparentObjectContent _theory _coreType body ->
+ unless
+ (object `Set.member` canonicalTermGlobals body)
+ (Left
+ (SemanticGlobalContextualRequirementNotReferenced
+ symbol object))
+ _ -> impossible "a contextual expansion was not transparent"
+
+data SemanticEnvironmentDelta
+ = EmptySemanticEnvironmentDelta
+ | SemanticGlobalBindings ![SemanticGlobalBinding]
+ | SemanticGlobalBindingsAndStructures
+ ![SemanticGlobalBinding]
+ ![SemanticStructureDescriptor]
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+data SemanticStructurePhrase = SemanticStructurePhrase
+ !Pattern
+ !Pattern
+ !Marker
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+semanticStructurePhrase :: LexicalItemSgPl -> SemanticStructurePhrase
+semanticStructurePhrase (LexicalItemSgPl forms marker) =
+ SemanticStructurePhrase (sg forms) (pl forms) marker
+
+semanticStructurePhraseSingular :: SemanticStructurePhrase -> Pattern
+semanticStructurePhraseSingular (SemanticStructurePhrase singular _ _) =
+ singular
+
+semanticStructurePhrasePlural :: SemanticStructurePhrase -> Pattern
+semanticStructurePhrasePlural (SemanticStructurePhrase _ plural _) =
+ plural
+
+semanticStructurePhraseMarker :: SemanticStructurePhrase -> Marker
+semanticStructurePhraseMarker (SemanticStructurePhrase _ _ marker) =
+ marker
+
+data SemanticStructureOperation = SemanticStructureOperation
+ !StructSymbol
+ !ObjectId
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+semanticStructureOperation
+ :: StructSymbol
+ -> ObjectId
+ -> SemanticStructureOperation
+semanticStructureOperation =
+ SemanticStructureOperation
+
+semanticStructureOperationSymbol
+ :: SemanticStructureOperation
+ -> StructSymbol
+semanticStructureOperationSymbol (SemanticStructureOperation symbol _) =
+ symbol
+
+semanticStructureOperationObject
+ :: SemanticStructureOperation
+ -> ObjectId
+semanticStructureOperationObject (SemanticStructureOperation _ object) =
+ object
+
+data SemanticStructureDescriptor = SemanticStructureDescriptor
+ !SemanticStructurePhrase
+ !(Maybe ObjectId)
+ ![SemanticStructurePhrase]
+ ![SemanticStructureOperation]
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+semanticStructureDescriptor
+ :: SemanticStructurePhrase
+ -> Maybe ObjectId
+ -> [SemanticStructurePhrase]
+ -> [SemanticStructureOperation]
+ -> Either SemanticEnvironmentError SemanticStructureDescriptor
+semanticStructureDescriptor structurePhrase predicate parents operations = do
+ case firstDuplicate parents of
+ Just duplicate ->
+ Left (DuplicateSemanticStructureParent duplicate)
+ Nothing -> pure ()
+ when
+ (structurePhrase `elem` parents)
+ (Left (SelfSemanticStructureParent structurePhrase))
+ case firstDuplicate (semanticStructureOperationSymbol <$> operations) of
+ Just duplicate ->
+ Left (DuplicateSemanticStructureOperation duplicate)
+ Nothing -> pure ()
+ pure
+ (SemanticStructureDescriptor
+ structurePhrase predicate parents operations)
+
+semanticStructureDescriptorPhrase
+ :: SemanticStructureDescriptor
+ -> SemanticStructurePhrase
+semanticStructureDescriptorPhrase
+ (SemanticStructureDescriptor structurePhrase _ _ _) =
+ structurePhrase
+
+semanticStructureDescriptorPredicate
+ :: SemanticStructureDescriptor
+ -> Maybe ObjectId
+semanticStructureDescriptorPredicate
+ (SemanticStructureDescriptor _ predicate _ _) =
+ predicate
+
+semanticStructureDescriptorParents
+ :: SemanticStructureDescriptor
+ -> [SemanticStructurePhrase]
+semanticStructureDescriptorParents
+ (SemanticStructureDescriptor _ _ parents _) =
+ parents
+
+semanticStructureDescriptorOperations
+ :: SemanticStructureDescriptor
+ -> [SemanticStructureOperation]
+semanticStructureDescriptorOperations
+ (SemanticStructureDescriptor _ _ _ operations) =
+ operations
+
+data SemanticEnvironmentError
+ = DuplicateSemanticGlobalKey !SemanticGlobalKey
+ | NonCanonicalSemanticGlobalBindingOrder
+ | DuplicateSemanticStructure !SemanticStructurePhrase
+ | NonCanonicalSemanticStructureOrder
+ | DuplicateSemanticStructureParent !SemanticStructurePhrase
+ | SelfSemanticStructureParent !SemanticStructurePhrase
+ | DuplicateSemanticStructureOperation !StructSymbol
+ deriving stock (Show, Eq)
+
+emptySemanticEnvironmentDelta :: SemanticEnvironmentDelta
+emptySemanticEnvironmentDelta =
+ EmptySemanticEnvironmentDelta
+
+semanticEnvironmentDelta
+ :: [SemanticGlobalBinding]
+ -> Either SemanticEnvironmentError SemanticEnvironmentDelta
+semanticEnvironmentDelta bindings =
+ semanticEnvironmentWithStructures bindings []
+
+semanticEnvironmentWithStructures
+ :: [SemanticGlobalBinding]
+ -> [SemanticStructureDescriptor]
+ -> Either SemanticEnvironmentError SemanticEnvironmentDelta
+semanticEnvironmentWithStructures [] [] =
+ Right EmptySemanticEnvironmentDelta
+semanticEnvironmentWithStructures bindings structures = do
+ case firstDuplicate (semanticGlobalBindingKey <$> bindings) of
+ Just duplicate ->
+ Left (DuplicateSemanticGlobalKey duplicate)
+ Nothing ->
+ pure ()
+ unless
+ (bindings == List.sortOn semanticGlobalBindingKey bindings)
+ (Left NonCanonicalSemanticGlobalBindingOrder)
+ case firstDuplicate (semanticStructureDescriptorPhrase <$> structures) of
+ Just duplicate ->
+ Left (DuplicateSemanticStructure duplicate)
+ Nothing -> pure ()
+ unless
+ ( structures
+ == List.sortOn semanticStructureDescriptorPhrase structures
+ )
+ (Left NonCanonicalSemanticStructureOrder)
+ pure
+ (case structures of
+ [] -> SemanticGlobalBindings bindings
+ _ -> SemanticGlobalBindingsAndStructures bindings structures)
+
+semanticEnvironmentBindings
+ :: SemanticEnvironmentDelta
+ -> [SemanticGlobalBinding]
+semanticEnvironmentBindings = \case
+ EmptySemanticEnvironmentDelta -> []
+ SemanticGlobalBindings bindings -> bindings
+ SemanticGlobalBindingsAndStructures bindings _ -> bindings
+
+semanticEnvironmentStructures
+ :: SemanticEnvironmentDelta
+ -> [SemanticStructureDescriptor]
+semanticEnvironmentStructures = \case
+ EmptySemanticEnvironmentDelta -> []
+ SemanticGlobalBindings{} -> []
+ SemanticGlobalBindingsAndStructures _ structures -> structures
+
+
+data DeclarationInterfaceDelta = DeclarationInterfaceDelta
+ !DeclarationSlot
+ ![SemanticFactOccurrence]
+ ![SemanticAlias]
+ ![ObjectId]
+ ![PropositionId]
+ !SemanticEnvironmentDelta
+ deriving stock (Show, Eq, Ord, Generic)
+
+data DeclarationInterfaceError
+ = DeclarationFactOwnerMismatch !FactSlot
+ | DuplicateDeclarationFactSlot !FactSlot
+ | DuplicateDeclarationFactFingerprint
+ !SemanticFactOccurrenceFingerprint
+ | DuplicateDeclarationAlias !SemanticName
+ | DuplicateDeclarationObject !ObjectId
+ | DuplicateDeclarationProposition !PropositionId
+ | DeclarationFactPropositionMissing !PropositionId
+ deriving stock (Show, Eq)
+
+declarationInterfaceDelta
+ :: DeclarationSlot
+ -> [SemanticFactOccurrence]
+ -> [SemanticAlias]
+ -> [ObjectId]
+ -> [PropositionId]
+ -> SemanticEnvironmentDelta
+ -> Either DeclarationInterfaceError DeclarationInterfaceDelta
+declarationInterfaceDelta
+ slot facts aliases objects propositions environment = do
+ traverse_ validateFact facts
+ rejectDuplicate
+ DuplicateDeclarationFactSlot
+ (semanticFactSlot <$> facts)
+ rejectDuplicate
+ DuplicateDeclarationFactFingerprint
+ (semanticFactFingerprint <$> facts)
+ rejectDuplicate
+ DuplicateDeclarationAlias
+ (semanticAliasName <$> aliases)
+ rejectDuplicate DuplicateDeclarationObject objects
+ rejectDuplicate DuplicateDeclarationProposition propositions
+ pure
+ (DeclarationInterfaceDelta
+ slot facts aliases objects propositions environment)
+ where
+ owner = declarationSlotModule slot
+ propositionSet = Set.fromList propositions
+
+ validateFact occurrence = do
+ unless
+ (factSlotModule (semanticFactSlot occurrence) == owner)
+ (Left
+ (DeclarationFactOwnerMismatch
+ (semanticFactSlot occurrence)))
+ let proposition =
+ semanticFactProposition occurrence
+ unless
+ (proposition `Set.member` propositionSet)
+ (Left (DeclarationFactPropositionMissing proposition))
+
+declarationDeltaSlot
+ :: DeclarationInterfaceDelta
+ -> DeclarationSlot
+declarationDeltaSlot
+ (DeclarationInterfaceDelta slot _ _ _ _ _) =
+ slot
+
+declarationDeltaFacts
+ :: DeclarationInterfaceDelta
+ -> [SemanticFactOccurrence]
+declarationDeltaFacts
+ (DeclarationInterfaceDelta _ facts _ _ _ _) =
+ facts
+
+declarationDeltaAliases
+ :: DeclarationInterfaceDelta
+ -> [SemanticAlias]
+declarationDeltaAliases
+ (DeclarationInterfaceDelta _ _ aliases _ _ _) =
+ aliases
+
+declarationDeltaObjects
+ :: DeclarationInterfaceDelta
+ -> [ObjectId]
+declarationDeltaObjects
+ (DeclarationInterfaceDelta _ _ _ objects _ _) =
+ objects
+
+declarationDeltaPropositions
+ :: DeclarationInterfaceDelta
+ -> [PropositionId]
+declarationDeltaPropositions
+ (DeclarationInterfaceDelta _ _ _ _ propositions _) =
+ propositions
+
+declarationDeltaEnvironment
+ :: DeclarationInterfaceDelta
+ -> SemanticEnvironmentDelta
+declarationDeltaEnvironment
+ (DeclarationInterfaceDelta _ _ _ _ _ environment) =
+ environment
+
+
+newtype SemanticInterfaceId =
+ SemanticInterfaceId CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+semanticInterfaceIdDigest
+ :: SemanticInterfaceId
+ -> CacheDigest
+semanticInterfaceIdDigest (SemanticInterfaceId digest) =
+ digest
+
+data SemanticInterface = SemanticInterface
+ !ModuleName
+ ![SemanticInterfaceId]
+ ![DeclarationInterfaceDelta]
+ !SemanticInterfaceId
+ deriving stock (Show, Eq, Ord)
+
+data SemanticInterfaceError
+ = DuplicateDirectSemanticInterface !SemanticInterfaceId
+ | SemanticDeclarationOwnerMismatch !DeclarationSlot
+ | NonIncreasingDeclarationSlots
+ | NonIncreasingFactSlots
+ | SemanticInterfaceIdMismatch
+ !SemanticInterfaceId
+ !SemanticInterfaceId
+ deriving stock (Show, Eq)
+
+renderSemanticInterfaceError :: SemanticInterfaceError -> Text
+renderSemanticInterfaceError = \case
+ DuplicateDirectSemanticInterface interface ->
+ "direct semantic interface occurs more than once: "
+ <> Text.pack (show interface)
+ SemanticDeclarationOwnerMismatch slot ->
+ "declaration belongs to a different module: "
+ <> Text.pack (show slot)
+ NonIncreasingDeclarationSlots ->
+ "declaration slots are not in increasing order"
+ NonIncreasingFactSlots ->
+ "fact slots are not in increasing order"
+ SemanticInterfaceIdMismatch expected actual ->
+ "semantic interface identity mismatch: expected "
+ <> Text.pack (show expected)
+ <> ", found " <> Text.pack (show actual)
+
+semanticInterface
+ :: ModuleName
+ -> [SemanticInterfaceId]
+ -> [DeclarationInterfaceDelta]
+ -> Either SemanticInterfaceError SemanticInterface
+semanticInterface owner direct declarations = do
+ validateSemanticInterfaceStructure owner direct declarations
+ let identity =
+ computeSemanticInterfaceId owner direct declarations
+ pure (SemanticInterface owner direct declarations identity)
+
+validateSemanticInterface
+ :: ModuleName
+ -> [SemanticInterfaceId]
+ -> [DeclarationInterfaceDelta]
+ -> SemanticInterfaceId
+ -> Either SemanticInterfaceError SemanticInterface
+validateSemanticInterface owner direct declarations asserted = do
+ validateSemanticInterfaceStructure owner direct declarations
+ let computed =
+ computeSemanticInterfaceId
+ owner direct declarations
+ unless
+ (asserted == computed)
+ (Left
+ (SemanticInterfaceIdMismatch asserted computed))
+ pure
+ (SemanticInterface
+ owner direct declarations asserted)
+
+validateSemanticInterfaceStructure
+ :: ModuleName
+ -> [SemanticInterfaceId]
+ -> [DeclarationInterfaceDelta]
+ -> Either SemanticInterfaceError ()
+validateSemanticInterfaceStructure owner direct declarations = do
+ rejectDuplicate
+ DuplicateDirectSemanticInterface
+ direct
+ traverse_
+ (\delta ->
+ unless
+ (declarationSlotModule
+ (declarationDeltaSlot delta)
+ == owner)
+ (Left
+ (SemanticDeclarationOwnerMismatch
+ (declarationDeltaSlot delta))))
+ declarations
+ unless
+ (strictlyIncreasing
+ ( localDeclarationOrdinalValue
+ . declarationSlotOrdinal
+ . declarationDeltaSlot
+ <$> declarations))
+ (Left NonIncreasingDeclarationSlots)
+ unless
+ (strictlyIncreasing
+ ( localFactOrdinalValue
+ . factSlotOrdinal
+ . semanticFactSlot
+ <$> concatMap
+ declarationDeltaFacts
+ declarations))
+ (Left NonIncreasingFactSlots)
+
+semanticInterfaceOwner :: SemanticInterface -> ModuleName
+semanticInterfaceOwner
+ (SemanticInterface owner _ _ _) =
+ owner
+
+semanticInterfaceDirectInputs
+ :: SemanticInterface
+ -> [SemanticInterfaceId]
+semanticInterfaceDirectInputs
+ (SemanticInterface _ direct _ _) =
+ direct
+
+semanticInterfaceDeclarations
+ :: SemanticInterface
+ -> [DeclarationInterfaceDelta]
+semanticInterfaceDeclarations
+ (SemanticInterface _ _ declarations _) =
+ declarations
+
+semanticInterfaceAssertedId
+ :: SemanticInterface
+ -> SemanticInterfaceId
+semanticInterfaceAssertedId
+ (SemanticInterface _ _ _ asserted) =
+ asserted
+
+computeSemanticInterfaceId
+ :: ModuleName
+ -> [SemanticInterfaceId]
+ -> [DeclarationInterfaceDelta]
+ -> SemanticInterfaceId
+computeSemanticInterfaceId owner direct declarations =
+ SemanticInterfaceId
+ (hashCacheFields
+ "felix-semantic-interface-v1"
+ [ encodeCache (putModuleNameCache owner)
+ , encodeCache
+ (putCacheList
+ putSemanticInterfaceIdCache
+ direct)
+ , encodeCache
+ (putCacheList
+ putDeclarationInterfaceDeltaCache
+ declarations)
+ ])
+
+
+newtype PrefixContextId =
+ PrefixContextId CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+data PrefixContextError
+ = DuplicateInitialPrefixSemanticInput !SemanticInterfaceId
+ deriving stock (Show, Eq)
+
+initialPrefixContextId
+ :: TheoryId
+ -> ModuleName
+ -> [SemanticInterfaceId]
+ -> Either PrefixContextError PrefixContextId
+initialPrefixContextId theory owner direct = do
+ rejectDuplicate
+ DuplicateInitialPrefixSemanticInput
+ direct
+ pure
+ (PrefixContextId
+ (hashCacheFields
+ "felix-prefix-initial-v1"
+ [ encodeCache (putTheoryIdCache theory)
+ , encodeCache (putModuleNameCache owner)
+ , encodeCache
+ (putCacheList
+ putSemanticInterfaceIdCache
+ direct)
+ ]))
+
+nextPrefixContextId
+ :: PrefixContextId
+ -> DeclarationInterfaceDelta
+ -> PrefixContextId
+nextPrefixContextId previous delta =
+ PrefixContextId
+ (hashCacheFields
+ "felix-prefix-step-v1"
+ [ encodeCache (putPrefixContextIdCache previous)
+ , encodeCache
+ (putDeclarationInterfaceDeltaCache delta)
+ ])
+
+prefixContextIdDigest :: PrefixContextId -> CacheDigest
+prefixContextIdDigest (PrefixContextId digest) =
+ digest
+
+
+newtype ProofSyntaxId =
+ ProofSyntaxId CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+proofSyntaxId :: ByteString -> ProofSyntaxId
+proofSyntaxId bytes =
+ ProofSyntaxId
+ (hashCacheFields "felix-proof-syntax-v1" [bytes])
+
+newtype DeclarationSyntaxId =
+ DeclarationSyntaxId CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+declarationSyntaxId :: ByteString -> DeclarationSyntaxId
+declarationSyntaxId bytes =
+ DeclarationSyntaxId
+ (hashCacheFields "felix-declaration-syntax-v1" [bytes])
+
+newtype ProofValidationKey =
+ ProofValidationKey CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+proofValidationKey
+ :: TheoremId
+ -> ProofSyntaxId
+ -> PrefixContextId
+ -> ProofValidationKey
+proofValidationKey theorem (ProofSyntaxId syntax) prefix =
+ ProofValidationKey
+ (hashCacheFields
+ "felix-proof-validation"
+ [ mathematicalDigestBytes
+ (theoremIdDigest theorem)
+ , cacheDigestBytes syntax
+ , encodeCache (putPrefixContextIdCache prefix)
+ ])
+
+proofValidationKeyDigest
+ :: ProofValidationKey
+ -> CacheDigest
+proofValidationKeyDigest (ProofValidationKey digest) =
+ digest
+
+data ProofValidationRecord = ProofValidationRecord
+ !ProofValidationKey
+ !ValidationCertificate
+ deriving stock (Show, Eq, Ord, Generic)
+
+proofValidationRecord
+ :: ProofValidationKey
+ -> ValidationCertificate
+ -> ProofValidationRecord
+proofValidationRecord =
+ ProofValidationRecord
+
+proofValidationRecordKey
+ :: ProofValidationRecord
+ -> ProofValidationKey
+proofValidationRecordKey
+ (ProofValidationRecord key _) =
+ key
+
+proofValidationRecordCertificate
+ :: ProofValidationRecord
+ -> ValidationCertificate
+proofValidationRecordCertificate
+ (ProofValidationRecord _ certificate) =
+ certificate
+
+newtype DeclarationValidationKey =
+ DeclarationValidationKey CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+declarationValidationKey
+ :: DeclarationSyntaxId
+ -> PrefixContextId
+ -> [ObjectId]
+ -> [TheoremId]
+ -> DeclarationValidationKey
+declarationValidationKey
+ (DeclarationSyntaxId syntax)
+ prefix objects theorems =
+ DeclarationValidationKey
+ (hashCacheFields
+ "felix-declaration-validation"
+ [ cacheDigestBytes syntax
+ , encodeCache (putPrefixContextIdCache prefix)
+ , encodeCache (putCacheList putObjectIdCache objects)
+ , encodeCache
+ (putCacheList
+ (putMathematicalDigestCache . theoremIdDigest)
+ theorems)
+ ])
+
+declarationValidationKeyDigest
+ :: DeclarationValidationKey
+ -> CacheDigest
+declarationValidationKeyDigest
+ (DeclarationValidationKey digest) =
+ digest
+
+data DeclarationValidationRecord = DeclarationValidationRecord
+ !DeclarationValidationKey
+ ![ValidationCertificate]
+ deriving stock (Show, Eq, Ord, Generic)
+
+declarationValidationRecord
+ :: DeclarationValidationKey
+ -> [ValidationCertificate]
+ -> DeclarationValidationRecord
+declarationValidationRecord =
+ DeclarationValidationRecord
+
+declarationValidationRecordKey
+ :: DeclarationValidationRecord
+ -> DeclarationValidationKey
+declarationValidationRecordKey
+ (DeclarationValidationRecord key _) =
+ key
+
+declarationValidationRecordCertificates
+ :: DeclarationValidationRecord
+ -> [ValidationCertificate]
+declarationValidationRecordCertificates
+ (DeclarationValidationRecord _ certificates) =
+ certificates
+
+
+data ModuleArtifactKey = ModuleArtifactKey
+ !ModuleName
+ !ParsedModuleId
+ ![SemanticInterfaceId]
+ !TheoryId
+ deriving stock (Show, Eq, Ord)
+
+data ModuleArtifactKeyError
+ = DuplicateModuleArtifactSemanticInput
+ !SemanticInterfaceId
+ deriving stock (Show, Eq)
+
+moduleArtifactKey
+ :: ModuleName
+ -> ParsedModuleId
+ -> [SemanticInterfaceId]
+ -> TheoryId
+ -> Either ModuleArtifactKeyError ModuleArtifactKey
+moduleArtifactKey owner parsed direct theory = do
+ rejectDuplicate
+ DuplicateModuleArtifactSemanticInput
+ direct
+ pure (ModuleArtifactKey owner parsed direct theory)
+
+moduleArtifactKeyOwner :: ModuleArtifactKey -> ModuleName
+moduleArtifactKeyOwner (ModuleArtifactKey owner _parsed _direct _theory) =
+ owner
+
+moduleArtifactKeyDirectSemanticInputs
+ :: ModuleArtifactKey
+ -> [SemanticInterfaceId]
+moduleArtifactKeyDirectSemanticInputs
+ (ModuleArtifactKey _owner _parsed direct _theory) =
+ direct
+
+moduleArtifactKeyTheory :: ModuleArtifactKey -> TheoryId
+moduleArtifactKeyTheory
+ (ModuleArtifactKey _owner _parsed _direct theory) =
+ theory
+
+newtype ModuleArtifactId =
+ ModuleArtifactId CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+moduleArtifactId :: ModuleArtifactKey -> ModuleArtifactId
+moduleArtifactId key =
+ ModuleArtifactId
+ (hashCacheFields
+ "felix-module-artifact-v1"
+ [encodeCache (putModuleArtifactKeyCache key)])
+
+moduleArtifactIdDigest :: ModuleArtifactId -> CacheDigest
+moduleArtifactIdDigest (ModuleArtifactId digest) =
+ digest
+
+data ModuleArtifactResult = ModuleArtifactResult
+ !ModuleArtifactId
+ !SyntaxInterfaceId
+ !SemanticInterfaceId
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+moduleArtifactResult
+ :: ModuleArtifactKey
+ -> SyntaxInterfaceId
+ -> SemanticInterfaceId
+ -> ModuleArtifactResult
+moduleArtifactResult key =
+ ModuleArtifactResult (moduleArtifactId key)
+
+moduleArtifactResultId
+ :: ModuleArtifactResult
+ -> ModuleArtifactId
+moduleArtifactResultId
+ (ModuleArtifactResult identity _ _) =
+ identity
+
+moduleArtifactResultSyntax
+ :: ModuleArtifactResult
+ -> SyntaxInterfaceId
+moduleArtifactResultSyntax
+ (ModuleArtifactResult _ syntax _) =
+ syntax
+
+moduleArtifactResultSemantic
+ :: ModuleArtifactResult
+ -> SemanticInterfaceId
+moduleArtifactResultSemantic
+ (ModuleArtifactResult _ _ semantic) =
+ semantic
+
+
+putSemanticFactOccurrenceFingerprintCache
+ :: SemanticFactOccurrenceFingerprint
+ -> CachePut
+putSemanticFactOccurrenceFingerprintCache
+ (SemanticFactOccurrenceFingerprint digest) =
+ putCacheDigest digest
+
+getSemanticFactOccurrenceFingerprintCache
+ :: CacheGet SemanticFactOccurrenceFingerprint
+getSemanticFactOccurrenceFingerprintCache =
+ SemanticFactOccurrenceFingerprint <$> getCacheDigest
+
+putSemanticFactOccurrenceCache
+ :: SemanticFactOccurrence
+ -> CachePut
+putSemanticFactOccurrenceCache
+ (SemanticFactOccurrence slot authority eligibility) = do
+ putFactSlotCache slot
+ putFactAuthorityCache authority
+ putEligibility eligibility
+
+getSemanticFactOccurrenceCache
+ :: CacheGet SemanticFactOccurrence
+getSemanticFactOccurrenceCache =
+ semanticFactOccurrence
+ <$> getFactSlotCache
+ <*> getFactAuthorityCache
+ <*> getEligibility
+
+putSemanticEnvironmentDeltaCache
+ :: SemanticEnvironmentDelta
+ -> CachePut
+putSemanticEnvironmentDeltaCache EmptySemanticEnvironmentDelta =
+ putCacheTag 0x00
+putSemanticEnvironmentDeltaCache (SemanticGlobalBindings bindings) = do
+ putCacheTag 0x01
+ putCacheList putSemanticGlobalBindingCache bindings
+putSemanticEnvironmentDeltaCache
+ (SemanticGlobalBindingsAndStructures bindings structures) = do
+ putCacheTag 0x02
+ putCacheList putSemanticGlobalBindingCache bindings
+ putCacheList putSemanticStructureDescriptorCache structures
+
+getSemanticEnvironmentDeltaCache
+ :: CacheGet SemanticEnvironmentDelta
+getSemanticEnvironmentDeltaCache =
+ getCacheTag >>= \case
+ 0x00 ->
+ pure EmptySemanticEnvironmentDelta
+ 0x01 -> do
+ bindings <- getCacheList getSemanticGlobalBindingCache
+ either
+ (fail . ("invalid semantic environment delta: " <>) . show)
+ pure
+ (semanticEnvironmentDelta bindings)
+ 0x02 -> do
+ bindings <- getCacheList getSemanticGlobalBindingCache
+ structures <- getCacheList getSemanticStructureDescriptorCache
+ either
+ (fail . ("invalid semantic environment delta: " <>) . show)
+ pure
+ (semanticEnvironmentWithStructures bindings structures)
+ tag ->
+ fail
+ ("unknown semantic environment delta tag "
+ <> show tag)
+
+putSemanticGlobalKeyCache :: SemanticGlobalKey -> CachePut
+putSemanticGlobalKeyCache = \case
+ SemanticLeftAdjective pat -> do
+ putCacheTag 0x00
+ putPatternCache pat
+ SemanticRightAdjective pat -> do
+ putCacheTag 0x01
+ putPatternCache pat
+ SemanticFunctionPhrase singular plural -> do
+ putCacheTag 0x02
+ putPatternCache singular
+ putPatternCache plural
+ SemanticNoun singular plural -> do
+ putCacheTag 0x03
+ putPatternCache singular
+ putPatternCache plural
+ SemanticVerb singular plural -> do
+ putCacheTag 0x04
+ putPatternCache singular
+ putPatternCache plural
+ SemanticRelation token arity -> do
+ putCacheTag 0x05
+ putTokenCache token
+ putCacheNatural (parameterArityValue arity)
+ SemanticExpressionFunction pat -> do
+ putCacheTag 0x06
+ putPatternCache pat
+ SemanticPrefixPredicate command arity -> do
+ putCacheTag 0x07
+ putCacheText command
+ putCacheNatural arity
+
+getSemanticGlobalKeyCache :: CacheGet SemanticGlobalKey
+getSemanticGlobalKeyCache =
+ getCacheTag >>= \case
+ 0x00 -> SemanticLeftAdjective <$> getPatternCache
+ 0x01 -> SemanticRightAdjective <$> getPatternCache
+ 0x02 ->
+ SemanticFunctionPhrase
+ <$> getPatternCache
+ <*> getPatternCache
+ 0x03 ->
+ SemanticNoun
+ <$> getPatternCache
+ <*> getPatternCache
+ 0x04 ->
+ SemanticVerb
+ <$> getPatternCache
+ <*> getPatternCache
+ 0x05 ->
+ SemanticRelation
+ <$> getTokenCache
+ <*> (ParameterArity <$> getCacheNatural)
+ 0x06 -> SemanticExpressionFunction <$> getPatternCache
+ 0x07 ->
+ SemanticPrefixPredicate
+ <$> getCacheText
+ <*> getCacheNatural
+ tag ->
+ fail ("unknown semantic global key tag " <> show tag)
+
+putSemanticGlobalBindingCache :: SemanticGlobalBinding -> CachePut
+putSemanticGlobalBindingCache (SemanticGlobalBinding key target) = do
+ putSemanticGlobalKeyCache key
+ case target of
+ GlobalReference identity -> do
+ putCacheTag 0x00
+ putObjectIdCache identity
+ TransparentExpansion identity -> do
+ putCacheTag 0x01
+ putObjectIdCache identity
+ ContextualTransparentExpansion identity requirements -> do
+ putCacheTag 0x02
+ putObjectIdCache identity
+ putCanonicalCacheMap
+ (\(StructSymbol symbol) -> putCacheText symbol)
+ putObjectIdCache
+ requirements
+
+getSemanticGlobalBindingCache :: CacheGet SemanticGlobalBinding
+getSemanticGlobalBindingCache =
+ SemanticGlobalBinding
+ <$> getSemanticGlobalKeyCache
+ <*> (getCacheTag >>= \case
+ 0x00 -> GlobalReference <$> getObjectIdCache
+ 0x01 -> TransparentExpansion <$> getObjectIdCache
+ 0x02 ->
+ ContextualTransparentExpansion
+ <$> getObjectIdCache
+ <*> getCanonicalCacheMap
+ (StructSymbol <$> getCacheText)
+ getObjectIdCache
+ tag ->
+ fail
+ ("unknown semantic global target tag "
+ <> show tag))
+
+putSemanticStructureDescriptorCache
+ :: SemanticStructureDescriptor
+ -> CachePut
+putSemanticStructureDescriptorCache
+ (SemanticStructureDescriptor structurePhrase predicate parents operations) = do
+ putSemanticStructurePhraseCache structurePhrase
+ putCacheMaybe putObjectIdCache predicate
+ putCacheList putSemanticStructurePhraseCache parents
+ putCacheList putSemanticStructureOperationCache operations
+
+getSemanticStructureDescriptorCache
+ :: CacheGet SemanticStructureDescriptor
+getSemanticStructureDescriptorCache = do
+ structurePhrase <- getSemanticStructurePhraseCache
+ predicate <- getCacheMaybe getObjectIdCache
+ parents <- getCacheList getSemanticStructurePhraseCache
+ operations <- getCacheList getSemanticStructureOperationCache
+ either
+ (fail . ("invalid semantic structure descriptor: " <>) . show)
+ pure
+ (semanticStructureDescriptor structurePhrase predicate parents operations)
+
+putSemanticStructurePhraseCache
+ :: SemanticStructurePhrase
+ -> CachePut
+putSemanticStructurePhraseCache
+ (SemanticStructurePhrase singular plural (Marker marker)) = do
+ putPatternCache singular
+ putPatternCache plural
+ putCacheText marker
+
+getSemanticStructurePhraseCache
+ :: CacheGet SemanticStructurePhrase
+getSemanticStructurePhraseCache =
+ SemanticStructurePhrase
+ <$> getPatternCache
+ <*> getPatternCache
+ <*> (Marker <$> getCacheText)
+
+putSemanticStructureOperationCache
+ :: SemanticStructureOperation
+ -> CachePut
+putSemanticStructureOperationCache
+ (SemanticStructureOperation (StructSymbol symbol) object) = do
+ putCacheText symbol
+ putObjectIdCache object
+
+getSemanticStructureOperationCache
+ :: CacheGet SemanticStructureOperation
+getSemanticStructureOperationCache =
+ SemanticStructureOperation
+ <$> (StructSymbol <$> getCacheText)
+ <*> getObjectIdCache
+
+putDeclarationInterfaceDeltaCache
+ :: DeclarationInterfaceDelta
+ -> CachePut
+putDeclarationInterfaceDeltaCache
+ (DeclarationInterfaceDelta
+ slot facts aliases objects propositions environment) = do
+ putDeclarationSlotCache slot
+ putCacheList putSemanticFactOccurrenceCache facts
+ putCacheList putSemanticAliasCache aliases
+ putCacheList putObjectIdCache objects
+ putCacheList putPropositionIdCache propositions
+ putSemanticEnvironmentDeltaCache environment
+
+getDeclarationInterfaceDeltaCache
+ :: CacheGet DeclarationInterfaceDelta
+getDeclarationInterfaceDeltaCache = do
+ slot <- getDeclarationSlotCache
+ facts <- getCacheList getSemanticFactOccurrenceCache
+ aliases <- getCacheList getSemanticAliasCache
+ objects <- getCacheList getObjectIdCache
+ propositions <- getCacheList getPropositionIdCache
+ environment <- getSemanticEnvironmentDeltaCache
+ either
+ (fail . ("invalid declaration interface delta: " <>) . show)
+ pure
+ (declarationInterfaceDelta
+ slot facts aliases objects propositions environment)
+
+putSemanticInterfaceCache :: SemanticInterface -> CachePut
+putSemanticInterfaceCache
+ (SemanticInterface owner direct declarations asserted) = do
+ putModuleNameCache owner
+ putCacheList putSemanticInterfaceIdCache direct
+ putCacheList putDeclarationInterfaceDeltaCache declarations
+ putSemanticInterfaceIdCache asserted
+
+getSemanticInterfaceCache :: CacheGet SemanticInterface
+getSemanticInterfaceCache = do
+ owner <- getModuleNameCache
+ direct <- getCacheList getSemanticInterfaceIdCache
+ declarations <- getCacheList getDeclarationInterfaceDeltaCache
+ asserted <- getSemanticInterfaceIdCache
+ either
+ (fail . ("invalid semantic interface: " <>) . show)
+ pure
+ (validateSemanticInterface
+ owner direct declarations asserted)
+
+putSemanticInterfaceIdCache :: SemanticInterfaceId -> CachePut
+putSemanticInterfaceIdCache (SemanticInterfaceId digest) =
+ putCacheDigest digest
+
+getSemanticInterfaceIdCache :: CacheGet SemanticInterfaceId
+getSemanticInterfaceIdCache =
+ SemanticInterfaceId <$> getCacheDigest
+
+putPrefixContextIdCache :: PrefixContextId -> CachePut
+putPrefixContextIdCache (PrefixContextId digest) =
+ putCacheDigest digest
+
+getPrefixContextIdCache :: CacheGet PrefixContextId
+getPrefixContextIdCache =
+ PrefixContextId <$> getCacheDigest
+
+putModuleArtifactKeyCache :: ModuleArtifactKey -> CachePut
+putModuleArtifactKeyCache =
+ putModuleArtifactKeyFields
+
+getModuleArtifactKeyCache :: CacheGet ModuleArtifactKey
+getModuleArtifactKeyCache = do
+ owner <- getModuleNameCache
+ parsed <- getParsedModuleIdCache
+ direct <- getCacheList getSemanticInterfaceIdCache
+ theory <- getTheoryIdCache
+ either
+ (fail . ("invalid module artifact key: " <>) . show)
+ pure
+ (moduleArtifactKey owner parsed direct theory)
+
+putModuleArtifactIdCache :: ModuleArtifactId -> CachePut
+putModuleArtifactIdCache (ModuleArtifactId digest) =
+ putCacheDigest digest
+
+getModuleArtifactIdCache :: CacheGet ModuleArtifactId
+getModuleArtifactIdCache =
+ ModuleArtifactId <$> getCacheDigest
+
+putModuleArtifactResultCache
+ :: ModuleArtifactResult
+ -> CachePut
+putModuleArtifactResultCache
+ (ModuleArtifactResult identity syntax semantic) = do
+ putModuleArtifactIdCache identity
+ putSyntaxInterfaceIdCache syntax
+ putSemanticInterfaceIdCache semantic
+
+getModuleArtifactResultCache
+ :: ModuleArtifactId
+ -> CacheGet ModuleArtifactResult
+getModuleArtifactResultCache expected = do
+ asserted <- getModuleArtifactIdCache
+ unless
+ (asserted == expected)
+ (fail "module artifact result ID mismatch")
+ ModuleArtifactResult asserted
+ <$> getSyntaxInterfaceIdCache
+ <*> getSemanticInterfaceIdCache
+
+putProofValidationRecordCache
+ :: ProofValidationRecord
+ -> CachePut
+putProofValidationRecordCache
+ (ProofValidationRecord key certificate) = do
+ putProofValidationKeyCache key
+ putValidationCertificateCache certificate
+
+getProofValidationRecordCache
+ :: CacheGet ProofValidationRecord
+getProofValidationRecordCache =
+ ProofValidationRecord
+ <$> getProofValidationKeyCache
+ <*> getValidationCertificateCache
+
+putDeclarationValidationRecordCache
+ :: DeclarationValidationRecord
+ -> CachePut
+putDeclarationValidationRecordCache
+ (DeclarationValidationRecord key certificates) = do
+ putDeclarationValidationKeyCache key
+ putCacheList putValidationCertificateCache certificates
+
+getDeclarationValidationRecordCache
+ :: CacheGet DeclarationValidationRecord
+getDeclarationValidationRecordCache =
+ DeclarationValidationRecord
+ <$> getDeclarationValidationKeyCache
+ <*> getCacheList getValidationCertificateCache
+
+
+putDeclarationSlotCache :: DeclarationSlot -> CachePut
+putDeclarationSlotCache (DeclarationSlot owner ordinal) = do
+ putModuleNameCache owner
+ putCacheNatural (localDeclarationOrdinalValue ordinal)
+
+getDeclarationSlotCache :: CacheGet DeclarationSlot
+getDeclarationSlotCache =
+ DeclarationSlot
+ <$> getModuleNameCache
+ <*> (localDeclarationOrdinal <$> getCacheNatural)
+
+putFactSlotCache :: FactSlot -> CachePut
+putFactSlotCache (FactSlot owner ordinal) = do
+ putModuleNameCache owner
+ putCacheNatural (localFactOrdinalValue ordinal)
+
+getFactSlotCache :: CacheGet FactSlot
+getFactSlotCache =
+ FactSlot
+ <$> getModuleNameCache
+ <*> (localFactOrdinal <$> getCacheNatural)
+
+putModuleNameCache :: ModuleName -> CachePut
+putModuleNameCache owner = do
+ putMathematicalDigestCache
+ (sourceNamespaceDigest
+ (moduleNameNamespace owner))
+ putCacheText
+ (Text.pack
+ (safeRelativePathFilePath
+ (moduleNameRelativePath owner)))
+
+getModuleNameCache :: CacheGet ModuleName
+getModuleNameCache = do
+ namespace <-
+ sourceNamespaceIdFromDigest
+ <$> getMathematicalDigestCache
+ rawPath <- Text.unpack <$> getCacheText
+ relative <-
+ either
+ (fail . ("invalid cache module path: " <>) . show)
+ pure
+ (safeRelativePath rawPath)
+ pure (moduleNameFromParts namespace relative)
+
+putSemanticAliasCache :: SemanticAlias -> CachePut
+putSemanticAliasCache (SemanticAlias name target) = do
+ putSemanticNameCache name
+ putSemanticFactOccurrenceFingerprintCache target
+
+getSemanticAliasCache :: CacheGet SemanticAlias
+getSemanticAliasCache =
+ SemanticAlias
+ <$> getSemanticNameCache
+ <*> getSemanticFactOccurrenceFingerprintCache
+
+putSemanticNameCache :: SemanticName -> CachePut
+putSemanticNameCache (SemanticName name) =
+ putCacheText name
+
+getSemanticNameCache :: CacheGet SemanticName
+getSemanticNameCache =
+ SemanticName <$> getCacheText
+
+putEligibility :: FactSearchEligibility -> CachePut
+putEligibility = \case
+ SearchEligible -> putCacheTag 0x00
+ SearchIneligible -> putCacheTag 0x01
+
+getEligibility :: CacheGet FactSearchEligibility
+getEligibility =
+ getCacheTag >>= \case
+ 0x00 -> pure SearchEligible
+ 0x01 -> pure SearchIneligible
+ tag ->
+ fail ("unknown fact-search eligibility tag " <> show tag)
+
+putModuleArtifactKeyFields :: ModuleArtifactKey -> CachePut
+putModuleArtifactKeyFields
+ (ModuleArtifactKey owner parsed direct theory) = do
+ putModuleNameCache owner
+ putParsedModuleIdCache parsed
+ putCacheList putSemanticInterfaceIdCache direct
+ putTheoryIdCache theory
+
+putProofValidationKeyCache :: ProofValidationKey -> CachePut
+putProofValidationKeyCache (ProofValidationKey digest) =
+ putCacheDigest digest
+
+getProofValidationKeyCache :: CacheGet ProofValidationKey
+getProofValidationKeyCache =
+ ProofValidationKey <$> getCacheDigest
+
+putDeclarationValidationKeyCache
+ :: DeclarationValidationKey
+ -> CachePut
+putDeclarationValidationKeyCache
+ (DeclarationValidationKey digest) =
+ putCacheDigest digest
+
+getDeclarationValidationKeyCache
+ :: CacheGet DeclarationValidationKey
+getDeclarationValidationKeyCache =
+ DeclarationValidationKey <$> getCacheDigest
+
+firstDuplicate :: Ord value => [value] -> Maybe value
+firstDuplicate =
+ go Set.empty
+ where
+ go _ [] =
+ Nothing
+ go seen (value : rest)
+ | value `Set.member` seen =
+ Just value
+ | otherwise =
+ go (Set.insert value seen) rest
+
+rejectDuplicate
+ :: Ord value
+ => (value -> error)
+ -> [value]
+ -> Either error ()
+rejectDuplicate makeError values =
+ maybe
+ (Right ())
+ (Left . makeError)
+ (firstDuplicate values)
+
+strictlyIncreasing :: Ord value => [value] -> Bool
+strictlyIncreasing values =
+ and
+ (zipWith (<) values (drop 1 values))
diff --git a/source/Felix/Checking/SetConstruction.hs b/source/Felix/Checking/SetConstruction.hs
new file mode 100644
index 0000000..a5e1d80
--- /dev/null
+++ b/source/Felix/Checking/SetConstruction.hs
@@ -0,0 +1,1191 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Checked source semantics for named separation and functional replacement.
+--
+-- A value of 'NamedSetConstruction' is the sole transient owner of the
+-- source decomposition. Its smart constructors validate the complete
+-- telescope and derive one canonical term. Local views, transparent content,
+-- direct extensional facts, and cache-scoped descriptors all consume that
+-- same checked value.
+module Felix.Checking.SetConstruction
+ ( NamedSetConstruction
+ , checkedSeparationConstruction
+ , checkedFunctionalReplacementConstruction
+ , namedSetConstructionTerm
+ , namedSetConstructionLocalViews
+ , namedSetConstructionClosedBody
+ , SetConstructionFoundation
+ , setConstructionFoundation
+ , checkedFoundationSetConstruction
+ , NamedSetConstructionFact
+ , namedSetConstructionFactProposition
+ , namedSetConstructionFactDescriptor
+ , namedSetConstructionObjectFact
+ , CheckedRelationalSetConstruction
+ , checkedRelationalReplacementConstruction
+ , relationalSetConstructionTerm
+ , relationalSetConstructionFunctionality
+ , relationalSetConstructionClosedFunctionality
+ , relationalSetConstructionLocalViews
+ , relationalSetConstructionClosedBody
+ , RelationalSetConstructionFact
+ , relationalSetConstructionFactProposition
+ , relationalSetConstructionFactDescriptor
+ , relationalSetConstructionObjectFact
+ ) where
+
+import Base hiding (Empty)
+import Felix.Checking.Core
+import Felix.Checking.Foundation
+import Felix.Checking.Identity
+import Felix.Cache.Codec
+ ( CacheDigest
+ , encodeCache
+ , hashCacheFields
+ , putCanonicalTermCache
+ , putCoreTypeCache
+ )
+
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Numeric.Natural (Natural)
+
+
+-- | The exact fixed rows used by the narrow derived extensionality schema.
+-- Proof-local construction obtains these rows through the confined foundation
+-- lookup; declaration authorization obtains them from 'CheckedFoundation'.
+data SetConstructionFoundation = SetConstructionFoundation
+ !(FrozenCheckedCore Void)
+ !(FrozenCheckedCore Void)
+ !(FrozenCheckedCore Void)
+ !(FrozenCheckedCore Void)
+
+setConstructionFoundation
+ :: FrozenCheckedCore Void
+ -> FrozenCheckedCore Void
+ -> FrozenCheckedCore Void
+ -> FrozenCheckedCore Void
+ -> SetConstructionFoundation
+setConstructionFoundation =
+ SetConstructionFoundation
+
+checkedFoundationSetConstruction
+ :: CheckedFoundation
+ -> SetConstructionFoundation
+checkedFoundationSetConstruction foundation =
+ SetConstructionFoundation
+ (foundationAxiomFrozen foundation FamilyUnionCharacteristic)
+ (foundationAxiomFrozen foundation SeparationCharacteristic)
+ (foundationAxiomFrozen foundation ReplacementCharacteristic)
+ (foundationAxiomFrozen foundation SetChooseWitness)
+
+data NamedSetConstruction global = NamedSetConstruction
+ ![CoreType]
+ !(Map.Map global CoreType)
+ !(NamedSetConstructionShape global)
+ !(BuiltSetConstruction global)
+ deriving stock (Eq)
+
+data NamedSetConstructionShape global
+ = SeparationShape
+ !(CanonicalTerm global)
+ !(CanonicalTerm global)
+ | FunctionalReplacementShape
+ !(NonEmpty (CanonicalTerm global))
+ !(CanonicalTerm global)
+ !(Maybe (CanonicalTerm global))
+ deriving stock (Eq)
+
+-- | The one canonical build result retained by the checked construction.
+-- Characteristic applications describe the exact primitive rows used by the
+-- derived theorem schema; the flattened body is the deterministic composition
+-- of those rows for the source telescope.
+data BuiltSetConstruction global = BuiltSetConstruction
+ !(CanonicalTerm global)
+ !(CanonicalTerm global)
+ ![CheckedCharacteristicApplication global]
+ deriving stock (Eq)
+
+data CheckedCharacteristicApplication global =
+ CheckedCharacteristicApplication
+ !CoreIntrinsicTag
+ ![CoreType]
+ !(CanonicalTerm global)
+ !(NonEmpty (CoreType, CanonicalTerm global))
+ !(CanonicalTerm global)
+ deriving stock (Eq)
+
+-- | One checked relational replacement. Unlike the unconditional named
+-- constructions above, its flattened membership theorem is available only
+-- after the separately checked functionality proposition has authority.
+-- This value owns the source telescope, the one canonical choice/replacement
+-- term, and the exact primitive characteristic applications used by that
+-- narrow derived schema.
+data CheckedRelationalSetConstruction global =
+ CheckedRelationalSetConstruction
+ ![CoreType]
+ !(Map.Map global CoreType)
+ !(CanonicalTerm global)
+ !(CanonicalTerm global)
+ !(CanonicalTerm global)
+ !(CanonicalTerm global)
+ !(CanonicalTerm global)
+ ![CheckedCharacteristicApplication global]
+ deriving stock (Eq)
+
+-- | Validate one source relational replacement. The relation is checked in
+-- the nearest-first context @[range, domain] <> outer@, matching the source
+-- binder order @y x A P@ without retaining source syntax.
+checkedRelationalReplacementConstruction
+ :: Ord global
+ => (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Maybe (CheckedRelationalSetConstruction global)
+checkedRelationalReplacementConstruction globalType domain relation = do
+ guard (scopedCoreType domain == TySet)
+ guard (scopedCoreType relation == TyProp)
+ let context = scopedCoreContext domain
+ guard (scopedCoreContext relation == TySet : TySet : context)
+ globals <-
+ captureGlobalTypes globalType
+ [scopedCoreTerm domain, scopedCoreTerm relation]
+ let domainTerm = scopedCoreTerm domain
+ relationTerm = scopedCoreTerm relation
+ domainPredicate = CLam TySet (logicalExists relationTerm)
+ restrictedDomain = applyIntrinsic2 Sep domainTerm domainPredicate
+ choiceFunction =
+ CLam TySet
+ (applyIntrinsic SetChoose (CLam TySet relationTerm))
+ replacement = applyIntrinsic2 Repl restrictedDomain choiceFunction
+ functionality = relationalFunctionality domainTerm relationTerm
+ membership = relationalMembership domainTerm relationTerm
+ applications =
+ [ characteristicApplication
+ Sep context restrictedDomain
+ [ (TySet, domainTerm)
+ , (TyArrow TySet TyProp, domainPredicate)
+ ]
+ , characteristicApplication
+ Repl context replacement
+ [ (TySet, restrictedDomain)
+ , (TyArrow TySet TySet, choiceFunction)
+ ]
+ ]
+ construction =
+ CheckedRelationalSetConstruction
+ context globals domainTerm relationTerm replacement
+ functionality membership applications
+ _ <- checkedRelationalDerived construction context TySet replacement
+ _ <- checkedRelationalDerived construction context TyProp functionality
+ _ <- checkedRelationalDerived
+ construction (TySet : context) TyProp membership
+ pure construction
+
+-- | Check one source separation. The callback supplies the exact visible
+-- type of every global used by its already checked components.
+checkedSeparationConstruction
+ :: Ord global
+ => (global -> Maybe CoreType)
+ -> ScopedCheckedCore global
+ -> ScopedCheckedCore global
+ -> Maybe (NamedSetConstruction global)
+checkedSeparationConstruction globalType bound predicate = do
+ guard (scopedCoreType bound == TySet)
+ guard (scopedCoreType predicate == TyProp)
+ let context = scopedCoreContext bound
+ guard (scopedCoreContext predicate == TySet : context)
+ globals <-
+ captureGlobalTypes globalType
+ [scopedCoreTerm bound, scopedCoreTerm predicate]
+ finishConstruction
+ context
+ globals
+ (SeparationShape
+ (scopedCoreTerm bound)
+ (scopedCoreTerm predicate))
+
+-- | Check source-ordered functional replacement once. Domain @i@ is checked
+-- beneath exactly the preceding @i - 1@ source binders; the value and optional
+-- condition are checked beneath the complete telescope.
+checkedFunctionalReplacementConstruction
+ :: Ord global
+ => (global -> Maybe CoreType)
+ -> NonEmpty (ScopedCheckedCore global)
+ -> ScopedCheckedCore global
+ -> Maybe (ScopedCheckedCore global)
+ -> Maybe (NamedSetConstruction global)
+checkedFunctionalReplacementConstruction globalType domains value condition = do
+ let domainList = NonEmpty.toList domains
+ firstDomain = NonEmpty.head domains
+ guard (scopedCoreType firstDomain == TySet)
+ let context = scopedCoreContext firstDomain
+ expectedDomainContexts =
+ [ replicate index TySet <> context
+ | index <- [0 .. length domainList - 1]
+ ]
+ valueContext = replicate (length domainList) TySet <> context
+ guard
+ (and
+ (zipWith
+ (\domain expected ->
+ scopedCoreType domain == TySet
+ && scopedCoreContext domain == expected)
+ domainList
+ expectedDomainContexts))
+ guard
+ (scopedCoreType value == TySet
+ && scopedCoreContext value == valueContext)
+ traverse_
+ (\predicate ->
+ guard
+ (scopedCoreType predicate == TyProp
+ && scopedCoreContext predicate == valueContext))
+ condition
+ globals <-
+ captureGlobalTypes globalType
+ ( (scopedCoreTerm <$> domainList)
+ <> [scopedCoreTerm value]
+ <> maybeToList (scopedCoreTerm <$> condition)
+ )
+ finishConstruction
+ context
+ globals
+ (FunctionalReplacementShape
+ (scopedCoreTerm <$> domains)
+ (scopedCoreTerm value)
+ (scopedCoreTerm <$> condition))
+
+namedSetConstructionTerm
+ :: Ord global
+ => NamedSetConstruction global
+ -> ScopedCheckedCore global
+namedSetConstructionTerm construction =
+ fromMaybe
+ (impossible "a checked construction lost its canonical term")
+ (checkedDerived
+ construction
+ (constructionContext construction)
+ TySet
+ (constructionCanonicalTerm construction))
+
+-- | Introduce a fresh named set and return adjacent FOF extensional and exact
+-- equation locals. Weakening is internal so construction-local binders and
+-- source-domain order cannot drift at a caller.
+namedSetConstructionLocalViews
+ :: Ord global
+ => SetConstructionFoundation
+ -> NamedSetConstruction global
+ -> Maybe
+ ( ScopedCheckedCore global
+ , ScopedCheckedCore global
+ )
+namedSetConstructionLocalViews foundation construction = do
+ weakened <- weakenConstruction TySet construction
+ let context = TySet : constructionContext construction
+ target = CBound 0
+ extensional <- extensionalView foundation target weakened
+ equation <- checkedDerived weakened context TyProp
+ (CEq TySet target (constructionCanonicalTerm weakened))
+ pure (extensional, equation)
+
+-- | Closed transparent content derived from the sole canonical construction
+-- term. This retains the pre-existing object-content identity exactly.
+namedSetConstructionClosedBody
+ :: Ord global
+ => NamedSetConstruction global
+ -> FrozenCheckedCore global
+namedSetConstructionClosedBody construction =
+ fromMaybe
+ (impossible "a checked construction did not close")
+ (freezeDerived construction closedType closedTerm)
+ where
+ context = constructionContext construction
+ closedType = foldr TyArrow TySet (reverse context)
+ closedTerm =
+ foldl (flip CLam) (constructionCanonicalTerm construction) context
+
+data NamedSetConstructionFact = NamedSetConstructionFact
+ !(FrozenCheckedCore ObjectId)
+ !CacheDigest
+
+namedSetConstructionFactProposition
+ :: NamedSetConstructionFact
+ -> FrozenCheckedCore ObjectId
+namedSetConstructionFactProposition
+ (NamedSetConstructionFact proposition _descriptor) =
+ proposition
+
+namedSetConstructionFactDescriptor
+ :: NamedSetConstructionFact
+ -> CacheDigest
+namedSetConstructionFactDescriptor
+ (NamedSetConstructionFact _proposition descriptor) =
+ descriptor
+
+-- | Derive the only proposition authorized by
+-- @CheckedSetConstructionExtensionality@. This is a deliberately small
+-- trusted theorem schema over the fixed foundation: every primitive
+-- characteristic specialization is checked against its exact membership
+-- formula before the source telescope is composed. The caller cannot supply
+-- either the resulting proposition or its cache descriptor.
+namedSetConstructionObjectFact
+ :: SetConstructionFoundation
+ -> ObjectId
+ -> NamedSetConstruction ObjectId
+ -> Maybe NamedSetConstructionFact
+namedSetConstructionObjectFact foundation object construction = do
+ let context = constructionContext construction
+ objectType = foldr TyArrow TySet (reverse context)
+ globals <- insertGlobalType object objectType (constructionGlobals construction)
+ let withObject = replaceConstructionGlobals globals construction
+ target =
+ foldl
+ CApp
+ (CGlobal object)
+ [ CBound (fromIntegral index)
+ | index <- reverse [0 .. length context - 1]
+ ]
+ view <- extensionalView foundation target withObject
+ proposition <- freezeDerived withObject TyProp
+ (foldl
+ (flip CForall)
+ (scopedCoreTerm view)
+ context)
+ selfView <- extensionalView
+ foundation
+ (constructionCanonicalTerm construction)
+ construction
+ closedSelf <- freezeDerived construction TyProp
+ (foldl
+ (flip CForall)
+ (scopedCoreTerm selfView)
+ context)
+ let closedBody = namedSetConstructionClosedBody construction
+ descriptor =
+ hashCacheFields
+ "felix-checked-named-set-construction-v1"
+ [ encodeCache do
+ putCoreTypeCache (frozenCoreType closedBody)
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm closedBody)
+ , encodeCache do
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm closedSelf)
+ ]
+ pure (NamedSetConstructionFact proposition descriptor)
+
+relationalSetConstructionTerm
+ :: Ord global
+ => CheckedRelationalSetConstruction global
+ -> ScopedCheckedCore global
+relationalSetConstructionTerm construction =
+ fromMaybe
+ (impossible "a checked relational construction lost its canonical term")
+ (checkedRelationalDerived
+ construction
+ (relationalConstructionContext construction)
+ TySet
+ (relationalConstructionCanonicalTerm construction))
+
+-- | The exact source functionality obligation, still scoped by the outer
+-- definition parameters. It is discharged independently before the derived
+-- extensional theorem can be authorized.
+relationalSetConstructionFunctionality
+ :: Ord global
+ => CheckedRelationalSetConstruction global
+ -> ScopedCheckedCore global
+relationalSetConstructionFunctionality construction =
+ fromMaybe
+ (impossible "a checked relational construction lost functionality")
+ (checkedRelationalDerived
+ construction
+ (relationalConstructionContext construction)
+ TyProp
+ (relationalConstructionFunctionalityTerm construction))
+
+relationalSetConstructionClosedFunctionality
+ :: Ord global
+ => CheckedRelationalSetConstruction global
+ -> FrozenCheckedCore global
+relationalSetConstructionClosedFunctionality =
+ closeRelationalFunctionality
+
+-- | Introduce a fresh named set. Assuming the exact checked functionality
+-- proposition, derive its two local views; no arbitrary proposition can
+-- unlock the extensional view. The enclosing proof transaction owns the
+-- corresponding authority.
+relationalSetConstructionLocalViews
+ :: Ord global
+ => SetConstructionFoundation
+ -> CheckedRelationalSetConstruction global
+ -> ScopedCheckedCore global
+ -> Maybe
+ ( ScopedCheckedCore global
+ , ScopedCheckedCore global
+ )
+relationalSetConstructionLocalViews foundation construction functionality = do
+ guard
+ (functionality
+ == relationalSetConstructionFunctionality construction)
+ validateRelationalSchema foundation construction
+ let context = TySet : relationalConstructionContext construction
+ target = CBound 0
+ extensional <- checkedRelationalDerived construction context TyProp
+ (CForall TySet
+ (CEq TyProp
+ (member (CBound 0) (CBound 1))
+ (shiftCanonical 1 1
+ (relationalConstructionMembershipTerm construction))))
+ equation <- checkedRelationalDerived construction context TyProp
+ (CEq TySet
+ target
+ (shiftCanonical 1 0
+ (relationalConstructionCanonicalTerm construction)))
+ pure (extensional, equation)
+
+relationalSetConstructionClosedBody
+ :: Ord global
+ => CheckedRelationalSetConstruction global
+ -> FrozenCheckedCore global
+relationalSetConstructionClosedBody construction =
+ fromMaybe
+ (impossible "a checked relational construction did not close")
+ (freezeRelationalDerived construction closedType closedTerm)
+ where
+ context = relationalConstructionContext construction
+ closedType = foldr TyArrow TySet (reverse context)
+ closedTerm =
+ foldl
+ (flip CLam)
+ (relationalConstructionCanonicalTerm construction)
+ context
+
+data RelationalSetConstructionFact = RelationalSetConstructionFact
+ !(FrozenCheckedCore ObjectId)
+ !CacheDigest
+
+relationalSetConstructionFactProposition
+ :: RelationalSetConstructionFact
+ -> FrozenCheckedCore ObjectId
+relationalSetConstructionFactProposition
+ (RelationalSetConstructionFact proposition _descriptor) =
+ proposition
+
+relationalSetConstructionFactDescriptor
+ :: RelationalSetConstructionFact
+ -> CacheDigest
+relationalSetConstructionFactDescriptor
+ (RelationalSetConstructionFact _proposition descriptor) =
+ descriptor
+
+-- | The direct relational schema is a deterministic theorem over the fixed
+-- separation, choice-witness, and replacement rows. The functionality fact
+-- is a real strictly-earlier candidate: its exact proposition is checked here
+-- and its authority safety is consumed separately by declaration admission.
+relationalSetConstructionObjectFact
+ :: SetConstructionFoundation
+ -> ObjectId
+ -> CheckedRelationalSetConstruction ObjectId
+ -> FrozenCheckedCore ObjectId
+ -> Maybe RelationalSetConstructionFact
+relationalSetConstructionObjectFact
+ foundation object construction functionality = do
+ let expectedFunctionality =
+ closeRelationalFunctionality construction
+ guard (functionality == expectedFunctionality)
+ validateRelationalSchema foundation construction
+ let context = relationalConstructionContext construction
+ objectType = foldr TyArrow TySet (reverse context)
+ globals <- insertGlobalType
+ object objectType (relationalConstructionGlobals construction)
+ let withObject = replaceRelationalGlobals globals construction
+ target =
+ foldl
+ CApp
+ (CGlobal object)
+ [ CBound (fromIntegral index)
+ | index <- reverse [0 .. length context - 1]
+ ]
+ membership = relationalConstructionMembershipTerm withObject
+ proposition <- freezeRelationalDerived withObject TyProp
+ (foldl
+ (flip CForall)
+ (CForall TySet
+ (CEq TyProp
+ (member (CBound 0) (shiftCanonical 1 0 target))
+ membership))
+ context)
+ closedSelf <- freezeRelationalDerived construction TyProp
+ (foldl
+ (flip CForall)
+ (CForall TySet
+ (CEq TyProp
+ (member
+ (CBound 0)
+ (shiftCanonical 1 0
+ (relationalConstructionCanonicalTerm construction)))
+ (relationalConstructionMembershipTerm construction)))
+ context)
+ let closedBody = relationalSetConstructionClosedBody construction
+ descriptor =
+ hashCacheFields
+ "felix-checked-named-relational-set-construction-v1"
+ [ encodeCache do
+ putCoreTypeCache (frozenCoreType closedBody)
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm closedBody)
+ , encodeCache do
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm expectedFunctionality)
+ , encodeCache do
+ putCanonicalTermCache putObjectIdCache
+ (frozenCoreTerm closedSelf)
+ ]
+ pure (RelationalSetConstructionFact proposition descriptor)
+
+finishConstruction
+ :: Ord global
+ => [CoreType]
+ -> Map.Map global CoreType
+ -> NamedSetConstructionShape global
+ -> Maybe (NamedSetConstruction global)
+finishConstruction context globals shape = do
+ let built = buildConstruction context shape
+ construction = NamedSetConstruction context globals shape built
+ _ <- checkedDerived construction context TySet
+ (builtConstructionTerm built)
+ _ <- checkedDerived construction (TySet : context) TyProp
+ (builtConstructionMembership built)
+ pure construction
+
+buildConstruction
+ :: Eq global
+ => [CoreType]
+ -> NamedSetConstructionShape global
+ -> BuiltSetConstruction global
+buildConstruction context = \case
+ SeparationShape bound predicate ->
+ let function = CLam TySet predicate
+ term = applyIntrinsic2 Sep bound function
+ membership =
+ logicalAnd
+ (member (CBound 0) (shiftCanonical 1 0 bound))
+ predicate
+ application =
+ characteristicApplication
+ Sep context term
+ [ (TySet, bound)
+ , (TyArrow TySet TyProp, function)
+ ]
+ in BuiltSetConstruction term membership [application]
+ FunctionalReplacementShape domains value condition ->
+ let (term, applications) =
+ buildFunctionalReplacement
+ context
+ domains
+ value
+ condition
+ domainList = NonEmpty.toList domains
+ binderCount = length domainList
+ terminal =
+ logicalConjunction
+ ( maybeToList
+ (shiftCanonical 1 (fromIntegral binderCount)
+ <$> condition)
+ <> [ CEq TySet
+ (CBound (fromIntegral binderCount))
+ (shiftCanonical 1
+ (fromIntegral binderCount)
+ value)
+ ]
+ )
+ membership =
+ foldr
+ (\(depth, domain) rest ->
+ logicalExists
+ (logicalAnd
+ (member
+ (CBound 0)
+ (shiftCanonical 1 0
+ (shiftCanonical 1 depth domain)))
+ rest))
+ terminal
+ (zip [0 :: Natural ..] domainList)
+ in BuiltSetConstruction term membership applications
+
+-- The only functional-replacement term builder. Its result is reused by the
+-- transparent body, exact-content matching, characteristic validation, local
+-- views, and descriptor derivation.
+buildFunctionalReplacement
+ :: [CoreType]
+ -> NonEmpty (CanonicalTerm global)
+ -> CanonicalTerm global
+ -> Maybe (CanonicalTerm global)
+ -> (CanonicalTerm global, [CheckedCharacteristicApplication global])
+buildFunctionalReplacement context (domain :| remaining) value condition =
+ case remaining of
+ [] ->
+ let predicate = CLam TySet <$> condition
+ filtered = maybe domain
+ (applyIntrinsic2 Sep domain)
+ predicate
+ function = CLam TySet value
+ replacement = applyIntrinsic2 Repl filtered function
+ separationApplications = case predicate of
+ Nothing -> []
+ Just checkedPredicate ->
+ [ characteristicApplication
+ Sep context filtered
+ [ (TySet, domain)
+ , (TyArrow TySet TyProp, checkedPredicate)
+ ]
+ ]
+ replacementApplication =
+ characteristicApplication
+ Repl context replacement
+ [ (TySet, filtered)
+ , (TyArrow TySet TySet, function)
+ ]
+ in
+ ( replacement
+ , separationApplications <> [replacementApplication]
+ )
+ next : rest ->
+ let (nested, nestedApplications) =
+ buildFunctionalReplacement
+ (TySet : context)
+ (next :| rest)
+ value
+ condition
+ function = CLam TySet nested
+ replacement = applyIntrinsic2 Repl domain function
+ union = applyIntrinsic FamilyUnion replacement
+ in
+ ( union
+ , characteristicApplication
+ Repl context replacement
+ [ (TySet, domain)
+ , (TyArrow TySet TySet, function)
+ ]
+ : characteristicApplication
+ FamilyUnion context union
+ [(TySet, replacement)]
+ : nestedApplications
+ )
+
+characteristicApplication
+ :: CoreIntrinsicTag
+ -> [CoreType]
+ -> CanonicalTerm global
+ -> [(CoreType, CanonicalTerm global)]
+ -> CheckedCharacteristicApplication global
+characteristicApplication intrinsic context target arguments =
+ CheckedCharacteristicApplication
+ intrinsic
+ context
+ target
+ (NonEmpty.fromList arguments)
+ (expectedCharacteristicBody intrinsic arguments)
+
+expectedCharacteristicBody
+ :: CoreIntrinsicTag
+ -> [(CoreType, CanonicalTerm global)]
+ -> CanonicalTerm global
+expectedCharacteristicBody intrinsic arguments =
+ case (intrinsic, arguments) of
+ (Sep, [(_boundType, bound), (_predicateType, CLam TySet predicate)]) ->
+ logicalAnd
+ (member (CBound 0) (shiftCanonical 2 0 bound))
+ (shiftCanonical 1 1 predicate)
+ (Repl, [(_domainType, domain), (_functionType, CLam TySet value)]) ->
+ logicalExists
+ (logicalAnd
+ (member (CBound 0) (shiftCanonical 3 0 domain))
+ (CEq TySet
+ (CBound 1)
+ (shiftCanonical 2 1 value)))
+ (FamilyUnion, [(_familyType, family)]) ->
+ logicalExists
+ (logicalAnd
+ (member (CBound 0) (shiftCanonical 3 0 family))
+ (member (CBound 1) (CBound 0)))
+ _ ->
+ impossible "invalid checked set-construction characteristic"
+
+extensionalView
+ :: Ord global
+ => SetConstructionFoundation
+ -> CanonicalTerm global
+ -> NamedSetConstruction global
+ -> Maybe (ScopedCheckedCore global)
+extensionalView foundation target construction = do
+ traverse_
+ (validateCharacteristic foundation construction)
+ (constructionApplications construction)
+ checkedDerived construction (constructionContext construction) TyProp
+ (CForall TySet
+ (CEq TyProp
+ (member (CBound 0) (shiftCanonical 1 0 target))
+ (constructionMembership construction)))
+
+-- Each primitive step is specialized from the actual fixed row, and the
+-- complete normalized membership body is checked. The final flattened view
+-- is then the deterministic composition of these exact primitive schemas.
+validateCharacteristic
+ :: Ord global
+ => SetConstructionFoundation
+ -> NamedSetConstruction global
+ -> CheckedCharacteristicApplication global
+ -> Maybe ()
+validateCharacteristic foundation construction
+ (CheckedCharacteristicApplication
+ intrinsic context targetTerm argumentTerms expectedBody) = do
+ row <- characteristicRow foundation intrinsic
+ target <- checkedDerived construction context TySet targetTerm
+ arguments <- traverse
+ (\(coreType, term) ->
+ checkedDerived construction context coreType term)
+ argumentTerms
+ specialized <- scopedCharacteristicDefinition row target arguments
+ case scopedCoreTerm specialized of
+ CForall TySet
+ (CEq TyProp actualMembership actualBody)
+ | actualMembership
+ == member (CBound 0) (CBound 1)
+ , scopedCoreContext specialized
+ == TySet : scopedCoreContext target
+ , actualBody == expectedBody ->
+ pure ()
+ _ -> Nothing
+
+characteristicRow
+ :: SetConstructionFoundation
+ -> CoreIntrinsicTag
+ -> Maybe (FrozenCheckedCore Void)
+characteristicRow
+ (SetConstructionFoundation
+ familyUnion separation replacement _setChoose) =
+ \case
+ FamilyUnion -> Just familyUnion
+ Sep -> Just separation
+ Repl -> Just replacement
+ _ -> Nothing
+
+validateRelationalSchema
+ :: Ord global
+ => SetConstructionFoundation
+ -> CheckedRelationalSetConstruction global
+ -> Maybe ()
+validateRelationalSchema foundation construction = do
+ traverse_
+ (validateRelationalCharacteristic foundation construction)
+ (relationalConstructionApplications construction)
+ validateChoiceWitness foundation construction
+
+validateRelationalCharacteristic
+ :: Ord global
+ => SetConstructionFoundation
+ -> CheckedRelationalSetConstruction global
+ -> CheckedCharacteristicApplication global
+ -> Maybe ()
+validateRelationalCharacteristic foundation construction
+ (CheckedCharacteristicApplication
+ intrinsic context targetTerm argumentTerms expectedBody) = do
+ row <- characteristicRow foundation intrinsic
+ target <- checkedRelationalDerived construction context TySet targetTerm
+ arguments <- traverse
+ (\(coreType, term) ->
+ checkedRelationalDerived construction context coreType term)
+ argumentTerms
+ specialized <- scopedCharacteristicDefinition row target arguments
+ case scopedCoreTerm specialized of
+ CForall TySet (CEq TyProp actualMembership actualBody)
+ | actualMembership == member (CBound 0) (CBound 1)
+ , scopedCoreContext specialized
+ == TySet : scopedCoreContext target
+ , actualBody == expectedBody ->
+ pure ()
+ _ -> Nothing
+
+validateChoiceWitness
+ :: Ord global
+ => SetConstructionFoundation
+ -> CheckedRelationalSetConstruction global
+ -> Maybe ()
+validateChoiceWitness
+ (SetConstructionFoundation
+ _familyUnion _separation _replacement setChoose)
+ construction = do
+ let context = relationalConstructionContext construction
+ relation = relationalConstructionRelation construction
+ predicate = CLam TySet relation
+ choice = applyIntrinsic SetChoose predicate
+ witnessContext = TySet : TySet : context
+ target <- checkedRelationalDerived construction witnessContext TySet
+ (shiftCanonical 1 0 choice)
+ checkedPredicate <-
+ checkedRelationalDerived construction witnessContext
+ (TyArrow TySet TyProp)
+ (shiftCanonical 1 0 predicate)
+ witness <- checkedRelationalDerived construction witnessContext TySet
+ (CBound 0)
+ specialized <-
+ scopedCharacteristicDefinition
+ setChoose target (checkedPredicate :| [witness])
+ guard
+ (scopedCoreContext specialized
+ == TySet : TySet : TySet : context)
+ guard
+ (scopedCoreTerm specialized
+ == CImp
+ (shiftCanonical 1 0 relation)
+ (shiftCanonical 1 1 relation))
+
+relationalFunctionality
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+relationalFunctionality domain relation =
+ CForall TySet
+ (CImp
+ (member (CBound 0) (shiftCanonical 1 0 domain))
+ (CForall TySet
+ (CForall TySet
+ (CImp
+ (logicalAnd
+ (shiftCanonical 1 0 relation)
+ (shiftCanonical 1 1 relation))
+ (CEq TySet (CBound 1) (CBound 0))))))
+
+relationalMembership
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+relationalMembership domain relation =
+ logicalExists
+ (logicalAnd
+ (member (CBound 0) (shiftCanonical 2 0 domain))
+ (applyRelation
+ (shiftCanonical 2 0
+ (CLam TySet (CLam TySet relation)))
+ (CBound 0)
+ (CBound 1)))
+ where
+ applyRelation function domainValue rangeValue =
+ case function of
+ CLam TySet domainBody ->
+ case instantiateCanonical domainValue domainBody of
+ CLam TySet rangeBody ->
+ instantiateCanonical rangeValue rangeBody
+ _ -> impossible "a checked relation lost its range binder"
+ _ -> impossible "a checked relation lost its domain binder"
+
+weakenConstruction
+ :: Ord global
+ => CoreType
+ -> NamedSetConstruction global
+ -> Maybe (NamedSetConstruction global)
+weakenConstruction binderType construction =
+ finishConstruction
+ (binderType : constructionContext construction)
+ (constructionGlobals construction)
+ (case constructionShape construction of
+ SeparationShape bound predicate ->
+ SeparationShape
+ (shiftCanonical 1 0 bound)
+ (shiftCanonical 1 1 predicate)
+ FunctionalReplacementShape domains value condition ->
+ let domainList = NonEmpty.toList domains
+ weakenedDomains =
+ zipWith
+ (\depth domain -> shiftCanonical 1 depth domain)
+ [0..]
+ domainList
+ binderDepth = fromIntegral (length domainList)
+ in FunctionalReplacementShape
+ (NonEmpty.fromList weakenedDomains)
+ (shiftCanonical 1 binderDepth value)
+ (shiftCanonical 1 binderDepth <$> condition))
+
+captureGlobalTypes
+ :: Ord global
+ => (global -> Maybe CoreType)
+ -> [CanonicalTerm global]
+ -> Maybe (Map.Map global CoreType)
+captureGlobalTypes globalType terms =
+ Map.fromList <$> traverse capture
+ (Set.toAscList (foldMap canonicalTermGlobals terms))
+ where
+ capture global = do
+ coreType <- globalType global
+ pure (global, coreType)
+
+insertGlobalType
+ :: Ord global
+ => global
+ -> CoreType
+ -> Map.Map global CoreType
+ -> Maybe (Map.Map global CoreType)
+insertGlobalType global coreType globals =
+ case Map.lookup global globals of
+ Nothing -> Just (Map.insert global coreType globals)
+ Just existing
+ | existing == coreType -> Just globals
+ | otherwise -> Nothing
+
+checkedDerived
+ :: Ord global
+ => NamedSetConstruction global
+ -> [CoreType]
+ -> CoreType
+ -> CanonicalTerm global
+ -> Maybe (ScopedCheckedCore global)
+checkedDerived construction context expected term = do
+ checked <- either (const Nothing) Just
+ (checkScopedCanonicalCore
+ (`Map.lookup` constructionGlobals construction)
+ context
+ term)
+ guard (scopedCoreType checked == expected)
+ pure checked
+
+freezeDerived
+ :: Ord global
+ => NamedSetConstruction global
+ -> CoreType
+ -> CanonicalTerm global
+ -> Maybe (FrozenCheckedCore global)
+freezeDerived construction expected term = do
+ checked <- checkedDerived construction [] expected term
+ closeScopedCore checked
+
+checkedRelationalDerived
+ :: Ord global
+ => CheckedRelationalSetConstruction global
+ -> [CoreType]
+ -> CoreType
+ -> CanonicalTerm global
+ -> Maybe (ScopedCheckedCore global)
+checkedRelationalDerived construction context expected term = do
+ checked <- either (const Nothing) Just
+ (checkScopedCanonicalCore
+ (`Map.lookup` relationalConstructionGlobals construction)
+ context
+ term)
+ guard (scopedCoreType checked == expected)
+ pure checked
+
+freezeRelationalDerived
+ :: Ord global
+ => CheckedRelationalSetConstruction global
+ -> CoreType
+ -> CanonicalTerm global
+ -> Maybe (FrozenCheckedCore global)
+freezeRelationalDerived construction expected term = do
+ checked <- checkedRelationalDerived construction [] expected term
+ closeScopedCore checked
+
+closeRelationalFunctionality
+ :: Ord global
+ => CheckedRelationalSetConstruction global
+ -> FrozenCheckedCore global
+closeRelationalFunctionality construction =
+ fromMaybe
+ (impossible "a relational functionality proposition did not close")
+ (freezeRelationalDerived construction TyProp
+ (foldl
+ (flip CForall)
+ (relationalConstructionFunctionalityTerm construction)
+ (relationalConstructionContext construction)))
+
+replaceRelationalGlobals
+ :: Map.Map global CoreType
+ -> CheckedRelationalSetConstruction global
+ -> CheckedRelationalSetConstruction global
+replaceRelationalGlobals globals
+ (CheckedRelationalSetConstruction
+ context _oldGlobals domain relation term functionality
+ membership applications) =
+ CheckedRelationalSetConstruction
+ context globals domain relation term functionality membership applications
+
+relationalConstructionContext
+ :: CheckedRelationalSetConstruction global
+ -> [CoreType]
+relationalConstructionContext
+ (CheckedRelationalSetConstruction
+ context _globals _domain _relation _term _functionality
+ _membership _applications) =
+ context
+
+relationalConstructionGlobals
+ :: CheckedRelationalSetConstruction global
+ -> Map.Map global CoreType
+relationalConstructionGlobals
+ (CheckedRelationalSetConstruction
+ _context globals _domain _relation _term _functionality
+ _membership _applications) =
+ globals
+
+relationalConstructionRelation
+ :: CheckedRelationalSetConstruction global
+ -> CanonicalTerm global
+relationalConstructionRelation
+ (CheckedRelationalSetConstruction
+ _context _globals _domain relation _term _functionality
+ _membership _applications) =
+ relation
+
+relationalConstructionCanonicalTerm
+ :: CheckedRelationalSetConstruction global
+ -> CanonicalTerm global
+relationalConstructionCanonicalTerm
+ (CheckedRelationalSetConstruction
+ _context _globals _domain _relation term _functionality
+ _membership _applications) =
+ term
+
+relationalConstructionFunctionalityTerm
+ :: CheckedRelationalSetConstruction global
+ -> CanonicalTerm global
+relationalConstructionFunctionalityTerm
+ (CheckedRelationalSetConstruction
+ _context _globals _domain _relation _term functionality
+ _membership _applications) =
+ functionality
+
+relationalConstructionMembershipTerm
+ :: CheckedRelationalSetConstruction global
+ -> CanonicalTerm global
+relationalConstructionMembershipTerm
+ (CheckedRelationalSetConstruction
+ _context _globals _domain _relation _term _functionality
+ membership _applications) =
+ membership
+
+relationalConstructionApplications
+ :: CheckedRelationalSetConstruction global
+ -> [CheckedCharacteristicApplication global]
+relationalConstructionApplications
+ (CheckedRelationalSetConstruction
+ _context _globals _domain _relation _term _functionality
+ _membership applications) =
+ applications
+
+replaceConstructionGlobals
+ :: Map.Map global CoreType
+ -> NamedSetConstruction global
+ -> NamedSetConstruction global
+replaceConstructionGlobals globals
+ (NamedSetConstruction context _oldGlobals shape built) =
+ NamedSetConstruction context globals shape built
+
+constructionContext :: NamedSetConstruction global -> [CoreType]
+constructionContext (NamedSetConstruction context _globals _shape _built) =
+ context
+
+constructionGlobals
+ :: NamedSetConstruction global
+ -> Map.Map global CoreType
+constructionGlobals (NamedSetConstruction _context globals _shape _built) =
+ globals
+
+constructionShape
+ :: NamedSetConstruction global
+ -> NamedSetConstructionShape global
+constructionShape (NamedSetConstruction _context _globals shape _built) =
+ shape
+
+constructionCanonicalTerm
+ :: NamedSetConstruction global
+ -> CanonicalTerm global
+constructionCanonicalTerm
+ (NamedSetConstruction _context _globals _shape built) =
+ builtConstructionTerm built
+
+constructionMembership
+ :: NamedSetConstruction global
+ -> CanonicalTerm global
+constructionMembership
+ (NamedSetConstruction _context _globals _shape built) =
+ builtConstructionMembership built
+
+constructionApplications
+ :: NamedSetConstruction global
+ -> [CheckedCharacteristicApplication global]
+constructionApplications
+ (NamedSetConstruction _context _globals _shape built) =
+ builtConstructionApplications built
+
+builtConstructionTerm
+ :: BuiltSetConstruction global
+ -> CanonicalTerm global
+builtConstructionTerm (BuiltSetConstruction term _membership _applications) =
+ term
+
+builtConstructionMembership
+ :: BuiltSetConstruction global
+ -> CanonicalTerm global
+builtConstructionMembership
+ (BuiltSetConstruction _term membership _applications) =
+ membership
+
+builtConstructionApplications
+ :: BuiltSetConstruction global
+ -> [CheckedCharacteristicApplication global]
+builtConstructionApplications
+ (BuiltSetConstruction _term _membership applications) =
+ applications
+
+applyIntrinsic
+ :: CoreIntrinsicTag
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+applyIntrinsic intrinsic argument =
+ CApp (CIntrinsic intrinsic) argument
+
+applyIntrinsic2
+ :: CoreIntrinsicTag
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+applyIntrinsic2 intrinsic first second =
+ CApp (CApp (CIntrinsic intrinsic) first) second
+
+member
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+member = applyIntrinsic2 Member
+
+logicalNot :: CanonicalTerm global -> CanonicalTerm global
+logicalNot proposition = CImp proposition CFalsum
+
+logicalAnd
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+logicalAnd left right =
+ logicalNot (CImp left (logicalNot right))
+
+logicalTruth :: CanonicalTerm global
+logicalTruth = CImp CFalsum CFalsum
+
+logicalConjunction
+ :: Eq global
+ => [CanonicalTerm global]
+ -> CanonicalTerm global
+logicalConjunction =
+ foldr combine logicalTruth
+ where
+ combine proposition remaining
+ | proposition == logicalTruth = remaining
+ | remaining == logicalTruth = proposition
+ | otherwise = logicalAnd proposition remaining
+
+logicalExists :: CanonicalTerm global -> CanonicalTerm global
+logicalExists body =
+ logicalNot (CForall TySet (logicalNot body))
diff --git a/source/Felix/Checking/Typed/Inductive.hs b/source/Felix/Checking/Typed/Inductive.hs
new file mode 100644
index 0000000..4094083
--- /dev/null
+++ b/source/Felix/Checking/Typed/Inductive.hs
@@ -0,0 +1,4732 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Direct checked lowering of the current one-carrier set-valued inductive
+-- declaration.
+module Felix.Checking.Typed.Inductive
+ ( DirectInductive(..)
+ , DirectInductiveClause(..)
+ , DirectInductiveCondition(..)
+ , RecursiveCarrierContext
+ , RecursiveCarrierContextError(..)
+ , prepareRecursiveCarrierContext
+ , directRecursiveCarrierContext
+ , recursiveCarrierContextSymbols
+ , SourceGlobal(..)
+ , PreparedTypedInductive
+ , typedInductiveCarrierType
+ , typedInductiveCarrierBody
+ , typedInductiveGuardTargets
+ , PreparedTypedInductiveMonotonicity
+ , typedInductiveMonotonicities
+ , typedInductiveMonotonicityLocation
+ , typedInductiveMonotonicityTarget
+ , typedInductiveContextInventory
+ , PreparedTypedInductiveFact
+ , typedInductiveFacts
+ , typedInductiveFactMarker
+ , typedInductiveFactTarget
+ , typedInductiveFactRules
+ , typedInductiveFactRequiresMonotonicities
+ , typedInductiveFactDerivation
+ , prepareTypedClosedTerm
+ , prepareTypedClosedFormula
+ , prepareTypedInductive
+ , TypedInductiveError(..)
+ ) where
+
+import Base hiding (Empty)
+import Felix.Checking.Core
+import Felix.Checking.Exact.Vocabulary
+import Felix.Checking.Foundation
+import Felix.Checking.Kernel.Derivation
+import Felix.Checking.Kernel.Proof
+import Felix.Report.Location (Location)
+import Felix.Syntax.Internal
+
+import Control.Monad ((<=<), foldM)
+import Data.Bifunctor (first)
+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.Vector (Vector)
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+import Bound.Scope (fromScope, instantiate)
+import Bound.Var (Var(..))
+
+
+data DirectInductive = DirectInductive
+ { directInductiveParams :: ![VarSymbol]
+ , directInductiveDomain :: !Term
+ , directInductiveClauses
+ :: !(NonEmpty DirectInductiveClause)
+ }
+
+data DirectInductiveClause = DirectInductiveClause
+ { directClauseVariables :: ![VarSymbol]
+ , directClauseConditions
+ :: ![DirectInductiveCondition]
+ , directClauseResult :: !Term
+ }
+
+data DirectInductiveCondition
+ = DirectSideCondition !Formula
+ | DirectRecursiveCondition !Term !RecursiveCarrierContext
+
+data RecursiveCarrierVariable
+ = RecursiveCarrierHole
+ | RecursiveCarrierSourceVariable !VarSymbol
+ deriving stock (Show, Eq, Ord)
+
+-- | A validated, capture-free one-hole carrier context in the same
+-- first-order set-term fragment lowered by this module. The source carrier
+-- application itself has been replaced, so the inductive symbol cannot
+-- survive inside this value.
+data RecursiveCarrierContext = RecursiveCarrierContext
+ !Location
+ !(ExprOf RecursiveCarrierVariable)
+ deriving stock (Show, Eq, Ord)
+
+data RecursiveCarrierContextError
+ = RecursiveCarrierWrongArguments !Location
+ | RecursiveCarrierUnsupportedContext !Location
+ deriving stock (Show, Eq)
+
+prepareRecursiveCarrierContext
+ :: FunctionSymbol
+ -> [VarSymbol]
+ -> Term
+ -> Either RecursiveCarrierContextError RecursiveCarrierContext
+prepareRecursiveCarrierContext carrier parameters source =
+ RecursiveCarrierContext (exprLocation source) <$> go source
+ where
+ carrierSymbol = SymbolMixfix carrier
+
+ go = \case
+ TermVar variable ->
+ pure
+ (TermVar
+ (RecursiveCarrierSourceVariable variable))
+ TermSymbol location symbol arguments
+ | symbol == carrierSymbol ->
+ if sameCarrierArguments arguments parameters
+ then pure (TermVar RecursiveCarrierHole)
+ else Left (RecursiveCarrierWrongArguments location)
+ | otherwise ->
+ TermSymbol location symbol <$> traverse go arguments
+ unsupported ->
+ Left
+ (RecursiveCarrierUnsupportedContext
+ (exprLocation unsupported))
+
+ sameCarrierArguments arguments variables =
+ length arguments == length variables
+ && and
+ (zipWith
+ (\argument variable ->
+ argument == TermVar variable)
+ arguments
+ variables)
+
+recursiveCarrierContextSymbols
+ :: RecursiveCarrierContext
+ -> Set Symbol
+recursiveCarrierContextSymbols
+ (RecursiveCarrierContext _location source) =
+ mentionedSymbols source
+
+directRecursiveCarrierContext :: Location -> RecursiveCarrierContext
+directRecursiveCarrierContext location =
+ RecursiveCarrierContext location (TermVar RecursiveCarrierHole)
+
+data SourceGlobal global = SourceGlobal
+ !global
+ !(Maybe (FrozenCheckedCore global))
+
+data InductiveGlobal global where
+ InductiveGlobal
+ :: Eq global
+ => !global
+ -> !CoreType
+ -> InductiveGlobal global
+
+instance Eq (InductiveGlobal global) where
+ InductiveGlobal left _leftType
+ == InductiveGlobal right _rightType =
+ left == right
+
+inductiveGlobalIdentity :: InductiveGlobal global -> global
+inductiveGlobalIdentity (InductiveGlobal identity _coreType) =
+ identity
+
+inductiveGlobalType :: InductiveGlobal global -> CoreType
+inductiveGlobalType (InductiveGlobal _identity coreType) =
+ coreType
+
+data PreparedTypedInductive global = PreparedTypedInductive
+ !CoreType
+ !(FrozenCheckedCore global)
+ !(Vector (FrozenCheckedCore global))
+ !(Vector (PreparedTypedInductiveMonotonicity global))
+ !(Vector (FrozenCheckedCore global))
+ !(NonEmpty (PreparedTypedInductiveFact global))
+
+data PreparedTypedInductiveMonotonicity global =
+ PreparedTypedInductiveMonotonicity
+ !Location
+ !(FrozenCheckedCore global)
+
+typedInductiveMonotonicities
+ :: PreparedTypedInductive global
+ -> Vector (PreparedTypedInductiveMonotonicity global)
+typedInductiveMonotonicities
+ (PreparedTypedInductive
+ _carrierType
+ _body
+ _guards
+ monotonicities
+ _contexts
+ _facts) =
+ monotonicities
+
+typedInductiveMonotonicityLocation
+ :: PreparedTypedInductiveMonotonicity global
+ -> Location
+typedInductiveMonotonicityLocation
+ (PreparedTypedInductiveMonotonicity location _target) =
+ location
+
+typedInductiveMonotonicityTarget
+ :: PreparedTypedInductiveMonotonicity global
+ -> FrozenCheckedCore global
+typedInductiveMonotonicityTarget
+ (PreparedTypedInductiveMonotonicity _location target) =
+ target
+
+typedInductiveContextInventory
+ :: PreparedTypedInductive global
+ -> Vector (FrozenCheckedCore global)
+typedInductiveContextInventory
+ (PreparedTypedInductive
+ _carrierType _body _guards _monotonicities contexts _facts) =
+ contexts
+
+data CheckedRecursiveCarrierContext global =
+ CheckedRecursiveCarrierContext
+ ![VarSymbol]
+ !(FrozenCheckedCore global)
+
+data PreparedInductiveSource global = PreparedInductiveSource
+ { preparedInductiveParams :: ![VarSymbol]
+ , preparedInductiveDomain :: !Term
+ , preparedInductiveClauses
+ :: !(NonEmpty (PreparedInductiveClause global))
+ }
+
+data PreparedInductiveClause global = PreparedInductiveClause
+ { preparedClauseVariables :: ![VarSymbol]
+ , preparedClauseConditions
+ :: ![PreparedInductiveCondition global]
+ , preparedClauseResult :: !Term
+ }
+
+data PreparedInductiveCondition global
+ = PreparedSideCondition !Formula
+ | PreparedDirectRecursiveCondition
+ !Term
+ !(CheckedRecursiveCarrierContext global)
+ | PreparedNestedRecursiveCondition
+ !Term
+ !(CheckedRecursiveCarrierContext global)
+ !ImportIx
+ !(FrozenCheckedCore global)
+
+data PreparedInductiveGuard global
+ = PreparedFoundationGuard !FoundationAxiomTag
+ | PreparedImportedGuard
+ !ImportIx
+ !(FrozenCheckedCore global)
+
+typedInductiveCarrierType
+ :: PreparedTypedInductive global
+ -> CoreType
+typedInductiveCarrierType
+ (PreparedTypedInductive
+ carrierType
+ _body
+ _guards
+ _monotonicities
+ _contexts
+ _facts) =
+ carrierType
+
+typedInductiveCarrierBody
+ :: PreparedTypedInductive global
+ -> FrozenCheckedCore global
+typedInductiveCarrierBody
+ (PreparedTypedInductive
+ _carrierType
+ body
+ _guards
+ _monotonicities
+ _contexts
+ _facts) =
+ body
+
+typedInductiveGuardTargets
+ :: PreparedTypedInductive global
+ -> Vector (FrozenCheckedCore global)
+typedInductiveGuardTargets
+ (PreparedTypedInductive
+ _carrierType
+ _body
+ guards
+ _monotonicities
+ _contexts
+ _facts) =
+ guards
+
+newtype PreparedTypedInductiveFact global =
+ PreparedTypedInductiveFact
+ ( Marker
+ , FrozenCheckedCore global
+ , NonEmpty KernelRuleTag
+ , Bool
+ , KernelDerivation global
+ )
+
+typedInductiveFacts
+ :: PreparedTypedInductive global
+ -> NonEmpty (PreparedTypedInductiveFact global)
+typedInductiveFacts
+ (PreparedTypedInductive
+ _carrierType
+ _body
+ _guards
+ _monotonicities
+ _contexts
+ facts) =
+ facts
+
+typedInductiveFactMarker
+ :: PreparedTypedInductiveFact global
+ -> Marker
+typedInductiveFactMarker
+ (PreparedTypedInductiveFact
+ (marker, _target, _rule, _monotonicities, _derivation)) =
+ marker
+
+typedInductiveFactTarget
+ :: PreparedTypedInductiveFact global
+ -> FrozenCheckedCore global
+typedInductiveFactTarget
+ (PreparedTypedInductiveFact
+ (_marker, target, _rule, _monotonicities, _derivation)) =
+ target
+
+typedInductiveFactRules
+ :: PreparedTypedInductiveFact global
+ -> NonEmpty KernelRuleTag
+typedInductiveFactRules
+ (PreparedTypedInductiveFact
+ (_marker, _target, rules, _monotonicities, _derivation)) =
+ rules
+
+typedInductiveFactRequiresMonotonicities
+ :: PreparedTypedInductiveFact global
+ -> Bool
+typedInductiveFactRequiresMonotonicities
+ (PreparedTypedInductiveFact
+ (_marker, _target, _rules, required, _derivation)) =
+ required
+
+typedInductiveFactDerivation
+ :: PreparedTypedInductiveFact global
+ -> KernelDerivation global
+typedInductiveFactDerivation
+ (PreparedTypedInductiveFact
+ (_marker, _target, _rule, _monotonicities, derivation)) =
+ derivation
+
+-- | Lower one closed source formula through the exact primitive/global
+-- policy used by the direct-inductive compiler.
+prepareTypedClosedFormula
+ :: Eq global
+ => (global -> CoreType)
+ -> (Symbol -> Maybe (SourceGlobal global))
+ -> Formula
+ -> Either TypedInductiveError (FrozenCheckedCore global)
+prepareTypedClosedFormula globalType resolveGlobal formula = do
+ term <-
+ lowerFormulaWith
+ True
+ (fmap (mapSourceGlobal wrapGlobal) . resolveGlobal)
+ emptyEnvironment
+ formula
+ checked <-
+ first TypedInductiveCoreError
+ (checkCanonicalCore
+ (Just . inductiveGlobalType)
+ term)
+ pure (mapFrozenGlobals inductiveGlobalIdentity checked)
+ where
+ wrapGlobal identity =
+ InductiveGlobal identity (globalType identity)
+
+prepareTypedClosedTerm
+ :: Eq global
+ => (global -> CoreType)
+ -> (Symbol -> Maybe (SourceGlobal global))
+ -> Term
+ -> Either TypedInductiveError (FrozenCheckedCore global)
+prepareTypedClosedTerm globalType resolveGlobal term = do
+ canonical <-
+ lowerTerm
+ (fmap (mapSourceGlobal wrapGlobal) . resolveGlobal)
+ emptyEnvironment
+ term
+ checked <-
+ first TypedInductiveCoreError
+ (checkCanonicalCore
+ (Just . inductiveGlobalType)
+ canonical)
+ pure (mapFrozenGlobals inductiveGlobalIdentity checked)
+ where
+ wrapGlobal identity =
+ InductiveGlobal identity (globalType identity)
+
+data TypedInductiveError
+ = TypedInductiveDuplicateBinder !VarSymbol
+ | TypedInductiveUnknownLocal !VarSymbol
+ | TypedInductiveUnsupportedExpression !Text
+ | TypedInductiveCoreError !CoreCheckError
+ | TypedInductiveProofError !KernelProofBuildError
+ | TypedInductiveProofRemainedOpen
+ | TypedInductiveFactPreparationFailed
+ !Marker
+ !TypedInductiveError
+ | TypedInductivePreparationContext
+ !Text
+ !TypedInductiveError
+ deriving stock (Show, Eq)
+
+data InductiveEnvironment = InductiveEnvironment
+ !(Map VarSymbol Natural)
+
+emptyEnvironment :: InductiveEnvironment
+emptyEnvironment =
+ InductiveEnvironment Map.empty
+
+extendEnvironment
+ :: VarSymbol
+ -> InductiveEnvironment
+ -> Either
+ TypedInductiveError
+ InductiveEnvironment
+extendEnvironment variable
+ (InductiveEnvironment variables)
+ | Map.member variable variables =
+ Left
+ (TypedInductiveDuplicateBinder
+ variable)
+ | otherwise =
+ Right
+ (InductiveEnvironment
+ (Map.insert variable 0
+ (succ <$> variables)))
+
+lookupEnvironment
+ :: VarSymbol
+ -> InductiveEnvironment
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+lookupEnvironment variable
+ (InductiveEnvironment variables) =
+ maybe
+ (Left
+ (TypedInductiveUnknownLocal
+ variable))
+ (Right . CBound)
+ (Map.lookup variable variables)
+
+prepareTypedInductive
+ :: Eq global
+ => (global -> CoreType)
+ -> CheckedFoundation
+ -> (Symbol -> Maybe (SourceGlobal global))
+ -> Marker
+ -> DirectInductive
+ -> Either
+ TypedInductiveError
+ (PreparedTypedInductive global)
+prepareTypedInductive
+ globalType
+ foundation
+ resolveGlobal
+ marker
+ inductive =
+ mapPreparedTypedInductive inductiveGlobalIdentity
+ <$> prepareTypedInductiveInternal
+ foundation
+ (fmap (mapSourceGlobal wrapGlobal) . resolveGlobal)
+ marker
+ inductive
+ where
+ wrapGlobal identity =
+ InductiveGlobal identity (globalType identity)
+
+prepareTypedInductiveInternal
+ :: CheckedFoundation
+ -> (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> Marker
+ -> DirectInductive
+ -> Either
+ TypedInductiveError
+ (PreparedTypedInductive (InductiveGlobal global))
+prepareTypedInductiveInternal
+ foundation
+ resolveGlobal
+ marker
+ inductive = do
+ guards <-
+ traverse
+ (prepareDirectGuardTarget
+ resolveGlobal
+ inductive)
+ (directInductiveClauses
+ inductive)
+ let preparedGuards =
+ assignGuardSources foundation
+ (NonEmpty.toList guards)
+ nextImport =
+ fromIntegral
+ (length
+ [ ()
+ | PreparedImportedGuard{} <- preparedGuards
+ ])
+ (preparedSource, monotonicities, contexts) <-
+ prepareInductiveSource
+ resolveGlobal
+ nextImport
+ inductive
+ carrierBody <-
+ prepareCarrierBody
+ resolveGlobal
+ preparedSource
+ facts <-
+ prepareFacts
+ foundation
+ resolveGlobal
+ marker
+ preparedSource
+ (case preparedGuards of
+ firstGuard : remainingGuards ->
+ firstGuard :| remainingGuards
+ [] ->
+ impossible
+ "a nonempty inductive declaration produced no guards")
+ pure
+ (PreparedTypedInductive
+ carrierType
+ carrierBody
+ (Vector.fromList
+ [ target
+ | PreparedImportedGuard
+ _index target <- preparedGuards
+ ])
+ (Vector.fromList monotonicities)
+ (Vector.fromList contexts)
+ facts)
+ where
+ carrierType =
+ foldr
+ TyArrow
+ TySet
+ (TySet
+ <$ directInductiveParams
+ inductive)
+
+mapSourceGlobal
+ :: (left -> right)
+ -> SourceGlobal left
+ -> SourceGlobal right
+mapSourceGlobal transform (SourceGlobal identity body) =
+ SourceGlobal
+ (transform identity)
+ (mapFrozenGlobals transform <$> body)
+
+mapPreparedTypedInductive
+ :: (left -> right)
+ -> PreparedTypedInductive left
+ -> PreparedTypedInductive right
+mapPreparedTypedInductive transform
+ (PreparedTypedInductive
+ carrierType body guards monotonicities contexts facts) =
+ PreparedTypedInductive
+ carrierType
+ (mapFrozenGlobals transform body)
+ (mapFrozenGlobals transform <$> guards)
+ (mapMonotonicity transform <$> monotonicities)
+ (mapFrozenGlobals transform <$> contexts)
+ (mapPreparedFact transform <$> facts)
+ where
+ mapMonotonicity mapGlobal
+ (PreparedTypedInductiveMonotonicity location target) =
+ PreparedTypedInductiveMonotonicity
+ location
+ (mapFrozenGlobals mapGlobal target)
+
+ mapPreparedFact mapGlobal
+ (PreparedTypedInductiveFact
+ (marker, target, rule, requiresMonotonicities, derivation)) =
+ PreparedTypedInductiveFact
+ ( marker
+ , mapFrozenGlobals mapGlobal target
+ , rule
+ , requiresMonotonicities
+ , mapKernelDerivationGlobals mapGlobal derivation
+ )
+
+data MonotonicityInventory global = MonotonicityInventory
+ ![(FrozenCheckedCore global, ImportIx)]
+ !Natural
+ ![PreparedTypedInductiveMonotonicity global]
+ ![FrozenCheckedCore global]
+
+prepareInductiveSource
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> Natural
+ -> DirectInductive
+ -> Either
+ TypedInductiveError
+ ( PreparedInductiveSource (InductiveGlobal global)
+ , [PreparedTypedInductiveMonotonicity (InductiveGlobal global)]
+ , [FrozenCheckedCore (InductiveGlobal global)]
+ )
+prepareInductiveSource resolveGlobal firstImport inductive = do
+ (final, clauses) <-
+ prepareClauses
+ (MonotonicityInventory [] firstImport [] [])
+ (NonEmpty.toList (directInductiveClauses inductive))
+ let MonotonicityInventory
+ _targets _next monotonicities contexts = final
+ pure
+ ( PreparedInductiveSource
+ (directInductiveParams inductive)
+ (directInductiveDomain inductive)
+ (NonEmpty.fromList clauses)
+ , reverse monotonicities
+ , reverse contexts
+ )
+ where
+ variablesFor clause =
+ directInductiveParams inductive
+ <> directClauseVariables clause
+
+ prepareClauses inventory = \case
+ [] -> pure (inventory, [])
+ clause : remaining -> do
+ (afterClause, preparedClause) <-
+ prepareClause inventory clause
+ (final, preparedRemaining) <-
+ prepareClauses afterClause remaining
+ pure (final, preparedClause : preparedRemaining)
+
+ prepareClause inventory clause = do
+ (next, conditions) <-
+ prepareConditions inventory clause
+ (directClauseConditions clause)
+ pure
+ ( next
+ , PreparedInductiveClause
+ (directClauseVariables clause)
+ conditions
+ (directClauseResult clause)
+ )
+
+ prepareConditions inventory _clause [] =
+ pure (inventory, [])
+ prepareConditions inventory clause (condition : remaining) = do
+ (next, prepared) <-
+ prepareCondition inventory clause condition
+ (final, preparedRemaining) <-
+ prepareConditions next clause remaining
+ pure (final, prepared : preparedRemaining)
+
+ prepareCondition inventory _clause (DirectSideCondition formula) =
+ pure (inventory, PreparedSideCondition formula)
+ prepareCondition
+ (MonotonicityInventory targets next facts contexts)
+ clause
+ (DirectRecursiveCondition recursiveTerm sourceContext) = do
+ checkedContext <-
+ prepareRecursiveCarrierTemplate
+ resolveGlobal
+ (variablesFor clause)
+ sourceContext
+ let template = checkedRecursiveCarrierTemplate checkedContext
+ withContext currentFacts =
+ MonotonicityInventory
+ targets next currentFacts (template : contexts)
+ if recursiveCarrierContextIsDirect sourceContext
+ then pure
+ ( withContext facts
+ , PreparedDirectRecursiveCondition
+ recursiveTerm checkedContext
+ )
+ else do
+ target <-
+ prepareRecursiveCarrierMonotonicityTarget
+ checkedContext
+ let RecursiveCarrierContext location _source = sourceContext
+ case List.lookup target targets of
+ Just index ->
+ pure
+ ( MonotonicityInventory
+ targets next facts (template : contexts)
+ , PreparedNestedRecursiveCondition
+ recursiveTerm checkedContext index target
+ )
+ Nothing ->
+ let index = importIx next
+ in pure
+ ( MonotonicityInventory
+ ((target, index) : targets)
+ (next + 1)
+ (PreparedTypedInductiveMonotonicity
+ location target : facts)
+ (template : contexts)
+ , PreparedNestedRecursiveCondition
+ recursiveTerm checkedContext index target
+ )
+
+checkedRecursiveCarrierTemplate
+ :: CheckedRecursiveCarrierContext global
+ -> FrozenCheckedCore global
+checkedRecursiveCarrierTemplate
+ (CheckedRecursiveCarrierContext _variables template) =
+ template
+
+recursiveCarrierContextIsDirect :: RecursiveCarrierContext -> Bool
+recursiveCarrierContextIsDirect
+ (RecursiveCarrierContext _location source) =
+ source == TermVar RecursiveCarrierHole
+
+prepareRecursiveCarrierTemplate
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> [VarSymbol]
+ -> RecursiveCarrierContext
+ -> Either
+ TypedInductiveError
+ (CheckedRecursiveCarrierContext (InductiveGlobal global))
+prepareRecursiveCarrierTemplate resolveGlobal variables context = do
+ body <-
+ buildUnderVariables emptyEnvironment variables \environment -> do
+ underHole <- shiftEnvironment environment
+ lowerRecursiveCarrierContext
+ resolveGlobal underHole (CBound 0) context
+ checked <-
+ first TypedInductiveCoreError
+ (checkCanonicalCore
+ (Just . inductiveGlobalType)
+ -- Transparent expansion may leave beta redexes. Freeze one
+ -- normalized template so routing, generated laws, and kernel
+ -- transport all see the same first-order shape.
+ (betaNormalizeCanonical
+ (closeLambdas (length variables + 1) body)))
+ pure (CheckedRecursiveCarrierContext variables checked)
+
+prepareRecursiveCarrierMonotonicityTarget
+ :: CheckedRecursiveCarrierContext (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (FrozenCheckedCore (InductiveGlobal global))
+prepareRecursiveCarrierMonotonicityTarget
+ context@(CheckedRecursiveCarrierContext variables _template) = do
+ target <-
+ buildUnderVariables emptyEnvironment variables \environment -> do
+ underSets <- shiftEnvironment =<< shiftEnvironment environment
+ left <-
+ instantiateRecursiveCarrier
+ underSets (CBound 1) context
+ right <-
+ instantiateRecursiveCarrier
+ underSets (CBound 0) context
+ pure
+ (CImp
+ (subsetTerm (CBound 1) (CBound 0))
+ (subsetTerm left right))
+ freezeClosedTarget
+ (closeForalls (length variables + 2) target)
+
+instantiateRecursiveCarrier
+ :: InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CheckedRecursiveCarrierContext (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+instantiateRecursiveCarrier environment replacement
+ context@(CheckedRecursiveCarrierContext variables _template) = do
+ arguments <- traverse (`lookupEnvironment` environment) variables
+ foldM instantiateLambda
+ (frozenCoreTerm (checkedRecursiveCarrierTemplate context))
+ (arguments <> [replacement])
+ where
+ instantiateLambda term argument =
+ case term of
+ CLam TySet body ->
+ pure (instantiateCanonical argument body)
+ _ ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ "a checked recursive carrier context lost its set telescope")
+
+lowerRecursiveCarrierContext
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> RecursiveCarrierContext
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+lowerRecursiveCarrierContext resolveGlobal environment replacement
+ (RecursiveCarrierContext _location source) =
+ go source
+ where
+ go = \case
+ TermVar RecursiveCarrierHole ->
+ pure replacement
+ TermVar (RecursiveCarrierSourceVariable variable) ->
+ lookupEnvironment variable environment
+ TermSymbol _location symbol arguments -> do
+ lowered <- traverse go arguments
+ lowerApplicationTerms resolveGlobal symbol lowered
+ _ ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ "a validated recursive carrier context left the supported set-term fragment")
+
+prepareCarrierBody
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (FrozenCheckedCore (InductiveGlobal global))
+prepareCarrierBody resolveGlobal inductive = do
+ body <-
+ buildUnderVariables
+ emptyEnvironment
+ (preparedInductiveParams
+ inductive)
+ (\env -> fixedPointTerm
+ resolveGlobal
+ inductive
+ env)
+ let closed =
+ closeLambdas
+ (length
+ (preparedInductiveParams
+ inductive))
+ body
+ checked <-
+ first TypedInductiveCoreError
+ (checkCanonicalCore
+ (Just . inductiveGlobalType)
+ closed)
+ pure checked
+
+prepareDirectGuardTarget
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> DirectInductive
+ -> DirectInductiveClause
+ -> Either
+ TypedInductiveError
+ (FrozenCheckedCore (InductiveGlobal global))
+prepareDirectGuardTarget
+ resolveGlobal
+ inductive
+ clause = do
+ target <-
+ buildUnderVariables
+ emptyEnvironment
+ (directInductiveParams inductive
+ <> directClauseVariables clause)
+ (\environment -> do
+ domain <-
+ lowerTerm
+ resolveGlobal
+ environment
+ (directInductiveDomain
+ inductive)
+ conditions <-
+ traverse
+ (directConditionTerm
+ resolveGlobal
+ environment
+ domain)
+ (directClauseConditions
+ clause)
+ result <-
+ lowerTerm
+ resolveGlobal
+ environment
+ (directClauseResult
+ clause)
+ pure
+ (impliesIfNeeded
+ (conjunctionList
+ conditions)
+ (memberTerm
+ result
+ domain)))
+ freezeClosedTarget
+ (closeForalls
+ (length
+ (directInductiveParams inductive
+ <> directClauseVariables clause))
+ target)
+
+assignGuardSources
+ :: CheckedFoundation
+ -> [FrozenCheckedCore (InductiveGlobal global)]
+ -> [PreparedInductiveGuard (InductiveGlobal global)]
+assignGuardSources foundation =
+ snd . List.mapAccumL assign 0
+ where
+ assign nextImport target =
+ case matchingFoundationAxiom target of
+ Just tag ->
+ (nextImport, PreparedFoundationGuard tag)
+ Nothing ->
+ ( nextImport + 1
+ , PreparedImportedGuard
+ (importIx nextImport)
+ target
+ )
+
+ matchingFoundationAxiom target =
+ List.find
+ (\tag ->
+ mapFrozenGlobals
+ absurd
+ (foundationAxiomFrozen
+ foundation
+ tag)
+ == target)
+ [minBound .. maxBound]
+
+prepareFacts
+ :: CheckedFoundation
+ -> (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> Marker
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> NonEmpty (PreparedInductiveGuard (InductiveGlobal global))
+ -> Either
+ TypedInductiveError
+ (NonEmpty (PreparedTypedInductiveFact (InductiveGlobal global)))
+prepareFacts
+ foundation
+ resolveGlobal
+ marker
+ inductive
+ guards = do
+ introductions <-
+ sequence
+ (NonEmpty.zipWith
+ (\clauseIndex pair ->
+ first
+ (TypedInductiveFactPreparationFailed
+ (introMarker
+ marker
+ (clauseIndex + 1)))
+ (prepareIntroductionFact
+ foundation
+ resolveGlobal
+ marker
+ inductive
+ clauseIndex
+ pair))
+ (0 :| [1 ..])
+ (NonEmpty.zip
+ guards
+ (preparedInductiveClauses
+ inductive)))
+ domainSubset <-
+ first
+ (TypedInductiveFactPreparationFailed
+ (derivedMarker marker "dom_subset"))
+ (prepareDomainSubsetFact
+ foundation
+ resolveGlobal
+ marker
+ inductive)
+ cases <-
+ first
+ (TypedInductiveFactPreparationFailed
+ (derivedMarker marker "cases"))
+ (prepareCasesFact
+ foundation
+ resolveGlobal
+ marker
+ inductive)
+ induction <-
+ first
+ (TypedInductiveFactPreparationFailed
+ (derivedMarker marker "induct"))
+ (prepareInductionFact
+ foundation
+ resolveGlobal
+ marker
+ inductive)
+ pure
+ (introductions
+ <> (domainSubset
+ :| [cases, induction]))
+
+prepareIntroductionFact
+ :: CheckedFoundation
+ -> (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> Marker
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> Natural
+ -> (PreparedInductiveGuard (InductiveGlobal global), PreparedInductiveClause (InductiveGlobal global))
+ -> Either
+ TypedInductiveError
+ (PreparedTypedInductiveFact (InductiveGlobal global))
+prepareIntroductionFact
+ foundation
+ resolveGlobal
+ marker
+ inductive
+ clauseIndex
+ (guardSource, clause) = do
+ proof <-
+ proveUnderVariables
+ (rootProofContext
+ foundation
+ (Just . inductiveGlobalType))
+ emptyEnvironment
+ (preparedInductiveParams inductive
+ <> preparedClauseVariables clause)
+ (\context environment -> do
+ domain <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ (preparedInductiveDomain
+ inductive)
+ operator <-
+ checkedTerm context
+ =<< operatorTerm
+ resolveGlobal
+ inductive
+ environment
+ fixedPoint <-
+ checkedTerm context
+ =<< fixedPointTerm
+ resolveGlobal
+ inductive
+ environment
+ predicate <-
+ checkedTerm context
+ =<< operatorPredicateAt
+ resolveGlobal
+ inductive
+ environment
+ (scopedCoreTerm
+ fixedPoint)
+ appliedOperatorSet <-
+ checkedTerm context
+ (CApp
+ (scopedCoreTerm operator)
+ (scopedCoreTerm
+ fixedPoint))
+ conditions <-
+ traverse
+ (checkedTerm context <=<
+ conditionTerm
+ resolveGlobal
+ environment
+ (scopedCoreTerm
+ fixedPoint))
+ (preparedClauseConditions
+ clause)
+ result <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ (preparedClauseResult
+ clause)
+ let conditionTerms =
+ scopedCoreTerm <$> conditions
+ first
+ (TypedInductivePreparationContext
+ "introduction premises")
+ (provePremises
+ context
+ conditionTerms
+ (\premiseProofs -> do
+ guardProof <-
+ preparedGuardProof
+ context
+ guardSource
+ specializedGuard <-
+ first
+ (TypedInductivePreparationContext
+ "introduction guard specialization")
+ (eliminateWrittenForalls
+ context
+ environment
+ (preparedInductiveParams
+ inductive
+ <> preparedClauseVariables
+ clause)
+ guardProof)
+ guardPremiseProofs <-
+ sequence
+ [ case condition of
+ PreparedSideCondition _formula ->
+ pure premiseProof
+ PreparedDirectRecursiveCondition
+ recursiveTerm _context -> do
+ bound <-
+ first
+ TypedInductiveProofError
+ (setLfpBoundProof
+ context
+ domain
+ operator)
+ recursiveElement <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ recursiveTerm
+ implication <-
+ first
+ TypedInductiveProofError
+ (forallEliminationProof
+ context
+ bound
+ recursiveElement)
+ first
+ TypedInductiveProofError
+ (implicationEliminationProof
+ context
+ implication
+ premiseProof)
+ nested@PreparedNestedRecursiveCondition{} -> do
+ bound <-
+ first TypedInductiveProofError
+ (setLfpBoundProof
+ context
+ domain
+ operator)
+ recursiveElement <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ (preparedRecursiveTerm
+ nested)
+ transportNestedRecursiveMembership
+ context
+ environment
+ nested
+ fixedPoint
+ domain
+ recursiveElement
+ bound
+ premiseProof
+ | (condition, premiseProof) <-
+ zip
+ (preparedClauseConditions
+ clause)
+ premiseProofs
+ ]
+ inDomain <-
+ case conjunctionList
+ conditionTerms of
+ Nothing ->
+ pure specializedGuard
+ Just _condition -> do
+ conjunction <-
+ conjunctionIntroductionList
+ context
+ guardPremiseProofs
+ first
+ (TypedInductivePreparationContext
+ "introduction guard application")
+ (first TypedInductiveProofError
+ (implicationEliminationProof
+ context
+ specializedGuard
+ conjunction))
+ equality <-
+ first TypedInductiveProofError
+ (equalityReflexivityProof
+ context
+ result)
+ clauseProof <-
+ conjunctionIntroductionList
+ context
+ (premiseProofs
+ <> [equality])
+ clauseExists <-
+ first
+ (TypedInductivePreparationContext
+ "introduction witnesses")
+ (introduceClauseWitnesses
+ resolveGlobal
+ inductive
+ clause
+ context
+ environment
+ (scopedCoreTerm
+ fixedPoint)
+ result
+ clauseProof)
+ alternatives <-
+ clauseFormulaTerms
+ resolveGlobal
+ inductive
+ environment
+ (scopedCoreTerm
+ fixedPoint)
+ (scopedCoreTerm
+ result)
+ disjunction <-
+ first
+ (TypedInductivePreparationContext
+ "introduction disjunction")
+ (injectDisjunction
+ context
+ clauseIndex
+ alternatives
+ clauseExists)
+ inOperator <-
+ first
+ (TypedInductivePreparationContext
+ "introduction separation")
+ (separationBackward
+ context
+ domain
+ predicate
+ result
+ inDomain
+ disjunction)
+ inAppliedTarget <-
+ checkedTerm context
+ (memberTerm
+ (scopedCoreTerm
+ result)
+ (scopedCoreTerm
+ appliedOperatorSet))
+ inAppliedOperator <-
+ first TypedInductiveProofError
+ (conversionProof
+ context
+ inOperator
+ inAppliedTarget)
+ monotone <-
+ first
+ (TypedInductivePreparationContext
+ "introduction bounded monotonicity")
+ (proveBoundedMonotonicity
+ foundation
+ resolveGlobal
+ inductive
+ context
+ environment)
+ fixed <-
+ first TypedInductiveProofError
+ (setLfpFixedProof
+ context
+ domain
+ operator
+ monotone)
+ reversedFixed <-
+ first
+ (TypedInductivePreparationContext
+ "introduction fixed-point symmetry")
+ (first TypedInductiveProofError
+ (equalityReverseProof
+ context
+ fixed))
+ first
+ (TypedInductivePreparationContext
+ "introduction fixed-point transport")
+ (transportMembership
+ context
+ result
+ reversedFixed
+ inAppliedOperator))))
+ preparedFact
+ (introMarker
+ marker
+ (clauseIndex + 1))
+ (if any isRecursiveCondition
+ (preparedClauseConditions clause)
+ then SetLfpBound :| [SetLfpFixed]
+ else SetLfpFixed :| [])
+ True
+ proof
+ where
+ isRecursiveCondition = \case
+ PreparedDirectRecursiveCondition{} -> True
+ PreparedNestedRecursiveCondition{} -> True
+ PreparedSideCondition{} -> False
+
+preparedGuardProof
+ :: ProofContext (InductiveGlobal global)
+ -> PreparedInductiveGuard (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+preparedGuardProof context = \case
+ PreparedFoundationGuard tag ->
+ first TypedInductiveProofError
+ (foundationProof context tag)
+ PreparedImportedGuard index target ->
+ first TypedInductiveProofError
+ (importedProof context index target)
+
+prepareDomainSubsetFact
+ :: CheckedFoundation
+ -> (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> Marker
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (PreparedTypedInductiveFact (InductiveGlobal global))
+prepareDomainSubsetFact
+ foundation
+ resolveGlobal
+ marker
+ inductive = do
+ proof <-
+ proveUnderVariables
+ (rootProofContext
+ foundation
+ (Just . inductiveGlobalType))
+ emptyEnvironment
+ (preparedInductiveParams
+ inductive)
+ (\context environment -> do
+ domain <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ (preparedInductiveDomain
+ inductive)
+ operator <-
+ checkedTerm context
+ =<< operatorTerm
+ resolveGlobal
+ inductive
+ environment
+ first TypedInductiveProofError
+ (setLfpBoundProof
+ context
+ domain
+ operator))
+ preparedFact
+ (derivedMarker marker "dom_subset")
+ (SetLfpBound :| [])
+ False
+ proof
+
+prepareCasesFact
+ :: CheckedFoundation
+ -> (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> Marker
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (PreparedTypedInductiveFact (InductiveGlobal global))
+prepareCasesFact
+ foundation
+ resolveGlobal
+ marker
+ inductive = do
+ proof <-
+ proveUnderVariables
+ (rootProofContext
+ foundation
+ (Just . inductiveGlobalType))
+ emptyEnvironment
+ (preparedInductiveParams
+ inductive)
+ (\parameterContext parameterEnvironment ->
+ forallIntroductionTyped
+ parameterContext
+ TySet
+ (\context result -> do
+ environment <-
+ shiftEnvironment
+ parameterEnvironment
+ domain <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ (preparedInductiveDomain
+ inductive)
+ operator <-
+ checkedTerm context
+ =<< operatorTerm
+ resolveGlobal
+ inductive
+ environment
+ fixedPoint <-
+ checkedTerm context
+ =<< fixedPointTerm
+ resolveGlobal
+ inductive
+ environment
+ predicate <-
+ checkedTerm context
+ =<< operatorPredicateAt
+ resolveGlobal
+ inductive
+ environment
+ (scopedCoreTerm
+ fixedPoint)
+ explicitOperatorSet <-
+ checkedTerm context
+ =<< separationSetAt
+ resolveGlobal
+ inductive
+ environment
+ (scopedCoreTerm
+ fixedPoint)
+ member <-
+ checkedTerm context
+ (memberTerm
+ (scopedCoreTerm result)
+ (scopedCoreTerm
+ fixedPoint))
+ implicationIntroductionTyped
+ context
+ member
+ (\withMember memberProof -> do
+ monotone <-
+ proveBoundedMonotonicity
+ foundation
+ resolveGlobal
+ inductive
+ withMember
+ environment
+ fixed <-
+ first TypedInductiveProofError
+ (setLfpFixedProof
+ withMember
+ domain
+ operator
+ monotone)
+ inOperator <-
+ transportMembership
+ withMember
+ result
+ fixed
+ memberProof
+ explicitMembership <-
+ checkedTerm withMember
+ (memberTerm
+ (scopedCoreTerm
+ result)
+ (scopedCoreTerm
+ explicitOperatorSet))
+ inSeparation <-
+ first TypedInductiveProofError
+ (conversionProof
+ withMember
+ inOperator
+ explicitMembership)
+ separation <-
+ separationForward
+ withMember
+ domain
+ predicate
+ result
+ inSeparation
+ predicateResult <-
+ predicateAt
+ resolveGlobal
+ inductive
+ environment
+ (scopedCoreTerm
+ fixedPoint)
+ (scopedCoreTerm
+ result)
+ predicateProof <-
+ first TypedInductiveProofError
+ (conjunctionRightProof
+ withMember
+ (memberTerm
+ (scopedCoreTerm result)
+ (scopedCoreTerm
+ domain))
+ (CApp
+ (scopedCoreTerm
+ predicate)
+ (scopedCoreTerm
+ result))
+ separation)
+ predicateTarget <-
+ checkedTerm
+ withMember
+ predicateResult
+ first TypedInductiveProofError
+ (conversionProof
+ withMember
+ predicateProof
+ predicateTarget))))
+ preparedFact
+ (derivedMarker marker "cases")
+ (SetLfpFixed :| [])
+ True
+ proof
+
+prepareInductionFact
+ :: CheckedFoundation
+ -> (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> Marker
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (PreparedTypedInductiveFact (InductiveGlobal global))
+prepareInductionFact
+ foundation
+ resolveGlobal
+ marker
+ inductive = do
+ proof <-
+ proveUnderVariables
+ (rootProofContext
+ foundation
+ (Just . inductiveGlobalType))
+ emptyEnvironment
+ (preparedInductiveParams
+ inductive)
+ (\parameterContext parameterEnvironment ->
+ forallIntroductionTyped
+ parameterContext
+ TySet
+ (\subsetContext subset -> do
+ environment <-
+ shiftEnvironment
+ parameterEnvironment
+ closures <-
+ closureTerms
+ resolveGlobal
+ inductive
+ environment
+ (scopedCoreTerm subset)
+ closureConjunction <-
+ checkedTerm subsetContext
+ (fromMaybe
+ (CImp CFalsum CFalsum)
+ (conjunctionList
+ closures))
+ implicationIntroductionTyped
+ subsetContext
+ closureConjunction
+ (\withClosures closuresProof -> do
+ fixedPoint <-
+ checkedTerm withClosures
+ =<< fixedPointTerm
+ resolveGlobal
+ inductive
+ environment
+ proveSubset
+ withClosures
+ fixedPoint
+ subset
+ (\elementContext element memberProof -> do
+ elementEnvironment <-
+ shiftEnvironment environment
+ domain <-
+ checkedTerm elementContext
+ =<< lowerTerm
+ resolveGlobal
+ elementEnvironment
+ (preparedInductiveDomain
+ inductive)
+ operator <-
+ checkedTerm elementContext
+ =<< operatorTerm
+ resolveGlobal
+ inductive
+ elementEnvironment
+ fixedPointAtElement <-
+ checkedTerm elementContext
+ =<< fixedPointTerm
+ resolveGlobal
+ inductive
+ elementEnvironment
+ subsetAtElement <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ subset)
+ predicate <-
+ membershipPredicate
+ elementContext
+ subsetAtElement
+ monotone <-
+ first
+ (TypedInductivePreparationContext
+ "induction bounded monotonicity")
+ (proveBoundedMonotonicity
+ foundation
+ resolveGlobal
+ inductive
+ elementContext
+ elementEnvironment)
+ closure <-
+ first
+ (TypedInductivePreparationContext
+ "induction closure")
+ (proveInductionClosure
+ foundation
+ resolveGlobal
+ inductive
+ elementContext
+ elementEnvironment
+ fixedPointAtElement
+ operator
+ subsetAtElement
+ closures
+ closuresProof)
+ inducted <-
+ first
+ (TypedInductivePreparationContext
+ "induction fixed-point rule")
+ (first TypedInductiveProofError
+ (setLfpInductProof
+ elementContext
+ domain
+ operator
+ predicate
+ element
+ monotone
+ memberProof
+ closure))
+ expected <-
+ checkedTerm elementContext
+ (memberTerm
+ (scopedCoreTerm
+ element)
+ (scopedCoreTerm
+ subsetAtElement))
+ first TypedInductiveProofError
+ (conversionProof
+ elementContext
+ inducted
+ expected)))))
+ preparedFact
+ (derivedMarker marker "induct")
+ (SetLfpInduct :| [])
+ True
+ proof
+
+proveUnderVariables
+ :: ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> [VarSymbol]
+ -> ( ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+ )
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+proveUnderVariables context environment variables build =
+ case variables of
+ [] ->
+ build context environment
+ variable : remaining -> do
+ extendedEnvironment <-
+ extendEnvironment
+ variable
+ environment
+ forallIntroductionTyped
+ context
+ TySet
+ (\extended _bound ->
+ proveUnderVariables
+ extended
+ extendedEnvironment
+ remaining
+ build)
+
+checkedTerm
+ :: ProofContext (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (ScopedCheckedCore (InductiveGlobal global))
+checkedTerm context =
+ first TypedInductiveProofError
+ . scopedTerm context
+
+preparedFact
+ :: Marker
+ -> NonEmpty KernelRuleTag
+ -> Bool
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (PreparedTypedInductiveFact (InductiveGlobal global))
+preparedFact marker rules requiresMonotonicities proof = do
+ target <-
+ maybe
+ (Left TypedInductiveProofRemainedOpen)
+ Right
+ (closeScopedCore
+ (builtProofStatement proof))
+ pure
+ (PreparedTypedInductiveFact
+ ( marker
+ , target
+ , canonicalRules rules
+ , requiresMonotonicities
+ , builtProofDerivation proof
+ ))
+ where
+ canonicalRules supplied =
+ case Set.toAscList
+ (Set.fromList (NonEmpty.toList supplied)) of
+ firstRule : remainingRules ->
+ firstRule :| remainingRules
+ [] ->
+ impossible "a nonempty guarded-rule set became empty"
+
+introMarker :: Marker -> Natural -> Marker
+introMarker (Marker marker) index =
+ Marker
+ (marker
+ <> "_intro_"
+ <> Text.pack (show index))
+
+derivedMarker :: Marker -> Text -> Marker
+derivedMarker (Marker marker) suffix =
+ Marker
+ (marker <> "_" <> suffix)
+
+eliminateWrittenForalls
+ :: ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> [VarSymbol]
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+eliminateWrittenForalls
+ context
+ environment
+ variables
+ initial =
+ foldM
+ (\proof variable -> do
+ argument <-
+ checkedTerm context
+ =<< lookupEnvironment
+ variable
+ environment
+ first TypedInductiveProofError
+ (forallEliminationProof
+ context
+ proof
+ argument))
+ initial
+ variables
+
+provePremises
+ :: ProofContext (InductiveGlobal global)
+ -> [CanonicalTerm (InductiveGlobal global)]
+ -> ( [BuiltProof (InductiveGlobal global)]
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+ )
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+provePremises context premises build =
+ case conjunctionList premises of
+ Nothing ->
+ build []
+ Just conjunction -> do
+ proposition <-
+ checkedTerm context conjunction
+ implicationIntroductionTyped
+ context
+ proposition
+ (\extended conjunctionProof -> do
+ projections <-
+ projectConjunctionList
+ extended
+ premises
+ conjunctionProof
+ build projections)
+
+conjunctionIntroductionList
+ :: ProofContext (InductiveGlobal global)
+ -> [BuiltProof (InductiveGlobal global)]
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+conjunctionIntroductionList _context [] =
+ Left
+ (TypedInductiveUnsupportedExpression
+ "an empty conjunction has no introduction proof")
+conjunctionIntroductionList context (firstProof : remaining) =
+ foldM
+ (\left right ->
+ first TypedInductiveProofError
+ (conjunctionIntroductionProof
+ context
+ left
+ right))
+ firstProof
+ remaining
+
+projectConjunctionList
+ :: ProofContext (InductiveGlobal global)
+ -> [CanonicalTerm (InductiveGlobal global)]
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ [BuiltProof (InductiveGlobal global)]
+projectConjunctionList _context [] _proof =
+ pure []
+projectConjunctionList _context [_only] proof =
+ pure [proof]
+projectConjunctionList context terms proof = do
+ let preceding =
+ List.init terms
+ final =
+ List.last terms
+ precedingTerm =
+ fromMaybe
+ CFalsum
+ (conjunctionList preceding)
+ precedingProof <-
+ first TypedInductiveProofError
+ (conjunctionLeftProof
+ context
+ precedingTerm
+ final
+ proof)
+ finalProof <-
+ first TypedInductiveProofError
+ (conjunctionRightProof
+ context
+ precedingTerm
+ final
+ proof)
+ (<> [finalProof])
+ <$> projectConjunctionList
+ context
+ preceding
+ precedingProof
+
+introduceClauseWitnesses
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> PreparedInductiveClause (InductiveGlobal global)
+ -> ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+introduceClauseWitnesses
+ resolveGlobal
+ _inductive
+ clause
+ context
+ environment
+ candidate
+ result
+ bodyProof =
+ introduce
+ (preparedClauseVariables clause)
+ where
+ introduce [] =
+ pure bodyProof
+ introduce (variable : remaining) = do
+ inner <-
+ introduce remaining
+ witness <-
+ checkedTerm context
+ =<< lookupEnvironment
+ variable
+ environment
+ underBinderEnvironment <-
+ rebindEnvironment
+ variable
+ =<< shiftEnvironment
+ environment
+ let candidate' =
+ shiftCanonicalTerm 1 0 candidate
+ result' =
+ shiftCanonicalTerm
+ 1
+ 0
+ (scopedCoreTerm result)
+ bodyUnderBinderTerm <-
+ clauseFormulaWithBinders
+ resolveGlobal
+ clause
+ underBinderEnvironment
+ candidate'
+ result'
+ remaining
+ bodyUnderBinder <-
+ first TypedInductiveCoreError
+ (checkScopedCanonicalCore
+ (Just . inductiveGlobalType)
+ (TySet
+ : proofContextTypes
+ context)
+ bodyUnderBinderTerm)
+ first TypedInductiveProofError
+ (existentialIntroductionProof
+ context
+ TySet
+ bodyUnderBinder
+ witness
+ inner)
+
+clauseFormulaWithBinders
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveClause (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> [VarSymbol]
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+clauseFormulaWithBinders
+ resolveGlobal
+ clause
+ environment
+ candidate
+ result = \case
+ [] -> do
+ conditions <-
+ traverse
+ (conditionTerm
+ resolveGlobal
+ environment
+ candidate)
+ (preparedClauseConditions
+ clause)
+ clauseResult <-
+ lowerTerm
+ resolveGlobal
+ environment
+ (preparedClauseResult clause)
+ pure
+ (fromMaybe
+ (CEq TySet result clauseResult)
+ (conjunctionList
+ (conditions
+ <> [CEq
+ TySet
+ result
+ clauseResult])))
+ variable : remaining -> do
+ extended <-
+ rebindEnvironment
+ variable
+ =<< shiftEnvironment
+ environment
+ body <-
+ clauseFormulaWithBinders
+ resolveGlobal
+ clause
+ extended
+ (shiftCanonicalTerm
+ 1
+ 0
+ candidate)
+ (shiftCanonicalTerm
+ 1
+ 0
+ result)
+ remaining
+ pure (existentialTerm TySet body)
+
+rebindEnvironment
+ :: VarSymbol
+ -> InductiveEnvironment
+ -> Either
+ TypedInductiveError
+ InductiveEnvironment
+rebindEnvironment variable
+ (InductiveEnvironment variables) =
+ pure
+ (InductiveEnvironment
+ (Map.insert variable 0 variables))
+
+injectDisjunction
+ :: ProofContext (InductiveGlobal global)
+ -> Natural
+ -> [CanonicalTerm (InductiveGlobal global)]
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+injectDisjunction context index alternatives proof =
+ case splitAtNatural index alternatives of
+ Nothing ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ "inductive clause index is outside the source-ordered alternatives")
+ Just (preceding, _selected, following) -> do
+ selectedProof <-
+ case preceding of
+ [] ->
+ pure proof
+ _ -> do
+ let precedingTerm =
+ disjunctionList preceding
+ first TypedInductiveProofError
+ (disjunctionRightProof
+ context
+ precedingTerm
+ proof)
+ foldM
+ (\current followingTerm ->
+ first TypedInductiveProofError
+ (disjunctionLeftProof
+ context
+ followingTerm
+ current))
+ selectedProof
+ following
+ where
+ splitAtNatural
+ :: Natural
+ -> [a]
+ -> Maybe ([a], a, [a])
+ splitAtNatural =
+ go []
+ where
+ go _preceding _index [] =
+ Nothing
+ go preceding 0 (selected : rest) =
+ Just
+ ( reverse preceding
+ , selected
+ , rest
+ )
+ go preceding current (item : rest) =
+ go
+ (item : preceding)
+ (current - 1)
+ rest
+
+closureTerms
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ [CanonicalTerm (InductiveGlobal global)]
+closureTerms
+ resolveGlobal
+ inductive
+ environment
+ subset =
+ traverse closureFor
+ (NonEmpty.toList
+ (preparedInductiveClauses
+ inductive))
+ where
+ closureFor clause = do
+ clauseEnvironment <-
+ extendVariables
+ environment
+ (preparedClauseVariables clause)
+ let binderCount =
+ fromIntegral
+ (length
+ (preparedClauseVariables
+ clause))
+ subset' =
+ shiftCanonicalTerm
+ binderCount
+ 0
+ subset
+ conditions <-
+ traverse
+ (conditionTerm
+ resolveGlobal
+ clauseEnvironment
+ subset')
+ (preparedClauseConditions
+ clause)
+ result <-
+ lowerTerm
+ resolveGlobal
+ clauseEnvironment
+ (preparedClauseResult clause)
+ pure
+ (closeForalls
+ (length
+ (preparedClauseVariables
+ clause))
+ (impliesIfNeeded
+ (conjunctionList conditions)
+ (memberTerm
+ result
+ subset')))
+
+proveBoundedMonotonicity
+ :: CheckedFoundation
+ -> (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+proveBoundedMonotonicity
+ _foundation
+ resolveGlobal
+ inductive
+ context
+ environment = do
+ domain <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ (preparedInductiveDomain
+ inductive)
+ operator <-
+ checkedTerm context
+ =<< operatorTerm
+ resolveGlobal
+ inductive
+ environment
+ operatorDomain <-
+ checkedTerm context
+ (CApp
+ (scopedCoreTerm operator)
+ (scopedCoreTerm domain))
+ bounded <-
+ first
+ (TypedInductivePreparationContext
+ "bounded monotonicity range")
+ (proveSubset
+ context
+ operatorDomain
+ domain
+ (\elementContext element membership -> do
+ elementEnvironment <-
+ shiftEnvironment environment
+ domainAtElement <-
+ checkedTerm elementContext
+ =<< lowerTerm
+ resolveGlobal
+ elementEnvironment
+ (preparedInductiveDomain
+ inductive)
+ predicate <-
+ checkedTerm elementContext
+ =<< operatorPredicateAt
+ resolveGlobal
+ inductive
+ elementEnvironment
+ (scopedCoreTerm
+ domainAtElement)
+ explicitOperator <-
+ checkedTerm elementContext
+ =<< separationSetAt
+ resolveGlobal
+ inductive
+ elementEnvironment
+ (scopedCoreTerm
+ domainAtElement)
+ explicitMembership <-
+ checkedTerm elementContext
+ (memberTerm
+ (scopedCoreTerm element)
+ (scopedCoreTerm
+ explicitOperator))
+ separatedMembership <-
+ first TypedInductiveProofError
+ (conversionProof
+ elementContext
+ membership
+ explicitMembership)
+ characteristic <-
+ separationForward
+ elementContext
+ domainAtElement
+ predicate
+ element
+ separatedMembership
+ first TypedInductiveProofError
+ (conjunctionLeftProof
+ elementContext
+ (memberTerm
+ (scopedCoreTerm element)
+ (scopedCoreTerm
+ domainAtElement))
+ (CApp
+ (scopedCoreTerm predicate)
+ (scopedCoreTerm element))
+ characteristic)))
+ monotone <-
+ first
+ (TypedInductivePreparationContext
+ "bounded monotonicity relation")
+ (forallIntroductionTyped
+ context
+ TySet
+ (\xContext _x ->
+ forallIntroductionTyped
+ xContext
+ TySet
+ (\xyContext _y -> do
+ xyEnvironment <-
+ shiftEnvironment
+ =<< shiftEnvironment
+ environment
+ x <-
+ checkedTerm xyContext
+ (CBound 1)
+ y <-
+ checkedTerm xyContext
+ (CBound 0)
+ domainXY <-
+ checkedTerm xyContext
+ =<< lowerTerm
+ resolveGlobal
+ xyEnvironment
+ (preparedInductiveDomain
+ inductive)
+ relation <-
+ checkedTerm xyContext
+ (conjunctionTerm
+ (subsetTerm
+ (scopedCoreTerm x)
+ (scopedCoreTerm y))
+ (subsetTerm
+ (scopedCoreTerm y)
+ (scopedCoreTerm
+ domainXY)))
+ implicationIntroductionTyped
+ xyContext
+ relation
+ (\relatedContext _relationProof -> do
+ operatorXY <-
+ checkedTerm relatedContext
+ =<< operatorTerm
+ resolveGlobal
+ inductive
+ xyEnvironment
+ operatorX <-
+ checkedTerm relatedContext
+ (CApp
+ (scopedCoreTerm
+ operatorXY)
+ (scopedCoreTerm x))
+ operatorY <-
+ checkedTerm relatedContext
+ (CApp
+ (scopedCoreTerm
+ operatorXY)
+ (scopedCoreTerm y))
+ proveSubset
+ relatedContext
+ operatorX
+ operatorY
+ (\elementContext element membership -> do
+ elementEnvironment <-
+ shiftEnvironment
+ xyEnvironment
+ xAtElement <-
+ checkedTerm
+ elementContext
+ (CBound 2)
+ yAtElement <-
+ checkedTerm
+ elementContext
+ (CBound 1)
+ domainAtElement <-
+ checkedTerm
+ elementContext
+ =<< lowerTerm
+ resolveGlobal
+ elementEnvironment
+ (preparedInductiveDomain
+ inductive)
+ predicateX <-
+ checkedTerm
+ elementContext
+ =<< operatorPredicateAt
+ resolveGlobal
+ inductive
+ elementEnvironment
+ (scopedCoreTerm
+ xAtElement)
+ predicateY <-
+ checkedTerm
+ elementContext
+ =<< operatorPredicateAt
+ resolveGlobal
+ inductive
+ elementEnvironment
+ (scopedCoreTerm
+ yAtElement)
+ explicitX <-
+ checkedTerm
+ elementContext
+ =<< separationSetAt
+ resolveGlobal
+ inductive
+ elementEnvironment
+ (scopedCoreTerm
+ xAtElement)
+ memberExplicitX <-
+ checkedTerm
+ elementContext
+ (memberTerm
+ (scopedCoreTerm
+ element)
+ (scopedCoreTerm
+ explicitX))
+ separatedX <-
+ first TypedInductiveProofError
+ (conversionProof
+ elementContext
+ membership
+ memberExplicitX)
+ characteristicX <-
+ separationForward
+ elementContext
+ domainAtElement
+ predicateX
+ element
+ separatedX
+ inDomain <-
+ first
+ (TypedInductivePreparationContext
+ "monotonicity domain projection")
+ (first TypedInductiveProofError
+ (conjunctionLeftProof
+ elementContext
+ (memberTerm
+ (scopedCoreTerm
+ element)
+ (scopedCoreTerm
+ domainAtElement))
+ (CApp
+ (scopedCoreTerm
+ predicateX)
+ (scopedCoreTerm
+ element))
+ characteristicX))
+ satisfiesX <-
+ first
+ (TypedInductivePreparationContext
+ "monotonicity predicate projection")
+ (first TypedInductiveProofError
+ (conjunctionRightProof
+ elementContext
+ (memberTerm
+ (scopedCoreTerm
+ element)
+ (scopedCoreTerm
+ domainAtElement))
+ (CApp
+ (scopedCoreTerm
+ predicateX)
+ (scopedCoreTerm
+ element))
+ characteristicX))
+ alternativesX <-
+ clauseFormulaTerms
+ resolveGlobal
+ inductive
+ elementEnvironment
+ (scopedCoreTerm
+ xAtElement)
+ (scopedCoreTerm
+ element)
+ alternativesY <-
+ clauseFormulaTerms
+ resolveGlobal
+ inductive
+ elementEnvironment
+ (scopedCoreTerm
+ yAtElement)
+ (scopedCoreTerm
+ element)
+ predicateXTarget <-
+ checkedTerm
+ elementContext
+ (disjunctionList
+ alternativesX)
+ satisfiesX' <-
+ first TypedInductiveProofError
+ (conversionProof
+ elementContext
+ satisfiesX
+ predicateXTarget)
+ satisfiesY <-
+ first
+ (TypedInductivePreparationContext
+ "monotonicity predicate transport")
+ (transformPredicateProof
+ resolveGlobal
+ inductive
+ elementContext
+ elementEnvironment
+ (scopedCoreTerm
+ xAtElement)
+ (scopedCoreTerm
+ yAtElement)
+ (scopedCoreTerm
+ domainAtElement)
+ (scopedCoreTerm
+ element)
+ alternativesX
+ alternativesY
+ satisfiesX')
+ separatedY <-
+ first
+ (TypedInductivePreparationContext
+ "monotonicity separation")
+ (separationBackward
+ elementContext
+ domainAtElement
+ predicateY
+ element
+ inDomain
+ satisfiesY)
+ operatorYAtElement <-
+ first
+ TypedInductiveCoreError
+ (weakenScopedCore
+ (Just
+ . inductiveGlobalType)
+ TySet
+ operatorY)
+ explicitMembershipY <-
+ checkedTerm
+ elementContext
+ (memberTerm
+ (scopedCoreTerm
+ element)
+ (scopedCoreTerm
+ operatorYAtElement))
+ first TypedInductiveProofError
+ (conversionProof
+ elementContext
+ separatedY
+ explicitMembershipY))))))
+ first
+ (TypedInductivePreparationContext
+ "bounded monotonicity conjunction")
+ (first TypedInductiveProofError
+ (conjunctionIntroductionProof
+ context
+ bounded
+ monotone))
+
+transformPredicateProof
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> [CanonicalTerm (InductiveGlobal global)]
+ -> [CanonicalTerm (InductiveGlobal global)]
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+transformPredicateProof
+ resolveGlobal
+ inductive
+ context
+ environment
+ candidateX
+ candidateY
+ domain
+ result
+ alternativesX
+ alternativesY
+ proof = do
+ target <-
+ checkedTerm context
+ (disjunctionList alternativesY)
+ first
+ (TypedInductivePreparationContext
+ "predicate disjunction elimination")
+ (eliminateDisjunctionAlternatives
+ context
+ alternativesX
+ proof
+ target
+ (\caseContext clauseIndex clauseProof -> do
+ clause <-
+ maybe
+ (Left
+ (TypedInductiveUnsupportedExpression
+ "inductive clause inventory changed during proof construction"))
+ Right
+ (atNatural
+ clauseIndex
+ (NonEmpty.toList
+ (preparedInductiveClauses
+ inductive)))
+ first
+ (TypedInductivePreparationContext
+ "predicate witness elimination")
+ (eliminateClauseWitnesses
+ resolveGlobal
+ clause
+ caseContext
+ environment
+ candidateX
+ result
+ clauseProof
+ target
+ (\depth
+ leafContext
+ leafEnvironment
+ candidateXAtLeaf
+ resultAtLeaf
+ bodyProof -> do
+ let candidateYAtLeaf =
+ shiftCanonicalTerm
+ depth
+ 0
+ candidateY
+ domainAtLeaf =
+ shiftCanonicalTerm
+ depth
+ 0
+ domain
+ alternativesYAtLeaf =
+ shiftCanonicalTerm
+ depth
+ 0
+ <$> alternativesY
+ bodyY <-
+ first
+ (TypedInductivePreparationContext
+ "predicate clause body")
+ (transformClauseBody
+ resolveGlobal
+ clause
+ leafContext
+ leafEnvironment
+ candidateXAtLeaf
+ candidateYAtLeaf
+ domainAtLeaf
+ resultAtLeaf
+ bodyProof)
+ resultAtLeaf' <-
+ checkedTerm
+ leafContext
+ resultAtLeaf
+ alternativeY <-
+ introduceClauseWitnesses
+ resolveGlobal
+ inductive
+ clause
+ leafContext
+ leafEnvironment
+ candidateYAtLeaf
+ resultAtLeaf'
+ bodyY
+ first
+ (TypedInductivePreparationContext
+ "predicate disjunction injection")
+ (injectDisjunction
+ leafContext
+ clauseIndex
+ alternativesYAtLeaf
+ alternativeY)))))
+
+transformClauseBody
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveClause (InductiveGlobal global)
+ -> ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+transformClauseBody
+ resolveGlobal
+ clause
+ context
+ environment
+ candidateX
+ candidateY
+ domain
+ result
+ proof = do
+ conditionsX <-
+ traverse
+ (conditionTerm
+ resolveGlobal
+ environment
+ candidateX)
+ (preparedClauseConditions
+ clause)
+ clauseResult <-
+ lowerTerm
+ resolveGlobal
+ environment
+ (preparedClauseResult clause)
+ let equality =
+ CEq TySet result clauseResult
+ bodyTermsX =
+ conditionsX <> [equality]
+ projections <-
+ projectConjunctionList
+ context
+ bodyTermsX
+ proof
+ let (conditionProofs, equalityProofs) =
+ splitAt
+ (length conditionsX)
+ projections
+ transformedConditions <-
+ sequence
+ [ case condition of
+ PreparedSideCondition _formula ->
+ pure conditionProof
+ PreparedDirectRecursiveCondition
+ recursiveTerm _context -> do
+ recursiveElement <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ recursiveTerm
+ subsetProof <-
+ subsetRelationHypothesis
+ context
+ candidateX
+ candidateY
+ domain
+ implication <-
+ first TypedInductiveProofError
+ (forallEliminationProof
+ context
+ subsetProof
+ recursiveElement)
+ first TypedInductiveProofError
+ (implicationEliminationProof
+ context
+ implication
+ conditionProof)
+ nested@PreparedNestedRecursiveCondition{} -> do
+ recursiveElement <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ (preparedRecursiveTerm nested)
+ subsetProof <-
+ subsetRelationHypothesis
+ context
+ candidateX
+ candidateY
+ domain
+ left <- checkedTerm context candidateX
+ right <- checkedTerm context candidateY
+ transportNestedRecursiveMembership
+ context
+ environment
+ nested
+ left
+ right
+ recursiveElement
+ subsetProof
+ conditionProof
+ | (condition, conditionProof) <-
+ zip
+ (preparedClauseConditions clause)
+ conditionProofs
+ ]
+ equalityProof <-
+ case equalityProofs of
+ [only] ->
+ pure only
+ _ ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ "inductive clause equality projection is inconsistent")
+ conjunctionIntroductionList
+ context
+ (transformedConditions
+ <> [equalityProof])
+
+subsetRelationHypothesis
+ :: ProofContext (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+subsetRelationHypothesis
+ context
+ left
+ right
+ domain = do
+ let leftSubsetRight =
+ subsetTerm left right
+ rightSubsetDomain =
+ subsetTerm right domain
+ relation =
+ conjunctionTerm
+ leftSubsetRight
+ rightSubsetDomain
+ relationTerm <-
+ checkedTerm context relation
+ relationProof <-
+ first TypedInductiveProofError
+ (hypothesisProof
+ context
+ relationTerm)
+ first TypedInductiveProofError
+ (conjunctionLeftProof
+ context
+ leftSubsetRight
+ rightSubsetDomain
+ relationProof)
+
+preparedRecursiveTerm
+ :: PreparedInductiveCondition global
+ -> Term
+preparedRecursiveTerm = \case
+ PreparedDirectRecursiveCondition term _context -> term
+ PreparedNestedRecursiveCondition term _context _index _target -> term
+ PreparedSideCondition{} ->
+ impossible "a side condition has no recursive element"
+
+transportNestedRecursiveMembership
+ :: ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> PreparedInductiveCondition (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+transportNestedRecursiveMembership
+ context
+ environment
+ condition
+ left
+ right
+ element
+ subsetProof
+ membership = do
+ monotonicity <-
+ nestedRecursiveMonotonicityProof
+ context environment condition left right subsetProof
+ implication <-
+ first TypedInductiveProofError
+ (forallEliminationProof
+ context monotonicity element)
+ first TypedInductiveProofError
+ (implicationEliminationProof
+ context implication membership)
+
+nestedRecursiveMonotonicityProof
+ :: ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> PreparedInductiveCondition (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+nestedRecursiveMonotonicityProof
+ context
+ environment
+ (PreparedNestedRecursiveCondition
+ _term
+ (CheckedRecursiveCarrierContext variables _template)
+ index
+ target)
+ left
+ right
+ subsetProof = do
+ theorem <-
+ first TypedInductiveProofError
+ (importedProof context index target)
+ specialized <-
+ eliminateWrittenForalls
+ context environment variables theorem
+ atLeft <-
+ first TypedInductiveProofError
+ (forallEliminationProof context specialized left)
+ atRight <-
+ first TypedInductiveProofError
+ (forallEliminationProof context atLeft right)
+ first TypedInductiveProofError
+ (implicationEliminationProof
+ context atRight subsetProof)
+nestedRecursiveMonotonicityProof
+ _context _environment _condition _left _right _subsetProof =
+ Left
+ (TypedInductiveUnsupportedExpression
+ "nested carrier transport requires a monotonicity import")
+
+proveInductionCandidateSubset
+ :: ProofContext (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+proveInductionCandidateSubset
+ context fixedPoint predicate candidate subset =
+ proveSubset
+ context candidate subset
+ (\elementContext element membership -> do
+ fixedPointAtElement <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ fixedPoint)
+ predicateAtElement <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ predicate)
+ explicitMembership <-
+ checkedTerm elementContext
+ (memberTerm
+ (scopedCoreTerm element)
+ (apply2
+ (CIntrinsic Sep)
+ (scopedCoreTerm fixedPointAtElement)
+ (scopedCoreTerm predicateAtElement)))
+ membership' <-
+ first TypedInductiveProofError
+ (conversionProof
+ elementContext membership explicitMembership)
+ characteristic <-
+ separationForward
+ elementContext
+ fixedPointAtElement
+ predicateAtElement
+ element
+ membership'
+ satisfies <-
+ first TypedInductiveProofError
+ (conjunctionRightProof
+ elementContext
+ (memberTerm
+ (scopedCoreTerm element)
+ (scopedCoreTerm fixedPointAtElement))
+ (CApp
+ (scopedCoreTerm predicateAtElement)
+ (scopedCoreTerm element))
+ characteristic)
+ expected <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ subset)
+ target <-
+ checkedTerm elementContext
+ (memberTerm
+ (scopedCoreTerm element)
+ (scopedCoreTerm expected))
+ first TypedInductiveProofError
+ (conversionProof
+ elementContext satisfies target))
+
+eliminateClauseWitnesses
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveClause (InductiveGlobal global)
+ -> ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ( Natural
+ -> ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+ )
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+eliminateClauseWitnesses
+ resolveGlobal
+ clause
+ initialContext
+ initialEnvironment
+ initialCandidate
+ initialResult
+ initialProof
+ initialTarget
+ finish =
+ go
+ 0
+ initialContext
+ initialEnvironment
+ initialCandidate
+ initialResult
+ initialProof
+ initialTarget
+ (preparedClauseVariables clause)
+ where
+ go depth context environment candidate result proof target = \case
+ [] ->
+ finish
+ depth
+ context
+ environment
+ candidate
+ result
+ proof
+ variable : remaining -> do
+ underBinderEnvironment <-
+ rebindEnvironment
+ variable
+ =<< shiftEnvironment
+ environment
+ let candidate' =
+ shiftCanonicalTerm
+ 1
+ 0
+ candidate
+ result' =
+ shiftCanonicalTerm
+ 1
+ 0
+ result
+ bodyUnderBinderTerm <-
+ clauseFormulaWithBinders
+ resolveGlobal
+ clause
+ underBinderEnvironment
+ candidate'
+ result'
+ remaining
+ bodyUnderBinder <-
+ first TypedInductiveCoreError
+ (checkScopedCanonicalCore
+ (Just . inductiveGlobalType)
+ (TySet
+ : proofContextTypes
+ context)
+ bodyUnderBinderTerm)
+ targetUnderBinder <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ target)
+ first TypedInductiveProofError
+ (existentialEliminationProof
+ context
+ TySet
+ bodyUnderBinder
+ proof
+ target
+ (\underBinderContext _witness bodyProof ->
+ first typedAsProofError
+ (go
+ (depth + 1)
+ underBinderContext
+ underBinderEnvironment
+ candidate'
+ result'
+ bodyProof
+ targetUnderBinder
+ remaining)))
+
+eliminateDisjunctionAlternatives
+ :: ProofContext (InductiveGlobal global)
+ -> [CanonicalTerm (InductiveGlobal global)]
+ -> BuiltProof (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ( ProofContext (InductiveGlobal global)
+ -> Natural
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+ )
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+eliminateDisjunctionAlternatives
+ initialContext
+ alternatives
+ proof
+ target
+ handle =
+ go initialContext 0 alternatives proof
+ where
+ go _context _offset [] _proof =
+ Left
+ (TypedInductiveUnsupportedExpression
+ "an inductive predicate has no alternatives")
+ go context offset [_only] onlyProof =
+ handle context offset onlyProof
+ go context offset current currentProof = do
+ let preceding =
+ List.init current
+ final =
+ List.last current
+ precedingTerm =
+ disjunctionList preceding
+ finalIndex =
+ offset
+ + fromIntegral
+ (length preceding)
+ first TypedInductiveProofError
+ (disjunctionEliminationProof
+ context
+ precedingTerm
+ final
+ currentProof
+ target
+ (\leftContext leftProof ->
+ first typedAsProofError
+ (go
+ leftContext
+ offset
+ preceding
+ leftProof))
+ (\rightContext rightProof ->
+ first typedAsProofError
+ (handle
+ rightContext
+ finalIndex
+ rightProof)))
+
+proveInductionClosure
+ :: CheckedFoundation
+ -> (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> [CanonicalTerm (InductiveGlobal global)]
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+proveInductionClosure
+ _foundation
+ resolveGlobal
+ inductive
+ context
+ environment
+ fixedPoint
+ operator
+ subset
+ closures
+ _closuresProof =
+ forallIntroductionTyped
+ context
+ TySet
+ (\elementContext element -> do
+ elementEnvironment <-
+ shiftEnvironment environment
+ fixedPointAtElement <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ fixedPoint)
+ operatorAtElement <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ operator)
+ subsetAtElement <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ subset)
+ inductionPredicate <-
+ membershipPredicate
+ elementContext
+ subsetAtElement
+ candidate <-
+ checkedTerm elementContext
+ (apply2
+ (CIntrinsic Sep)
+ (scopedCoreTerm
+ fixedPointAtElement)
+ (scopedCoreTerm
+ inductionPredicate))
+ unfolded <-
+ checkedTerm elementContext
+ (CApp
+ (scopedCoreTerm
+ operatorAtElement)
+ (scopedCoreTerm
+ candidate))
+ premise <-
+ checkedTerm elementContext
+ (memberTerm
+ (scopedCoreTerm element)
+ (scopedCoreTerm unfolded))
+ implicationIntroductionTyped
+ elementContext
+ premise
+ (\withMember memberProof -> do
+ domain <-
+ checkedTerm withMember
+ =<< lowerTerm
+ resolveGlobal
+ elementEnvironment
+ (preparedInductiveDomain
+ inductive)
+ operatorPredicate <-
+ checkedTerm withMember
+ =<< operatorPredicateAt
+ resolveGlobal
+ inductive
+ elementEnvironment
+ (scopedCoreTerm
+ candidate)
+ explicitOperator <-
+ checkedTerm withMember
+ =<< separationSetAt
+ resolveGlobal
+ inductive
+ elementEnvironment
+ (scopedCoreTerm
+ candidate)
+ explicitMembership <-
+ checkedTerm withMember
+ (memberTerm
+ (scopedCoreTerm
+ element)
+ (scopedCoreTerm
+ explicitOperator))
+ separated <-
+ first TypedInductiveProofError
+ (conversionProof
+ withMember
+ memberProof
+ explicitMembership)
+ characteristic <-
+ separationForward
+ withMember
+ domain
+ operatorPredicate
+ element
+ separated
+ predicateProof <-
+ first TypedInductiveProofError
+ (conjunctionRightProof
+ withMember
+ (memberTerm
+ (scopedCoreTerm
+ element)
+ (scopedCoreTerm
+ domain))
+ (CApp
+ (scopedCoreTerm
+ operatorPredicate)
+ (scopedCoreTerm
+ element))
+ characteristic)
+ alternatives <-
+ clauseFormulaTerms
+ resolveGlobal
+ inductive
+ elementEnvironment
+ (scopedCoreTerm
+ candidate)
+ (scopedCoreTerm
+ element)
+ predicateTarget <-
+ checkedTerm
+ withMember
+ (disjunctionList
+ alternatives)
+ predicateProof' <-
+ first TypedInductiveProofError
+ (conversionProof
+ withMember
+ predicateProof
+ predicateTarget)
+ result <-
+ checkedTerm withMember
+ (memberTerm
+ (scopedCoreTerm
+ element)
+ (scopedCoreTerm
+ subsetAtElement))
+ subsetMembership <-
+ first
+ (TypedInductivePreparationContext
+ "induction clause alternatives")
+ (eliminateDisjunctionAlternatives
+ withMember
+ alternatives
+ predicateProof'
+ result
+ (\caseContext clauseIndex clauseProof -> do
+ clause <-
+ maybe
+ (Left
+ (TypedInductiveUnsupportedExpression
+ "inductive closure clause index is outside the source inventory"))
+ Right
+ (atNatural
+ clauseIndex
+ (NonEmpty.toList
+ (preparedInductiveClauses
+ inductive)))
+ first
+ (TypedInductivePreparationContext
+ "induction clause witnesses")
+ (eliminateClauseWitnesses
+ resolveGlobal
+ clause
+ caseContext
+ elementEnvironment
+ (scopedCoreTerm
+ candidate)
+ (scopedCoreTerm
+ element)
+ clauseProof
+ result
+ (\depth
+ leafContext
+ leafEnvironment
+ _candidateAtLeaf
+ resultAtLeaf
+ bodyProof ->
+ first
+ (TypedInductivePreparationContext
+ "induction clause proof")
+ (proveInductionClause
+ resolveGlobal
+ inductive
+ clauseIndex
+ clause
+ leafContext
+ leafEnvironment
+ (shiftCanonicalTerm
+ depth
+ 0
+ (scopedCoreTerm
+ fixedPointAtElement))
+ (shiftCanonicalTerm
+ depth
+ 0
+ (scopedCoreTerm
+ inductionPredicate))
+ (shiftCanonicalTerm
+ depth
+ 0
+ (scopedCoreTerm
+ subsetAtElement))
+ (shiftCanonicalTerm
+ (depth + 2)
+ 0
+ <$> closures)
+ resultAtLeaf
+ bodyProof)))))
+ appliedPredicate <-
+ checkedTerm withMember
+ (CApp
+ (scopedCoreTerm
+ inductionPredicate)
+ (scopedCoreTerm
+ element))
+ first TypedInductiveProofError
+ (conversionProof
+ withMember
+ subsetMembership
+ appliedPredicate)))
+
+proveInductionClause
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> Natural
+ -> PreparedInductiveClause (InductiveGlobal global)
+ -> ProofContext (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> [CanonicalTerm (InductiveGlobal global)]
+ -> CanonicalTerm (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+proveInductionClause
+ resolveGlobal
+ _inductive
+ clauseIndex
+ clause
+ context
+ environment
+ fixedPoint
+ predicate
+ subset
+ closures
+ result
+ bodyProof = do
+ conditions <-
+ traverse
+ (conditionTerm
+ resolveGlobal
+ environment
+ (apply2
+ (CIntrinsic Sep)
+ fixedPoint
+ predicate))
+ (preparedClauseConditions
+ clause)
+ clauseResult <-
+ lowerTerm
+ resolveGlobal
+ environment
+ (preparedClauseResult clause)
+ let equality =
+ CEq TySet result clauseResult
+ bodyTerms =
+ conditions <> [equality]
+ projections <-
+ first
+ (TypedInductivePreparationContext
+ "induction clause body projections")
+ (projectConjunctionList
+ context
+ bodyTerms
+ bodyProof)
+ let (conditionProofs, equalityProofs) =
+ splitAt
+ (length conditions)
+ projections
+ closureConditionProofs <-
+ sequence
+ [ case condition of
+ PreparedSideCondition _formula ->
+ pure conditionProof
+ PreparedDirectRecursiveCondition
+ recursiveTerm _context -> do
+ recursiveElement <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ recursiveTerm
+ fixedPoint' <-
+ checkedTerm context fixedPoint
+ predicate' <-
+ checkedTerm context predicate
+ separated <-
+ separationForward
+ context
+ fixedPoint'
+ predicate'
+ recursiveElement
+ conditionProof
+ predicateMembership <-
+ first TypedInductiveProofError
+ (conjunctionRightProof
+ context
+ (memberTerm
+ (scopedCoreTerm
+ recursiveElement)
+ fixedPoint)
+ (CApp
+ predicate
+ (scopedCoreTerm
+ recursiveElement))
+ separated)
+ expected <-
+ checkedTerm context
+ (memberTerm
+ (scopedCoreTerm
+ recursiveElement)
+ subset)
+ first TypedInductiveProofError
+ (conversionProof
+ context
+ predicateMembership
+ expected)
+ nested@PreparedNestedRecursiveCondition{} -> do
+ recursiveElement <-
+ checkedTerm context
+ =<< lowerTerm
+ resolveGlobal
+ environment
+ (preparedRecursiveTerm nested)
+ fixedPoint' <- checkedTerm context fixedPoint
+ predicate' <- checkedTerm context predicate
+ candidate <-
+ checkedTerm context
+ (apply2
+ (CIntrinsic Sep)
+ fixedPoint
+ predicate)
+ subset' <- checkedTerm context subset
+ candidateSubset <-
+ proveInductionCandidateSubset
+ context
+ fixedPoint'
+ predicate'
+ candidate
+ subset'
+ transportNestedRecursiveMembership
+ context
+ environment
+ nested
+ candidate
+ subset'
+ recursiveElement
+ candidateSubset
+ conditionProof
+ | (condition, conditionProof) <-
+ zip
+ (preparedClauseConditions clause)
+ conditionProofs
+ ]
+ closureConjunction <-
+ case conjunctionList closures of
+ Nothing ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ "inductive closure inventory is empty")
+ Just conjunction ->
+ first
+ (TypedInductivePreparationContext
+ "induction closure conjunction")
+ (checkedTerm context conjunction)
+ allClosures <-
+ first
+ (TypedInductivePreparationContext
+ "induction closure hypothesis")
+ (first TypedInductiveProofError
+ (hypothesisProof
+ context
+ closureConjunction))
+ closureProofs <-
+ first
+ (TypedInductivePreparationContext
+ "induction closure projections")
+ (projectConjunctionList
+ context
+ closures
+ allClosures)
+ selectedClosure <-
+ maybe
+ (Left
+ (TypedInductiveUnsupportedExpression
+ "inductive closure projection is outside the source inventory"))
+ Right
+ (atNatural clauseIndex closureProofs)
+ specializedClosure <-
+ first
+ (TypedInductivePreparationContext
+ "induction closure specialization")
+ (eliminateWrittenForalls
+ context
+ environment
+ (preparedClauseVariables clause)
+ selectedClosure)
+ resultMembership <-
+ case closureConditionProofs of
+ [] ->
+ pure specializedClosure
+ _ -> do
+ conjunction <-
+ conjunctionIntroductionList
+ context
+ closureConditionProofs
+ first TypedInductiveProofError
+ (implicationEliminationProof
+ context
+ specializedClosure
+ conjunction)
+ equalityProof <-
+ case equalityProofs of
+ [only] ->
+ pure only
+ _ ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ "inductive closure equality projection is inconsistent")
+ subset' <-
+ first
+ (TypedInductivePreparationContext
+ "induction subset target")
+ (checkedTerm context subset)
+ first
+ (TypedInductivePreparationContext
+ "induction result transport")
+ (transportElementMembership
+ context
+ subset'
+ equalityProof
+ resultMembership)
+
+transportElementMembership
+ :: ProofContext (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+transportElementMembership
+ context
+ set
+ equality
+ membership = do
+ (sourceElement, targetElement) <-
+ case scopedCoreTerm
+ (builtProofStatement
+ equality) of
+ CEq TySet source target ->
+ Right (source, target)
+ _ ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ "element transport requires set equality")
+ predicate <-
+ membershipPredicate
+ context
+ set
+ predicateReflexivity <-
+ first TypedInductiveProofError
+ (equalityReflexivityProof
+ context
+ predicate)
+ propositionEquality <-
+ first TypedInductiveProofError
+ (equalityCongruenceApplicationProof
+ context
+ predicateReflexivity
+ equality)
+ reversed <-
+ first TypedInductiveProofError
+ (equalityReverseProof
+ context
+ propositionEquality)
+ appliedTarget <-
+ checkedTerm context
+ (CApp
+ (scopedCoreTerm predicate)
+ targetElement)
+ targetMembership <-
+ first TypedInductiveProofError
+ (conversionProof
+ context
+ membership
+ appliedTarget)
+ transported <-
+ first TypedInductiveProofError
+ (equalityModusPonensProof
+ context
+ reversed
+ targetMembership)
+ sourceMembership <-
+ checkedTerm context
+ (memberTerm
+ sourceElement
+ (scopedCoreTerm set))
+ first TypedInductiveProofError
+ (conversionProof
+ context
+ transported
+ sourceMembership)
+
+predicateAt
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+predicateAt
+ resolveGlobal
+ inductive
+ environment
+ candidate
+ result =
+ disjunctionList
+ <$> clauseFormulaTerms
+ resolveGlobal
+ inductive
+ environment
+ candidate
+ result
+
+clauseFormulaTerms
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ [CanonicalTerm (InductiveGlobal global)]
+clauseFormulaTerms
+ resolveGlobal
+ inductive
+ environment
+ candidate
+ result =
+ traverse
+ (clauseFormulaAt
+ resolveGlobal
+ environment
+ candidate
+ result)
+ (NonEmpty.toList
+ (preparedInductiveClauses
+ inductive))
+
+clauseFormulaAt
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> CanonicalTerm (InductiveGlobal global)
+ -> PreparedInductiveClause (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+clauseFormulaAt
+ resolveGlobal
+ environment
+ candidate
+ result
+ clause = do
+ clauseEnvironment <-
+ extendVariables
+ environment
+ (preparedClauseVariables clause)
+ let binderCount =
+ fromIntegral
+ (length
+ (preparedClauseVariables
+ clause))
+ candidate' =
+ shiftCanonicalTerm binderCount 0 candidate
+ result' =
+ shiftCanonicalTerm binderCount 0 result
+ conditions <-
+ traverse
+ (conditionTerm
+ resolveGlobal
+ clauseEnvironment
+ candidate')
+ (preparedClauseConditions
+ clause)
+ clauseResult <-
+ lowerTerm
+ resolveGlobal
+ clauseEnvironment
+ (preparedClauseResult clause)
+ pure
+ (closeExistentials
+ (length
+ (preparedClauseVariables
+ clause))
+ (fromMaybe
+ (CEq TySet result' clauseResult)
+ (conjunctionList
+ (conditions
+ <> [CEq
+ TySet
+ result'
+ clauseResult]))))
+
+extendVariables
+ :: InductiveEnvironment
+ -> [VarSymbol]
+ -> Either
+ TypedInductiveError
+ InductiveEnvironment
+extendVariables =
+ go []
+ where
+ go _seen environment [] =
+ Right environment
+ go seen environment (variable : remaining)
+ | variable `elem` seen =
+ Left
+ (TypedInductiveDuplicateBinder
+ variable)
+ | otherwise = do
+ extended <-
+ rebindEnvironment
+ variable
+ =<< shiftEnvironment
+ environment
+ go
+ (variable : seen)
+ extended
+ remaining
+
+membershipPredicate
+ :: ProofContext (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (ScopedCheckedCore (InductiveGlobal global))
+membershipPredicate context set = do
+ weakenedSet <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ set)
+ checkedTerm context
+ (CLam TySet
+ (memberTerm
+ (CBound 0)
+ (scopedCoreTerm
+ weakenedSet)))
+
+operatorPredicateAt
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+operatorPredicateAt
+ resolveGlobal
+ inductive
+ environment
+ candidate = do
+ extended <-
+ shiftEnvironment environment
+ body <-
+ predicateAt
+ resolveGlobal
+ inductive
+ extended
+ (shiftCanonicalTerm
+ 1
+ 0
+ candidate)
+ (CBound 0)
+ pure (CLam TySet body)
+
+separationSetAt
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+separationSetAt
+ resolveGlobal
+ inductive
+ environment
+ candidate = do
+ domain <-
+ lowerTerm
+ resolveGlobal
+ environment
+ (preparedInductiveDomain
+ inductive)
+ predicate <-
+ operatorPredicateAt
+ resolveGlobal
+ inductive
+ environment
+ candidate
+ pure
+ (apply2
+ (CIntrinsic Sep)
+ domain
+ predicate)
+
+foundationInstance
+ :: ProofContext (InductiveGlobal global)
+ -> FoundationAxiomTag
+ -> [ScopedCheckedCore (InductiveGlobal global)]
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+foundationInstance context tag arguments = do
+ initial <-
+ first TypedInductiveProofError
+ (foundationProof context tag)
+ foldM
+ (\proof argument ->
+ first TypedInductiveProofError
+ (forallEliminationProof
+ context
+ proof
+ argument))
+ initial
+ arguments
+
+separationForward
+ :: ProofContext (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+separationForward
+ context
+ domain
+ predicate
+ element
+ membership = do
+ characteristic <-
+ foundationInstance
+ context
+ SeparationCharacteristic
+ [domain, predicate, element]
+ first TypedInductiveProofError
+ (equalityModusPonensProof
+ context
+ characteristic
+ membership)
+
+separationBackward
+ :: ProofContext (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+separationBackward
+ context
+ domain
+ predicate
+ element
+ inDomain
+ satisfies = do
+ characteristic <-
+ foundationInstance
+ context
+ SeparationCharacteristic
+ [domain, predicate, element]
+ reversed <-
+ first TypedInductiveProofError
+ (equalityReverseProof
+ context
+ characteristic)
+ expectedSatisfies <-
+ checkedTerm context
+ (CApp
+ (scopedCoreTerm predicate)
+ (scopedCoreTerm element))
+ satisfies' <-
+ first TypedInductiveProofError
+ (conversionProof
+ context
+ satisfies
+ expectedSatisfies)
+ conjunction <-
+ first TypedInductiveProofError
+ (conjunctionIntroductionProof
+ context
+ inDomain
+ satisfies')
+ first TypedInductiveProofError
+ (equalityModusPonensProof
+ context
+ reversed
+ conjunction)
+
+transportMembership
+ :: ProofContext (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+transportMembership
+ context
+ element
+ setEquality
+ membership = do
+ (sourceSet, targetSet) <-
+ case scopedCoreTerm
+ (builtProofStatement
+ setEquality) of
+ CEq TySet source target ->
+ Right (source, target)
+ _ ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ "membership transport requires set equality")
+ weakenedElement <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ element)
+ function <-
+ checkedTerm context
+ (CLam TySet
+ (memberTerm
+ (scopedCoreTerm
+ weakenedElement)
+ (CBound 0)))
+ functionReflexivity <-
+ first TypedInductiveProofError
+ (equalityReflexivityProof
+ context
+ function)
+ propositionEquality <-
+ first TypedInductiveProofError
+ (equalityCongruenceApplicationProof
+ context
+ functionReflexivity
+ setEquality)
+ appliedSource <-
+ checkedTerm context
+ (CApp
+ (scopedCoreTerm function)
+ sourceSet)
+ sourceMembership <-
+ first TypedInductiveProofError
+ (conversionProof
+ context
+ membership
+ appliedSource)
+ transported <-
+ first TypedInductiveProofError
+ (equalityModusPonensProof
+ context
+ propositionEquality
+ sourceMembership)
+ targetMembership <-
+ checkedTerm context
+ (memberTerm
+ (scopedCoreTerm element)
+ targetSet)
+ first TypedInductiveProofError
+ (conversionProof
+ context
+ transported
+ targetMembership)
+
+proveSubset
+ :: ProofContext (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ( ProofContext (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+ )
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+proveSubset context left right proveElement =
+ forallIntroductionTyped
+ context
+ TySet
+ (\extended element -> do
+ left' <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ left)
+ right' <-
+ first TypedInductiveCoreError
+ (weakenScopedCore
+ (Just . inductiveGlobalType)
+ TySet
+ right)
+ memberLeft <-
+ checkedTerm extended
+ (memberTerm
+ (scopedCoreTerm element)
+ (scopedCoreTerm left'))
+ implicationIntroductionTyped
+ extended
+ memberLeft
+ (\withMember memberProof ->
+ proveElement
+ withMember
+ element
+ memberProof
+ >>= \result -> do
+ expected <-
+ checkedTerm withMember
+ (memberTerm
+ (scopedCoreTerm
+ element)
+ (scopedCoreTerm
+ right'))
+ if builtProofStatement result
+ == expected
+ then pure result
+ else
+ Left
+ (TypedInductiveUnsupportedExpression
+ "subset proof produced the wrong membership target")))
+
+typedAsProofError
+ :: TypedInductiveError
+ -> KernelProofBuildError
+typedAsProofError = \case
+ TypedInductiveProofError err ->
+ err
+ err ->
+ ProofSetLfpRuleFailed
+ (Text.pack (show err))
+
+implicationIntroductionTyped
+ :: ProofContext (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> ( ProofContext (InductiveGlobal global)
+ -> BuiltProof (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+ )
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+implicationIntroductionTyped context premise build =
+ first TypedInductiveProofError
+ (implicationIntroductionProof
+ context
+ premise
+ (\extended proof ->
+ first typedAsProofError
+ (build extended proof)))
+
+forallIntroductionTyped
+ :: ProofContext (InductiveGlobal global)
+ -> CoreType
+ -> ( ProofContext (InductiveGlobal global)
+ -> ScopedCheckedCore (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+ )
+ -> Either
+ TypedInductiveError
+ (BuiltProof (InductiveGlobal global))
+forallIntroductionTyped context binderType build =
+ first TypedInductiveProofError
+ (forallIntroductionProof
+ context
+ binderType
+ (\extended variable ->
+ first typedAsProofError
+ (build extended variable)))
+
+buildUnderVariables
+ :: InductiveEnvironment
+ -> [VarSymbol]
+ -> ( InductiveEnvironment
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+ )
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+buildUnderVariables
+ environment
+ []
+ build =
+ build environment
+buildUnderVariables
+ environment
+ (variable : remaining)
+ build = do
+ extended <-
+ extendEnvironment
+ variable
+ environment
+ buildUnderVariables
+ extended
+ remaining
+ build
+
+fixedPointTerm
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+fixedPointTerm resolveGlobal inductive environment = do
+ domain <-
+ lowerTerm
+ resolveGlobal
+ environment
+ (preparedInductiveDomain
+ inductive)
+ operator <-
+ operatorTerm
+ resolveGlobal
+ inductive
+ environment
+ pure
+ (CApp
+ (CApp
+ (CIntrinsic ISetLfp)
+ domain)
+ operator)
+
+operatorTerm
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> PreparedInductiveSource (InductiveGlobal global)
+ -> InductiveEnvironment
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+operatorTerm resolveGlobal inductive parameterEnvironment = do
+ candidateEnvironment <-
+ shiftEnvironment parameterEnvironment
+ domain <-
+ lowerTerm
+ resolveGlobal
+ candidateEnvironment
+ (preparedInductiveDomain
+ inductive)
+ resultEnvironment <-
+ shiftEnvironment candidateEnvironment
+ clauses <-
+ traverse
+ (clausePredicateTerm
+ resolveGlobal
+ resultEnvironment)
+ (preparedInductiveClauses
+ inductive)
+ pure
+ (CLam TySet
+ (CApp
+ (CApp
+ (CIntrinsic Sep)
+ domain)
+ (CLam TySet
+ (disjunctionList
+ (NonEmpty.toList
+ clauses)))))
+
+clausePredicateTerm
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> InductiveEnvironment
+ -> PreparedInductiveClause (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+clausePredicateTerm
+ resolveGlobal
+ resultEnvironment
+ clause = do
+ clauseEnvironment <-
+ extendVariables
+ resultEnvironment
+ (preparedClauseVariables clause)
+ let variableCount =
+ fromIntegral
+ (length
+ (preparedClauseVariables
+ clause))
+ resultVariable =
+ CBound variableCount
+ candidate =
+ CBound (variableCount + 1)
+ conditions <-
+ traverse
+ (conditionTerm
+ resolveGlobal
+ clauseEnvironment
+ candidate)
+ (preparedClauseConditions
+ clause)
+ result <-
+ lowerTerm
+ resolveGlobal
+ clauseEnvironment
+ (preparedClauseResult
+ clause)
+ pure
+ (closeExistentials
+ (length
+ (preparedClauseVariables clause))
+ (fromMaybe
+ (CEq
+ TySet
+ resultVariable
+ result)
+ (conjunctionList
+ (conditions
+ <> [CEq
+ TySet
+ resultVariable
+ result]))))
+
+directConditionTerm
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> DirectInductiveCondition
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+directConditionTerm resolveGlobal environment candidate = \case
+ DirectSideCondition formula ->
+ lowerFormula resolveGlobal environment formula
+ DirectRecursiveCondition term context -> do
+ carrier <-
+ betaNormalizeCanonical
+ <$> lowerRecursiveCarrierContext
+ resolveGlobal environment candidate context
+ memberTerm
+ <$> lowerTerm resolveGlobal environment term
+ <*> pure carrier
+
+conditionTerm
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> InductiveEnvironment
+ -> CanonicalTerm (InductiveGlobal global)
+ -> PreparedInductiveCondition (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+conditionTerm resolveGlobal environment candidate = \case
+ PreparedSideCondition formula ->
+ lowerFormula
+ resolveGlobal
+ environment
+ formula
+ PreparedDirectRecursiveCondition term context -> do
+ carrier <-
+ instantiateRecursiveCarrier environment candidate context
+ memberTerm
+ <$> lowerTerm
+ resolveGlobal
+ environment
+ term
+ <*> pure carrier
+ PreparedNestedRecursiveCondition term context _index _target -> do
+ carrier <-
+ instantiateRecursiveCarrier environment candidate context
+ memberTerm
+ <$> lowerTerm resolveGlobal environment term
+ <*> pure carrier
+
+shiftEnvironment
+ :: InductiveEnvironment
+ -> Either
+ TypedInductiveError
+ InductiveEnvironment
+shiftEnvironment
+ (InductiveEnvironment variables) =
+ pure
+ (InductiveEnvironment
+ (succ <$> variables))
+
+lowerFormula
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> InductiveEnvironment
+ -> Formula
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+lowerFormula =
+ lowerFormulaWith False
+
+lowerFormulaWith
+ :: Bool
+ -> (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> InductiveEnvironment
+ -> Formula
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+lowerFormulaWith allowQuantified resolveGlobal environment = \case
+ IsElementOf _location element set ->
+ memberTerm
+ <$> lowerTerm
+ resolveGlobal
+ environment
+ element
+ <*> lowerTerm
+ resolveGlobal
+ environment
+ set
+ Equals _location left right ->
+ CEq TySet
+ <$> lowerTerm resolveGlobal environment left
+ <*> lowerTerm resolveGlobal environment right
+ NotEquals _location left right ->
+ notTerm
+ <$> (CEq TySet
+ <$> lowerTerm resolveGlobal environment left
+ <*> lowerTerm resolveGlobal environment right)
+ IsSubsetOf _location left right -> do
+ extended <-
+ shiftEnvironment environment
+ left' <-
+ lowerTerm resolveGlobal extended left
+ right' <-
+ lowerTerm resolveGlobal extended right
+ pure
+ (CForall TySet
+ (CImp
+ (memberTerm
+ (CBound 0)
+ left')
+ (memberTerm
+ (CBound 0)
+ right')))
+ Bottom ->
+ pure CFalsum
+ Top ->
+ pure (CImp CFalsum CFalsum)
+ Not _location proposition ->
+ notTerm
+ <$> lowerFormulaWith allowQuantified
+ resolveGlobal
+ environment
+ proposition
+ left `Implies` right ->
+ CImp
+ <$> lowerFormulaWith allowQuantified
+ resolveGlobal environment left
+ <*> lowerFormulaWith allowQuantified
+ resolveGlobal environment right
+ left `And` right ->
+ conjunctionTerm
+ <$> lowerFormulaWith allowQuantified
+ resolveGlobal environment left
+ <*> lowerFormulaWith allowQuantified
+ resolveGlobal environment right
+ left `Or` right ->
+ disjunctionTerm
+ <$> lowerFormulaWith allowQuantified
+ resolveGlobal environment left
+ <*> lowerFormulaWith allowQuantified
+ resolveGlobal environment right
+ left `Iff` right ->
+ CEq TyProp
+ <$> lowerFormulaWith allowQuantified
+ resolveGlobal environment left
+ <*> lowerFormulaWith allowQuantified
+ resolveGlobal environment right
+ Atomic _location predicate arguments ->
+ lowerPredicateApplication
+ resolveGlobal
+ environment
+ (SymbolPredicate predicate)
+ arguments
+ Quantified quantifier scope
+ | allowQuantified -> do
+ let variables =
+ nubOrd
+ [ variable
+ | B variable <- toList (fromScope scope)
+ ]
+ body = instantiate TermVar scope
+ extended <- extendVariables environment variables
+ lowered <-
+ lowerFormulaWith
+ allowQuantified
+ resolveGlobal
+ extended
+ body
+ pure
+ (case quantifier of
+ Universally ->
+ closeForalls (length variables) lowered
+ Existentially ->
+ closeExistentials (length variables) lowered)
+ _ ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ "quantified and higher-order side conditions are not supported by the typed inductive slice")
+
+lowerTerm
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> InductiveEnvironment
+ -> Term
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+lowerTerm resolveGlobal environment = \case
+ TermVar variable ->
+ lookupEnvironment
+ variable
+ environment
+ EmptySet _location ->
+ pure (CIntrinsic Empty)
+ TermSymbol _location (SymbolInteger integer) [] ->
+ pure
+ (COpaqueInteger
+ (toInteger integer))
+ TermSymbol _location symbol arguments ->
+ lowerApplication
+ resolveGlobal
+ environment
+ symbol
+ arguments
+ _ ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ "higher-order source terms are not supported by the typed inductive slice")
+
+lowerApplication
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> InductiveEnvironment
+ -> Symbol
+ -> [Expr]
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+lowerApplication resolveGlobal environment symbol arguments = do
+ arguments' <-
+ traverse
+ (lowerTerm
+ resolveGlobal
+ environment)
+ arguments
+ lowerApplicationTerms resolveGlobal symbol arguments'
+
+lowerPredicateApplication
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> InductiveEnvironment
+ -> Symbol
+ -> [Expr]
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+lowerPredicateApplication resolveGlobal environment symbol arguments = do
+ arguments' <-
+ traverse
+ (lowerTerm
+ resolveGlobal
+ environment)
+ arguments
+ case classifyExactSymbol symbol of
+ ExactFixedPrimitive meaning ->
+ maybe
+ (lowerApplicationTerms resolveGlobal symbol arguments')
+ Right
+ (lowerFixedEqualityPredicate meaning arguments')
+ _ ->
+ lowerApplicationTerms resolveGlobal symbol arguments'
+
+lowerApplicationTerms
+ :: (Symbol -> Maybe (SourceGlobal (InductiveGlobal global)))
+ -> Symbol
+ -> [CanonicalTerm (InductiveGlobal global)]
+ -> Either
+ TypedInductiveError
+ (CanonicalTerm (InductiveGlobal global))
+lowerApplicationTerms resolveGlobal symbol arguments' =
+ case dispatchFixedSetTerm symbol arguments' of
+ LoweredFixedSetTerm term ->
+ pure term
+ RejectedFixedSetTerm ->
+ Left
+ (TypedInductiveUnsupportedExpression
+ ("fixed source symbol is not a supported set term: "
+ <> symbolText symbol))
+ NotFixedSetTerm -> do
+ SourceGlobal reference body <-
+ maybe
+ (Left
+ (TypedInductiveUnsupportedExpression
+ ("source symbol is not typed: "
+ <> symbolText symbol)))
+ Right
+ (resolveGlobal symbol)
+ pure
+ (foldl'
+ CApp
+ (maybe
+ (CGlobal reference)
+ frozenCoreTerm
+ body)
+ arguments')
+
+memberTerm
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+memberTerm =
+ apply2 (CIntrinsic Member)
+
+subsetTerm
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+subsetTerm left right =
+ CForall TySet
+ (CImp
+ (memberTerm
+ (CBound 0)
+ (shiftCanonicalTerm
+ 1
+ 0
+ left))
+ (memberTerm
+ (CBound 0)
+ (shiftCanonicalTerm
+ 1
+ 0
+ right)))
+
+apply2
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+apply2 function firstArgument secondArgument =
+ CApp
+ (CApp function firstArgument)
+ secondArgument
+
+notTerm
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+notTerm proposition =
+ CImp proposition CFalsum
+
+conjunctionList
+ :: [CanonicalTerm global]
+ -> Maybe (CanonicalTerm global)
+conjunctionList = \case
+ [] ->
+ Nothing
+ firstTerm : remaining ->
+ Just
+ (foldl'
+ conjunctionTerm
+ firstTerm
+ remaining)
+
+disjunctionList
+ :: [CanonicalTerm global]
+ -> CanonicalTerm global
+disjunctionList = \case
+ [] ->
+ CFalsum
+ firstTerm : remaining ->
+ foldl'
+ disjunctionTerm
+ firstTerm
+ remaining
+
+impliesIfNeeded
+ :: Maybe (CanonicalTerm global)
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+impliesIfNeeded = \case
+ Nothing ->
+ id
+ Just premise ->
+ CImp premise
+
+closeLambdas
+ :: Int
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+closeLambdas binderCount body =
+ foldl'
+ (\current _ ->
+ CLam TySet current)
+ body
+ [1 .. binderCount]
+
+closeForalls
+ :: Int
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+closeForalls binderCount body =
+ foldl'
+ (\current _ ->
+ CForall TySet current)
+ body
+ [1 .. binderCount]
+
+closeExistentials
+ :: Int
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+closeExistentials binderCount body =
+ foldl'
+ (\current _ ->
+ existentialTerm TySet current)
+ body
+ [1 .. binderCount]
+
+shiftCanonicalTerm
+ :: Natural
+ -> Natural
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+shiftCanonicalTerm amount cutoff = \case
+ CBound index
+ | index >= cutoff ->
+ CBound (index + amount)
+ | otherwise ->
+ CBound index
+ CGlobal global ->
+ CGlobal global
+ CIntrinsic intrinsic ->
+ CIntrinsic intrinsic
+ COpaqueInteger integer ->
+ COpaqueInteger integer
+ CApp function argument ->
+ CApp
+ (shiftCanonicalTerm
+ amount
+ cutoff
+ function)
+ (shiftCanonicalTerm
+ amount
+ cutoff
+ argument)
+ CLam binderType body ->
+ CLam binderType
+ (shiftCanonicalTerm
+ amount
+ (cutoff + 1)
+ body)
+ CFalsum ->
+ CFalsum
+ CImp premise conclusion ->
+ CImp
+ (shiftCanonicalTerm
+ amount
+ cutoff
+ premise)
+ (shiftCanonicalTerm
+ amount
+ cutoff
+ conclusion)
+ CEq operandType left right ->
+ CEq operandType
+ (shiftCanonicalTerm
+ amount
+ cutoff
+ left)
+ (shiftCanonicalTerm
+ amount
+ cutoff
+ right)
+ CForall binderType body ->
+ CForall binderType
+ (shiftCanonicalTerm
+ amount
+ (cutoff + 1)
+ body)
+
+atNatural :: Natural -> [a] -> Maybe a
+atNatural _index [] =
+ Nothing
+atNatural 0 (value : _rest) =
+ Just value
+atNatural index (_value : rest) =
+ atNatural (index - 1) rest
+
+freezeClosedTarget
+ :: CanonicalTerm (InductiveGlobal global)
+ -> Either
+ TypedInductiveError
+ (FrozenCheckedCore (InductiveGlobal global))
+freezeClosedTarget =
+ first TypedInductiveCoreError
+ . checkCanonicalCore
+ (Just . inductiveGlobalType)
+
+symbolText :: Symbol -> Text
+symbolText = \case
+ SymbolMixfix symbol ->
+ case mixfixMarker symbol of
+ Marker text ->
+ text
+ SymbolFun symbol ->
+ case lexicalItemSgPlMarker symbol of
+ Marker text ->
+ text
+ SymbolInteger integer ->
+ Text.pack (show integer)
+ SymbolPredicate predicate ->
+ case predicateObjectMarker predicate of
+ Marker text ->
+ text
diff --git a/source/Felix/CommandLine.hs b/source/Felix/CommandLine.hs
index 701020f..448d698 100644
--- a/source/Felix/CommandLine.hs
+++ b/source/Felix/CommandLine.hs
@@ -26,10 +26,10 @@ import Felix.Store qualified as Store
import Felix.Verification qualified as Verification
import Felix.Version qualified as Version
import Felix.Workspace qualified as Workspace
-import Render.Html.Export qualified as HtmlExport
-import Render.Html.Layout qualified as HtmlLayout
-import Render.Html.Output qualified as HtmlOutput
-import Report.Location
+import Felix.Render.Html.Export qualified as HtmlExport
+import Felix.Render.Html.Layout qualified as HtmlLayout
+import Felix.Render.Html.Output qualified as HtmlOutput
+import Felix.Report.Location
import Control.Monad (unless, when)
import Data.Maybe (catMaybes)
diff --git a/source/Felix/Math/Codec.hs b/source/Felix/Math/Codec.hs
index 4a5c7b1..a551f53 100644
--- a/source/Felix/Math/Codec.hs
+++ b/source/Felix/Math/Codec.hs
@@ -24,7 +24,7 @@ module Felix.Math.Codec
) where
import Base hiding (Empty)
-import Checking.Core
+import Felix.Checking.Core
import Control.DeepSeq (NFData)
import Crypto.Hash qualified as Crypto
diff --git a/source/Felix/Meaning.hs b/source/Felix/Meaning.hs
index 60c4cfc..268a1a6 100644
--- a/source/Felix/Meaning.hs
+++ b/source/Felix/Meaning.hs
@@ -9,12 +9,12 @@ module Felix.Meaning where
import Base
-import Syntax.Abstract (Sign(..))
-import Syntax.Abstract qualified as Raw
-import Syntax.Internal (VarSymbol(..), pattern FreshVar)
-import Syntax.Internal qualified as Sem
-import Syntax.LexicalPhrase (unsafeReadPhrase)
-import Report.Location
+import Felix.Syntax.Abstract (Sign(..))
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Internal (VarSymbol(..), pattern FreshVar)
+import Felix.Syntax.Internal qualified as Sem
+import Felix.Syntax.LexicalPhrase (unsafeReadPhrase)
+import Felix.Report.Location
import Bound
import Control.Monad.Except
diff --git a/source/Felix/OutputPlan.hs b/source/Felix/OutputPlan.hs
index 58314db..78d7e7a 100644
--- a/source/Felix/OutputPlan.hs
+++ b/source/Felix/OutputPlan.hs
@@ -18,7 +18,7 @@ module Felix.OutputPlan
import Base
import Felix.Source
import Felix.Store
-import Render.Html.Output qualified as Html
+import Felix.Render.Html.Output qualified as Html
import Control.Exception (displayException)
import Data.List qualified as List
diff --git a/source/Felix/Parse.hs b/source/Felix/Parse.hs
index e03feb9..0a46d82 100644
--- a/source/Felix/Parse.hs
+++ b/source/Felix/Parse.hs
@@ -90,9 +90,9 @@ import Felix.Source
import Felix.Source.Content qualified as Content
import Felix.Source.Graph
import Felix.Store qualified as Store
-import Report.Location
-import Syntax.Abstract qualified as Raw
-import Syntax.Adapt
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Adapt
( LexicalScanError(..)
, ScannedLexicalItem
, SyntaxMaterializationError(..)
@@ -101,11 +101,11 @@ import Syntax.Adapt
, scanChunk
, scannedItemMarker
)
-import Syntax.Concrete (grammar)
-import Syntax.Interface
-import Syntax.Lexicon (Lexicon)
-import Syntax.Pragma
-import Syntax.Token
+import Felix.Syntax.Concrete (grammar)
+import Felix.Syntax.Interface
+import Felix.Syntax.Lexicon (Lexicon)
+import Felix.Syntax.Pragma
+import Felix.Syntax.Token
import Control.DeepSeq (NFData, force)
import Control.Exception (Exception, evaluate)
diff --git a/source/Felix/Parsed/Identity.hs b/source/Felix/Parsed/Identity.hs
index e11b2c7..b76c26e 100644
--- a/source/Felix/Parsed/Identity.hs
+++ b/source/Felix/Parsed/Identity.hs
@@ -20,7 +20,7 @@ module Felix.Parsed.Identity
import Base
import Felix.Cache.Codec
import Felix.Source.Content
-import Syntax.Interface
+import Felix.Syntax.Interface
import Control.DeepSeq (NFData)
import Data.ByteString (ByteString)
diff --git a/source/Felix/Parsed/Payload.hs b/source/Felix/Parsed/Payload.hs
index 2506973..5e63c70 100644
--- a/source/Felix/Parsed/Payload.hs
+++ b/source/Felix/Parsed/Payload.hs
@@ -30,11 +30,11 @@ import Base
import Felix.Cache.Codec
import Felix.Parsed.Identity qualified as Identity
import Felix.Source
-import Report.Location
-import Syntax.Abstract qualified as Raw
-import Syntax.Interface
-import Syntax.LexicalPhrase qualified as Phrase
-import Syntax.Token qualified as Token
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Interface
+import Felix.Syntax.LexicalPhrase qualified as Phrase
+import Felix.Syntax.Token qualified as Token
import Control.DeepSeq (NFData)
import Control.Monad (unless)
diff --git a/source/Felix/Prelude.hs b/source/Felix/Prelude.hs
index 28ad961..ccd5623 100644
--- a/source/Felix/Prelude.hs
+++ b/source/Felix/Prelude.hs
@@ -30,10 +30,10 @@ import Felix.Parse
import Felix.Parsed.Identity qualified as Parsed
import Felix.Source
import Felix.Source.Graph
-import Report.Location
-import Syntax.Adapt (SyntaxMaterializationError)
-import Syntax.Interface
-import Syntax.Pragma
+import Felix.Report.Location
+import Felix.Syntax.Adapt (SyntaxMaterializationError)
+import Felix.Syntax.Interface
+import Felix.Syntax.Pragma
import Control.Exception (IOException, displayException, try)
import Data.ByteString (ByteString)
diff --git a/source/Felix/Provers.hs b/source/Felix/Provers.hs
index eb6f62c..16ef655 100644
--- a/source/Felix/Provers.hs
+++ b/source/Felix/Provers.hs
@@ -79,9 +79,9 @@ module Felix.Provers
) where
import Base
-import Checking.Authority qualified as Authority
-import Checking.Backend.Problem
-import Checking.Backend.Tptp
+import Felix.Checking.Authority qualified as Authority
+import Felix.Checking.Backend.Problem
+import Felix.Checking.Backend.Tptp
import Control.Concurrent.STM
( STM
@@ -130,7 +130,7 @@ import Data.Text qualified as Text
import Data.Text.Encoding qualified as TextEncoding
import Data.Text.Encoding.Error qualified as TextEncodingError
import Numeric.Natural (Natural)
-import Report.Location (Location)
+import Felix.Report.Location (Location)
import System.Exit (ExitCode(..))
import System.Posix.Signals (sigKILL, signalProcessGroup)
import System.Posix.Types (ProcessGroupID)
diff --git a/source/Felix/Render/Html.hs b/source/Felix/Render/Html.hs
new file mode 100644
index 0000000..7081e52
--- /dev/null
+++ b/source/Felix/Render/Html.hs
@@ -0,0 +1,3964 @@
+{-# 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 "<x" text
+ (digit, rest) <- Text.uncons text'
+ guard (isDigit digit)
+ rest' <- Text.stripPrefix "/>" rest
+ let slot = digitToInt digit
+ guard (slot > 0 && slot <= 9)
+ pure (slot, rest')
+
+ _unusedLineNo = lineNo
+
+
+renderBlock :: HintMap -> ReferenceContext -> BlockRenderInfo -> Html ()
+renderBlock hints references (_index, block, blockId) = case block of
+ BlockAxiom _loc title marker axiom ->
+ renderCustomBlock blockId "axiom-" "Axiom" (Just marker) title (renderAxiom hints axiom)
+ BlockClaim kind _loc title marker claim ->
+ renderCustomBlock blockId (claimKindElement kind) (claimKindPrefix kind) (Just marker) title (renderClaim hints claim)
+ BlockProof _start proof _end ->
+ renderProofBlock hints references proof
+ BlockDefn _loc title marker defn ->
+ renderCustomBlock blockId "definition-" "Definition" (Just marker) title (renderDefn hints defn)
+ BlockAbbr _loc title marker abbr ->
+ renderCustomBlock blockId "abbreviation-" "Abbreviation" (Just marker) title (renderAbbreviation hints abbr)
+ BlockData _loc title marker datatype ->
+ renderCustomBlock blockId "datatype-" "Datatype" (Just marker) title (renderDatatype hints datatype)
+ BlockInductive _loc title marker ind ->
+ renderCustomBlock blockId "inductive-" "Inductive" (Just marker) title (renderInductive hints marker ind)
+ BlockSig _loc title marker asms sig ->
+ renderCustomBlock blockId "signature-" "Signature" (Just marker) title (renderSignatureBlock hints asms sig)
+ BlockStruct _loc title marker structDefn ->
+ renderCustomBlock blockId "struct-" "Structure" (Just marker) title (renderStructDefn hints structDefn)
+
+renderTocEntry :: (Int, Text, Block) -> Html ()
+renderTocEntry (index, blockId, block) =
+ li_ do
+ a_ [href_ (renderUrlFragment blockId)] do
+ span_ (toHtml (blockPrefixText block))
+ case formatMarker (blockMarkerOf block) of
+ Nothing ->
+ when (blockNeedsIndexLabel block) do
+ code_ (toHtml (Text.pack (show index)))
+ Just marker ->
+ code_ (toHtml marker)
+
+includeInToc :: Block -> Bool
+includeInToc = \case
+ BlockProof{} -> False
+ _ -> True
+
+renderCustomBlock :: Text -> Text -> Text -> Maybe Marker -> Maybe BlockTitle -> Html () -> Html ()
+renderCustomBlock blockId name prefix mmarker mtitle body =
+ term name (id_ blockId : previewTargetAttributes prefix mmarker mtitle) do
+ renderBlockLead prefix mmarker mtitle True
+ body
+
+previewTargetAttributes :: Text -> Maybe Marker -> Maybe BlockTitle -> [Attributes]
+previewTargetAttributes prefix mmarker mtitle =
+ case formatMarker mmarker of
+ Nothing ->
+ []
+ Just marker ->
+ [ makeAttributes "data-preview-kind" prefix
+ , makeAttributes "data-preview-label" marker
+ ]
+ <> case formatBlockTitle mtitle of
+ Nothing ->
+ []
+ Just title ->
+ [makeAttributes "data-preview-title" title]
+
+renderProofBlock :: HintMap -> ReferenceContext -> Proof -> Html ()
+renderProofBlock hints references proof
+ | proofStepCount proof >= proofCollapseThreshold =
+ term "proof-" do
+ details_ do
+ summary_ (renderBlockLead "Proof" Nothing Nothing False)
+ renderProof hints references proof
+ | otherwise =
+ term "proof-" do
+ renderBlockLead "Proof" Nothing Nothing True
+ renderProof hints references proof
+
+blockAnchorId :: Int -> Block -> Text
+blockAnchorId index block =
+ case formatMarker (blockMarkerOf block) of
+ Just marker -> marker
+ Nothing -> sanitizeIdFragment (Text.toLower (blockPrefixText block) <> "-" <> Text.pack (show index))
+
+sanitizeIdFragment :: Text -> Text
+sanitizeIdFragment =
+ Text.dropWhile (== '-') . Text.map sanitize . Text.toLower
+ where
+ sanitize c
+ | isAlphaNum c = c
+ | c == '-' || c == '_' = c
+ | otherwise = '-'
+
+blockPrefixText :: Block -> Text
+blockPrefixText = \case
+ BlockAxiom{} -> "Axiom"
+ BlockClaim kind _ _ _ _ -> claimKindPrefix kind
+ BlockProof{} -> "Proof"
+ BlockDefn{} -> "Definition"
+ BlockAbbr{} -> "Abbreviation"
+ BlockData{} -> "Datatype"
+ BlockInductive{} -> "Inductive"
+ BlockSig{} -> "Signature"
+ BlockStruct{} -> "Structure"
+
+blockMarkerOf :: Block -> Maybe Marker
+blockMarkerOf = \case
+ BlockAxiom _ _ marker _ -> Just marker
+ BlockClaim _ _ _ marker _ -> Just marker
+ BlockProof{} -> Nothing
+ BlockDefn _ _ marker _ -> Just marker
+ BlockAbbr _ _ marker _ -> Just marker
+ BlockData _ _ marker _ -> Just marker
+ BlockInductive _ _ marker _ -> Just marker
+ BlockSig _ _ marker _ _ -> Just marker
+ BlockStruct _ _ marker _ -> Just marker
+
+blockTitleOf :: Block -> Maybe BlockTitle
+blockTitleOf = \case
+ BlockAxiom _ title _ _ -> title
+ BlockClaim _ _ title _ _ -> title
+ BlockProof{} -> Nothing
+ BlockDefn _ title _ _ -> title
+ BlockAbbr _ title _ _ -> title
+ BlockData _ title _ _ -> title
+ BlockInductive _ title _ _ -> title
+ BlockSig _ title _ _ _ -> title
+ BlockStruct _ title _ _ -> title
+
+blockNeedsIndexLabel :: Block -> Bool
+blockNeedsIndexLabel block = case (formatMarker (blockMarkerOf block), formatBlockTitle (blockTitleOf block)) of
+ (Nothing, Nothing) -> True
+ _ -> False
+
+renderBlockLead :: Text -> Maybe Marker -> Maybe BlockTitle -> Bool -> Html ()
+renderBlockLead prefix mmarker mtitle withTrailingSpace =
+ term "head-" do
+ toHtml prefix
+ case formatMarker mmarker of
+ Nothing -> skip
+ Just marker ->
+ term "id-" (toHtml marker)
+ case formatBlockTitle mtitle of
+ Nothing ->
+ toHtml ("." <> suffix)
+ Just title -> do
+ toHtml (" (" :: Text)
+ term "title-" (toHtml title)
+ toHtml (")." <> suffix)
+ where
+ suffix :: Text
+ suffix
+ | withTrailingSpace = " "
+ | otherwise = ""
+
+formatMarker :: Maybe Marker -> Maybe Text
+formatMarker = \case
+ Nothing -> Nothing
+ Just marker ->
+ let text = Text.strip (markerText marker)
+ in if Text.null text then Nothing else Just text
+
+formatBlockTitle :: Maybe BlockTitle -> Maybe Text
+formatBlockTitle =
+ fmap capitalizeTitle . nonEmptyTitle
+ where
+ nonEmptyTitle = \case
+ Nothing -> Nothing
+ Just toks ->
+ let text = Text.strip (Text.unwords (tokToText <$> toks))
+ in if Text.null text then Nothing else Just text
+
+capitalizeTitle :: Text -> Text
+capitalizeTitle text = case Text.uncons text of
+ Nothing -> text
+ Just (c, rest) -> Text.cons (toUpper c) rest
+
+claimKindElement :: ClaimKind -> Text
+claimKindElement = \case
+ Proposition -> "proposition-"
+ Theorem -> "theorem-"
+ Lemma -> "lemma-"
+ Corollary -> "corollary-"
+ PlainClaim -> "claim-"
+
+claimKindPrefix :: ClaimKind -> Text
+claimKindPrefix = \case
+ Proposition -> "Proposition"
+ Theorem -> "Theorem"
+ Lemma -> "Lemma"
+ Corollary -> "Corollary"
+ PlainClaim -> "Claim"
+
+
+renderAxiom :: HintMap -> Axiom -> Html ()
+renderAxiom hints (Axiom asms stmt) =
+ renderWithAssumptions hints asms stmt
+
+renderClaim :: HintMap -> Claim -> Html ()
+renderClaim hints (Claim asms stmt) =
+ renderWithAssumptions hints asms stmt
+
+renderWithAssumptions :: HintMap -> [Asm] -> Stmt -> Html ()
+renderWithAssumptions hints asms stmt = do
+ case asms of
+ [] -> renderStmtInline hints stmt
+ _ -> do
+ toHtml ("Suppose " :: Text)
+ renderAsmList hints asms
+ toHtml (". Then " :: Text)
+ renderStmtInline hints stmt
+ toHtml ("." :: Text)
+
+renderDefn :: HintMap -> Defn -> Html ()
+renderDefn hints = \case
+ Defn asms headStmt stmt ->
+ do
+ when (not (null asms)) do
+ toHtml ("If " :: Text)
+ renderAsmList hints asms
+ toHtml (", then " :: Text)
+ renderDefnHead hints headStmt
+ toHtml (" iff " :: Text)
+ renderStmtInline hints stmt
+ toHtml ("." :: Text)
+ DefnFun asms fun maybeSymbol resultTerm ->
+ do
+ when (not (null asms)) do
+ toHtml ("If " :: Text)
+ renderAsmList hints asms
+ toHtml (", then " :: Text)
+ renderFunInline renderVarInline fun
+ case maybeSymbol of
+ Nothing -> skip
+ Just symbolicTerm -> do
+ toHtml (", " :: Text)
+ renderTermInline hints symbolicTerm
+ toHtml (" is " :: Text)
+ renderTermInline hints resultTerm
+ toHtml ("." :: Text)
+ DefnOp symb expr ->
+ do
+ inlineMath do
+ renderSymbolPatternMath hints symb
+ moText "="
+ renderExprMathRow hints expr
+ toHtml ("." :: Text)
+
+renderDefnHead :: HintMap -> DefnHead -> Html ()
+renderDefnHead hints = \case
+ DefnAdj maybeNp var adj -> do
+ renderTypedVar hints maybeNp var
+ toHtml (" is " :: Text)
+ renderAdjInline renderVarInline adj
+ DefnVerb maybeNp var verb -> do
+ renderTypedVar hints maybeNp var
+ toHtml (" " :: Text)
+ renderVerbInline False renderVarInline verb
+ DefnNoun var noun -> do
+ renderVarInline var
+ toHtml (" is a " :: Text)
+ renderNounInline False renderVarInline noun
+ DefnSymbolicPredicate predi marker vars ->
+ inlineMath
+ ( renderHintedMathRow
+ hints
+ PredicateHint
+ marker
+ (ExprVar <$> toList vars)
+ (renderPrefixPredicateFallback predi (renderVarMath <$> toList vars))
+ )
+ DefnRel x rel params y ->
+ inlineMath (renderRelationApplication hints Positive [ExprVar x] (Relation Nowhere rel [ExprVar p | p <- params]) [ExprVar y])
+
+renderTypedVar :: HintMap -> Maybe (NounPhrase Maybe) -> VarSymbol -> Html ()
+renderTypedVar hints = \case
+ Nothing -> renderVarInline
+ Just np -> \var -> do
+ renderNounPhraseMaybe hints np
+ toHtml (" " :: Text)
+ renderVarInline var
+
+
+renderAbbreviation :: HintMap -> Abbreviation -> Html ()
+renderAbbreviation hints = \case
+ AbbreviationAdj var adj stmt ->
+ do
+ renderVarInline var
+ toHtml (" is " :: Text)
+ renderAdjInline renderVarInline adj
+ toHtml (" stands for " :: Text)
+ renderStmtInline hints stmt
+ toHtml ("." :: Text)
+ AbbreviationVerb var verb stmt ->
+ do
+ renderVarInline var
+ toHtml (" " :: Text)
+ renderVerbInline False renderVarInline verb
+ toHtml (" stands for " :: Text)
+ renderStmtInline hints stmt
+ toHtml ("." :: Text)
+ AbbreviationNoun var noun stmt ->
+ do
+ renderVarInline var
+ toHtml (" is a " :: Text)
+ renderNounInline False renderVarInline noun
+ toHtml (" stands for " :: Text)
+ renderStmtInline hints stmt
+ toHtml ("." :: Text)
+ AbbreviationRel x rel params y stmt ->
+ do
+ inlineMath (renderRelationApplication hints Positive [ExprVar x] (Relation Nowhere rel [ExprVar p | p <- params]) [ExprVar y])
+ toHtml (" stands for " :: Text)
+ renderStmtInline hints stmt
+ toHtml ("." :: Text)
+ AbbreviationFun fun bodyTerm ->
+ do
+ renderFunInline renderVarInline fun
+ toHtml (" stands for " :: Text)
+ renderTermInline hints bodyTerm
+ toHtml ("." :: Text)
+ AbbreviationEq symb expr ->
+ do
+ renderSymbolPatternInline hints symb
+ toHtml (" stands for " :: Text)
+ inlineMath (renderExprMathRow hints expr)
+ toHtml ("." :: Text)
+
+renderDatatype :: HintMap -> Datatype -> Html ()
+renderDatatype hints Datatype{..} = do
+ toHtml ("Datatype of " :: Text)
+ inlineMath (renderExprMathRow hints datatypeHeadExpr)
+ toHtml ("." :: Text)
+ ul_ do
+ traverse_ renderDatatypeClause (toList datatypeClauses)
+ -- Derived facts require checked semantic context.
+ where
+ renderDatatypeClause :: DatatypeClause -> Html ()
+ renderDatatypeClause DatatypeClause{..} = li_ do
+ inlineMath do
+ renderRelationApplication hints Positive [datatypeClauseConstructorExpr] (Relation Nowhere ElementSymbol []) [datatypeClauseTargetExpr]
+ case datatypeClausePremises of
+ [] ->
+ toHtml ("." :: Text)
+ premises -> do
+ toHtml (" for " :: Text)
+ joinHtml (toHtml (" and " :: Text)) (renderDatatypePremise <$> premises)
+ toHtml ("." :: Text)
+
+ renderDatatypePremise :: (VarSymbol, Expr) -> Html ()
+ renderDatatypePremise (x, domain) =
+ inlineMath (renderRelationApplication hints Positive [ExprVar x] (Relation Nowhere ElementSymbol []) [domain])
+
+exprVar :: Expr -> Maybe VarSymbol
+exprVar = \case
+ ExprVar x ->
+ Just x
+ _ ->
+ Nothing
+
+substituteExpr :: Map.Map VarSymbol Expr -> Expr -> Expr
+substituteExpr env = \case
+ ExprVar x ->
+ fromMaybe (ExprVar x) (Map.lookup x env)
+ ExprInteger loc n ->
+ ExprInteger loc n
+ ExprOp loc symbol args ->
+ ExprOp loc symbol (substituteExpr env <$> args)
+ ExprStructOp loc symbol expr ->
+ ExprStructOp loc symbol (substituteExpr env <$> expr)
+ ExprFiniteSet loc exprs ->
+ ExprFiniteSet loc (substituteExpr env <$> exprs)
+ ExprSep loc x bound stmt ->
+ ExprSep loc x (substituteExpr env bound) stmt
+ ExprReplace loc expr bounds maybeStmt ->
+ ExprReplace loc (substituteExpr env expr) ((\(x, bound) -> (x, substituteExpr env bound)) <$> bounds) maybeStmt
+ ExprReplacePred loc x y expr stmt ->
+ ExprReplacePred loc x y (substituteExpr env expr) stmt
+
+elementOfFormula :: Expr -> Expr -> Formula
+elementOfFormula left right =
+ FormulaChain (ChainBase (left :| []) Positive (Relation Nowhere ElementSymbol []) (right :| []))
+
+semanticSubsetFormula :: Set VarSymbol -> Expr -> Expr -> Formula
+semanticSubsetFormula reserved left right =
+ forallIfNeeded [witnessVar]
+ (impliesFormula
+ (elementOfFormula (ExprVar witnessVar) left)
+ (elementOfFormula (ExprVar witnessVar) right))
+ where
+ witnessVar = freshDatatypeVar (reserved <> Set.fromList (exprVars left <> exprVars right)) "x"
+
+equalsFormula :: Expr -> Expr -> Formula
+equalsFormula left right =
+ FormulaChain (ChainBase (left :| []) Positive (Relation Nowhere EqSymbol []) (right :| []))
+
+impliesFormula :: Formula -> Formula -> Formula
+impliesFormula left right =
+ Connected Nowhere Implication left right
+
+formulaConjunction :: [Formula] -> Formula
+formulaConjunction = \case
+ [] ->
+ PropositionalConstant Nowhere IsTop
+ phi : rest ->
+ foldl' (\left right -> Connected Nowhere Conjunction left right) phi rest
+
+formulaDisjunction :: [Formula] -> Formula
+formulaDisjunction = \case
+ [] ->
+ PropositionalConstant Nowhere IsBottom
+ phi : rest ->
+ foldl' (\left right -> Connected Nowhere Disjunction left right) phi rest
+
+forallIfNeeded :: [VarSymbol] -> Formula -> Formula
+forallIfNeeded [] phi = phi
+forallIfNeeded vars phi =
+ FormulaQuantified Nowhere Universally (NonEmpty.fromList vars) Unbounded phi
+
+existsIfNeeded :: [VarSymbol] -> Formula -> Formula
+existsIfNeeded [] phi = phi
+existsIfNeeded vars phi =
+ FormulaQuantified Nowhere Existentially (NonEmpty.fromList vars) Unbounded phi
+
+impliesFrom :: [Formula] -> Formula -> Formula
+impliesFrom [] conclusion = conclusion
+impliesFrom premises conclusion = impliesFormula (formulaConjunction premises) conclusion
+
+freshDatatypeVar :: Set VarSymbol -> Text -> VarSymbol
+freshDatatypeVar used base =
+ List.head
+ [ NamedVar candidate
+ | candidate <- base : [base <> Text.pack (show n) | n <- [(1 :: Int) ..]]
+ , NamedVar candidate `Set.notMember` used
+ ]
+
+renderInductive :: HintMap -> Marker -> Inductive -> Html ()
+renderInductive hints marker Inductive{..} = do
+ toHtml ("Inductive definition of " :: Text)
+ renderSymbolPatternInline hints inductiveSymbolPattern
+ toHtml (" over " :: Text)
+ inlineMath (renderExprMathRow hints inductiveDomain)
+ toHtml ("." :: Text)
+ ul_ do
+ traverse_ renderIntro (toList inductiveIntros)
+ case inductiveDerivedFacts marker Inductive{..} of
+ [] ->
+ skip
+ derivedFacts ->
+ details_ do
+ summary_ (toHtml ("Derived facts" :: Text))
+ ul_ [class_ "inductive-derived-facts"] do
+ traverse_ (renderInductiveDerivedFact hints) derivedFacts
+ where
+ renderIntro :: IntroRule -> Html ()
+ renderIntro IntroRule{..} = li_ do
+ case introConditions of
+ [] -> skip
+ _ -> do
+ toHtml ("If " :: Text)
+ joinHtml (toHtml (" and " :: Text)) (inlineMath . renderFormulaMath hints <$> introConditions)
+ toHtml (", then " :: Text)
+ inlineMath (renderFormulaMath hints introResult)
+ toHtml ("." :: Text)
+
+renderInductiveDerivedFact :: HintMap -> InductiveDerivedFact -> Html ()
+renderInductiveDerivedFact hints InductiveDerivedFact{inductiveDerivedFactMarker, inductiveDerivedFactFormula} =
+ li_ (id_ (markerText inductiveDerivedFactMarker) : previewTargetAttributes "Inductive Fact" (Just inductiveDerivedFactMarker) Nothing) do
+ term "head-" do
+ code_ (toHtml (markerText inductiveDerivedFactMarker))
+ toHtml (": " :: Text)
+ inlineMath (renderFormulaMath hints inductiveDerivedFactFormula)
+ toHtml ("." :: Text)
+
+data InductiveDerivedFact = InductiveDerivedFact
+ { inductiveDerivedFactMarker :: Marker
+ , inductiveDerivedFactFormula :: Formula
+ }
+
+data InductiveRenderInfo = InductiveRenderInfo
+ { inductiveFactBaseMarker :: Marker
+ , inductiveRenderParams :: [VarSymbol]
+ , inductiveRenderCarrierExpr :: Expr
+ , inductiveRenderDomainExpr :: Expr
+ , inductiveRenderClauses :: NonEmpty InductiveRenderClause
+ }
+
+data InductiveRenderClause = InductiveRenderClause
+ { inductiveRenderClauseVars :: [VarSymbol]
+ , inductiveRenderClauseConditions :: [InductiveRenderCondition]
+ , inductiveRenderClauseResultExpr :: Expr
+ }
+
+data InductiveRenderCondition
+ = InductiveRenderSideCondition Formula
+ | InductiveRenderRecursiveCondition Expr Expr
+
+inductiveDerivedFacts :: Marker -> Inductive -> [InductiveDerivedFact]
+inductiveDerivedFacts marker inductive = case inductiveRenderInfo marker inductive of
+ Nothing ->
+ []
+ Just info ->
+ inductiveIntroFacts info
+ <> [ InductiveDerivedFact (inductiveDomSubsetMarker info) (inductiveDomSubsetFormula info)
+ , InductiveDerivedFact (inductiveCasesMarker info) (inductiveCasesFormula info)
+ , InductiveDerivedFact (inductiveInductMarker info) (inductiveInductFormula info)
+ ]
+
+inductiveRenderInfo :: Marker -> Inductive -> Maybe InductiveRenderInfo
+inductiveRenderInfo marker Inductive{inductiveSymbolPattern = SymbolPattern inductiveSymbol inductiveParams, inductiveDomain, inductiveIntros} = do
+ inductiveRenderClauses <- traverse (inductiveRenderClause inductiveSymbol inductiveParams) inductiveIntros
+ pure InductiveRenderInfo
+ { inductiveFactBaseMarker = marker
+ , inductiveRenderParams = inductiveParams
+ , inductiveRenderCarrierExpr = ExprOp Nowhere inductiveSymbol (ExprVar <$> inductiveParams)
+ , inductiveRenderDomainExpr = inductiveDomain
+ , inductiveRenderClauses
+ }
+ where
+ inductiveRenderClause :: MixfixItem -> [VarSymbol] -> IntroRule -> Maybe InductiveRenderClause
+ inductiveRenderClause symbol params IntroRule{introConditions, introResult} = do
+ inductiveRenderClauseResultExpr <- inductiveRenderResultExpr symbol params introResult
+ inductiveRenderClauseConditions <- traverse (inductiveRenderCondition symbol params) introConditions
+ let paramSet = Set.fromList params
+ inductiveRenderClauseVars =
+ List.filter (`Set.notMember` paramSet)
+ (orderedRenderVars (concatMap formulaVars introConditions <> exprVars inductiveRenderClauseResultExpr))
+ pure InductiveRenderClause
+ { inductiveRenderClauseVars
+ , inductiveRenderClauseConditions
+ , inductiveRenderClauseResultExpr
+ }
+
+inductiveRenderResultExpr :: MixfixItem -> [VarSymbol] -> Formula -> Maybe Expr
+inductiveRenderResultExpr symbol params = \case
+ FormulaChain (ChainBase (resultExpr :| []) Positive (Relation _ ElementSymbol []) (carrierExpr :| []))
+ | sameInductiveCarrierExpr symbol params carrierExpr ->
+ Just resultExpr
+ _ ->
+ Nothing
+
+inductiveRenderCondition :: MixfixItem -> [VarSymbol] -> Formula -> Maybe InductiveRenderCondition
+inductiveRenderCondition symbol params phi
+ | not (formulaMentionsFunction symbol phi) =
+ Just (InductiveRenderSideCondition phi)
+ | otherwise = case phi of
+ FormulaChain (ChainBase (recursiveTerm :| []) Positive (Relation _ ElementSymbol []) (recursiveCarrier :| []))
+ | not (exprMentionsFunction symbol recursiveTerm) ->
+ InductiveRenderRecursiveCondition recursiveTerm <$> replaceCarrierExpr symbol params recursiveCarrier
+ _ ->
+ Nothing
+ where
+ replaceCarrierExpr :: MixfixItem -> [VarSymbol] -> Expr -> Maybe Expr
+ replaceCarrierExpr target targetParams =
+ replaceInductiveCarrierExpr target targetParams (ExprVar "__inductive")
+
+inductiveIntroFacts :: InductiveRenderInfo -> [InductiveDerivedFact]
+inductiveIntroFacts info =
+ [ InductiveDerivedFact (inductiveIntroMarker info index) (inductiveIntroFormula info clause)
+ | (index, clause) <- zip [(1 :: Int) ..] (NonEmpty.toList (inductiveRenderClauses info))
+ ]
+
+inductiveIntroMarker :: InductiveRenderInfo -> Int -> Marker
+inductiveIntroMarker info index =
+ Marker (markerText (inductiveFactBaseMarker info) <> "_intro_" <> Text.pack (show index))
+
+inductiveDomSubsetMarker :: InductiveRenderInfo -> Marker
+inductiveDomSubsetMarker info =
+ Marker (markerText (inductiveFactBaseMarker info) <> "_dom_subset")
+
+inductiveCasesMarker :: InductiveRenderInfo -> Marker
+inductiveCasesMarker info =
+ Marker (markerText (inductiveFactBaseMarker info) <> "_cases")
+
+inductiveInductMarker :: InductiveRenderInfo -> Marker
+inductiveInductMarker info =
+ Marker (markerText (inductiveFactBaseMarker info) <> "_induct")
+
+inductiveIntroFormula :: InductiveRenderInfo -> InductiveRenderClause -> Formula
+inductiveIntroFormula info clause =
+ forallIfNeeded (orderedRenderVars (inductiveRenderParams info <> inductiveRenderClauseVars clause))
+ (impliesFrom premises conclusion)
+ where
+ premises = inductiveRenderConditionFormula info <$> inductiveRenderClauseConditions clause
+ conclusion = elementOfFormula (inductiveRenderClauseResultExpr clause) (inductiveRenderCarrierExpr info)
+
+inductiveDomSubsetFormula :: InductiveRenderInfo -> Formula
+inductiveDomSubsetFormula info =
+ forallIfNeeded (inductiveRenderParams info)
+ (semanticSubsetFormula (inductiveUsedVars info) (inductiveRenderCarrierExpr info) (inductiveRenderDomainExpr info))
+
+inductiveCasesFormula :: InductiveRenderInfo -> Formula
+inductiveCasesFormula info =
+ forallIfNeeded (orderedRenderVars (inductiveRenderParams info <> [witnessVar]))
+ (impliesFormula (elementOfFormula (ExprVar witnessVar) (inductiveRenderCarrierExpr info)) (formulaDisjunction disjuncts))
+ where
+ witnessVar = freshDatatypeVar (inductiveUsedVars info) "x"
+ disjuncts = inductiveCaseDisjunct witnessVar <$> NonEmpty.toList (inductiveRenderClauses info)
+ inductiveCaseDisjunct x clause =
+ existsIfNeeded (inductiveRenderClauseVars clause)
+ (formulaConjunction (premises <> [equalsFormula (ExprVar x) (inductiveRenderClauseResultExpr clause)]))
+ where
+ premises = inductiveRenderConditionFormula info <$> inductiveRenderClauseConditions clause
+
+inductiveInductFormula :: InductiveRenderInfo -> Formula
+inductiveInductFormula info =
+ forallIfNeeded (orderedRenderVars (inductiveRenderParams info <> [subsetVar]))
+ (impliesFrom closures conclusion)
+ where
+ subsetVar = freshDatatypeVar (inductiveUsedVars info) "S"
+ closures = inductiveInductionClosure subsetVar <$> NonEmpty.toList (inductiveRenderClauses info)
+ conclusion =
+ semanticSubsetFormula
+ (Set.insert subsetVar (inductiveUsedVars info))
+ (inductiveRenderCarrierExpr info)
+ (ExprVar subsetVar)
+
+inductiveInductionClosure :: VarSymbol -> InductiveRenderClause -> Formula
+inductiveInductionClosure subsetVar clause =
+ forallIfNeeded (inductiveRenderClauseVars clause) (impliesFrom premises conclusion)
+ where
+ premises = inductiveRenderConditionFormulaAt (ExprVar subsetVar) <$> inductiveRenderClauseConditions clause
+ conclusion = elementOfFormula (inductiveRenderClauseResultExpr clause) (ExprVar subsetVar)
+
+inductiveRenderConditionFormula :: InductiveRenderInfo -> InductiveRenderCondition -> Formula
+inductiveRenderConditionFormula info =
+ inductiveRenderConditionFormulaAt (inductiveRenderCarrierExpr info)
+
+inductiveRenderConditionFormulaAt :: Expr -> InductiveRenderCondition -> Formula
+inductiveRenderConditionFormulaAt replacement = \case
+ InductiveRenderSideCondition phi ->
+ phi
+ InductiveRenderRecursiveCondition recursiveTerm recursiveCarrierTemplate ->
+ elementOfFormula recursiveTerm (substituteExpr (Map.singleton "__inductive" replacement) recursiveCarrierTemplate)
+
+inductiveUsedVars :: InductiveRenderInfo -> Set VarSymbol
+inductiveUsedVars info =
+ Set.fromList
+ ( inductiveRenderParams info
+ <> [ var
+ | clause <- NonEmpty.toList (inductiveRenderClauses info)
+ , var <- inductiveRenderClauseVars clause
+ ]
+ )
+
+sameInductiveCarrierExpr :: MixfixItem -> [VarSymbol] -> Expr -> Bool
+sameInductiveCarrierExpr symbol params = \case
+ ExprOp _ symbol' args ->
+ symbol == symbol'
+ && length params == length args
+ && and (zipWith (\param arg -> exprVar arg == Just param) params args)
+ _ ->
+ False
+
+replaceInductiveCarrierExpr :: MixfixItem -> [VarSymbol] -> Expr -> Expr -> Maybe Expr
+replaceInductiveCarrierExpr symbol params replacement = go
+ where
+ go = \case
+ ExprVar x ->
+ Just (ExprVar x)
+ ExprInteger loc n ->
+ Just (ExprInteger loc n)
+ ExprOp loc symbol' args
+ | sameInductiveCarrierExpr symbol params (ExprOp loc symbol' args) ->
+ Just replacement
+ | symbol == symbol' ->
+ Nothing
+ | otherwise ->
+ ExprOp loc symbol' <$> traverse go args
+ ExprStructOp loc structSymbol expr ->
+ ExprStructOp loc structSymbol <$> traverse go expr
+ ExprFiniteSet loc exprs ->
+ ExprFiniteSet loc <$> traverse go exprs
+ ExprSep{} ->
+ Nothing
+ ExprReplace{} ->
+ Nothing
+ ExprReplacePred{} ->
+ Nothing
+
+formulaMentionsFunction :: MixfixItem -> Formula -> Bool
+formulaMentionsFunction symbol = \case
+ FormulaChain chain ->
+ chainMentionsFunction symbol chain
+ FormulaPredicate _loc _predicate _marker exprs ->
+ any (exprMentionsFunction symbol) exprs
+ Connected _loc _conn left right ->
+ formulaMentionsFunction symbol left || formulaMentionsFunction symbol right
+ FormulaNeg _loc phi ->
+ formulaMentionsFunction symbol phi
+ FormulaQuantified _loc _quant _vars _bound phi ->
+ formulaMentionsFunction symbol phi
+ PropositionalConstant{} ->
+ False
+
+chainMentionsFunction :: MixfixItem -> Chain -> Bool
+chainMentionsFunction symbol = \case
+ ChainBase left _sign relation right ->
+ any (exprMentionsFunction symbol) left || relationMentionsFunction symbol relation || any (exprMentionsFunction symbol) right
+ ChainCons left _sign relation rest ->
+ any (exprMentionsFunction symbol) left || relationMentionsFunction symbol relation || chainMentionsFunction symbol rest
+
+relationMentionsFunction :: MixfixItem -> Relation -> Bool
+relationMentionsFunction symbol = \case
+ Relation _loc _relationSymbol exprs ->
+ any (exprMentionsFunction symbol) exprs
+ RelationExpr _loc expr ->
+ exprMentionsFunction symbol expr
+
+exprMentionsFunction :: MixfixItem -> Expr -> Bool
+exprMentionsFunction symbol = \case
+ ExprVar{} ->
+ False
+ ExprInteger{} ->
+ False
+ ExprOp _loc symbol' args ->
+ symbol == symbol' || any (exprMentionsFunction symbol) args
+ ExprStructOp _loc _structSymbol expr ->
+ maybe False (exprMentionsFunction symbol) expr
+ ExprFiniteSet _loc exprs ->
+ any (exprMentionsFunction symbol) exprs
+ ExprSep _loc _var bound _stmt ->
+ exprMentionsFunction symbol bound
+ ExprReplace _loc expr bounds _maybeStmt ->
+ exprMentionsFunction symbol expr || any (exprMentionsFunction symbol . snd) bounds
+ ExprReplacePred _loc _x _y expr _stmt ->
+ exprMentionsFunction symbol expr
+
+formulaVars :: Formula -> [VarSymbol]
+formulaVars = \case
+ FormulaChain chain ->
+ chainVars chain
+ FormulaPredicate _loc _predicate _marker exprs ->
+ concatMap exprVars exprs
+ Connected _loc _conn left right ->
+ formulaVars left <> formulaVars right
+ FormulaNeg _loc phi ->
+ formulaVars phi
+ FormulaQuantified _loc _quant vars _bound phi ->
+ List.filter (`notElem` toList vars) (formulaVars phi)
+ PropositionalConstant{} ->
+ []
+
+chainVars :: Chain -> [VarSymbol]
+chainVars = \case
+ ChainBase left _sign relation right ->
+ concatMap exprVars (toList left) <> relationVars relation <> concatMap exprVars (toList right)
+ ChainCons left _sign relation rest ->
+ concatMap exprVars (toList left) <> relationVars relation <> chainVars rest
+
+relationVars :: Relation -> [VarSymbol]
+relationVars = \case
+ Relation _loc _relationSymbol exprs ->
+ concatMap exprVars exprs
+ RelationExpr _loc expr ->
+ exprVars expr
+
+exprVars :: Expr -> [VarSymbol]
+exprVars = \case
+ ExprVar x ->
+ [x]
+ ExprInteger{} ->
+ []
+ ExprOp _loc _symbol args ->
+ concatMap exprVars args
+ ExprStructOp _loc _structSymbol expr ->
+ maybe [] exprVars expr
+ ExprFiniteSet _loc exprs ->
+ concatMap exprVars exprs
+ ExprSep _loc x bound _stmt ->
+ List.filter (/= x) (exprVars bound)
+ ExprReplace _loc expr bounds _maybeStmt ->
+ let boundVars = fst <$> toList bounds
+ free = exprVars expr <> concatMap (exprVars . snd) (toList bounds)
+ in List.filter (`notElem` boundVars) free
+ ExprReplacePred _loc x y expr _stmt ->
+ List.filter (\var -> var /= x && var /= y) (exprVars expr)
+
+orderedRenderVars :: [VarSymbol] -> [VarSymbol]
+orderedRenderVars =
+ reverse . snd . foldl' step (Set.empty, [])
+ where
+ step (seen, acc) x
+ | x `Set.member` seen = (seen, acc)
+ | otherwise = (Set.insert x seen, x : acc)
+
+renderSignatureBlock :: HintMap -> [Asm] -> Signature -> Html ()
+renderSignatureBlock hints asms sig = do
+ case asms of
+ [] -> skip
+ _ -> do
+ toHtml ("Assumptions: " :: Text)
+ renderAsmList hints asms
+ toHtml ("." :: Text)
+ when (not (null asms)) do
+ p_ do
+ renderSignature hints sig
+ toHtml ("." :: Text)
+ when (null asms) do
+ renderSignature hints sig
+ toHtml ("." :: Text)
+
+renderSignature :: HintMap -> Signature -> Html ()
+renderSignature hints = \case
+ SignatureAdj var adj -> do
+ renderVarInline var
+ toHtml (" can be " :: Text)
+ renderAdjInline renderVarInline adj
+ SignatureVerb var verb -> do
+ renderVarInline var
+ toHtml (" can " :: Text)
+ renderVerbInline False renderVarInline verb
+ SignatureNoun var noun -> do
+ renderVarInline var
+ toHtml (" is a " :: Text)
+ renderNounInline False renderVarInline noun
+ SignatureSymbolic symb np -> do
+ renderSymbolPatternInline hints symb
+ toHtml (" is a " :: Text)
+ renderNounPhraseMaybe hints np
+
+renderStructDefn :: HintMap -> StructDefn -> Html ()
+renderStructDefn hints StructDefn{..} = do
+ toHtml ("Structure phrase: " :: Text)
+ renderStructPhraseInline structPhrase
+ toHtml ("." :: Text)
+ p_ do
+ toHtml ("Label: " :: Text)
+ renderVarInline structLabel
+ toHtml ("." :: Text)
+ when (not (null structParents)) do
+ p_ do
+ toHtml ("Parents: " :: Text)
+ joinHtml (toHtml (", " :: Text)) (renderStructPhraseInline <$> structParents)
+ toHtml ("." :: Text)
+ when (not (null structFixes)) do
+ p_ do
+ toHtml ("Fixes: " :: Text)
+ inlineMath (joinHtml (moText ",") (renderStructSymbolName <$> structFixes))
+ toHtml ("." :: Text)
+ when (not (null structAssumes)) do
+ ul_ do
+ for_ structAssumes \(marker, stmt) -> li_ do
+ toHtml (markerText marker)
+ toHtml (": " :: Text)
+ renderStmtInline hints stmt
+ toHtml ("." :: Text)
+
+
+buildPreviewMap
+ :: HtmlRenderContext
+ -> Set Marker
+ -> AnchorMap
+ -> HtmlRenderIndex
+ -> Either HtmlRenderContextError PreviewMap
+buildPreviewMap
+ context
+ referencedMarkers
+ anchors
+ (HtmlRenderIndex targetIndex) =
+ Map.fromList <$> traverse makePreviewEntry indexedTargets
+ where
+ targets =
+ List.sortOn targetOrdinal
+ [ indexed
+ | marker <- Set.toList referencedMarkers
+ , marker `Map.notMember` anchors
+ , Just indexed@(IndexedReferenceTarget _ source _target) <-
+ [Map.lookup marker targetIndex]
+ , source /= htmlCurrentSource context
+ ]
+ targetOrdinal (IndexedReferenceTarget ordinal _source _target) =
+ ordinal
+ sourceTarget (IndexedReferenceTarget _ordinal source target) =
+ (source, target)
+ indexedTargets =
+ zip [1 :: Int ..] (sourceTarget <$> targets)
+
+ makePreviewEntry (index, (source, target)) = do
+ previewSourceLabel <-
+ htmlSourceLabel context source
+ previewSourceHref <-
+ htmlSourcePageHref context source
+ previewReferenceHref <-
+ htmlSourceFragmentHref
+ context
+ source
+ (targetAnchorId target)
+ let marker = targetMarker target
+ previewMarker = marker
+ previewKind = targetKind target
+ previewTitle = targetTitle target
+ previewId =
+ "reference-preview-" <> Text.pack (show index)
+ previewBody = targetBody target
+ Right (marker, PreviewEntry{..})
+
+renderPreviewStore :: HintMap -> PreviewMap -> Html ()
+renderPreviewStore hints previews =
+ div_ [class_ "reference-preview-store", makeAttributes "aria-hidden" "true"] do
+ traverse_ (renderPreviewEntry hints) (Map.elems previews)
+
+renderPreviewEntry :: HintMap -> PreviewEntry -> Html ()
+renderPreviewEntry hints PreviewEntry{..} =
+ div_ [id_ previewId, class_ "reference-preview-template"] do
+ div_ [class_ "reference-preview-heading"] do
+ toHtml previewKind
+ toHtml (" " :: Text)
+ code_ (toHtml (markerText previewMarker))
+ case previewTitle of
+ Nothing ->
+ skip
+ Just title -> do
+ toHtml (" (" :: Text)
+ toHtml title
+ toHtml (")" :: Text)
+ div_ [class_ "reference-preview-source"] do
+ toHtml ("from " :: Text)
+ a_ [href_ previewSourceHref] do
+ code_ (toHtml previewSourceLabel)
+ div_ [class_ "reference-preview-body"] do
+ previewBody hints
+
+renderPreviewBlockBody :: HintMap -> Block -> Html ()
+renderPreviewBlockBody hints = \case
+ BlockAxiom _loc _title _marker axiom ->
+ previewStatement (renderAxiom hints axiom)
+ BlockClaim _kind _loc _title _marker claim ->
+ previewStatement (renderClaim hints claim)
+ BlockDefn _loc _title _marker defn ->
+ previewStatement (renderDefn hints defn)
+ BlockAbbr _loc _title _marker abbr ->
+ previewStatement (renderAbbreviation hints abbr)
+ BlockData _loc _title _marker datatype ->
+ renderDatatype hints datatype
+ BlockInductive _loc _title marker ind ->
+ renderInductive hints marker ind
+ BlockSig _loc _title _marker asms sig ->
+ renderSignatureBlock hints asms sig
+ BlockStruct _loc _title _marker structDefn ->
+ renderStructDefn hints structDefn
+ BlockProof{} ->
+ skip
+
+previewStatement :: Html () -> Html ()
+previewStatement =
+ p_ [class_ "reference-preview-statement"]
+
+referenceTargetsOfBlockRenderInfo :: BlockRenderInfo -> [ReferenceTarget]
+referenceTargetsOfBlockRenderInfo (_index, block, blockId) =
+ maybeToList blockTarget <> inductiveTargets
+ where
+ blockTarget = do
+ marker <- blockMarkerOf block
+ pure ReferenceTarget
+ { targetMarker = marker
+ , targetAnchorId = blockId
+ , targetKind = blockPrefixText block
+ , targetTitle = formatBlockTitle (blockTitleOf block)
+ , targetBody = \hints -> renderPreviewBlockBody hints block
+ }
+
+ inductiveTargets = case block of
+ BlockInductive _loc _title marker inductive ->
+ [ ReferenceTarget
+ { targetMarker = inductiveDerivedFactMarker
+ , targetAnchorId = markerText inductiveDerivedFactMarker
+ , targetKind = "Inductive Fact"
+ , targetTitle = Nothing
+ , targetBody = \hints -> previewStatement (inlineMath (renderFormulaMath hints inductiveDerivedFactFormula))
+ }
+ | InductiveDerivedFact{inductiveDerivedFactMarker, inductiveDerivedFactFormula} <- inductiveDerivedFacts marker inductive
+ ]
+ _ ->
+ []
+
+
+renderProof :: HintMap -> ReferenceContext -> Proof -> Html ()
+renderProof hints references = \case
+ Omitted _loc ->
+ p_ "Omitted."
+ Qed mloc justification ->
+ renderProofTerminal mloc justification
+ Contradiction _loc justification ->
+ p_ do
+ toHtml ("Contradiction" :: Text)
+ renderJustificationSuffix references justification
+ toHtml ("." :: Text)
+ ByCase _loc cases -> do
+ p_ "Proof by cases."
+ term "proof-" (traverse_ (renderCase hints references) cases)
+ ByContradiction _loc proof -> do
+ p_ "Proof by contradiction."
+ term "proof-" (renderProof hints references proof)
+ BySetInduction _loc maybeTerm proof -> do
+ p_ do
+ toHtml ("Proof by set induction" :: Text)
+ case maybeTerm of
+ Nothing -> skip
+ Just targetTerm -> do
+ toHtml (" on " :: Text)
+ renderTermInline hints targetTerm
+ toHtml ("." :: Text)
+ term "proof-" (renderProof hints references proof)
+ ByOrdInduction _loc proof -> do
+ p_ "Proof by ordinal induction."
+ term "proof-" (renderProof hints references proof)
+ Assume _loc stmt proof -> do
+ p_ do
+ toHtml ("Assume " :: Text)
+ renderStmtInline hints stmt
+ toHtml ("." :: Text)
+ renderProofContinuation hints references proof
+ FixSymbolic _loc vars bound proof -> do
+ p_ do
+ toHtml ("Fix " :: Text)
+ renderVarListInline vars
+ renderBoundInline hints vars bound
+ toHtml ("." :: Text)
+ renderProofContinuation hints references proof
+ FixSuchThat _loc vars stmt proof -> do
+ p_ do
+ toHtml ("Fix " :: Text)
+ renderVarListInline vars
+ toHtml (" such that " :: Text)
+ renderStmtInline hints stmt
+ toHtml ("." :: Text)
+ renderProofContinuation hints references proof
+ Calc _loc maybeQuant calc proof -> do
+ renderCalc hints references maybeQuant calc
+ renderProofContinuation hints references proof
+ TakeVar _loc vars bound stmt justification proof -> do
+ p_ do
+ toHtml ("Take " :: Text)
+ renderVarListInline vars
+ renderBoundInline hints vars bound
+ toHtml (" such that " :: Text)
+ renderStmtInline hints stmt
+ renderJustificationSuffix references justification
+ toHtml ("." :: Text)
+ renderProofContinuation hints references proof
+ TakeNoun _loc np justification proof -> do
+ p_ do
+ toHtml ("Take " :: Text)
+ renderNounPhraseList hints np
+ renderJustificationSuffix references justification
+ toHtml ("." :: Text)
+ renderProofContinuation hints references proof
+ Have _loc maybeStmt stmt justification proof -> do
+ p_ do
+ case maybeStmt of
+ Nothing
+ | isImplicitProofEnd proof ->
+ skip
+ | otherwise ->
+ toHtml ("We have " :: Text)
+ Just premise -> do
+ toHtml ("Since " :: Text)
+ renderStmtInline hints premise
+ toHtml (", we have " :: Text)
+ renderStmtInline hints stmt
+ renderJustificationSuffix references justification
+ toHtml ("." :: Text)
+ renderProofContinuation hints references proof
+ Suffices _loc stmt justification proof -> do
+ p_ do
+ toHtml ("It suffices to show that " :: Text)
+ renderStmtInline hints stmt
+ renderJustificationSuffix references justification
+ toHtml ("." :: Text)
+ renderProofContinuation hints references proof
+ Subclaim _loc stmt subproof proof -> do
+ p_ do
+ toHtml ("Show " :: Text)
+ renderStmtInline hints stmt
+ toHtml ("." :: Text)
+ term "proof-" (renderProof hints references subproof)
+ renderProofContinuation hints references proof
+ Define _loc var expr proof -> do
+ p_ do
+ toHtml ("Let " :: Text)
+ renderVarEqInline hints var expr
+ toHtml ("." :: Text)
+ renderProofContinuation hints references proof
+ DefineFunction _loc fun arg value boundVar boundExpr proof -> do
+ p_ do
+ toHtml ("Let " :: Text)
+ renderFunctionEqInline hints fun arg value
+ toHtml (" for " :: Text)
+ renderVarInline boundVar
+ toHtml (" in " :: Text)
+ inlineMath (renderExprMathRow hints boundExpr)
+ toHtml ("." :: Text)
+ renderProofContinuation hints references proof
+ DefineFunctionLocal _loc fun arg _target domVar codVar rules proof -> do
+ p_ do
+ toHtml ("Let " :: Text)
+ renderFunctionCallInline fun arg
+ toHtml (" be locally defined from " :: Text)
+ renderVarInline domVar
+ toHtml (" to " :: Text)
+ renderVarInline codVar
+ toHtml ("." :: Text)
+ ul_ do
+ for_ (toList rules) \(ruleTerm, formula) -> li_ do
+ inlineMath (renderExprMathRow hints ruleTerm)
+ toHtml (" if " :: Text)
+ inlineMath (renderFormulaMath hints formula)
+ toHtml ("." :: Text)
+ renderProofContinuation hints references proof
+
+ where
+ renderProofTerminal :: Maybe Location -> Justification -> Html ()
+ renderProofTerminal mloc justification = case (mloc, justification) of
+ (Nothing, JustificationEmpty) ->
+ skip
+ (Just _, JustificationEmpty) ->
+ p_ "Trivial."
+ _ ->
+ p_ do
+ toHtml ("Follows" :: Text)
+ renderJustificationSuffix references justification
+ toHtml ("." :: Text)
+
+renderProofContinuation :: HintMap -> ReferenceContext -> Proof -> Html ()
+renderProofContinuation hints references proof =
+ unless (isImplicitProofEnd proof) (renderProof hints references proof)
+
+isImplicitProofEnd :: Proof -> Bool
+isImplicitProofEnd = \case
+ Qed Nothing JustificationEmpty -> True
+ _ -> False
+
+proofStepCount :: Proof -> Int
+proofStepCount = \case
+ Omitted _loc -> 1
+ Qed{} -> 1
+ Contradiction{} -> 1
+ ByCase _loc cases -> 1 + sum (caseStepCount <$> cases)
+ ByContradiction _loc proof -> 1 + proofStepCount proof
+ BySetInduction _loc _maybeTerm proof -> 1 + proofStepCount proof
+ ByOrdInduction _loc proof -> 1 + proofStepCount proof
+ Assume _loc _stmt proof -> 1 + proofStepCount proof
+ FixSymbolic _loc _vars _bound proof -> 1 + proofStepCount proof
+ FixSuchThat _loc _vars _stmt proof -> 1 + proofStepCount proof
+ Calc _loc _maybeQuant calc proof -> 1 + calcStepCount calc + proofStepCount proof
+ TakeVar _loc _vars _bound _stmt _justification proof -> 1 + proofStepCount proof
+ TakeNoun _loc _np _justification proof -> 1 + proofStepCount proof
+ Have _loc _maybeStmt _stmt _justification proof -> 1 + proofStepCount proof
+ Suffices _loc _stmt _justification proof -> 1 + proofStepCount proof
+ Subclaim _loc _stmt subproof proof -> 1 + proofStepCount subproof + proofStepCount proof
+ Define _loc _var _expr proof -> 1 + proofStepCount proof
+ DefineFunction _loc _fun _arg _value _boundVar _boundExpr proof -> 1 + proofStepCount proof
+ DefineFunctionLocal _loc _fun _arg _target _domVar _codVar rules proof ->
+ 1 + length rules + proofStepCount proof
+
+caseStepCount :: Case -> Int
+caseStepCount Case{caseProof} = 1 + proofStepCount caseProof
+
+calcStepCount :: Calc -> Int
+calcStepCount = \case
+ Equation _ steps -> length steps
+ Biconditionals _ steps -> length steps
+
+renderCase :: HintMap -> ReferenceContext -> Case -> Html ()
+renderCase hints references Case{..} =
+ term "proof-" do
+ p_ do
+ toHtml ("Case " :: Text)
+ renderStmtInline hints caseOf
+ toHtml ("." :: Text)
+ renderProof hints references caseProof
+
+renderCalc :: HintMap -> ReferenceContext -> Maybe CalcQuantifier -> Calc -> Html ()
+renderCalc hints references maybeQuant calc = do
+ p_ do
+ toHtml ("Calculation" :: Text)
+ case maybeQuant of
+ Nothing -> skip
+ Just quant -> do
+ toHtml (" for " :: Text)
+ renderCalcQuantifierInline hints quant
+ toHtml ("." :: Text)
+ blockMath (renderCalcMath hints calc)
+ let justifications = calcJustifications calc
+ when (not (null justifications)) do
+ ul_ do
+ traverse_ renderStepJustification justifications
+ where
+ renderStepJustification :: (Int, Justification) -> Html ()
+ renderStepJustification (_idx, JustificationEmpty) = skip
+ renderStepJustification (idx, jst) = li_ do
+ toHtml ("Step " <> Text.pack (show idx) <> ": " :: Text)
+ renderJustification references jst
+ toHtml ("." :: Text)
+
+renderCalcQuantifierInline :: HintMap -> CalcQuantifier -> Html ()
+renderCalcQuantifierInline hints (CalcQuantifier vars bound maybeStmt) = do
+ renderVarListInline vars
+ renderBoundInline hints vars bound
+ case maybeStmt of
+ Nothing -> skip
+ Just stmt -> do
+ toHtml (" such that " :: Text)
+ renderStmtInline hints stmt
+
+renderCalcMath :: HintMap -> Calc -> Html ()
+renderCalcMath hints = \case
+ Equation expr steps -> do
+ renderExprMathRow hints expr
+ for_ (toList steps) \(nextExpr, _jst) -> do
+ moText "="
+ renderExprMathRow hints nextExpr
+ Biconditionals phi steps -> do
+ renderFormulaMath hints phi
+ for_ (toList steps) \(nextPhi, _jst) -> do
+ moText "⇔"
+ renderFormulaMath hints nextPhi
+
+calcJustifications :: Calc -> [(Int, Justification)]
+calcJustifications = \case
+ Equation _ steps ->
+ zip [1..] (snd <$> toList steps)
+ Biconditionals _ steps ->
+ zip [1..] (snd <$> toList steps)
+
+
+renderStmtInline :: HintMap -> Stmt -> Html ()
+renderStmtInline hints = \case
+ StmtFormula phi ->
+ inlineMath (renderFormulaMath hints phi)
+ StmtVerbPhrase ts vp -> do
+ renderTermList hints ts
+ toHtml (" " :: Text)
+ renderVerbPhraseInline hints (length ts > 1) vp
+ StmtNoun ts np -> do
+ renderTermList hints ts
+ toHtml (if length ts > 1 then " are a " else " is a " :: Text)
+ renderNounPhraseMaybe hints np
+ StmtStruct t structPhrase -> do
+ renderTermInline hints t
+ toHtml (" is a " :: Text)
+ renderStructPhraseInline structPhrase
+ StmtNeg _loc stmt -> do
+ toHtml ("it is not the case that " :: Text)
+ renderStmtInline hints stmt
+ StmtExists _loc np -> do
+ toHtml ("there exists " :: Text)
+ renderNounPhraseList hints np
+ StmtConnected conn _loc stmt1 stmt2 -> do
+ renderConnectedStmtInline hints conn stmt1 stmt2
+ StmtQuantPhrase _loc qp stmt -> do
+ renderQuantPhraseInline hints qp
+ toHtml (" " :: Text)
+ renderStmtInline hints stmt
+ SymbolicQuantified _loc quant vars bound suchThat stmt -> do
+ toHtml (quantifierWord quant)
+ toHtml (" " :: Text)
+ renderBoundSubjectInline hints vars bound
+ renderQuantifiedTailInline hints quant suchThat stmt
+
+renderQuantPhraseInline :: HintMap -> QuantPhrase -> Html ()
+renderQuantPhraseInline hints (QuantPhrase quant np) = do
+ toHtml (quantifierWord quant)
+ toHtml (" " :: Text)
+ renderNounPhraseList hints np
+
+quantifierWord :: Quantifier -> Text
+quantifierWord = \case
+ Universally -> "for every"
+ Existentially -> "there exists"
+ Nonexistentially -> "there exists no"
+
+connectiveWord :: Connective -> Text
+connectiveWord = \case
+ Conjunction -> "and"
+ Disjunction -> "or"
+ Implication -> "implies"
+ Equivalence -> "iff"
+ ExclusiveOr -> "xor"
+ NegatedDisjunction -> "nor"
+
+renderConnectedStmtInline :: HintMap -> Connective -> Stmt -> Stmt -> Html ()
+renderConnectedStmtInline hints conn stmt1 stmt2 = case conn of
+ ExclusiveOr -> do
+ toHtml ("either " :: Text)
+ renderStmtInline hints stmt1
+ toHtml (" or " :: Text)
+ renderStmtInline hints stmt2
+ NegatedDisjunction -> do
+ toHtml ("neither " :: Text)
+ renderStmtInline hints stmt1
+ toHtml (" nor " :: Text)
+ renderStmtInline hints stmt2
+ _ -> do
+ renderStmtInline hints stmt1
+ toHtml (" " :: Text)
+ toHtml (connectiveWord conn)
+ toHtml (" " :: Text)
+ renderStmtInline hints stmt2
+
+renderQuantifiedTailInline :: HintMap -> Quantifier -> Maybe Stmt -> Stmt -> Html ()
+renderQuantifiedTailInline hints quant suchThat stmt =
+ case quant of
+ Universally -> do
+ for_ suchThat \suchStmt -> do
+ toHtml (" such that " :: Text)
+ renderStmtInline hints suchStmt
+ toHtml (" we have " :: Text)
+ renderStmtInline hints stmt
+ Existentially ->
+ renderExistentialTailInline hints suchThat stmt
+ Nonexistentially ->
+ renderExistentialTailInline hints suchThat stmt
+
+renderExistentialTailInline :: HintMap -> Maybe Stmt -> Stmt -> Html ()
+renderExistentialTailInline hints suchThat stmt = do
+ toHtml (" such that " :: Text)
+ case suchThat of
+ Nothing ->
+ renderStmtInline hints stmt
+ Just suchStmt -> do
+ renderStmtInline hints suchStmt
+ toHtml (" and " :: Text)
+ renderStmtInline hints stmt
+
+
+renderAsmList :: HintMap -> [Asm] -> Html ()
+renderAsmList hints asms =
+ joinHtml (toHtml ("; " :: Text)) (renderAsm hints <$> asms)
+
+renderAsm :: HintMap -> Asm -> Html ()
+renderAsm hints = \case
+ AsmSuppose stmt ->
+ renderStmtInline hints stmt
+ AsmLetNoun vars np -> do
+ renderVarListInline vars
+ toHtml (" be " :: Text)
+ renderNounPhraseMaybe hints np
+ AsmLetIn vars expr -> do
+ renderVarListInline vars
+ toHtml (" be in " :: Text)
+ inlineMath (renderExprMathRow hints expr)
+ AsmLetThe var fun -> do
+ renderVarInline var
+ toHtml (" be " :: Text)
+ renderFunInline renderTermInline' fun
+ where renderTermInline' = renderTermInline hints
+ AsmLetEq var expr -> do
+ renderVarEqInline hints var expr
+ AsmLetStruct var structPhrase -> do
+ renderVarInline var
+ toHtml (" be a " :: Text)
+ renderStructPhraseInline structPhrase
+
+
+renderTermInline :: HintMap -> Term -> Html ()
+renderTermInline hints = \case
+ TermExpr expr ->
+ inlineMath (renderExprMathRow hints expr)
+ TermFun fun -> do
+ toHtml ("the " :: Text)
+ renderFunInline (renderTermInline hints) fun
+ TermIota _loc var stmt -> do
+ toHtml ("the " :: Text)
+ renderVarInline var
+ toHtml (" such that " :: Text)
+ renderStmtInline hints stmt
+ TermQuantified quant _loc np -> do
+ toHtml (termQuantifierWord quant)
+ toHtml (" " :: Text)
+ renderNounPhraseMaybe hints np
+
+termQuantifierWord :: Quantifier -> Text
+termQuantifierWord = \case
+ Universally -> "every"
+ Existentially -> "some"
+ Nonexistentially -> "no"
+
+renderTermList :: HintMap -> NonEmpty Term -> Html ()
+renderTermList hints =
+ joinHtml (toHtml (" and " :: Text)) . fmap (renderTermInline hints) . toList
+
+renderNounPhraseMaybe :: HintMap -> NounPhrase Maybe -> Html ()
+renderNounPhraseMaybe hints (NounPhrase ls noun maybeName rs maybeSuchThat) = do
+ renderAdjListInline renderTermInline' ls
+ renderNounInline False renderTermInline' noun
+ case maybeName of
+ Nothing -> skip
+ Just name -> do
+ toHtml (" " :: Text)
+ renderVarInline name
+ renderAdjRListInline hints rs
+ case maybeSuchThat of
+ Nothing -> skip
+ Just stmt -> do
+ toHtml (" such that " :: Text)
+ renderStmtInline hints stmt
+ where
+ renderTermInline' = renderTermInline hints
+
+renderNounPhraseList :: HintMap -> NounPhrase [] -> Html ()
+renderNounPhraseList hints (NounPhrase ls noun names rs maybeSuchThat) = do
+ renderAdjListInline renderTermInline' ls
+ renderNounInline (length names > 1) renderTermInline' noun
+ when (not (null names)) do
+ toHtml (" " :: Text)
+ renderVarListInline (NonEmpty.fromList names)
+ renderAdjRListInline hints rs
+ case maybeSuchThat of
+ Nothing -> skip
+ Just stmt -> do
+ toHtml (" such that " :: Text)
+ renderStmtInline hints stmt
+ where
+ renderTermInline' = renderTermInline hints
+
+renderAdjListInline :: (a -> Html ()) -> [AdjLOf a] -> Html ()
+renderAdjListInline renderArg adjs =
+ unless (null adjs) do
+ joinHtml (toHtml (" " :: Text)) (renderAdjLInline renderArg <$> adjs)
+ toHtml (" " :: Text)
+
+renderAdjRListInline :: HintMap -> [AdjROf Term] -> Html ()
+renderAdjRListInline hints adjs =
+ unless (null adjs) do
+ toHtml (" " :: Text)
+ joinHtml (toHtml (" and " :: Text)) (renderAdjRInline hints <$> adjs)
+
+renderAdjLInline :: (a -> Html ()) -> AdjLOf a -> Html ()
+renderAdjLInline renderArg (AdjL _loc item args) =
+ renderLexicalItemInline renderArg item args
+
+renderAdjRInline :: HintMap -> AdjROf Term -> Html ()
+renderAdjRInline hints = \case
+ AdjR _loc item args ->
+ renderLexicalItemInline (renderTermInline hints) item args
+ AttrRThat verbPhrase -> do
+ toHtml ("that " :: Text)
+ renderVerbPhraseInline hints False verbPhrase
+
+renderAdjInline :: (a -> Html ()) -> AdjOf a -> Html ()
+renderAdjInline renderArg (Adj _loc item args) =
+ renderLexicalItemInline renderArg item args
+
+renderVerbInline :: Bool -> (a -> Html ()) -> VerbOf a -> Html ()
+renderVerbInline isPlural renderArg (Verb _loc item args) =
+ renderLexicalItemSgPlInline isPlural renderArg item args
+
+renderVerbPhraseInline :: HintMap -> Bool -> VerbPhrase -> Html ()
+renderVerbPhraseInline hints isPlural = \case
+ VPVerb verb ->
+ renderVerbInline isPlural (renderTermInline hints) verb
+ VPAdj adjs -> do
+ toHtml (if isPlural then "are " else "is " :: Text)
+ joinHtml (toHtml (" and " :: Text)) (renderAdjInline (renderTermInline hints) <$> toList adjs)
+ VPVerbNot verb -> do
+ toHtml (if isPlural then "do not " else "does not " :: Text)
+ renderVerbInline True (renderTermInline hints) verb
+ VPAdjNot adjs -> do
+ toHtml (if isPlural then "are not " else "is not " :: Text)
+ joinHtml (toHtml (" and " :: Text)) (renderAdjInline (renderTermInline hints) <$> toList adjs)
+
+renderNounInline :: Bool -> (a -> Html ()) -> NounOf a -> Html ()
+renderNounInline isPlural renderArg (Noun _loc item args) =
+ renderLexicalItemSgPlInline isPlural renderArg item args
+
+renderFunInline :: (a -> Html ()) -> FunOf a -> Html ()
+renderFunInline renderArg Fun{phrase, funArgs} =
+ renderLexicalItemSgPlInline False renderArg phrase funArgs
+
+renderStructPhraseInline :: StructPhrase -> Html ()
+renderStructPhraseInline item =
+ renderLexicalItemSgPlInline False renderTermInlinePlaceholder item []
+
+renderTermInlinePlaceholder :: a -> Html ()
+renderTermInlinePlaceholder _ = toHtml ("?" :: Text)
+
+renderLexicalItemInline :: (a -> Html ()) -> LexicalItem -> [a] -> Html ()
+renderLexicalItemInline renderArg item args =
+ renderPatternInline renderArg (lexicalItemPhrase item) args
+
+renderLexicalItemSgPlInline :: Bool -> (a -> Html ()) -> LexicalItemSgPl -> [a] -> Html ()
+renderLexicalItemSgPlInline isPlural renderArg item args =
+ renderPatternInline renderArg phrase args
+ where
+ phrase = if isPlural then pl (lexicalItemSgPlPhrase item) else sg (lexicalItemSgPlPhrase item)
+
+renderPatternInline :: (a -> Html ()) -> [Maybe Token] -> [a] -> Html ()
+renderPatternInline renderArg patternParts args =
+ joinHtml (toHtml (" " :: Text)) (go patternParts args)
+ where
+ go [] [] = []
+ go [] (_ : _) = error "renderPatternInline: too many arguments"
+ go (Nothing : rest) (arg : restArgs) = renderArg arg : go rest restArgs
+ go (Nothing : _) [] = error "renderPatternInline: not enough arguments"
+ go (Just tok : rest) restArgs = tokenTextHtml tok : go rest restArgs
+
+renderStmtMath :: HintMap -> Stmt -> Html ()
+renderStmtMath hints =
+ renderStmtMathFragments . stmtMathFragments hints
+
+renderStmtMathFragments :: StmtMathFragments -> Html ()
+renderStmtMathFragments =
+ go ""
+ where
+ go :: Text -> StmtMathFragments -> Html ()
+ go pending = \case
+ [] ->
+ flush pending
+ StmtMathProse text : rest ->
+ go (pending <> text) rest
+ StmtMathNode node : rest -> do
+ flush pending
+ node
+ go "" rest
+
+ flush :: Text -> Html ()
+ flush text =
+ let renderedText = preserveBoundarySpaces text
+ in unless (Text.null renderedText) (mtextText renderedText)
+
+ preserveBoundarySpaces :: Text -> Text
+ preserveBoundarySpaces text =
+ Text.replicate leadingCount nbsp
+ <> middleText
+ <> Text.replicate trailingCount nbsp
+ where
+ leadingCount = Text.length (Text.takeWhile (== ' ') text)
+ textAfterLeading = Text.drop leadingCount text
+ trailingCount = Text.length (Text.takeWhileEnd (== ' ') textAfterLeading)
+ middleText = Text.dropEnd trailingCount textAfterLeading
+ nbsp = Text.singleton '\160'
+
+stmtMathProse :: Text -> StmtMathFragments
+stmtMathProse text
+ | Text.null text = []
+ | otherwise = [StmtMathProse text]
+
+stmtMathNode :: Html () -> StmtMathFragments
+stmtMathNode html =
+ [StmtMathNode html]
+
+joinStmtMathFragments :: StmtMathFragments -> [StmtMathFragments] -> StmtMathFragments
+joinStmtMathFragments _ [] = []
+joinStmtMathFragments separator (first : rest) =
+ first <> foldMap (separator <>) rest
+
+stmtMathFragments :: HintMap -> Stmt -> StmtMathFragments
+stmtMathFragments hints = \case
+ StmtFormula phi ->
+ stmtMathNode (renderFormulaMath hints phi)
+ StmtVerbPhrase ts vp -> do
+ termListMathFragments hints ts
+ <> stmtMathProse " "
+ <> verbPhraseMathFragments hints (length ts > 1) vp
+ StmtNoun ts np -> do
+ termListMathFragments hints ts
+ <> stmtMathProse (if length ts > 1 then " are a " else " is a ")
+ <> nounPhraseMaybeMathFragments hints np
+ StmtStruct t structPhrase -> do
+ termMathFragments hints t
+ <> stmtMathProse " is a "
+ <> structPhraseMathFragments structPhrase
+ StmtNeg _loc stmt -> do
+ stmtMathProse "it is not the case that "
+ <> stmtMathFragments hints stmt
+ StmtExists _loc np -> do
+ stmtMathProse "there exists "
+ <> nounPhraseListMathFragments hints np
+ StmtConnected conn _loc stmt1 stmt2 -> do
+ connectedStmtMathFragments hints conn stmt1 stmt2
+ StmtQuantPhrase _loc qp stmt -> do
+ quantPhraseMathFragments hints qp
+ <> stmtMathProse " "
+ <> stmtMathFragments hints stmt
+ SymbolicQuantified _loc quant vars bound suchThat stmt -> do
+ stmtMathProse (quantifierWord quant <> " ")
+ <> boundSubjectMathFragments hints vars bound
+ <> quantifiedTailMathFragments hints quant suchThat stmt
+
+quantPhraseMathFragments :: HintMap -> QuantPhrase -> StmtMathFragments
+quantPhraseMathFragments hints (QuantPhrase quant np) =
+ stmtMathProse (quantifierWord quant <> " ")
+ <> nounPhraseListMathFragments hints np
+
+connectedStmtMathFragments :: HintMap -> Connective -> Stmt -> Stmt -> StmtMathFragments
+connectedStmtMathFragments hints conn stmt1 stmt2 = case conn of
+ ExclusiveOr ->
+ stmtMathProse "either "
+ <> stmtMathFragments hints stmt1
+ <> stmtMathProse " or "
+ <> stmtMathFragments hints stmt2
+ NegatedDisjunction ->
+ stmtMathProse "neither "
+ <> stmtMathFragments hints stmt1
+ <> stmtMathProse " nor "
+ <> stmtMathFragments hints stmt2
+ _ ->
+ stmtMathFragments hints stmt1
+ <> stmtMathProse (" " <> connectiveWord conn <> " ")
+ <> stmtMathFragments hints stmt2
+
+quantifiedTailMathFragments :: HintMap -> Quantifier -> Maybe Stmt -> Stmt -> StmtMathFragments
+quantifiedTailMathFragments hints quant suchThat stmt =
+ case quant of
+ Universally ->
+ foldMap
+ (\suchStmt -> stmtMathProse " such that " <> stmtMathFragments hints suchStmt)
+ suchThat
+ <> stmtMathProse " we have "
+ <> stmtMathFragments hints stmt
+ Existentially ->
+ existentialTailMathFragments hints suchThat stmt
+ Nonexistentially ->
+ existentialTailMathFragments hints suchThat stmt
+
+existentialTailMathFragments :: HintMap -> Maybe Stmt -> Stmt -> StmtMathFragments
+existentialTailMathFragments hints suchThat stmt =
+ stmtMathProse " such that "
+ <> case suchThat of
+ Nothing ->
+ stmtMathFragments hints stmt
+ Just suchStmt ->
+ stmtMathFragments hints suchStmt
+ <> stmtMathProse " and "
+ <> stmtMathFragments hints stmt
+
+termMathFragments :: HintMap -> Term -> StmtMathFragments
+termMathFragments hints = \case
+ TermExpr expr ->
+ stmtMathNode (renderExprMathRow hints expr)
+ TermFun fun -> do
+ stmtMathProse "the "
+ <> funMathFragments (termMathFragments hints) fun
+ TermIota _loc var stmt -> do
+ stmtMathProse "the "
+ <> stmtMathNode (renderVarMath var)
+ <> stmtMathProse " such that "
+ <> stmtMathFragments hints stmt
+ TermQuantified quant _loc np -> do
+ stmtMathProse (termQuantifierWord quant <> " ")
+ <> nounPhraseMaybeMathFragments hints np
+
+termListMathFragments :: HintMap -> NonEmpty Term -> StmtMathFragments
+termListMathFragments hints =
+ joinStmtMathFragments (stmtMathProse " and ") . fmap (termMathFragments hints) . toList
+
+nounPhraseMaybeMathFragments :: HintMap -> NounPhrase Maybe -> StmtMathFragments
+nounPhraseMaybeMathFragments hints (NounPhrase ls noun maybeName rs maybeSuchThat) =
+ adjListMathFragments renderTermMath' ls
+ <> nounMathFragments False renderTermMath' noun
+ <> foldMap (\name -> stmtMathProse " " <> stmtMathNode (renderVarMath name)) maybeName
+ <> adjRListMathFragments hints rs
+ <> foldMap (\stmt -> stmtMathProse " such that " <> stmtMathFragments hints stmt) maybeSuchThat
+ where
+ renderTermMath' = termMathFragments hints
+
+nounPhraseListMathFragments :: HintMap -> NounPhrase [] -> StmtMathFragments
+nounPhraseListMathFragments hints (NounPhrase ls noun names rs maybeSuchThat) =
+ adjListMathFragments renderTermMath' ls
+ <> nounMathFragments (length names > 1) renderTermMath' noun
+ <> if null names
+ then []
+ else stmtMathProse " " <> stmtMathNode (renderVarListMath (NonEmpty.fromList names))
+ <> adjRListMathFragments hints rs
+ <> foldMap (\stmt -> stmtMathProse " such that " <> stmtMathFragments hints stmt) maybeSuchThat
+ where
+ renderTermMath' = termMathFragments hints
+
+adjListMathFragments :: (a -> StmtMathFragments) -> [AdjLOf a] -> StmtMathFragments
+adjListMathFragments renderArg adjs =
+ if null adjs
+ then []
+ else joinStmtMathFragments (stmtMathProse " ") (renderAdjLMathFragments renderArg <$> adjs)
+ <> stmtMathProse " "
+
+adjRListMathFragments :: HintMap -> [AdjROf Term] -> StmtMathFragments
+adjRListMathFragments hints adjs =
+ if null adjs
+ then []
+ else stmtMathProse " "
+ <> joinStmtMathFragments (stmtMathProse " and ") (renderAdjRMathFragments hints <$> adjs)
+
+renderAdjLMathFragments :: (a -> StmtMathFragments) -> AdjLOf a -> StmtMathFragments
+renderAdjLMathFragments renderArg (AdjL _loc item args) =
+ lexicalItemStmtMathFragments renderArg item args
+
+renderAdjRMathFragments :: HintMap -> AdjROf Term -> StmtMathFragments
+renderAdjRMathFragments hints = \case
+ AdjR _loc item args ->
+ lexicalItemStmtMathFragments (termMathFragments hints) item args
+ AttrRThat verbPhrase ->
+ stmtMathProse "that "
+ <> verbPhraseMathFragments hints False verbPhrase
+
+adjMathFragments :: (a -> StmtMathFragments) -> AdjOf a -> StmtMathFragments
+adjMathFragments renderArg (Adj _loc item args) =
+ lexicalItemStmtMathFragments renderArg item args
+
+verbMathFragments :: Bool -> (a -> StmtMathFragments) -> VerbOf a -> StmtMathFragments
+verbMathFragments isPlural renderArg (Verb _loc item args) =
+ lexicalItemSgPlStmtMathFragments isPlural renderArg item args
+
+verbPhraseMathFragments :: HintMap -> Bool -> VerbPhrase -> StmtMathFragments
+verbPhraseMathFragments hints isPlural = \case
+ VPVerb verb ->
+ verbMathFragments isPlural (termMathFragments hints) verb
+ VPAdj adjs ->
+ stmtMathProse (if isPlural then "are " else "is ")
+ <> joinStmtMathFragments (stmtMathProse " and ") (adjMathFragments (termMathFragments hints) <$> toList adjs)
+ VPVerbNot verb ->
+ stmtMathProse (if isPlural then "do not " else "does not ")
+ <> verbMathFragments True (termMathFragments hints) verb
+ VPAdjNot adjs ->
+ stmtMathProse (if isPlural then "are not " else "is not ")
+ <> joinStmtMathFragments (stmtMathProse " and ") (adjMathFragments (termMathFragments hints) <$> toList adjs)
+
+nounMathFragments :: Bool -> (a -> StmtMathFragments) -> NounOf a -> StmtMathFragments
+nounMathFragments isPlural renderArg (Noun _loc item args) =
+ lexicalItemSgPlStmtMathFragments isPlural renderArg item args
+
+funMathFragments :: (a -> StmtMathFragments) -> FunOf a -> StmtMathFragments
+funMathFragments renderArg Fun{phrase, funArgs} =
+ lexicalItemSgPlStmtMathFragments False renderArg phrase funArgs
+
+structPhraseMathFragments :: StructPhrase -> StmtMathFragments
+structPhraseMathFragments item =
+ lexicalItemSgPlStmtMathFragments False termMathPlaceholderFragments item []
+
+termMathPlaceholderFragments :: a -> StmtMathFragments
+termMathPlaceholderFragments _ =
+ stmtMathProse "?"
+
+lexicalItemStmtMathFragments :: (a -> StmtMathFragments) -> LexicalItem -> [a] -> StmtMathFragments
+lexicalItemStmtMathFragments renderArg item args =
+ patternStmtMathFragments renderArg (lexicalItemPhrase item) args
+
+lexicalItemSgPlStmtMathFragments :: Bool -> (a -> StmtMathFragments) -> LexicalItemSgPl -> [a] -> StmtMathFragments
+lexicalItemSgPlStmtMathFragments isPlural renderArg item args =
+ patternStmtMathFragments renderArg phrase args
+ where
+ phrase = if isPlural then pl (lexicalItemSgPlPhrase item) else sg (lexicalItemSgPlPhrase item)
+
+patternStmtMathFragments :: (a -> StmtMathFragments) -> [Maybe Token] -> [a] -> StmtMathFragments
+patternStmtMathFragments renderArg patternParts args =
+ joinStmtMathFragments (stmtMathProse " ") (go patternParts args)
+ where
+ go [] [] = []
+ go [] (_ : _) = error "renderPatternStmtMath: too many arguments"
+ go (Nothing : rest) (arg : restArgs) = renderArg arg : go rest restArgs
+ go (Nothing : _) [] = error "renderPatternStmtMath: not enough arguments"
+ go (Just tok : rest) restArgs = renderStmtToken tok : go rest restArgs
+
+ renderStmtToken :: Token -> StmtMathFragments
+ renderStmtToken = \case
+ Word w -> stmtMathProse w
+ tok -> stmtMathNode (renderMathToken tok)
+
+
+renderFormulaMath :: HintMap -> Formula -> Html ()
+renderFormulaMath hints = \case
+ FormulaChain chain ->
+ renderChainMathRow hints chain
+ FormulaPredicate _loc predi marker exprs ->
+ renderHintedMathRow hints PredicateHint marker (toList exprs) (renderPrefixPredicateFallback predi (renderExprMath hints <$> toList exprs))
+ Connected _loc conn phi psi -> do
+ renderFormulaMath hints phi
+ moText (connectiveSymbol conn)
+ renderFormulaMath hints psi
+ FormulaNeg _loc phi -> do
+ moText "¬"
+ renderFormulaMath hints phi
+ FormulaQuantified _loc quant vars bound phi -> do
+ moText (quantifierSymbol quant)
+ renderVarListMath vars
+ renderBoundMath hints vars bound
+ moText "."
+ renderFormulaMath hints phi
+ PropositionalConstant _loc pc ->
+ moText (propositionalConstantSymbol pc)
+
+connectiveSymbol :: Connective -> Text
+connectiveSymbol = \case
+ Conjunction -> "∧"
+ Disjunction -> "∨"
+ Implication -> "⇒"
+ Equivalence -> "⇔"
+ ExclusiveOr -> "⊕"
+ NegatedDisjunction -> "↓"
+
+quantifierSymbol :: Quantifier -> Text
+quantifierSymbol = \case
+ Universally -> "∀"
+ Existentially -> "∃"
+ Nonexistentially -> "∄"
+
+propositionalConstantSymbol :: PropositionalConstant -> Text
+propositionalConstantSymbol = \case
+ IsBottom -> "⊥"
+ IsTop -> "⊤"
+
+renderChainMathRow :: HintMap -> Chain -> Html ()
+renderChainMathRow hints chain =
+ joinHtml (moText "∧") (renderLink <$> splatChain chain)
+ where
+ renderLink (lhs, sign, rel, rhs) =
+ renderRelationApplication hints sign (toList lhs) rel (toList rhs)
+
+ splatChain :: Chain -> [(NonEmpty Expr, Sign, Relation, NonEmpty Expr)]
+ splatChain = \case
+ ChainBase es sign rel es' ->
+ [(es, sign, rel, es')]
+ ChainCons es sign rel ch'@(ChainBase es' _ _ _) ->
+ (es, sign, rel, es') : splatChain ch'
+ ChainCons es sign rel ch'@(ChainCons es' _ _ _) ->
+ (es, sign, rel, es') : splatChain ch'
+
+renderBoundMath :: HintMap -> NonEmpty VarSymbol -> Bound -> Html ()
+renderBoundMath hints vars = \case
+ Unbounded -> skip
+ Bounded _loc sign rel expr -> do
+ moText ","
+ renderRelationApplication hints sign (ExprVar <$> toList vars) rel [expr]
+
+renderRelationApplication :: HintMap -> Sign -> [Expr] -> Relation -> [Expr] -> Html ()
+renderRelationApplication hints sign lhs rel rhs = case (sign, rel) of
+ (Negative, Relation _loc symbol [])
+ | Just negated <- negatedRelationSymbol (relationSymbolToken symbol) -> do
+ renderExprListMath hints lhs
+ moText negated
+ renderExprListMath hints rhs
+ _ ->
+ applySign sign (renderRelationCore hints lhs rel rhs)
+
+applySign :: Sign -> Html () -> Html ()
+applySign sign html = case sign of
+ Positive -> html
+ Negative -> do
+ mo_ "¬"
+ html
+
+renderRelationCore :: HintMap -> [Expr] -> Relation -> [Expr] -> Html ()
+renderRelationCore hints lhs rel rhs = case rel of
+ Relation _loc symbol relParams -> do
+ renderExprListMath hints lhs
+ renderRelationSymbolCore hints symbol relParams
+ renderExprListMath hints rhs
+ RelationExpr _loc expr -> do
+ renderExprListMath hints lhs
+ renderExprMath hints expr
+ renderExprListMath hints rhs
+
+renderRelationSymbolCore :: HintMap -> RelationSymbol -> [Expr] -> Html ()
+renderRelationSymbolCore hints symbol relParams =
+ renderHintedMathRow
+ hints
+ RelationHint
+ (relationSymbolMarker symbol)
+ relParams
+ (renderRelationFallback hints symbol relParams)
+
+renderRelationFallback :: HintMap -> RelationSymbol -> [Expr] -> Html ()
+renderRelationFallback hints symbol relParams =
+ merror_ (renderRelationFallbackCore hints symbol relParams)
+
+renderRelationFallbackCore :: HintMap -> RelationSymbol -> [Expr] -> Html ()
+renderRelationFallbackCore hints symbol relParams
+ | null relParams = renderRelationToken (relationSymbolToken symbol)
+ | otherwise = msub_ do
+ renderRelationToken (relationSymbolToken symbol)
+ mrow_ (renderExprListMath hints relParams)
+
+renderRelationToken :: Token -> Html ()
+renderRelationToken = \case
+ Command "in" -> moText "∈"
+ Command "ni" -> moText "∋"
+ Command "notin" -> moText "∉"
+ Command "meets" -> moText "⋈"
+ Command "notmeets" -> moText "⋈̸"
+ Command "subset" -> moText "⊂"
+ Command "subseteq" -> moText "⊆"
+ Command "supset" -> moText "⊃"
+ Command "supseteq" -> moText "⊇"
+ Command "neq" -> moText "≠"
+ tok -> renderMathToken tok
+
+negatedRelationSymbol :: Token -> Maybe Text
+negatedRelationSymbol = \case
+ Command "in" -> Just "∉"
+ Command "ni" -> Just "∌"
+ Command "subset" -> Just "⊄"
+ Command "subseteq" -> Just "⊈"
+ Command "supset" -> Just "⊅"
+ Command "supseteq" -> Just "⊉"
+ Command "meets" -> Just "⋈̸"
+ Symbol "=" -> Just "≠"
+ Symbol "<" -> Just "≮"
+ Symbol ">" -> Just "≯"
+ Symbol "≤" -> Just "≰"
+ Symbol "≥" -> Just "≱"
+ _ -> Nothing
+
+
+renderExprMath :: HintMap -> Expr -> Html ()
+renderExprMath hints expr = case expr of
+ ExprVar var ->
+ renderVarMath var
+ ExprInteger _loc n ->
+ mnText (Text.pack (show n))
+ ExprOp _loc item args ->
+ renderHintedMath hints OperatorHint (mixfixMarker item) args (renderPatternFallback (mixfixPattern item) (renderExprMath hints <$> args))
+ ExprStructOp _loc symb maybeExpr ->
+ let marker = structMarker symb
+ args = maybeToList maybeExpr
+ in renderHintedMath hints StructOpHint marker args (renderStructFallback symb (renderExprMath hints <$> args))
+ ExprFiniteSet{} ->
+ mrow_ (renderExprMathRow hints expr)
+ ExprSep{} ->
+ mrow_ (renderExprMathRow hints expr)
+ ExprReplace{} ->
+ mrow_ (renderExprMathRow hints expr)
+ ExprReplacePred{} ->
+ mrow_ (renderExprMathRow hints expr)
+
+renderExprMathRow :: HintMap -> Expr -> Html ()
+renderExprMathRow hints = \case
+ ExprVar var ->
+ renderVarMath var
+ ExprInteger _loc n ->
+ mnText (Text.pack (show n))
+ ExprOp _loc item args ->
+ renderHintedMathRow hints OperatorHint (mixfixMarker item) args (renderPatternFallback (mixfixPattern item) (renderExprMath hints <$> args))
+ ExprStructOp _loc symb maybeExpr ->
+ let marker = structMarker symb
+ args = maybeToList maybeExpr
+ in renderHintedMathRow hints StructOpHint marker args (renderStructFallback symb (renderExprMath hints <$> args))
+ ExprFiniteSet _loc exprs ->
+ renderFiniteSetMath hints (toList exprs)
+ ExprSep _loc var bound stmt -> do
+ moText "{"
+ renderVarMath var
+ moText "∈"
+ renderExprMathRow hints bound
+ moText "|"
+ renderStmtMath hints stmt
+ moText "}"
+ ExprReplace _loc expr bounds maybeStmt -> do
+ moText "{"
+ renderExprMathRow hints expr
+ moText "|"
+ renderReplaceBoundsMath hints (toList bounds)
+ for_ maybeStmt \stmt -> do
+ moText "|"
+ renderStmtMath hints stmt
+ moText "}"
+ ExprReplacePred _loc rangeVar domVar domExpr stmt -> do
+ moText "{"
+ renderVarMath rangeVar
+ moText "|"
+ moText "∃"
+ renderVarMath domVar
+ moText "∈"
+ renderExprMathRow hints domExpr
+ moText "."
+ renderStmtMath hints stmt
+ moText "}"
+
+renderReplaceBoundsMath :: HintMap -> [(VarSymbol, Expr)] -> Html ()
+renderReplaceBoundsMath hints =
+ joinHtml (moText ",") . fmap renderBound
+ where
+ renderBound (var, expr) = do
+ renderVarMath var
+ moText "∈"
+ renderExprMathRow hints expr
+
+renderExprListMath :: HintMap -> [Expr] -> Html ()
+renderExprListMath hints =
+ joinHtml (moText ",") . fmap (renderExprMath hints)
+
+renderFiniteSetMath :: HintMap -> [Expr] -> Html ()
+renderFiniteSetMath hints exprs =
+ do
+ moText "{"
+ renderExprListMath hints exprs
+ moText "}"
+
+renderHintedMath :: HintMap -> HintCategory -> Marker -> [Expr] -> Html () -> Html ()
+renderHintedMath hints category marker args fallback =
+ case Map.lookup (category, marker, length args) hints of
+ Nothing ->
+ fallback
+ Just RenderHint{..}
+ | renderHintArity /= length args ->
+ error ("Render hint arity mismatch for " <> show category <> " " <> show marker <> ": expected " <> show renderHintArity <> ", got " <> show (length args))
+ | otherwise ->
+ renderTemplateAsNode renderHintTemplate
+ where
+ renderedArgs = renderExprMath hints <$> args
+
+ renderTemplateAsNode :: [TemplatePiece] -> Html ()
+ renderTemplateAsNode = \case
+ [piece] ->
+ renderPiece piece
+ pieces ->
+ mrow_ (traverse_ renderPiece pieces)
+
+ renderPiece :: TemplatePiece -> Html ()
+ renderPiece = \case
+ Literal text -> toHtmlRaw text
+ Slot ix -> case nth (ix - 1) renderedArgs of
+ Just html -> html
+ Nothing -> error ("Render hint slot out of bounds for " <> show marker <> ": <x" <> show ix <> "/>")
+
+renderHintedMathRow :: HintMap -> HintCategory -> Marker -> [Expr] -> Html () -> Html ()
+renderHintedMathRow hints category marker args fallback =
+ case Map.lookup (category, marker, length args) hints of
+ Nothing ->
+ fallback
+ Just RenderHint{..}
+ | renderHintArity /= length args ->
+ error ("Render hint arity mismatch for " <> show category <> " " <> show marker <> ": expected " <> show renderHintArity <> ", got " <> show (length args))
+ | otherwise ->
+ traverse_ renderPiece renderHintTemplate
+ where
+ renderedArgs = renderExprMath hints <$> args
+
+ renderPiece :: TemplatePiece -> Html ()
+ renderPiece = \case
+ Literal text -> toHtmlRaw text
+ Slot ix -> case nth (ix - 1) renderedArgs of
+ Just html -> html
+ Nothing -> error ("Render hint slot out of bounds for " <> show marker <> ": <x" <> show ix <> "/>")
+
+renderPatternFallback :: Pattern -> [Html ()] -> Html ()
+renderPatternFallback patternParts renderedArgs =
+ merror_ (renderPatternMath patternParts renderedArgs)
+
+renderPatternMath :: Pattern -> [Html ()] -> Html ()
+renderPatternMath patternParts renderedArgs =
+ traverse_ id (go patternParts renderedArgs)
+ where
+ go End [] = []
+ go End (_ : _) = error "renderPatternMath: too many arguments"
+ go (HoleCons rest) (arg : args) = arg : go rest args
+ go (HoleCons _) [] = error "renderPatternMath: not enough arguments"
+ go (TokenCons tok rest) args = renderMathToken tok : go rest args
+
+renderPrefixPredicateFallback :: PrefixPredicate -> [Html ()] -> Html ()
+renderPrefixPredicateFallback (PrefixPredicate command _arity) renderedArgs =
+ merror_ do
+ miText command
+ when (not (null renderedArgs)) do
+ moText "("
+ joinHtml (moText ",") renderedArgs
+ moText ")"
+
+renderStructFallback :: StructSymbol -> [Html ()] -> Html ()
+renderStructFallback symb renderedArgs =
+ merror_ do
+ renderStructSymbolName symb
+ when (not (null renderedArgs)) do
+ moText "("
+ joinHtml (moText ",") renderedArgs
+ moText ")"
+
+renderStructSymbolName :: StructSymbol -> Html ()
+renderStructSymbolName (StructSymbol name) = miText name
+
+structMarker :: StructSymbol -> Marker
+structMarker (StructSymbol name) = Marker name
+
+renderMathToken :: Token -> Html ()
+renderMathToken = \case
+ Word w -> miText w
+ Variable v -> renderNamedVariableMath v
+ Symbol s -> moText s
+ Integer n -> mnText (Text.pack (show n))
+ Command cmd -> miText cmd
+ Label m -> mtextText ("label:" <> m)
+ Ref ms -> mtextText ("ref:" <> Text.intercalate "," (toList ms))
+ BeginEnv env -> mtextText ("begin:" <> env)
+ EndEnv env -> mtextText ("end:" <> env)
+ ParenL -> moText "("
+ ParenR -> moText ")"
+ BracketL -> moText "["
+ BracketR -> moText "]"
+ VisibleBraceL -> moText "{"
+ VisibleBraceR -> moText "}"
+ InvisibleBraceL -> moText "("
+ InvisibleBraceR -> moText ")"
+
+
+inlineMath :: Html () -> Html ()
+inlineMath inner = math_ inner
+
+blockMath :: Html () -> Html ()
+blockMath inner = math_ [displayblock_] inner
+
+renderVarInline :: VarSymbol -> Html ()
+renderVarInline = inlineMath . renderVarMath
+
+renderVarMath :: VarSymbol -> Html ()
+renderVarMath = \case
+ NamedVarAt _loc name ->
+ renderNamedVariableMath name
+ FreshVarAt _loc n ->
+ miText ("_" <> Text.pack (show n))
+
+renderNamedVariableMath :: Text -> Html ()
+renderNamedVariableMath rawName =
+ case displayVariable rawName of
+ VariableDisplay baseText Nothing ->
+ miText baseText
+ VariableDisplay baseText (Just (VariableTicks tickCount)) ->
+ msup_ do
+ miText baseText
+ renderPrimeSuperscript tickCount
+ VariableDisplay baseText (Just (VariableSubscript subscriptText)) ->
+ msub_ do
+ miText baseText
+ renderVariableSubscriptMath subscriptText
+
+renderPrimeSuperscript :: Int -> Html ()
+renderPrimeSuperscript tickCount
+ | tickCount <= 1 =
+ moText "′"
+ | otherwise =
+ mrow_ (foldMap (const (moText "′")) [1 .. tickCount])
+
+renderVariableSubscriptMath :: Text -> Html ()
+renderVariableSubscriptMath subscriptText
+ | Text.all isDigit subscriptText =
+ mnText subscriptText
+ | otherwise =
+ miText subscriptText
+
+renderVarEqInline :: HintMap -> VarSymbol -> Expr -> Html ()
+renderVarEqInline hints var expr =
+ inlineMath do
+ renderVarMath var
+ moText "="
+ renderExprMathRow hints expr
+
+renderFunctionCallInline :: VarSymbol -> VarSymbol -> Html ()
+renderFunctionCallInline fun arg =
+ inlineMath (renderFunctionCallMath fun arg)
+
+renderFunctionEqInline :: HintMap -> VarSymbol -> VarSymbol -> Expr -> Html ()
+renderFunctionEqInline hints fun arg expr =
+ inlineMath do
+ renderFunctionCallMath fun arg
+ moText "="
+ renderExprMathRow hints expr
+
+renderFunctionCallMath :: VarSymbol -> VarSymbol -> Html ()
+renderFunctionCallMath fun arg = do
+ renderVarMath fun
+ moText "("
+ renderVarMath arg
+ moText ")"
+
+renderVarListInline :: NonEmpty VarSymbol -> Html ()
+renderVarListInline vars =
+ inlineMath (renderVarListMath vars)
+
+renderVarListMath :: NonEmpty VarSymbol -> Html ()
+renderVarListMath vars =
+ joinHtml (moText ",") (renderVarMath <$> toList vars)
+
+renderBoundInline :: HintMap -> NonEmpty VarSymbol -> Bound -> Html ()
+renderBoundInline hints vars = \case
+ Unbounded -> skip
+ bound -> do
+ toHtml (" with " :: Text)
+ inlineMath (renderBoundPhraseMath hints vars bound)
+
+renderBoundSubjectInline :: HintMap -> NonEmpty VarSymbol -> Bound -> Html ()
+renderBoundSubjectInline hints vars = \case
+ Unbounded ->
+ renderVarListInline vars
+ bound ->
+ inlineMath (renderBoundSubjectMath hints vars bound)
+
+renderBoundPhraseMath :: HintMap -> NonEmpty VarSymbol -> Bound -> Html ()
+renderBoundPhraseMath hints vars = \case
+ Unbounded -> mrow_ skip
+ Bounded _loc sign rel expr ->
+ renderRelationApplication hints sign (ExprVar <$> toList vars) rel [expr]
+
+renderBoundSubjectMath :: HintMap -> NonEmpty VarSymbol -> Bound -> Html ()
+renderBoundSubjectMath hints vars = \case
+ Unbounded ->
+ renderVarListMath vars
+ Bounded _loc sign rel expr ->
+ renderRelationApplication hints sign (ExprVar <$> toList vars) rel [expr]
+
+boundSubjectMathFragments :: HintMap -> NonEmpty VarSymbol -> Bound -> StmtMathFragments
+boundSubjectMathFragments hints vars = \case
+ Unbounded ->
+ stmtMathNode (renderVarListMath vars)
+ bound ->
+ stmtMathNode (renderBoundSubjectMath hints vars bound)
+
+renderSymbolPatternInline :: HintMap -> SymbolPattern -> Html ()
+renderSymbolPatternInline hints =
+ inlineMath . renderSymbolPatternMath hints
+
+renderSymbolPatternMath :: HintMap -> SymbolPattern -> Html ()
+renderSymbolPatternMath hints (SymbolPattern symbol vars) =
+ renderHintedMathRow hints OperatorHint (mixfixMarker symbol) (ExprVar <$> vars) (renderPatternFallback (mixfixPattern symbol) (renderVarMath <$> vars))
+
+renderJustification :: ReferenceContext -> Justification -> Html ()
+renderJustification references = \case
+ JustificationRef markers -> do
+ toHtml ("by " :: Text)
+ renderMarkerReferences references (toList markers)
+ JustificationSetExt ->
+ toHtml ("by set extensionality" :: Text)
+ JustificationEmpty ->
+ skip
+ JustificationLocal ->
+ toHtml ("by local assumptions" :: Text)
+
+renderJustificationSuffix :: ReferenceContext -> Justification -> Html ()
+renderJustificationSuffix _ JustificationEmpty = skip
+renderJustificationSuffix references justification = do
+ toHtml (" " :: Text)
+ renderJustification references justification
+
+renderMarkerReferences :: ReferenceContext -> [Marker] -> Html ()
+renderMarkerReferences references markers
+ | length markers >= referenceGroupThreshold =
+ renderMarkerReferenceGroup references markers
+ | otherwise =
+ joinHtml (toHtml (", " :: Text)) (renderMarkerReference references <$> markers)
+
+renderMarkerReferenceGroup :: ReferenceContext -> [Marker] -> Html ()
+renderMarkerReferenceGroup references markers =
+ span_ groupAttributes do
+ toHtml ("[...]" :: Text)
+ span_ [class_ "reference-preview-group-items", makeAttributes "hidden" "hidden"] do
+ traverse_ (renderMarkerReferenceGroupItem references) markers
+ where
+ referenceCountLabel = Text.pack (show (length markers)) <> " references"
+
+ groupAttributes =
+ [ class_ "ref-badge has-preview ref-badge-group"
+ , makeAttributes "data-preview-group" "true"
+ , makeAttributes "data-reference-label" referenceCountLabel
+ , makeAttributes "aria-describedby" "reference-preview-popup"
+ , makeAttributes "tabindex" "0"
+ , makeAttributes "role" "button"
+ , makeAttributes "aria-label" ("Show " <> referenceCountLabel)
+ ]
+
+renderMarkerReferenceGroupItem :: ReferenceContext -> Marker -> Html ()
+renderMarkerReferenceGroupItem ReferenceContext{..} marker =
+ span_ itemAttributes skip
+ where
+ label = markerText marker
+ baseAttributes =
+ [ class_ "reference-preview-group-item"
+ , makeAttributes "data-reference-label" label
+ ]
+
+ itemAttributes =
+ baseAttributes <> case Map.lookup marker referenceAnchors of
+ Just anchor ->
+ [ makeAttributes
+ "data-preview-link"
+ (renderUrlFragment anchor)
+ , makeAttributes "data-preview-target-id" anchor
+ ]
+ Nothing ->
+ case Map.lookup marker referencePreviews of
+ Nothing ->
+ []
+ Just preview ->
+ [ makeAttributes
+ "data-preview-link"
+ (previewReferenceHref preview)
+ , makeAttributes "data-preview-id" (previewId preview)
+ ]
+
+renderMarkerReference :: ReferenceContext -> Marker -> Html ()
+renderMarkerReference ReferenceContext{..} marker =
+ case Map.lookup marker referenceAnchors of
+ Just anchor ->
+ a_
+ ( href_ (renderUrlFragment anchor)
+ : referenceAttributes
+ (Just (currentPreviewAttributes anchor))
+ )
+ (toHtml label)
+ Nothing ->
+ case Map.lookup marker referencePreviews of
+ Nothing ->
+ span_ (referenceAttributes Nothing) (toHtml label)
+ Just preview ->
+ a_
+ ( href_ (previewReferenceHref preview)
+ : referenceAttributes (Just (importedPreviewAttributes preview))
+ )
+ (toHtml label)
+ where
+ label = markerText marker
+
+ referenceAttributes preview =
+ [ class_ (if hasPreview preview then "ref-badge has-preview" else "ref-badge")
+ , makeAttributes "data-reference-label" label
+ ]
+ <> foldMap id preview
+
+ hasPreview =
+ \case
+ Nothing -> False
+ Just _ -> True
+
+ currentPreviewAttributes anchor =
+ [ makeAttributes "data-preview-target-id" anchor
+ , makeAttributes "aria-describedby" "reference-preview-popup"
+ ]
+
+ importedPreviewAttributes entry =
+ [ makeAttributes "data-preview-id" (previewId entry)
+ , makeAttributes "aria-describedby" "reference-preview-popup"
+ ]
+
+
+markerText :: Marker -> Text
+markerText (Marker text) = text
+
+tokenTextHtml :: Token -> Html ()
+tokenTextHtml = toHtml . tokToText
+
+joinHtml :: Html () -> [Html ()] -> Html ()
+joinHtml _ [] = mempty
+joinHtml separator (x : xs) = x <> foldMap (separator <>) xs
+
+miText :: Text -> Html ()
+miText = mi_ . toHtml
+
+moText :: Text -> Html ()
+moText = mo_ . toHtml
+
+mnText :: Text -> Html ()
+mnText = mn_ . toHtml
+
+mtextText :: Text -> Html ()
+mtextText = mtext_ . toHtml
diff --git a/source/Felix/Render/Html/Context.hs b/source/Felix/Render/Html/Context.hs
new file mode 100644
index 0000000..469d986
--- /dev/null
+++ b/source/Felix/Render/Html/Context.hs
@@ -0,0 +1,171 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Browser-facing routing authority for one rendered HTML page.
+module Felix.Render.Html.Context
+ ( HtmlRenderEnvironment
+ , htmlRenderEnvironment
+ , HtmlRenderContext
+ , HtmlRenderContextError(..)
+ , htmlRenderContext
+ , htmlRenderContextFromEnvironment
+ , htmlCurrentSource
+ , htmlCurrentPageUrl
+ , htmlCurrentPageLabel
+ , htmlRouteNamespaces
+ , htmlSourceUrl
+ , htmlSourceLabel
+ , htmlSourcePageHref
+ , htmlSourceFragmentHref
+ , htmlSupportScriptHref
+ ) where
+
+import Base
+import Felix.Source
+import Felix.Render.Html.Layout
+
+import Control.Exception (Exception)
+import Data.Map.Strict qualified as Map
+import Data.Text qualified as Text
+
+
+data HtmlRenderContextError
+ = HtmlCurrentSourceNotRouted !ResolvedSource
+ | HtmlReferencedSourceNotRouted !ResolvedSource
+ deriving stock (Show, Eq)
+
+instance Exception HtmlRenderContextError
+
+data HtmlRenderContext = HtmlRenderContext
+ { contextCurrentSource :: !ResolvedSource
+ , contextCurrentPageUrl :: !UrlPath
+ , contextEnvironment :: !HtmlRenderEnvironment
+ }
+ deriving stock (Show, Eq)
+
+data HtmlRenderEnvironment = HtmlRenderEnvironment
+ { environmentSourceUrls :: !(Map ResolvedSource UrlPath)
+ , environmentRouteNamespaces :: !(Map SourceMountId UrlPath)
+ , environmentSupportScriptUrl :: !UrlPath
+ }
+ deriving stock (Show, Eq)
+
+htmlRenderEnvironment :: HtmlLayout -> HtmlRenderEnvironment
+htmlRenderEnvironment layout =
+ HtmlRenderEnvironment
+ { environmentSourceUrls =
+ Map.fromList
+ [ (source, routeUrlPath route)
+ | (source, route) <- htmlPageRoutes layout
+ ]
+ , environmentRouteNamespaces =
+ htmlMountUrlPrefixes layout
+ , environmentSupportScriptUrl =
+ routeUrlPath (htmlSupportScriptRoute layout)
+ }
+
+htmlRenderContext
+ :: HtmlLayout
+ -> ResolvedSource
+ -> Either HtmlRenderContextError HtmlRenderContext
+htmlRenderContext layout =
+ htmlRenderContextFromEnvironment
+ (htmlRenderEnvironment layout)
+
+htmlRenderContextFromEnvironment
+ :: HtmlRenderEnvironment
+ -> ResolvedSource
+ -> Either HtmlRenderContextError HtmlRenderContext
+htmlRenderContextFromEnvironment environment currentSource = do
+ currentPageUrl <-
+ maybe
+ (Left (HtmlCurrentSourceNotRouted currentSource))
+ Right
+ (Map.lookup
+ currentSource
+ (environmentSourceUrls environment))
+ Right
+ HtmlRenderContext
+ { contextCurrentSource = currentSource
+ , contextCurrentPageUrl = currentPageUrl
+ , contextEnvironment = environment
+ }
+
+htmlCurrentSource :: HtmlRenderContext -> ResolvedSource
+htmlCurrentSource =
+ contextCurrentSource
+
+htmlCurrentPageUrl :: HtmlRenderContext -> UrlPath
+htmlCurrentPageUrl =
+ contextCurrentPageUrl
+
+htmlCurrentPageLabel :: HtmlRenderContext -> Text
+htmlCurrentPageLabel context =
+ resolvedSourceLabel (contextCurrentSource context)
+
+htmlRouteNamespaces
+ :: HtmlRenderContext
+ -> Map SourceMountId UrlPath
+htmlRouteNamespaces =
+ environmentRouteNamespaces . contextEnvironment
+
+htmlSourceUrl
+ :: HtmlRenderContext
+ -> ResolvedSource
+ -> Either HtmlRenderContextError UrlPath
+htmlSourceUrl HtmlRenderContext{contextEnvironment} source =
+ maybe
+ (Left (HtmlReferencedSourceNotRouted source))
+ Right
+ (Map.lookup source (environmentSourceUrls contextEnvironment))
+
+htmlSourceLabel
+ :: HtmlRenderContext
+ -> ResolvedSource
+ -> Either HtmlRenderContextError Text
+htmlSourceLabel context source = do
+ _url <- htmlSourceUrl context source
+ Right (resolvedSourceLabel source)
+
+htmlSourcePageHref
+ :: HtmlRenderContext
+ -> ResolvedSource
+ -> Either HtmlRenderContextError Text
+htmlSourcePageHref context source =
+ renderRelativeUrlPath
+ (contextCurrentPageUrl context)
+ <$> htmlSourceUrl context source
+
+htmlSourceFragmentHref
+ :: HtmlRenderContext
+ -> ResolvedSource
+ -> Text
+ -> Either HtmlRenderContextError Text
+htmlSourceFragmentHref context source fragment = do
+ target <- htmlSourceUrl context source
+ let encodedFragment =
+ renderUrlFragment fragment
+ Right
+ (if target == contextCurrentPageUrl context
+ then encodedFragment
+ else
+ renderRelativeUrlPath
+ (contextCurrentPageUrl context)
+ target
+ <> encodedFragment)
+
+htmlSupportScriptHref :: HtmlRenderContext -> Text
+htmlSupportScriptHref context =
+ renderRelativeUrlPath
+ (contextCurrentPageUrl context)
+ (environmentSupportScriptUrl
+ (contextEnvironment context))
+
+resolvedSourceLabel :: ResolvedSource -> Text
+resolvedSourceLabel source =
+ sourceMountIdText (resolvedSourceMount source)
+ <> ":"
+ <> Text.pack
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source))
diff --git a/source/Felix/Render/Html/Export.hs b/source/Felix/Render/Html/Export.hs
new file mode 100644
index 0000000..a3f0e39
--- /dev/null
+++ b/source/Felix/Render/Html/Export.hs
@@ -0,0 +1,277 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Prepare a complete HTML export from retained parsed presentation.
+module Felix.Render.Html.Export
+ ( HtmlPresentation
+ , htmlPresentationFromParsedWorkspace
+ , HtmlExportError(..)
+ , renderHtmlExportError
+ , prepareHtmlExport
+ , prepareHtmlExportWithLayout
+ , prepareHtmlExportWithLayoutFromRendererRoots
+ ) where
+
+import Base
+import Felix.Parse
+import Felix.Source
+import Felix.Render.Html qualified as Html
+import Felix.Render.Html.Context
+import Felix.Render.Html.Layout
+import Felix.Render.Html.Output
+import Felix.Syntax.Abstract (Block)
+
+import Control.Exception (Exception, IOException, displayException)
+import Control.Exception qualified as Exception
+import Data.Bifunctor (first)
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Text qualified as Text
+import Data.Text.Encoding qualified as TextEncoding
+import Data.Text.Encoding.Error (UnicodeException)
+import Data.Text.IO qualified as TextIO
+import System.Directory (doesFileExist)
+import System.FilePath.Posix ((</>))
+
+
+-- | The strict, invocation-local subset of parsed presentation needed by the
+-- renderer. Construction forces the complete module sequence and every page
+-- shell, severing references to parser payloads, syntax interfaces, and
+-- canonical cache payloads.
+data HtmlPresentation = HtmlPresentation
+ !(NonEmpty HtmlSourcePresentation)
+
+data HtmlSourcePresentation = HtmlSourcePresentation
+ !ResolvedSource
+ ![Block]
+
+htmlPresentationFromParsedWorkspace
+ :: ParsedSourceWorkspace
+ -> HtmlPresentation
+htmlPresentationFromParsedWorkspace workspace =
+ HtmlPresentation
+ (strictMapNonEmpty project parsedModules)
+ where
+ parsedModules =
+ parsedWorkspaceImportedBeforeImporter workspace
+ project parsedModule =
+ HtmlSourcePresentation
+ (parsedModuleResolved parsedModule)
+ (parsedModuleBlocks parsedModule)
+
+strictMapNonEmpty :: (a -> b) -> NonEmpty a -> NonEmpty b
+strictMapNonEmpty f (value :| values) =
+ let !firstPage = f value
+ !rest = strictMapList f values
+ in firstPage :| rest
+
+strictMapList :: (a -> b) -> [a] -> [b]
+strictMapList _ [] =
+ []
+strictMapList f (value : values) =
+ let !next = f value
+ !rest = strictMapList f values
+ in next : rest
+
+data HtmlExportError
+ = HtmlRendererDataNotFound !FilePath ![FilePath]
+ | HtmlRendererDataLookupFailed !FilePath !Text
+ | HtmlRendererDataReadFailed !FilePath !Text
+ | HtmlExportLayoutError !HtmlLayoutError
+ | HtmlExportContextError !HtmlRenderContextError
+ deriving stock (Show)
+
+instance Exception HtmlExportError
+
+renderHtmlExportError :: HtmlExportError -> Text
+renderHtmlExportError = \case
+ HtmlRendererDataNotFound requested searched ->
+ "renderer data " <> quotePath requested <> " was not found; searched "
+ <> Text.intercalate ", " (quotePath <$> searched)
+ HtmlRendererDataLookupFailed path reason ->
+ "could not locate renderer data " <> quotePath path <> ": " <> reason
+ HtmlRendererDataReadFailed path reason ->
+ "could not read renderer data " <> quotePath path <> ": " <> reason
+ HtmlExportLayoutError failure ->
+ renderHtmlLayoutError failure
+ HtmlExportContextError failure ->
+ case failure of
+ HtmlCurrentSourceNotRouted source ->
+ "current source has no HTML route: " <> sourceLabel source
+ HtmlReferencedSourceNotRouted source ->
+ "referenced source has no HTML route: " <> sourceLabel source
+ where
+ sourceLabel source =
+ sourceMountIdText (resolvedSourceMount source)
+ <> ":"
+ <> Text.pack
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source))
+
+ quotePath = Text.pack . show
+
+prepareHtmlExport
+ :: [(SourceMountId, [Text])]
+ -> HtmlPresentation
+ -> Text
+ -> Either HtmlExportError [PreparedHtmlArtifact]
+prepareHtmlExport
+ mountPrefixes
+ (HtmlPresentation presentation)
+ hints = do
+ let sources =
+ sourceOf <$> NonEmpty.toList presentation
+ layout <-
+ first
+ HtmlExportLayoutError
+ (layoutHtmlSources mountPrefixes sources)
+ prepareRenderedExport hints layout presentation
+ where
+ sourceOf (HtmlSourcePresentation source _blocks) =
+ source
+
+prepareHtmlExportWithLayout
+ :: HtmlLayout
+ -> HtmlPresentation
+ -> Text
+ -> Either HtmlExportError [PreparedHtmlArtifact]
+prepareHtmlExportWithLayout
+ layout
+ (HtmlPresentation presentation)
+ hints =
+ prepareRenderedExport hints layout presentation
+
+prepareHtmlExportWithLayoutFromRendererRoots
+ :: [FilePath]
+ -> HtmlLayout
+ -> HtmlPresentation
+ -> IO (Either HtmlExportError [PreparedHtmlArtifact])
+prepareHtmlExportWithLayoutFromRendererRoots roots layout presentation = do
+ hintsResult <- findAndReadRendererFile roots "lexicon.tsv"
+ pure do
+ hints <- hintsResult
+ prepareHtmlExportWithLayout layout presentation hints
+
+-- Renderer data follows the established current-directory,
+-- configured-library, and debug-directory lookup policy.
+findAndReadRendererFile
+ :: [FilePath]
+ -> FilePath
+ -> IO (Either HtmlExportError Text)
+findAndReadRendererFile roots path =
+ selectRendererData path ((</> path) <$> roots) >>= \case
+ Left failure -> pure (Left failure)
+ Right selectedPath -> do
+ readResult <- tryRendererRead (TextIO.readFile selectedPath)
+ pure case readResult of
+ Left reason ->
+ Left (HtmlRendererDataReadFailed selectedPath reason)
+ Right contents -> Right contents
+
+selectRendererData
+ :: FilePath
+ -> [FilePath]
+ -> IO (Either HtmlExportError FilePath)
+selectRendererData requested candidates = go candidates
+ where
+ go = \case
+ [] -> pure (Left (HtmlRendererDataNotFound requested candidates))
+ candidate : remaining ->
+ tryRendererIO (doesFileExist candidate) >>= \case
+ Left failure ->
+ pure
+ (Left
+ (HtmlRendererDataLookupFailed
+ candidate
+ (Text.pack (displayException failure))))
+ Right True -> pure (Right candidate)
+ Right False -> go remaining
+
+tryRendererIO :: IO value -> IO (Either IOException value)
+tryRendererIO = Exception.try
+
+tryRendererRead :: IO value -> IO (Either Text value)
+tryRendererRead action =
+ Exception.catch
+ (Exception.catch (Right <$> action) renderIOException)
+ renderUnicodeException
+ where
+ renderIOException :: IOException -> IO (Either Text value)
+ renderIOException = pure . Left . Text.pack . displayException
+
+ renderUnicodeException
+ :: UnicodeException
+ -> IO (Either Text value)
+ renderUnicodeException = pure . Left . Text.pack . displayException
+
+prepareRenderedExport
+ :: Text
+ -> HtmlLayout
+ -> NonEmpty HtmlSourcePresentation
+ -> Either HtmlExportError [PreparedHtmlArtifact]
+prepareRenderedExport hints layout presentation = do
+ let sourceBlocks =
+ (\(HtmlSourcePresentation source blocks) ->
+ (source, blocks))
+ <$> presentation
+ (unforcedRenderIndex, pages) =
+ Html.buildRenderIndex sourceBlocks
+ !renderIndex = unforcedRenderIndex
+ renderEnvironment =
+ htmlRenderEnvironment layout
+ pageArtifacts <-
+ traverse
+ (prepareSourceArtifact
+ renderEnvironment
+ layout
+ hints
+ renderIndex)
+ pages
+ let supportRoute =
+ htmlSupportScriptRoute layout
+ supportArtifact =
+ preparedHtmlArtifact
+ (routeDestination supportRoute)
+ (Right
+ (TextEncoding.encodeUtf8
+ Html.supportScriptAssetContents))
+ -- Page artifacts retain imported-before-importer source order. The
+ -- singleton support asset is published deterministically afterward.
+ Right (NonEmpty.toList pageArtifacts <> [supportArtifact])
+
+prepareSourceArtifact
+ :: HtmlRenderEnvironment
+ -> HtmlLayout
+ -> Text
+ -> Html.HtmlRenderIndex
+ -> Html.HtmlPagePresentation
+ -> Either
+ HtmlExportError
+ PreparedHtmlArtifact
+prepareSourceArtifact renderEnvironment layout hints renderIndex page = do
+ let source = Html.htmlPagePresentationSource page
+ context <-
+ first
+ HtmlExportContextError
+ (htmlRenderContextFromEnvironment
+ renderEnvironment
+ source)
+ route <-
+ maybe
+ (Left
+ (HtmlExportContextError
+ (HtmlCurrentSourceNotRouted source)))
+ Right
+ (htmlPageRoute layout source)
+ Right
+ (preparedHtmlArtifact
+ (routeDestination route)
+ (first renderHtmlExportError
+ (TextEncoding.encodeUtf8
+ <$> first
+ HtmlExportContextError
+ (Html.renderDocument
+ context
+ hints
+ renderIndex
+ page))))
diff --git a/source/Felix/Render/Html/Layout.hs b/source/Felix/Render/Html/Layout.hs
new file mode 100644
index 0000000..7e5bfcf
--- /dev/null
+++ b/source/Felix/Render/Html/Layout.hs
@@ -0,0 +1,602 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Pure browser and destination routing for a resolved source graph.
+module Felix.Render.Html.Layout
+ ( UrlSegment
+ , UrlSegmentError(..)
+ , urlSegment
+ , renderUrlSegment
+ , UrlPath
+ , urlPath
+ , renderUrlPath
+ , renderRelativeUrlPath
+ , renderUrlFragment
+ , HtmlRoute
+ , routeDestination
+ , routeUrlPath
+ , HtmlRouteOwner(..)
+ , HtmlUrlRouteCollision(..)
+ , HtmlDestinationRouteCollision(..)
+ , HtmlLayoutError(..)
+ , renderHtmlLayoutError
+ , HtmlLayout
+ , htmlPageRoutes
+ , htmlPageRoute
+ , htmlSupportScriptRoute
+ , htmlMountUrlPrefixes
+ , layoutHtmlSources
+ , layoutHtmlSourceGraph
+ ) where
+
+import Base
+import Felix.Source
+import Felix.Source.Graph
+
+import Control.Exception (Exception)
+import Data.Bifunctor (first)
+import Data.ByteString qualified as ByteString
+import Data.Char (chr)
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+import Data.Text.Encoding qualified as TextEncoding
+import Data.Word (Word8)
+import System.FilePath.Posix qualified as Posix
+
+
+-- | One canonical percent-encoded URL path segment.
+newtype UrlSegment = UrlSegment Text
+ deriving stock (Show, Eq, Ord)
+
+data UrlSegmentError
+ = EmptyUrlSegment
+ | DotUrlSegment !Text
+ | UrlSegmentContainsSeparator !Text
+ | UrlSegmentContainsNull !Text
+ deriving stock (Show, Eq)
+
+urlSegment :: Text -> Either UrlSegmentError UrlSegment
+urlSegment decoded
+ | Text.null decoded =
+ Left EmptyUrlSegment
+ | decoded == "." || decoded == ".." =
+ Left (DotUrlSegment decoded)
+ | "/" `Text.isInfixOf` decoded =
+ Left (UrlSegmentContainsSeparator decoded)
+ | "\0" `Text.isInfixOf` decoded =
+ Left (UrlSegmentContainsNull decoded)
+ | otherwise =
+ Right (UrlSegment (percentEncodeUtf8 decoded))
+
+renderUrlSegment :: UrlSegment -> Text
+renderUrlSegment (UrlSegment encoded) =
+ encoded
+
+
+-- | A root-relative URL path. Its segments are already encoded.
+newtype UrlPath = UrlPath [UrlSegment]
+ deriving stock (Show, Eq, Ord)
+
+urlPath :: [Text] -> Either UrlSegmentError UrlPath
+urlPath =
+ fmap UrlPath . traverse urlSegment
+
+renderUrlPath :: UrlPath -> Text
+renderUrlPath (UrlPath segments) =
+ "/" <> Text.intercalate "/" (renderUrlSegment <$> segments)
+
+-- | Render a target path relative to the directory of a current page.
+renderRelativeUrlPath :: UrlPath -> UrlPath -> Text
+renderRelativeUrlPath
+ (UrlPath currentPageSegments)
+ (UrlPath targetSegments) =
+ case relativeSegments of
+ [] ->
+ "."
+ _ ->
+ Text.intercalate "/" relativeSegments
+ where
+ currentDirectorySegments =
+ case reverse currentPageSegments of
+ [] ->
+ []
+ _page : directoryReversed ->
+ reverse directoryReversed
+ (remainingCurrent, remainingTarget) =
+ dropCommonPrefix currentDirectorySegments targetSegments
+ relativeSegments =
+ replicate (length remainingCurrent) ".."
+ <> (renderUrlSegment <$> remainingTarget)
+
+-- | Render an exact source marker as an encoded URL fragment.
+renderUrlFragment :: Text -> Text
+renderUrlFragment marker =
+ "#" <> percentEncodeUtf8 marker
+
+
+data HtmlRoute = HtmlRoute
+ { routeDestination :: !SafeRelativePath
+ , routeUrlPath :: !UrlPath
+ }
+ deriving stock (Show, Eq)
+
+data HtmlRouteOwner
+ = HtmlPage !ResolvedSource
+ | HtmlSupportScript
+ deriving stock (Show, Eq, Ord)
+
+data HtmlUrlRouteCollision = HtmlUrlRouteCollision
+ !UrlPath
+ !(NonEmpty HtmlRouteOwner)
+ deriving stock (Show, Eq)
+
+data HtmlDestinationRouteCollision = HtmlDestinationRouteCollision
+ !SafeRelativePath
+ !(NonEmpty HtmlRouteOwner)
+ | NestedHtmlDestinationRouteCollision
+ !SafeRelativePath
+ !HtmlRouteOwner
+ !SafeRelativePath
+ !HtmlRouteOwner
+ deriving stock (Show, Eq)
+
+data HtmlLayoutError
+ = DuplicateHtmlMountId !SourceMountId
+ | InvalidHtmlMountPrefixSegment
+ !SourceMountId
+ !Text
+ !UrlSegmentError
+ | DuplicateHtmlMountPrefix
+ ![Text]
+ !(NonEmpty SourceMountId)
+ | MissingHtmlMountPrefix !SourceMountId
+ | InvalidHtmlRouteSegment
+ !HtmlRouteOwner
+ !Text
+ !UrlSegmentError
+ | InvalidHtmlRouteDestination
+ !HtmlRouteOwner
+ !FilePath
+ !RelativePathError
+ | CollidingHtmlRoutes
+ ![HtmlUrlRouteCollision]
+ ![HtmlDestinationRouteCollision]
+ deriving stock (Show, Eq)
+
+instance Exception HtmlLayoutError
+
+renderHtmlLayoutError :: HtmlLayoutError -> Text
+renderHtmlLayoutError = \case
+ DuplicateHtmlMountId mount ->
+ "HTML mount is configured more than once: "
+ <> quoteText (sourceMountIdText mount)
+ InvalidHtmlMountPrefixSegment mount segment _problem ->
+ "HTML mount " <> quoteText (sourceMountIdText mount)
+ <> " has invalid route segment " <> quoteText segment
+ DuplicateHtmlMountPrefix prefix mounts ->
+ "HTML route prefix " <> quoteText (Text.intercalate "/" prefix)
+ <> " is shared by mounts "
+ <> Text.intercalate ", "
+ (quoteText . sourceMountIdText <$> toList mounts)
+ MissingHtmlMountPrefix mount ->
+ "no HTML route prefix is configured for mount "
+ <> quoteText (sourceMountIdText mount)
+ InvalidHtmlRouteSegment owner segment _problem ->
+ renderOwner owner <> " has invalid route segment " <> quoteText segment
+ InvalidHtmlRouteDestination owner path _problem ->
+ renderOwner owner <> " has invalid HTML destination " <> quotePath path
+ CollidingHtmlRoutes urlCollisions destinationCollisions ->
+ "HTML routes collide: "
+ <> Text.intercalate "; "
+ ( (renderUrlCollision <$> urlCollisions)
+ <> (renderDestinationCollision <$> destinationCollisions)
+ )
+ where
+ renderUrlCollision (HtmlUrlRouteCollision path owners) =
+ "URL " <> quoteText (renderUrlPath path)
+ <> " is owned by "
+ <> Text.intercalate ", " (renderOwner <$> toList owners)
+
+ renderDestinationCollision
+ (HtmlDestinationRouteCollision path owners) =
+ "destination "
+ <> quotePath (safeRelativePathFilePath path)
+ <> " is owned by "
+ <> Text.intercalate ", " (renderOwner <$> toList owners)
+ renderDestinationCollision
+ (NestedHtmlDestinationRouteCollision
+ ancestor ancestorOwner descendant descendantOwner) =
+ "destination " <> quotePath (safeRelativePathFilePath ancestor)
+ <> " for " <> renderOwner ancestorOwner
+ <> " is an ancestor of "
+ <> quotePath (safeRelativePathFilePath descendant)
+ <> " for " <> renderOwner descendantOwner
+
+renderOwner :: HtmlRouteOwner -> Text
+renderOwner = \case
+ HtmlPage source ->
+ "page "
+ <> quoteText
+ (sourceMountIdText (resolvedSourceMount source)
+ <> ":"
+ <> Text.pack
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source)))
+ HtmlSupportScript ->
+ "support script"
+
+quotePath :: FilePath -> Text
+quotePath = Text.pack . show
+
+quoteText :: Text -> Text
+quoteText = Text.pack . show
+
+data HtmlLayout = HtmlLayout
+ !(Map ResolvedSource HtmlRoute)
+ !HtmlRoute
+ !(Map SourceMountId UrlPath)
+ deriving stock (Show, Eq)
+
+htmlPageRoutes :: HtmlLayout -> [(ResolvedSource, HtmlRoute)]
+htmlPageRoutes (HtmlLayout routes _supportScript _mountPrefixes) =
+ Map.toAscList routes
+
+htmlPageRoute :: HtmlLayout -> ResolvedSource -> Maybe HtmlRoute
+htmlPageRoute (HtmlLayout routes _supportScript _mountPrefixes) source =
+ Map.lookup source routes
+
+htmlSupportScriptRoute :: HtmlLayout -> HtmlRoute
+htmlSupportScriptRoute (HtmlLayout _routes supportScript _mountPrefixes) =
+ supportScript
+
+htmlMountUrlPrefixes :: HtmlLayout -> Map SourceMountId UrlPath
+htmlMountUrlPrefixes (HtmlLayout _routes _supportScript mountPrefixes) =
+ mountPrefixes
+
+
+data ValidatedMountPrefix = ValidatedMountPrefix
+ ![Text]
+ ![UrlSegment]
+
+layoutHtmlSourceGraph
+ :: [(SourceMountId, [Text])]
+ -> ResolvedSourceGraph
+ -> Either HtmlLayoutError HtmlLayout
+layoutHtmlSourceGraph specifications graph =
+ layoutHtmlSources
+ specifications
+ (sourceNodeResolved <$> sourceGraphNodes graph)
+
+layoutHtmlSources
+ :: [(SourceMountId, [Text])]
+ -> [ResolvedSource]
+ -> Either HtmlLayoutError HtmlLayout
+layoutHtmlSources specifications inputSources = do
+ prefixes <- validateMountPrefixes specifications
+ let sources =
+ List.sort
+ inputSources
+ usedMounts =
+ Set.fromList (resolvedSourceMount <$> sources)
+ missingMounts =
+ usedMounts `Set.difference` Map.keysSet prefixes
+ case Set.lookupMin missingMounts of
+ Just missing ->
+ Left (MissingHtmlMountPrefix missing)
+ Nothing -> do
+ pageEntries <-
+ traverse
+ (makePageRoute prefixes)
+ sources
+ encodedSupportScript <-
+ encodeRouteSegments
+ HtmlSupportScript
+ supportScriptAssetComponents
+ supportScript <-
+ makeRoute
+ HtmlSupportScript
+ supportScriptAssetComponents
+ encodedSupportScript
+ let ownedRoutes =
+ (HtmlSupportScript, supportScript)
+ : [ (HtmlPage source, route)
+ | (source, route) <- pageEntries
+ ]
+ urlCollisions =
+ collectUrlCollisions ownedRoutes
+ destinationCollisions =
+ collectDestinationCollisions ownedRoutes
+ <> collectNestedDestinationCollisions ownedRoutes
+ if null urlCollisions && null destinationCollisions
+ then
+ Right
+ (HtmlLayout
+ (Map.fromList pageEntries)
+ supportScript
+ (Map.map
+ (\(ValidatedMountPrefix _decoded encoded) ->
+ UrlPath encoded)
+ prefixes))
+ else
+ Left
+ (CollidingHtmlRoutes
+ urlCollisions
+ destinationCollisions)
+
+validateMountPrefixes
+ :: [(SourceMountId, [Text])]
+ -> Either HtmlLayoutError (Map SourceMountId ValidatedMountPrefix)
+validateMountPrefixes specifications =
+ case duplicateValues (fst <$> specifications) of
+ duplicate : _ ->
+ Left (DuplicateHtmlMountId duplicate)
+ [] -> do
+ validated <- traverse validatePrefix (List.sort specifications)
+ case duplicatePrefixGroups validated of
+ duplicate : _ ->
+ Left duplicate
+ [] ->
+ Right
+ (Map.fromList
+ [ (mount, prefix)
+ | (mount, _decoded, prefix) <- validated
+ ])
+ where
+ validatePrefix (mount, decoded) = do
+ encoded <- traverse
+ (\segment ->
+ first
+ (InvalidHtmlMountPrefixSegment mount segment)
+ (urlSegment segment))
+ decoded
+ Right
+ ( mount
+ , decoded
+ , ValidatedMountPrefix decoded encoded
+ )
+
+duplicatePrefixGroups
+ :: [(SourceMountId, [Text], ValidatedMountPrefix)]
+ -> [HtmlLayoutError]
+duplicatePrefixGroups validated =
+ [ DuplicateHtmlMountPrefix prefix (firstMount :| otherMounts)
+ | (prefix, mounts) <-
+ Map.toAscList
+ (Map.fromListWith (<>)
+ [ (decoded, [mount])
+ | (mount, decoded, _prefix) <- validated
+ ])
+ , firstMount : secondMount : remainingMounts <-
+ [List.sort mounts]
+ , let otherMounts = secondMount : remainingMounts
+ ]
+
+duplicateValues :: Ord a => [a] -> [a]
+duplicateValues values =
+ [ value
+ | (value, multiplicity) <-
+ Map.toAscList
+ (Map.fromListWith (+)
+ [(value, 1 :: Int) | value <- values])
+ , multiplicity > 1
+ ]
+
+makePageRoute
+ :: Map SourceMountId ValidatedMountPrefix
+ -> ResolvedSource
+ -> Either HtmlLayoutError (ResolvedSource, HtmlRoute)
+makePageRoute prefixes source = do
+ prefix <- case Map.lookup (resolvedSourceMount source) prefixes of
+ Nothing ->
+ Left
+ (MissingHtmlMountPrefix
+ (resolvedSourceMount source))
+ Just found ->
+ Right found
+ let sourceComponents =
+ Text.splitOn
+ "/"
+ (Text.pack
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source)))
+ destinationComponents =
+ replaceFinalComponent
+ (\component ->
+ dropFinalExtension component <> ".html")
+ sourceComponents
+ urlComponents =
+ replaceFinalComponent
+ dropFinalExtension
+ sourceComponents
+ ValidatedMountPrefix decodedPrefix encodedPrefix =
+ prefix
+ owner = HtmlPage source
+ encodedPageComponents <-
+ encodeRouteSegments owner urlComponents
+ route <-
+ makeRoute
+ owner
+ (decodedPrefix <> destinationComponents)
+ (encodedPrefix <> encodedPageComponents)
+ Right (source, route)
+
+replaceFinalComponent :: (a -> a) -> [a] -> [a]
+replaceFinalComponent transform components =
+ case reverse components of
+ [] ->
+ []
+ final : precedingReversed ->
+ reverse precedingReversed <> [transform final]
+
+dropFinalExtension :: Text -> Text
+dropFinalExtension component =
+ case Text.breakOnEnd "." component of
+ ("", _suffix) ->
+ component
+ (".", _suffix) ->
+ component
+ (prefix, _suffix) ->
+ Text.dropEnd 1 prefix
+
+makeRoute
+ :: HtmlRouteOwner
+ -> [Text]
+ -> [UrlSegment]
+ -> Either HtmlLayoutError HtmlRoute
+makeRoute owner destinationComponents encodedUrlComponents = do
+ let destinationSpelling =
+ List.intercalate
+ "/"
+ (Text.unpack <$> destinationComponents)
+ destination <-
+ first
+ (InvalidHtmlRouteDestination
+ owner
+ destinationSpelling)
+ (safeRelativePath destinationSpelling)
+ Right
+ HtmlRoute
+ { routeDestination = destination
+ , routeUrlPath = UrlPath encodedUrlComponents
+ }
+
+encodeRouteSegments
+ :: HtmlRouteOwner
+ -> [Text]
+ -> Either HtmlLayoutError [UrlSegment]
+encodeRouteSegments owner =
+ traverse
+ (\decoded ->
+ first
+ (InvalidHtmlRouteSegment owner decoded)
+ (urlSegment decoded))
+
+collectUrlCollisions
+ :: [(HtmlRouteOwner, HtmlRoute)]
+ -> [HtmlUrlRouteCollision]
+collectUrlCollisions ownedRoutes =
+ [ HtmlUrlRouteCollision path owners
+ | (path, collidingOwners) <-
+ Map.toAscList
+ (Map.fromListWith (<>)
+ [ (routeUrlPath route, [owner])
+ | (owner, route) <- ownedRoutes
+ ])
+ , owners <-
+ collisionOwners collidingOwners
+ ]
+
+collectDestinationCollisions
+ :: [(HtmlRouteOwner, HtmlRoute)]
+ -> [HtmlDestinationRouteCollision]
+collectDestinationCollisions ownedRoutes =
+ [ HtmlDestinationRouteCollision destination owners
+ | (destination, collidingOwners) <-
+ Map.toAscList
+ (Map.fromListWith (<>)
+ [ (routeDestination route, [owner])
+ | (owner, route) <- ownedRoutes
+ ])
+ , owners <-
+ collisionOwners collidingOwners
+ ]
+
+collectNestedDestinationCollisions
+ :: [(HtmlRouteOwner, HtmlRoute)]
+ -> [HtmlDestinationRouteCollision]
+collectNestedDestinationCollisions ownedRoutes =
+ take 1
+ [ NestedHtmlDestinationRouteCollision
+ ancestor
+ ancestorOwner
+ descendant
+ descendantOwner
+ | ( (ancestorComponents, ancestor, ancestorOwner)
+ , (descendantComponents, descendant, descendantOwner)
+ ) <- zip destinations (drop 1 destinations)
+ , strictComponentPrefix ancestorComponents descendantComponents
+ ]
+ where
+ destinations =
+ List.sort
+ [ ( relativePathComponents destination
+ , destination
+ , owner
+ )
+ | (owner, route) <- ownedRoutes
+ , let destination = routeDestination route
+ ]
+
+relativePathComponents :: SafeRelativePath -> [FilePath]
+relativePathComponents =
+ Posix.splitDirectories . safeRelativePathFilePath
+
+strictComponentPrefix :: [FilePath] -> [FilePath] -> Bool
+strictComponentPrefix possibleAncestor possibleDescendant =
+ length possibleAncestor < length possibleDescendant
+ && possibleAncestor `List.isPrefixOf` possibleDescendant
+
+collisionOwners :: [HtmlRouteOwner] -> [NonEmpty HtmlRouteOwner]
+collisionOwners owners =
+ case List.sort owners of
+ firstOwner : secondOwner : rest ->
+ [firstOwner :| (secondOwner : rest)]
+ _ ->
+ []
+
+dropCommonPrefix :: Eq a => [a] -> [a] -> ([a], [a])
+dropCommonPrefix (left : lefts) (right : rights)
+ | left == right =
+ dropCommonPrefix lefts rights
+dropCommonPrefix left right =
+ (left, right)
+
+
+supportScriptAssetComponents :: [Text]
+supportScriptAssetComponents =
+ ["_static", "naproche-html.js"]
+
+percentEncodeUtf8 :: Text -> Text
+percentEncodeUtf8 =
+ Text.pack
+ . concatMap encodeByte
+ . ByteString.unpack
+ . TextEncoding.encodeUtf8
+
+encodeByte :: Word8 -> String
+encodeByte byte
+ | isUnreservedAscii byte =
+ [chr (fromIntegral byte)]
+ | otherwise =
+ [ '%'
+ , hexadecimalDigit (byte `div` 16)
+ , hexadecimalDigit (byte `mod` 16)
+ ]
+
+isUnreservedAscii :: Word8 -> Bool
+isUnreservedAscii byte =
+ isAsciiUpper byte
+ || isAsciiLower byte
+ || isAsciiDigit byte
+ || byte `elem` fmap (fromIntegral . fromEnum) ("-._~" :: String)
+
+isAsciiUpper :: Word8 -> Bool
+isAsciiUpper byte =
+ byte >= 65 && byte <= 90
+
+isAsciiLower :: Word8 -> Bool
+isAsciiLower byte =
+ byte >= 97 && byte <= 122
+
+isAsciiDigit :: Word8 -> Bool
+isAsciiDigit byte =
+ byte >= 48 && byte <= 57
+
+hexadecimalDigit :: Word8 -> Char
+hexadecimalDigit value
+ | value < 10 =
+ chr (fromIntegral value + fromEnum '0')
+ | otherwise =
+ chr (fromIntegral value - 10 + fromEnum 'A')
diff --git a/source/Felix/Render/Html/Output.hs b/source/Felix/Render/Html/Output.hs
new file mode 100644
index 0000000..8e36f97
--- /dev/null
+++ b/source/Felix/Render/Html/Output.hs
@@ -0,0 +1,471 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Confined filesystem authority for HTML output.
+--
+-- The output root is assumed to be user-owned and not concurrently changed by
+-- a hostile actor between planning and writing. Existing parent symlinks are
+-- accepted only when they resolve inside the canonical root. Final-target
+-- symlinks are rejected without following them; regular generated files may be
+-- replaced. This policy prevents stable-tree escapes, not TOCTOU attacks.
+module Felix.Render.Html.Output
+ ( PreparedHtmlArtifact
+ , preparedHtmlArtifact
+ , preparedHtmlArtifactDestination
+ , HtmlRoutePlan
+ , htmlRoutePlanDestinations
+ , planHtmlRoutes
+ , HtmlOutputPlan
+ , HtmlOutputError(..)
+ , renderHtmlOutputError
+ , planHtmlOutput
+ , planHtmlOutputAgainst
+ , HtmlPublicationError(..)
+ , renderHtmlPublicationError
+ , writeHtmlOutput
+ ) where
+
+import Base
+import Felix.Output.Atomic (writeBytesAtomically)
+import Felix.Source
+ ( SafeRelativePath
+ , safeRelativePathFilePath
+ )
+
+import Control.Exception (Exception, IOException, displayException)
+import Control.Exception qualified as Exception
+import Control.Monad (unless, when)
+import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE)
+import Data.ByteString (ByteString)
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+import System.Directory qualified as Directory
+import System.FilePath.Posix qualified as Posix
+import System.Posix.Files qualified as PosixFiles
+
+
+-- | One lazily rendered artifact. The destination is available for complete
+-- preflight without demanding the strict bytes or a renderer failure.
+data PreparedHtmlArtifact = PreparedHtmlArtifact
+ !SafeRelativePath
+ (Either Text ByteString)
+
+preparedHtmlArtifact
+ :: SafeRelativePath
+ -> Either Text ByteString
+ -> PreparedHtmlArtifact
+preparedHtmlArtifact =
+ PreparedHtmlArtifact
+
+preparedHtmlArtifactDestination
+ :: PreparedHtmlArtifact
+ -> SafeRelativePath
+preparedHtmlArtifactDestination
+ (PreparedHtmlArtifact destination _rendered) =
+ destination
+
+
+-- Constructors and absolute paths stay private to this module.
+newtype HtmlRoutePlan = HtmlRoutePlan
+ [(SafeRelativePath, FilePath)]
+
+htmlRoutePlanDestinations
+ :: HtmlRoutePlan
+ -> [(SafeRelativePath, FilePath)]
+htmlRoutePlanDestinations (HtmlRoutePlan routes) =
+ routes
+
+newtype HtmlOutputPlan = HtmlOutputPlan
+ [PlannedHtmlArtifact]
+
+data PlannedHtmlArtifact = PlannedHtmlArtifact
+ !SafeRelativePath
+ !FilePath
+ (Either Text ByteString)
+
+data HtmlOutputError
+ = EmptyPreparedHtmlOutput
+ | DuplicatePreparedHtmlDestination !SafeRelativePath
+ | HtmlOutputRouteMismatch
+ ![SafeRelativePath]
+ ![SafeRelativePath]
+ | EmptyHtmlOutputRoot
+ | HtmlOutputPathInspectionFailed !FilePath !Text
+ | HtmlOutputRootNotDirectory !FilePath
+ | HtmlOutputParentNotDirectory !FilePath
+ | HtmlOutputParentEscapesRoot !FilePath !FilePath
+ | HtmlOutputTargetIsSymbolicLink !FilePath
+ | HtmlOutputTargetNotRegularFile !FilePath
+ deriving stock (Show, Eq)
+
+renderHtmlOutputError :: HtmlOutputError -> Text
+renderHtmlOutputError = \case
+ EmptyPreparedHtmlOutput ->
+ "HTML output contains no artifacts"
+ DuplicatePreparedHtmlDestination relative ->
+ "HTML output contains destination more than once: "
+ <> quoteRelative relative
+ HtmlOutputRouteMismatch planned prepared ->
+ "prepared HTML destinations do not match the reserved routes; planned "
+ <> renderRelatives planned <> ", prepared " <> renderRelatives prepared
+ EmptyHtmlOutputRoot ->
+ "HTML output root is empty"
+ HtmlOutputPathInspectionFailed path reason ->
+ "could not inspect HTML output path " <> quotePath path <> ": " <> reason
+ HtmlOutputRootNotDirectory path ->
+ "HTML output root is not a directory: " <> quotePath path
+ HtmlOutputParentNotDirectory path ->
+ "HTML output parent is not a directory: " <> quotePath path
+ HtmlOutputParentEscapesRoot root parent ->
+ "HTML output parent " <> quotePath parent
+ <> " resolves outside root " <> quotePath root
+ HtmlOutputTargetIsSymbolicLink path ->
+ "HTML output target is a symbolic link: " <> quotePath path
+ HtmlOutputTargetNotRegularFile path ->
+ "HTML output target is not a regular file: " <> quotePath path
+ where
+ renderRelatives = Text.intercalate ", " . fmap quoteRelative
+
+quoteRelative :: SafeRelativePath -> Text
+quoteRelative = quotePath . safeRelativePathFilePath
+
+quotePath :: FilePath -> Text
+quotePath = Text.pack . show
+
+instance Exception HtmlOutputError
+
+-- | Validate every destination without changing the filesystem.
+planHtmlOutput
+ :: FilePath
+ -> [PreparedHtmlArtifact]
+ -> IO (Either HtmlOutputError HtmlOutputPlan)
+planHtmlOutput outputRoot artifacts = do
+ routes <- planHtmlRoutes
+ outputRoot
+ (preparedHtmlArtifactDestination <$> artifacts)
+ pure (routes >>= (`planHtmlOutputAgainst` artifacts))
+
+planHtmlRoutes
+ :: FilePath
+ -> [SafeRelativePath]
+ -> IO (Either HtmlOutputError HtmlRoutePlan)
+planHtmlRoutes outputRoot destinations =
+ runExceptT do
+ when (null destinations)
+ (throwE EmptyPreparedHtmlOutput)
+ case duplicateDestinations destinations of
+ duplicate : _ ->
+ throwE
+ (DuplicatePreparedHtmlDestination duplicate)
+ [] ->
+ pure ()
+ when (null outputRoot) (throwE EmptyHtmlOutputRoot)
+ absoluteRoot <-
+ inspectPath
+ outputRoot
+ (Directory.makeAbsolute outputRoot)
+ rootIsLink <- inspectSymbolicLink absoluteRoot
+ rootExists <-
+ inspectPath
+ absoluteRoot
+ (Directory.doesPathExist absoluteRoot)
+ rootIsDirectory <-
+ inspectPath
+ absoluteRoot
+ (Directory.doesDirectoryExist absoluteRoot)
+ when
+ ((rootIsLink || rootExists) && not rootIsDirectory)
+ (throwE (HtmlOutputRootNotDirectory absoluteRoot))
+ canonicalRoot <-
+ inspectPath
+ absoluteRoot
+ (Directory.canonicalizePath absoluteRoot)
+ planned <- for (List.sort destinations)
+ \relative -> do
+ let components =
+ Posix.splitDirectories
+ (safeRelativePathFilePath relative)
+ destination =
+ confinedDestination
+ absoluteRoot
+ components
+ preflightDestination
+ canonicalRoot
+ absoluteRoot
+ components
+ destination
+ pure
+ ( relative
+ , destination
+ )
+ pure (HtmlRoutePlan planned)
+
+planHtmlOutputAgainst
+ :: HtmlRoutePlan
+ -> [PreparedHtmlArtifact]
+ -> Either HtmlOutputError HtmlOutputPlan
+planHtmlOutputAgainst
+ (HtmlRoutePlan routes)
+ artifacts
+ | null artifacts =
+ Left EmptyPreparedHtmlOutput
+ | duplicate : _ <- duplicateDestinations preparedDestinations =
+ Left (DuplicatePreparedHtmlDestination duplicate)
+ | Set.fromList plannedDestinations
+ /= Set.fromList preparedDestinations =
+ Left
+ (HtmlOutputRouteMismatch
+ plannedDestinations
+ preparedDestinations)
+ | otherwise = HtmlOutputPlan <$> traverse attach artifacts
+ where
+ plannedDestinations = fst <$> routes
+ preparedDestinations =
+ preparedHtmlArtifactDestination <$> artifacts
+ routeDestinations = Map.fromList routes
+
+ attach (PreparedHtmlArtifact relative rendered) =
+ case Map.lookup relative routeDestinations of
+ Nothing ->
+ Left
+ (HtmlOutputRouteMismatch
+ plannedDestinations
+ preparedDestinations)
+ Just destination ->
+ Right
+ (PlannedHtmlArtifact
+ relative
+ destination
+ rendered)
+
+duplicateDestinations
+ :: [SafeRelativePath]
+ -> [SafeRelativePath]
+duplicateDestinations destinations =
+ [ destination
+ | (destination, multiplicity) <-
+ Map.toAscList
+ (Map.fromListWith (+)
+ [ (destination, 1 :: Int)
+ | destination <- destinations
+ ])
+ , multiplicity > 1
+ ]
+
+
+data HtmlPublicationError = IncompleteHtmlPublication
+ { committedHtmlDestinations :: ![SafeRelativePath]
+ , failedHtmlDestination :: !SafeRelativePath
+ , htmlPublicationFailure :: !Text
+ }
+ deriving stock (Show, Eq)
+
+instance Exception HtmlPublicationError
+
+renderHtmlPublicationError :: HtmlPublicationError -> [Text]
+renderHtmlPublicationError failure =
+ [ "HTML publication failed at "
+ <> quoteRelative (failedHtmlDestination failure)
+ <> ": " <> htmlPublicationFailure failure
+ ]
+ <> case committedHtmlDestinations failure of
+ [] -> []
+ committed ->
+ [ "HTML files published before the failure: "
+ <> Text.intercalate ", "
+ (quoteRelative <$> committed)
+ ]
+
+-- | Render, stage, and atomically replace each completely preflighted artifact
+-- in the supplied source order. No later artifact is rendered or staged
+-- before the preceding destination has been replaced.
+writeHtmlOutput
+ :: HtmlOutputPlan
+ -> IO (Either HtmlPublicationError ())
+writeHtmlOutput (HtmlOutputPlan planned) =
+ publishAll [] planned
+
+publishAll
+ :: [SafeRelativePath]
+ -> [PlannedHtmlArtifact]
+ -> IO (Either HtmlPublicationError ())
+publishAll _committed [] =
+ pure (Right ())
+publishAll
+ committedReversed
+ (PlannedHtmlArtifact relative destination rendered : remaining) =
+ case rendered of
+ Left failure ->
+ pure
+ (Left
+ (IncompleteHtmlPublication
+ { committedHtmlDestinations =
+ reverse committedReversed
+ , failedHtmlDestination = relative
+ , htmlPublicationFailure = failure
+ }))
+ Right bytes -> do
+ result <-
+ tryIOException
+ (stageAndReplace destination bytes)
+ case result of
+ Left err ->
+ pure
+ (Left
+ (publicationError
+ (reverse committedReversed)
+ relative
+ err))
+ Right () ->
+ publishAll
+ (relative : committedReversed)
+ remaining
+
+stageAndReplace
+ :: FilePath
+ -> ByteString
+ -> IO ()
+stageAndReplace destination bytes = do
+ let directory = Posix.takeDirectory destination
+ Directory.createDirectoryIfMissing True directory
+ writeBytesAtomically destination bytes
+
+publicationError
+ :: [SafeRelativePath]
+ -> SafeRelativePath
+ -> IOException
+ -> HtmlPublicationError
+publicationError committed failed err =
+ IncompleteHtmlPublication
+ { committedHtmlDestinations = committed
+ , failedHtmlDestination = failed
+ , htmlPublicationFailure =
+ Text.pack (displayException err)
+ }
+
+tryIOException :: IO a -> IO (Either IOException a)
+tryIOException =
+ Exception.try
+
+
+preflightDestination
+ :: FilePath
+ -> FilePath
+ -> [FilePath]
+ -> FilePath
+ -> ExceptT HtmlOutputError IO ()
+preflightDestination canonicalRoot outputRoot components destination = do
+ traverse_
+ (preflightParent canonicalRoot)
+ (destinationParents outputRoot components)
+ preflightTarget destination
+
+destinationParents :: FilePath -> [FilePath] -> [FilePath]
+destinationParents root components =
+ take
+ (length components)
+ (scanl (Posix.</>) root components)
+
+confinedDestination :: FilePath -> [FilePath] -> FilePath
+confinedDestination =
+ foldl' (Posix.</>)
+
+preflightParent
+ :: FilePath
+ -> FilePath
+ -> ExceptT HtmlOutputError IO ()
+preflightParent canonicalRoot parent = do
+ parentIsLink <- inspectSymbolicLink parent
+ parentExists <-
+ inspectPath parent (Directory.doesPathExist parent)
+ parentIsDirectory <-
+ inspectPath parent (Directory.doesDirectoryExist parent)
+ when (parentIsLink || parentExists) do
+ unless
+ parentIsDirectory
+ (throwE (HtmlOutputParentNotDirectory parent))
+ canonicalParent <-
+ inspectPath
+ parent
+ (Directory.canonicalizePath parent)
+ unless
+ (isComponentwiseChild canonicalRoot canonicalParent)
+ (throwE
+ (HtmlOutputParentEscapesRoot
+ parent
+ canonicalParent))
+
+preflightTarget
+ :: FilePath
+ -> ExceptT HtmlOutputError IO ()
+preflightTarget target = do
+ statusResult <-
+ liftIO
+ (tryIOError
+ (PosixFiles.getSymbolicLinkStatus target))
+ case statusResult of
+ Left err
+ | isDoesNotExistError err ->
+ pure ()
+ | otherwise ->
+ throwE
+ (HtmlOutputPathInspectionFailed
+ target
+ (Text.pack (displayException err)))
+ Right status
+ | PosixFiles.isSymbolicLink status ->
+ throwE
+ (HtmlOutputTargetIsSymbolicLink target)
+ | PosixFiles.isRegularFile status ->
+ pure ()
+ | otherwise ->
+ throwE
+ (HtmlOutputTargetNotRegularFile target)
+
+isComponentwiseChild :: FilePath -> FilePath -> Bool
+isComponentwiseChild root child =
+ canonicalComponents root
+ `List.isPrefixOf`
+ canonicalComponents child
+
+canonicalComponents :: FilePath -> [FilePath]
+canonicalComponents =
+ Posix.splitDirectories
+ . Posix.dropTrailingPathSeparator
+
+inspectSymbolicLink
+ :: FilePath
+ -> ExceptT HtmlOutputError IO Bool
+inspectSymbolicLink path = do
+ result <-
+ liftIO
+ (tryIOError
+ (Directory.pathIsSymbolicLink path))
+ case result of
+ Right isLink ->
+ pure isLink
+ Left err
+ | isDoesNotExistError err ->
+ pure False
+ | otherwise ->
+ throwE
+ (HtmlOutputPathInspectionFailed
+ path
+ (Text.pack (displayException err)))
+
+inspectPath
+ :: FilePath
+ -> IO a
+ -> ExceptT HtmlOutputError IO a
+inspectPath path action = do
+ result <- liftIO (tryIOError action)
+ case result of
+ Right value ->
+ pure value
+ Left err ->
+ throwE
+ (HtmlOutputPathInspectionFailed
+ path
+ (Text.pack (displayException err)))
diff --git a/source/Felix/Report/Location.hs b/source/Felix/Report/Location.hs
new file mode 100644
index 0000000..36b8997
--- /dev/null
+++ b/source/Felix/Report/Location.hs
@@ -0,0 +1,284 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+module Felix.Report.Location where
+
+import Base
+import Text.Megaparsec.Pos (SourcePos (sourceColumn, sourceLine), unPos)
+import Data.Bits
+import Control.DeepSeq (NFData)
+import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)
+import Data.IntMap.Strict qualified as IntMap
+import Data.Map.Strict qualified as Map
+import Data.Text qualified as Text
+import Data.Word (Word16, Word32)
+import System.IO.Unsafe (unsafePerformIO)
+
+-- | File identifier used in packed source locations.
+newtype FileId = FileId
+ { unFileId :: Word16
+ } deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (Hashable, NFData)
+
+-- | Packed source location.
+-- Bit layout (high to low):
+-- 16 bits file id | 24 bits line | 24 bits column.
+newtype Location = Location
+ { unLocation :: Word64
+ } deriving stock (Eq, Ord, Generic)
+ deriving anyclass (Hashable, NFData)
+
+data RegisteredFile = RegisteredFile
+ { registeredFileIdentity :: FilePath
+ , registeredFileDisplayPath :: FilePath
+ }
+
+data FileRegistry = FileRegistry
+ { registrationToFileId :: Map (FilePath, FilePath) FileId
+ , fileIdToFile :: IntMap RegisteredFile
+ , fileIdAllocator :: !FileIdAllocator
+ }
+
+data LocationRegistrationError
+ = FileIdSpaceExhausted
+ deriving stock (Show, Eq)
+
+data LocationConstructionError
+ = LocationCoordinateOutOfRange !Int !Int
+ deriving stock (Show, Eq)
+
+newtype FileIdAllocator = FileIdAllocator Word32
+ deriving stock (Show, Eq)
+
+initialFileIdAllocator :: FileIdAllocator
+initialFileIdAllocator = FileIdAllocator 0
+
+allocateFileId
+ :: FileIdAllocator
+ -> Either LocationRegistrationError (FileId, FileIdAllocator)
+allocateFileId (FileIdAllocator next)
+ | next >= fromIntegral (maxBound :: Word16) =
+ Left FileIdSpaceExhausted
+ | otherwise =
+ Right
+ ( FileId (fromIntegral next)
+ , FileIdAllocator (next + 1)
+ )
+
+initialFileRegistry :: FileRegistry
+initialFileRegistry = FileRegistry
+ { registrationToFileId = mempty
+ , fileIdToFile = mempty
+ , fileIdAllocator = initialFileIdAllocator
+ }
+
+fileRegistryRef :: IORef FileRegistry
+fileRegistryRef = unsafePerformIO (newIORef initialFileRegistry)
+{-# NOINLINE fileRegistryRef #-}
+
+registerFilePath
+ :: MonadIO io
+ => FilePath
+ -> io (Either LocationRegistrationError FileId)
+registerFilePath path =
+ registerFilePathWithDisplay path path
+
+-- | Register a file by physical identity while retaining a separate path for
+-- diagnostics. The identity must be stable and unique for the selected
+-- physical source. Distinct display paths receive distinct file identifiers so
+-- one workspace cannot inherit another workspace's presentation. Callers with
+-- only one path should use 'registerFilePath'.
+registerFilePathWithDisplay
+ :: MonadIO io
+ => FilePath
+ -> FilePath
+ -> io (Either LocationRegistrationError FileId)
+registerFilePathWithDisplay identity displayPath = liftIO $
+ atomicModifyIORef' fileRegistryRef \registry ->
+ case Map.lookup registration (registrationToFileId registry) of
+ Just fileId ->
+ (registry, Right fileId)
+ Nothing ->
+ case allocateFileId (fileIdAllocator registry) of
+ Left err ->
+ (registry, Left err)
+ Right (fileId, nextAllocator) ->
+ let fileIdInt = fromIntegral (unFileId fileId)
+ registeredFile =
+ RegisteredFile identity displayPath
+ registry' = FileRegistry
+ { registrationToFileId =
+ Map.insert
+ registration
+ fileId
+ (registrationToFileId registry)
+ , fileIdToFile =
+ IntMap.insert
+ fileIdInt
+ registeredFile
+ (fileIdToFile registry)
+ , fileIdAllocator = nextAllocator
+ }
+ in
+ (registry', Right fileId)
+ where
+ registration = (identity, displayPath)
+
+lookupFilePath :: FileId -> Maybe FilePath
+lookupFilePath fileId = unsafePerformIO do
+ registry <- readIORef fileRegistryRef
+ pure
+ (registeredFileDisplayPath <$>
+ IntMap.lookup
+ (fromIntegral (unFileId fileId))
+ (fileIdToFile registry))
+
+lookupFileIdentityPath :: FileId -> Maybe FilePath
+lookupFileIdentityPath fileId = unsafePerformIO do
+ registry <- readIORef fileRegistryRef
+ pure
+ (registeredFileIdentity <$>
+ IntMap.lookup
+ (fromIntegral (unFileId fileId))
+ (fileIdToFile registry))
+
+fileShift, lineShift :: Int
+fileShift = 48
+lineShift = 24
+
+fileMask, coordMask :: Word64
+fileMask = 0xFFFF
+coordMask = 0xFFFFFF
+
+nowhereWord :: Word64
+nowhereWord = maxBound
+
+mkLocationChecked
+ :: FileId
+ -> Int
+ -> Int
+ -> Either LocationConstructionError Location
+mkLocationChecked fileId line column
+ | line < 0
+ || column < 0
+ || lineWord > coordMask
+ || columnWord > coordMask =
+ Left (LocationCoordinateOutOfRange line column)
+ | otherwise =
+ Right
+ (Location
+ ( (fromIntegral (unFileId fileId) `shiftL` fileShift)
+ .|. (lineWord `shiftL` lineShift)
+ .|. columnWord
+ ))
+ where
+ lineWord = fromIntegral line :: Word64
+ columnWord = fromIntegral column :: Word64
+
+mkLocation :: FileId -> Int -> Int -> Location
+mkLocation fileId line column =
+ either
+ (impossible . ("mkLocation: " <>) . show)
+ id
+ (mkLocationChecked fileId line column)
+
+locFileId :: Location -> Maybe FileId
+locFileId (Location w)
+ | w == nowhereWord = Nothing
+ | otherwise = Just (FileId (fromIntegral ((w `shiftR` fileShift) .&. fileMask)))
+
+locFile :: Location -> FilePath
+locFile loc = case locFileId loc of
+ Nothing -> "<nowhere>"
+ Just fileId ->
+ fromMaybe ("<file#" <> show (unFileId fileId) <> ">") (lookupFilePath fileId)
+
+locLine :: Location -> Int
+locLine (Location w)
+ | w == nowhereWord = -1
+ | otherwise = fromIntegral ((w `shiftR` lineShift) .&. coordMask)
+
+locColumn :: Location -> Int
+locColumn (Location w)
+ | w == nowhereWord = -1
+ | otherwise = fromIntegral (w .&. coordMask)
+
+instance Show Location where
+ showsPrec p loc =
+ showParen (p > appPrec) $
+ showString "Location {locFile = "
+ . shows (locFile loc)
+ . showString ", locLine = "
+ . shows (locLine loc)
+ . showString ", locColumn = "
+ . shows (locColumn loc)
+ . showString "}"
+ where
+ appPrec = 10
+
+fromSourcePosChecked
+ :: FileId
+ -> SourcePos
+ -> Either LocationConstructionError Location
+fromSourcePosChecked fileId pos =
+ mkLocationChecked
+ fileId
+ (unPos (sourceLine pos))
+ (unPos (sourceColumn pos))
+
+prettyLocation :: Location -> String
+prettyLocation loc =
+ locFile loc <> " " <> show (locLine loc) <> ":" <> show (locColumn loc)
+
+-- | Render two locations together, qualifying equal display paths when they
+-- refer to different physical files.
+prettyLocationPair :: Location -> Location -> (String, String)
+prettyLocationPair firstLocation secondLocation =
+ if locFile firstLocation == locFile secondLocation
+ && firstIdentity /= secondIdentity
+ && isJust firstIdentity
+ && isJust secondIdentity
+ then
+ ( qualify firstIdentity firstLocation
+ , qualify secondIdentity secondLocation
+ )
+ else
+ (prettyLocation firstLocation, prettyLocation secondLocation)
+ where
+ firstIdentity =
+ locFileId firstLocation >>= lookupFileIdentityPath
+ secondIdentity =
+ locFileId secondLocation >>= lookupFileIdentityPath
+
+ qualify identity location =
+ locFile location
+ <> " (canonical "
+ <> maybe "<unknown>" show identity
+ <> ") "
+ <> show (locLine location)
+ <> ":"
+ <> show (locColumn location)
+
+locationToText :: Location -> Text
+locationToText loc = Text.pack (prettyLocation loc)
+
+-- | Things that have a location.
+class Locatable a where
+ locate :: a -> Location
+
+pattern Nowhere :: Location
+pattern Nowhere = Location 0xFFFFFFFFFFFFFFFF
+
+instance Locatable Location where
+ locate = id
+
+instance Locatable a => Locatable [a] where
+ locate [] = Nowhere
+ locate (x:_) = locate x
+
+instance Locatable a => Locatable (Maybe a) where
+ locate Nothing = Nowhere
+ locate (Just x) = locate x
+
+instance Locatable a => Locatable (NonEmpty a) where
+ locate (x :| _) = locate x
diff --git a/source/Felix/Source.hs b/source/Felix/Source.hs
index 350bb50..68d596c 100644
--- a/source/Felix/Source.hs
+++ b/source/Felix/Source.hs
@@ -80,7 +80,7 @@ import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Encoding
-import Report.Location
+import Felix.Report.Location
( Location
, LocationRegistrationError(..)
, locationToText
diff --git a/source/Felix/Source/Graph.hs b/source/Felix/Source/Graph.hs
index cb8990e..9c8753d 100644
--- a/source/Felix/Source/Graph.hs
+++ b/source/Felix/Source/Graph.hs
@@ -25,11 +25,11 @@ module Felix.Source.Graph
import Base
import Felix.Source
-import Report.Location
+import Felix.Report.Location
( FileId
, registerFilePathWithDisplay
)
-import Syntax.Token (Located(..), gatherImports)
+import Felix.Syntax.Token (Located(..), gatherImports)
import Control.Monad (unless)
import Control.Monad.State.Strict
diff --git a/source/Felix/Store.hs b/source/Felix/Store.hs
index 038b4dc..fb12670 100644
--- a/source/Felix/Store.hs
+++ b/source/Felix/Store.hs
@@ -54,16 +54,16 @@ module Felix.Store
) where
import Base
-import Checking.Core
-import Checking.Declaration qualified as Declaration
-import Checking.Identity
-import Checking.Materialization qualified as Materialization
-import Checking.Semantic
+import Felix.Checking.Core
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Identity
+import Felix.Checking.Materialization qualified as Materialization
+import Felix.Checking.Semantic
import Felix.Cache.Codec
import Felix.Module (ModuleName)
import Felix.Parsed.Identity qualified as Parsed
import Felix.Parsed.Payload qualified as ParsedPayload
-import Syntax.Interface qualified as Syntax
+import Felix.Syntax.Interface qualified as Syntax
import Control.Concurrent.MVar
( MVar
diff --git a/source/Felix/Syntax/Abstract.hs b/source/Felix/Syntax/Abstract.hs
new file mode 100644
index 0000000..b18612a
--- /dev/null
+++ b/source/Felix/Syntax/Abstract.hs
@@ -0,0 +1,863 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+
+-- | Data types for the abstract syntax tree and helper functions
+-- for constructing the lexicon.
+--
+module Felix.Syntax.Abstract
+ ( module Felix.Syntax.Abstract
+ , module Felix.Syntax.LexicalPhrase
+ , module Felix.Syntax.Token
+ ) where
+
+
+import Base
+import Felix.Syntax.LexicalPhrase (LexicalPhrase, SgPl(..), unsafeReadPhraseSgPl, unsafeReadPhrase)
+import Felix.Syntax.Token (Token(..), Located(..))
+import Felix.Report.Location
+
+import Control.DeepSeq (NFData)
+import Text.Earley.Mixfix (Holey)
+import Data.Text qualified as Text
+import Numeric.Natural (Natural)
+
+-- | Local "variable-like" symbols that can be captured by binders.
+data VarSymbol
+ = NamedVarAt Location Text -- ^ A named variable.
+ | FreshVarAt Location Int -- ^ A nameless (implicit) variable. Should only come from desugaring.
+ deriving (Generic, NFData)
+
+pattern NamedVar :: Text -> VarSymbol
+pattern NamedVar x <- NamedVarAt _ x where
+ NamedVar x = NamedVarAt Nowhere x
+
+pattern FreshVar :: Int -> VarSymbol
+pattern FreshVar n <- FreshVarAt _ n where
+ FreshVar n = FreshVarAt Nowhere n
+
+{-# COMPLETE NamedVarAt, FreshVarAt #-}
+{-# COMPLETE NamedVar, FreshVar #-}
+
+instance Show VarSymbol where
+ showsPrec d = \case
+ NamedVarAt _ x ->
+ showParen (d > 10) (showString "NamedVar " . showsPrec 11 x)
+ FreshVarAt _ n ->
+ showParen (d > 10) (showString "FreshVar " . showsPrec 11 n)
+
+instance Eq VarSymbol where
+ NamedVarAt _ x == NamedVarAt _ y = x == y
+ FreshVarAt _ n == FreshVarAt _ m = n == m
+ _ == _ = False
+
+instance Ord VarSymbol where
+ compare (NamedVarAt _ x) (NamedVarAt _ y) = compare x y
+ compare NamedVarAt{} FreshVarAt{} = LT
+ compare FreshVarAt{} NamedVarAt{} = GT
+ compare (FreshVarAt _ n) (FreshVarAt _ m) = compare n m
+
+instance Hashable VarSymbol where
+ hashWithSalt s = \case
+ NamedVarAt _ x -> hashWithSalt s (0 :: Int, x)
+ FreshVarAt _ n -> hashWithSalt s (1 :: Int, n)
+
+instance IsString VarSymbol where
+ fromString v = NamedVar $ Text.pack v
+
+instance Locatable VarSymbol where
+ locate = \case
+ NamedVarAt l _ -> l
+ FreshVarAt l _ -> l
+
+data Expr
+ = ExprVar VarSymbol
+ | ExprInteger Location Int
+ | ExprOp Location MixfixItem [Expr]
+ | ExprStructOp Location StructSymbol (Maybe Expr)
+ | ExprFiniteSet Location (NonEmpty Expr)
+ | ExprSep Location VarSymbol Expr Stmt
+ -- ^ Of the form /@{x ∈ X | P(x)}@/.
+ | ExprReplace Location Expr (NonEmpty (VarSymbol,Expr)) (Maybe Stmt)
+ -- ^ E.g.: /@{ f(x, y) | x ∈ X, y ∈ Y | P(x, y) }@/.
+ | ExprReplacePred Location VarSymbol VarSymbol Expr Stmt
+ -- ^ E.g.: /@{ y | \\exists x\\in X. P(x, y) }@/.
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable Expr where
+ locate = \case
+ ExprVar x -> locate x
+ ExprInteger l _ -> l
+ ExprOp l _ _ -> l
+ ExprStructOp l _ _ -> l
+ ExprFiniteSet l _ -> l
+ ExprSep l _ _ _ -> l
+ ExprReplace l _ _ _ -> l
+ ExprReplacePred l _ _ _ _ -> l
+
+
+data LexicalItem = LexicalItem Pattern Marker deriving (Show, Generic, NFData)
+
+instance Eq LexicalItem where
+ LexicalItem p _ == LexicalItem p' _ = p == p'
+
+instance Ord LexicalItem where
+ compare (LexicalItem p _) (LexicalItem p' _) = compare p p'
+
+instance Hashable LexicalItem where
+ hashWithSalt s (LexicalItem p _) = hashWithSalt s p
+
+data LexicalItemSgPl = LexicalItemSgPl (SgPl Pattern) Marker deriving (Show, Generic, NFData)
+
+instance Eq LexicalItemSgPl where
+ LexicalItemSgPl p _ == LexicalItemSgPl p' _ = sg p == sg p'
+
+instance Ord LexicalItemSgPl where
+ compare (LexicalItemSgPl p _) (LexicalItemSgPl p' _) = compare (sg p) (sg p')
+
+instance Hashable LexicalItemSgPl where
+ hashWithSalt s (LexicalItemSgPl p _) = hashWithSalt s (sg p)
+
+data Associativity
+ = LeftAssoc
+ | NonAssoc
+ | RightAssoc
+ deriving (Eq, Show, Ord, Generic, Hashable, NFData)
+
+data MixfixItem = MixfixItem Pattern Marker Associativity deriving (Eq, Show, Ord, Generic, Hashable, NFData)
+
+data Pattern = End | HoleCons Pattern | TokenCons Token Pattern deriving (Eq, Show, Ord, Generic, Hashable, NFData)
+
+type FunctionSymbol = MixfixItem
+
+newtype ParameterArity = ParameterArity Natural
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+zeroParameterArity :: ParameterArity
+zeroParameterArity = ParameterArity 0
+
+parameterArityOf :: Foldable f => f a -> ParameterArity
+parameterArityOf = ParameterArity . fromIntegral . length
+
+parameterArityValue :: ParameterArity -> Natural
+parameterArityValue (ParameterArity arity) = arity
+
+data RelationSymbol
+ = RelationSymbol Token ParameterArity Marker
+ deriving (Show, Eq, Ord, Generic, Hashable, NFData)
+
+newtype StructSymbol = StructSymbol { unStructSymbol :: Text }
+ deriving newtype (Show, Eq, Ord, Hashable, NFData)
+
+pattern ElementSymbol, NotElementSymbol :: RelationSymbol
+pattern ElementSymbol =
+ RelationSymbol (Command "in") (ParameterArity 0) "elem"
+pattern NotElementSymbol =
+ RelationSymbol (Command "notin") (ParameterArity 0) "notelem"
+
+pattern EqSymbol, NeqSymbol, SubseteqSymbol :: RelationSymbol
+pattern EqSymbol =
+ RelationSymbol (Symbol "=") (ParameterArity 0) "eq"
+pattern NeqSymbol =
+ RelationSymbol (Command "neq") (ParameterArity 0) "neq"
+pattern SubseteqSymbol =
+ RelationSymbol (Command "subseteq") (ParameterArity 0) "subseteq"
+
+-- | The ordinary source-level @cons@ function symbol.
+--
+-- Finite-set notation is intrinsic and does not desugar through this symbol.
+pattern ConsSymbol :: FunctionSymbol
+pattern ConsSymbol =
+ MixfixItem
+ (TokenCons (Command "cons")
+ (TokenCons InvisibleBraceL
+ (HoleCons
+ (TokenCons InvisibleBraceR
+ (TokenCons InvisibleBraceL
+ (HoleCons
+ (TokenCons InvisibleBraceR End)))))))
+ "cons"
+ NonAssoc
+
+-- | The predefined @pair@ function symbol used for desugaring tuple notation..
+pattern PairSymbol :: FunctionSymbol
+pattern PairSymbol =
+ MixfixItem
+ (TokenCons (Command "pair")
+ (TokenCons InvisibleBraceL
+ (HoleCons
+ (TokenCons InvisibleBraceR
+ (TokenCons InvisibleBraceL
+ (HoleCons
+ (TokenCons InvisibleBraceR End)))))))
+ "pair"
+ NonAssoc
+
+-- | The concrete binary-tuple surface recognized by the dedicated tuple
+-- grammar. It lowers to 'PairSymbol'.
+tupleSurfacePattern :: Pattern
+tupleSurfacePattern =
+ TokenCons ParenL
+ (HoleCons
+ (TokenCons (Symbol ",")
+ (HoleCons
+ (TokenCons ParenR End))))
+
+-- | The predefined unordered-pair function symbol.
+pattern UpairSymbol :: FunctionSymbol
+pattern UpairSymbol =
+ MixfixItem
+ (TokenCons (Command "upair")
+ (TokenCons InvisibleBraceL
+ (HoleCons
+ (TokenCons InvisibleBraceR
+ (TokenCons InvisibleBraceL
+ (HoleCons
+ (TokenCons InvisibleBraceR End)))))))
+ "upair"
+ NonAssoc
+
+-- | The fixed family-union function symbol.
+pattern UnionsSymbol :: FunctionSymbol
+pattern UnionsSymbol =
+ MixfixItem
+ (TokenCons (Command "unions")
+ (TokenCons InvisibleBraceL
+ (HoleCons
+ (TokenCons InvisibleBraceR End))))
+ "unions"
+ NonAssoc
+
+-- | Function application /@f(x)@/ desugars to /@\apply{f}{x}@/.
+pattern ApplySymbol :: FunctionSymbol
+pattern ApplySymbol =
+ MixfixItem
+ (TokenCons (Command "apply")
+ (TokenCons InvisibleBraceL
+ (HoleCons
+ (TokenCons InvisibleBraceR
+ (TokenCons InvisibleBraceL
+ (HoleCons
+ (TokenCons InvisibleBraceR End)))))))
+ "apply"
+ NonAssoc
+
+pattern DomSymbol :: FunctionSymbol
+pattern DomSymbol =
+ MixfixItem
+ (TokenCons (Command "dom")
+ (TokenCons InvisibleBraceL
+ (HoleCons
+ (TokenCons InvisibleBraceR End))))
+ "dom"
+ NonAssoc
+
+pattern CarrierSymbol :: StructSymbol
+pattern CarrierSymbol = StructSymbol "carrier"
+
+patternFromHoley :: Holey Token -> Pattern
+patternFromHoley = foldr step End
+ where
+ step = \case
+ Nothing -> HoleCons
+ Just tok -> TokenCons tok
+
+patternToHoley :: Pattern -> Holey Token
+patternToHoley = \case
+ End -> []
+ HoleCons pat -> Nothing : patternToHoley pat
+ TokenCons tok pat -> Just tok : patternToHoley pat
+
+mixfixPattern :: MixfixItem -> Pattern
+mixfixPattern (MixfixItem pat _ _) = pat
+
+mixfixMarker :: MixfixItem -> Marker
+mixfixMarker (MixfixItem _ m _) = m
+
+mixfixAssoc :: MixfixItem -> Associativity
+mixfixAssoc (MixfixItem _ _ assoc) = assoc
+
+mkMixfixItem :: Holey Token -> Marker -> Associativity -> MixfixItem
+mkMixfixItem pat m assoc = MixfixItem (patternFromHoley pat) m assoc
+
+lexicalItemPattern :: LexicalItem -> Pattern
+lexicalItemPattern (LexicalItem pat _) = pat
+
+lexicalItemMarker :: LexicalItem -> Marker
+lexicalItemMarker (LexicalItem _ m) = m
+
+lexicalItemPhrase :: LexicalItem -> LexicalPhrase
+lexicalItemPhrase = patternToHoley . lexicalItemPattern
+
+lexicalItemSgPlPattern :: LexicalItemSgPl -> SgPl Pattern
+lexicalItemSgPlPattern (LexicalItemSgPl pat _) = pat
+
+lexicalItemSgPlMarker :: LexicalItemSgPl -> Marker
+lexicalItemSgPlMarker (LexicalItemSgPl _ m) = m
+
+lexicalItemSgPlPhrase :: LexicalItemSgPl -> SgPl LexicalPhrase
+lexicalItemSgPlPhrase = fmap patternToHoley . lexicalItemSgPlPattern
+
+mkLexicalItem :: LexicalPhrase -> Marker -> LexicalItem
+mkLexicalItem pat m = LexicalItem (patternFromHoley pat) m
+
+mkLexicalItemSgPl :: SgPl LexicalPhrase -> Marker -> LexicalItemSgPl
+mkLexicalItemSgPl pat m = LexicalItemSgPl (patternFromHoley <$> pat) m
+
+relationSymbolToken :: RelationSymbol -> Token
+relationSymbolToken (RelationSymbol tok _ _) = tok
+
+relationSymbolParameterArity :: RelationSymbol -> ParameterArity
+relationSymbolParameterArity (RelationSymbol _ arity _) = arity
+
+relationSymbolMarker :: RelationSymbol -> Marker
+relationSymbolMarker (RelationSymbol _ _ m) = m
+
+relationSymbolPattern :: RelationSymbol -> Pattern
+relationSymbolPattern rel =
+ HoleCons (TokenCons (relationSymbolToken rel) (HoleCons End))
+
+structSymbolPattern :: StructSymbol -> Pattern
+structSymbolPattern (StructSymbol c) = TokenCons (Command c) End
+
+patternToken :: Pattern -> Maybe Token
+patternToken = \case
+ TokenCons tok End -> Just tok
+ _ -> Nothing
+
+markerFromToken :: Token -> Marker
+markerFromToken = \case
+ Word w -> Marker w
+ Symbol s -> Marker s
+ Command c -> Marker c
+ Integer n -> Marker (Text.pack (show n))
+ tok -> error ("markerFromToken: unsupported token " <> show tok)
+
+pattern ExprConst :: Location -> Token -> Expr
+pattern ExprConst l c <- ExprOp l (MixfixItem (TokenCons c End) _ NonAssoc) []
+ where
+ ExprConst l c = ExprOp l (MixfixItem (TokenCons c End) (markerFromToken c) NonAssoc) []
+
+pattern ExprApp :: Location -> Expr -> Expr -> Expr
+pattern ExprApp loc e1 e2 = ExprOp loc ApplySymbol [e1, e2]
+
+pattern ExprPair :: Location -> Expr -> Expr -> Expr
+pattern ExprPair loc e1 e2 = ExprOp loc PairSymbol [e1, e2]
+
+-- | Tuples are interpreted as nested pairs:
+-- the triple /@(a, b, c)@/ is interpreted as
+-- /@(a, (b, c))@/.
+-- This means that the product operation should also
+-- be right associative, so that /@(a, b, c)@/ can
+-- form elements of /@A\times B\times C@/.
+makeTuple :: Location -> NonEmpty Expr -> Expr
+makeTuple l = \case
+ e :| [] -> e
+ e :| (e' : es) -> ExprPair l e (makeTuple l (e' :| es))
+
+
+data Chain
+ = ChainBase (NonEmpty Expr) Sign Relation (NonEmpty Expr) -- left arguments, possibly empty list of parameters, right arguments
+ | ChainCons (NonEmpty Expr) Sign Relation Chain
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable Chain where
+ locate (ChainBase lhs _ _ _) = locate lhs
+ locate (ChainCons lhs _ _ _) = locate lhs
+
+data Relation
+ = Relation Location RelationSymbol [Expr] -- ^ E.g.: /@x \in X@/, potentially with parameters in braces
+ | RelationExpr Location Expr -- ^ E.g.: /@x \mathrel{R} y@/
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable Relation where
+ locate = \case
+ Relation l _ _ -> l
+ RelationExpr l _ -> l
+
+data Sign = Positive | Negative deriving (Show, Eq, Ord, Generic, NFData)
+
+data Formula
+ = FormulaChain Chain
+ | FormulaPredicate Location PrefixPredicate Marker (NonEmpty Expr)
+ | Connected Location Connective Formula Formula
+ | FormulaNeg Location Formula
+ | FormulaQuantified Location Quantifier (NonEmpty VarSymbol) Bound Formula
+ | PropositionalConstant Location PropositionalConstant
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable Formula where
+ locate = \case
+ FormulaChain chain -> locate chain
+ FormulaPredicate l _ _ _ -> l
+ Connected l _ _ _ -> l
+ FormulaNeg l _ -> l
+ FormulaQuantified l _ _ _ _ -> l
+ PropositionalConstant l _ -> l
+
+data PropositionalConstant = IsBottom | IsTop
+ deriving (Show, Eq, Ord, Generic, Hashable, NFData)
+
+data PrefixPredicate
+ = PrefixPredicate Text Int
+ deriving (Show, Eq, Ord, Generic, Hashable, NFData)
+
+
+data Connective
+ = Conjunction
+ | Disjunction
+ | Implication
+ | Equivalence
+ | ExclusiveOr
+ | NegatedDisjunction
+ deriving (Show, Eq, Ord, Generic, Hashable, NFData)
+
+
+
+mixfixLoc :: Locatable a => Holey (Located Token) -> [a] -> Location
+mixfixLoc parts args0 = go parts args0
+ where
+ go [] _ = Nowhere
+ go (Just ltok : _parts') _args' = startPos ltok
+ go (Nothing : parts') (a : args')
+ | locate a == Nowhere = go parts' args'
+ | otherwise = locate a
+ go (Nothing : parts') [] = go parts' []
+
+makeConnective :: Holey (Located Token) -> [Formula] -> Formula
+makeConnective parts@[Nothing, Just Located{unLocated = Command "implies"}, Nothing] [f1, f2] = Connected (mixfixLoc parts [f1, f2]) Implication f1 f2
+makeConnective parts@[Nothing, Just Located{unLocated = Command "land"}, Nothing] [f1, f2] = Connected (mixfixLoc parts [f1, f2]) Conjunction f1 f2
+makeConnective parts@[Nothing, Just Located{unLocated = Command "lor"}, Nothing] [f1, f2] = Connected (mixfixLoc parts [f1, f2]) Disjunction f1 f2
+makeConnective parts@[Nothing, Just Located{unLocated = Command "iff"}, Nothing] [f1, f2] = Connected (mixfixLoc parts [f1, f2]) Equivalence f1 f2
+makeConnective parts@[Just Located{unLocated = Command "lnot"}, Nothing] [f1] = FormulaNeg (mixfixLoc parts [f1]) f1
+makeConnective pat _ = error ("makeConnective does not handle the following connective correctly: " <> show pat)
+
+
+
+type StructPhrase = LexicalItemSgPl
+
+-- | For example 'an integer' would be
+-- > Noun (unsafeReadPhrase "integer[/s]") []
+type Noun = NounOf Term
+data NounOf a
+ = Noun Location LexicalItemSgPl [a]
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable (NounOf a) where
+ locate (Noun l _ _) = l
+
+
+
+
+type NounPhrase t = NounPhraseOf t Term
+-- NOTE: 'NounPhraseOf' is only used with arguments of type 'Term',
+-- but keeping the argument parameter @a@ allows the 'Show' and 'Eq'
+-- instances to remain decidable.
+data NounPhraseOf t a
+ = NounPhrase [AdjLOf a] (NounOf a) (t VarSymbol) [AdjROf a] (Maybe Stmt)
+ deriving (Generic)
+
+instance (Show a, Show (t VarSymbol)) => Show (NounPhraseOf t a) where
+ show (NounPhrase ls n vs rs ms) =
+ "NounPhrase ("
+ <> show ls <> ") ("
+ <> show n <> ") ("
+ <> show vs <> ") ("
+ <> show rs <> ") ("
+ <> show ms <> ")"
+
+instance (Eq a, Eq (t VarSymbol)) => Eq (NounPhraseOf t a) where
+ NounPhrase ls n vs rs ms == NounPhrase ls' n' vs' rs' ms' =
+ ls == ls' && n == n' && vs == vs' && rs == rs' && ms == ms'
+
+-- Raw syntax uses this lexicographic order for deterministic deduplication.
+instance (Ord a, Ord (t VarSymbol)) => Ord (NounPhraseOf t a) where
+ NounPhrase ls n vs rs ms `compare` NounPhrase ls' n' vs' rs' ms' =
+ compare
+ (ls, n, vs, rs, ms)
+ (ls', n', vs', rs', ms')
+
+instance
+ (NFData a, NFData (t VarSymbol))
+ => NFData (NounPhraseOf t a)
+
+-- | @Nameless a@ is quivalent to @Const () a@ (from "Data.Functor.Const").
+-- It describes a container that is unwilling to actually contain something.
+-- @Nameless@ lets us treat nouns with no names, one name, or many names uniformly.
+-- Thus @NounPhraseOf Nameless a@ is a noun phrase without a name and with arguments
+-- of type @a@.
+data Nameless a = Nameless deriving (Show, Eq, Ord, Generic, NFData)
+
+
+-- | Left adjectives modify nouns from the left side,
+-- e.g. /@even@/, /@continuous@/, and /@σ-finite@/.
+type AdjL = AdjLOf Term
+data AdjLOf a
+ = AdjL Location LexicalItem [a]
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable (AdjLOf a) where
+ locate (AdjL l _ _) = l
+
+
+-- | Right attributes consist of basic right adjectives, e.g.
+-- /@divisible by ?@/, or /@of finite type@/ and verb phrases
+-- marked with /@that@/, such as /@integer that divides n@/.
+-- In some cases these right attributes may be followed
+-- by an additional such-that phrase.
+type AdjR = AdjROf Term
+data AdjROf a
+ = AdjR Location LexicalItem [a]
+ | AttrRThat VerbPhrase
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable (AdjROf a) where
+ locate (AdjR l _ _) = l
+ locate (AttrRThat vp) = locate vp
+
+-- | Adjectives for parts of the AST where adjectives are not used
+-- to modify nouns and the L/R distinction does not matter, such as
+-- when then are used together with a copula (like /@n is even@/).
+type Adj = AdjOf Term
+data AdjOf a
+ = Adj Location LexicalItem [a]
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable (AdjOf a) where
+ locate (Adj l _ _) = l
+
+
+type Verb = VerbOf Term
+data VerbOf a
+ = Verb Location LexicalItemSgPl [a]
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable (VerbOf a) where
+ locate (Verb l _ _) = l
+
+
+type Fun = FunOf Term
+data FunOf a
+ = Fun {loc :: Location, phrase :: LexicalItemSgPl, funArgs :: [a]}
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable (FunOf a) where
+ locate = (.loc)
+
+
+type VerbPhrase = VerbPhraseOf Term
+data VerbPhraseOf a
+ = VPVerb (VerbOf a)
+ | VPAdj (NonEmpty (AdjOf a)) -- ^ @x is foo@ / @x is foo and bar@
+ | VPVerbNot (VerbOf a)
+ | VPAdjNot (NonEmpty (AdjOf a)) -- ^ @x is not foo@ / @x is neither foo nor bar@
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable (VerbPhraseOf a) where
+ locate = \case
+ VPVerb v -> locate v
+ VPAdj adjs -> locate adjs
+ VPVerbNot v -> locate v
+ VPAdjNot adjs -> locate adjs
+
+
+data Quantifier
+ = Universally
+ | Existentially
+ | Nonexistentially
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data QuantPhrase = QuantPhrase Quantifier (NounPhrase []) deriving (Show, Eq, Ord, Generic, NFData)
+
+
+data Term
+ = TermExpr Expr
+ -- ^ A symbolic expression.
+ | TermFun Fun
+ -- ^ Definite noun phrase, e.g. /@the derivative of $f$@/.
+ | TermIota Location VarSymbol Stmt
+ -- ^ Definite descriptor, e.g. /@an $x$ such that ...@//
+ | TermQuantified Quantifier Location (NounPhrase Maybe)
+ -- ^ Indefinite quantified notion, e.g. /@every even integer that divides $k$ ...@/.
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable Term where
+ locate :: Term -> Location
+ locate (TermExpr e) = locate e
+ locate (TermFun f) = f.loc
+ locate (TermIota l _ _) = l
+ locate (TermQuantified _ l _) = l
+
+
+data Stmt
+ = StmtFormula {formula :: Formula} -- ^ E.g.: /@We have \<Formula\>@/.
+ | StmtVerbPhrase {args :: NonEmpty Term, verb :: VerbPhrase} -- ^ E.g.: /@\<Term\> and \<Term\> \<verb\>@/.
+ | StmtNoun {args :: NonEmpty Term, noun :: (NounPhrase Maybe)} -- ^ E.g.: /@\<Term\> is a(n) \<NP\>@/.
+ | StmtStruct {arg :: Term, struct :: StructPhrase}
+ | StmtNeg {loc :: Location, stmt :: Stmt} -- ^ E.g.: /@It is not the case that \<Stmt\>@/.
+ | StmtExists {loc :: Location, np :: NounPhrase []} -- ^ E.g.: /@There exists a(n) \<NP\>@/.
+ | StmtConnected {conn :: Connective, mloc :: Maybe Location, stmt1 :: Stmt, stmt2 :: Stmt}
+ | StmtQuantPhrase {loc :: Location, qp :: QuantPhrase, stmt :: Stmt}
+ | SymbolicQuantified {loc :: Location, quant :: Quantifier, vars :: NonEmpty VarSymbol, b :: Bound, suchThat :: Maybe Stmt, stmt :: Stmt}
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable Stmt where
+ locate :: Stmt -> Location
+ locate StmtFormula{formula = phi} = locate phi
+ locate StmtConnected{mloc = Just p} = p
+ locate StmtConnected{mloc = Nothing, stmt1 = s} = locate s
+ locate StmtVerbPhrase{args = a :| _} = locate a
+ locate StmtNoun{args = a :| _} = locate a
+ locate StmtStruct{arg = a} = locate a
+ locate StmtNeg{loc = p} = p
+ locate StmtExists{loc = p} = p
+ locate StmtQuantPhrase{loc = p} = p
+ locate SymbolicQuantified{loc = p} = p
+
+data Bound = Unbounded | Bounded Location Sign Relation Expr deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable Bound where
+ locate = \case
+ Unbounded -> Nowhere
+ Bounded l _ _ _ -> l
+
+pattern SymbolicForall :: Location -> NonEmpty VarSymbol -> Bound -> Maybe Stmt -> Stmt -> Stmt
+pattern SymbolicForall loc vs bound suchThat have = SymbolicQuantified loc Universally vs bound suchThat have
+
+pattern SymbolicExists :: Location -> NonEmpty VarSymbol -> Bound -> Stmt -> Stmt
+pattern SymbolicExists loc vs bound suchThat = SymbolicQuantified loc Existentially vs bound Nothing suchThat
+
+makeSymbolicNotExists :: Location -> NonEmpty VarSymbol -> Bound -> Stmt -> Stmt
+makeSymbolicNotExists p vs bound st = StmtNeg p (SymbolicExists p vs bound st)
+
+data Asm
+ = AsmSuppose Stmt
+ | AsmLetNoun (NonEmpty VarSymbol) (NounPhrase Maybe) -- ^ E.g.: /@let k be an integer@/
+ | AsmLetIn (NonEmpty VarSymbol) Expr -- ^ E.g.: /@let $k\in\integers$@/
+ | AsmLetThe VarSymbol Fun -- ^ E.g.: /@let $g$ be the derivative of $f$@/
+ | AsmLetEq VarSymbol Expr -- ^ E.g.: /@let $m = n + k$@/
+ | AsmLetStruct VarSymbol StructPhrase -- ^ E.g.: /@let $A$ be a monoid@/
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data Axiom = Axiom [Asm] Stmt
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data Claim = Claim [Asm] Stmt
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+-- | The head of the definition describes the part before the /@iff@/,
+-- i.e. the definiendum. An optional noun-phrase corresponds to an optional
+-- type annotation for the 'Term' of the head. The last part of the head
+-- is the lexical phrase that is defined.
+--
+-- > "A natural number $n$ divides $m$ iff ..."
+-- > ^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^ ^^^
+-- > type annotation variable verb definiens
+-- > (a noun phrase) (all args are vars) (a statement)
+--
+data DefnHead
+ = DefnAdj (Maybe (NounPhrase Maybe)) VarSymbol (AdjOf VarSymbol)
+ | DefnVerb (Maybe (NounPhrase Maybe)) VarSymbol (VerbOf VarSymbol)
+ | DefnNoun VarSymbol (NounOf VarSymbol)
+ | DefnSymbolicPredicate PrefixPredicate Marker (NonEmpty VarSymbol)
+ | DefnRel VarSymbol RelationSymbol [VarSymbol] VarSymbol
+ -- ^ E.g.: /@$x \subseteq y$ iff [...@/
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data Defn
+ = Defn [Asm] DefnHead Stmt
+ | DefnFun [Asm] (FunOf VarSymbol) (Maybe Term) Term
+ -- ^ A 'DefnFun' consists of the functional noun (which must start with /@the@/)
+ -- and an optional specification of a symbolic equivalent. The symbolic equivalent
+ -- does not need to have the same variables as the full functional noun pattern.
+ --
+ -- > "The tensor product of $U$ and $V$ over $K$, $U\tensor V$, is ..."
+ -- > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^
+ -- > definiendum symbolic eqv. definiens
+ -- > (a functional noun) (an exression) (a term)
+ --
+ | DefnOp SymbolPattern Expr
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data CalcQuantifier
+ = CalcQuantifier (NonEmpty VarSymbol) Bound (Maybe Stmt)
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data Proof
+ = Omitted Location
+ | Qed (Maybe Location) Justification
+ -- ^ Ends of a proof, leaving automation to discharge the current goal using the given justification.
+ | Contradiction Location Justification
+ -- ^ Ends a proof by deriving absurdity using the given justification.
+ | ByCase Location [Case]
+ | ByContradiction Location Proof
+ | BySetInduction Location (Maybe Term) Proof
+ -- ^ ∈-induction.
+ | ByOrdInduction Location Proof
+ -- ^ Transfinite induction for ordinals.
+ | Assume Location Stmt Proof
+ | FixSymbolic Location (NonEmpty VarSymbol) Bound Proof
+ | FixSuchThat Location (NonEmpty VarSymbol) Stmt Proof
+ | Calc Location (Maybe CalcQuantifier) Calc Proof
+ -- ^ Simplify goals that are implications or disjunctions.
+ | TakeVar Location (NonEmpty VarSymbol) Bound Stmt Justification Proof
+ | TakeNoun Location (NounPhrase []) Justification Proof
+ | Have Location (Maybe Stmt) Stmt Justification Proof
+ -- ^ /@Since \<stmt\>, we have \<stmt\> by \<ref\>.@/
+ | Suffices Location Stmt Justification Proof
+ -- ^ /@It suffices to show that [...]. [...]@/
+ | Subclaim Location Stmt Proof Proof
+ -- ^ A claim is a sublemma with its own proof:
+ -- /@Show \<goal stmt\>. \<steps\>. \<continue other proof\>.@/
+ | Define Location VarSymbol Expr Proof
+ -- ^ Local definition.
+ --
+ | DefineFunction Location VarSymbol VarSymbol Expr VarSymbol Expr Proof
+ -- ^ Local function definition, e.g. /@Let $f(x) = e$ for $x\\in d$@/.
+ -- The first 'VarSymbol' is the newly defined symbol, the second one is the argument.
+ -- The first 'Expr' is the value, the final variable and expr specify a bound (the domain of the function).
+
+
+
+
+ | DefineFunctionLocal Location VarSymbol VarSymbol Expr VarSymbol VarSymbol (NonEmpty (Expr, Formula)) Proof
+ -- ^ Local function definition, but in this case we give the domain and target an the rules for $xs$ in some sub domains.
+ --
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+-- | An inline justification.
+data Justification
+ = JustificationRef (NonEmpty Marker)
+ | JustificationSetExt
+ | JustificationEmpty
+ | JustificationLocal -- ^ Use only local assumptions
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+
+-- | A case of a case split.
+data Case = Case
+ { caseOf :: Stmt
+ , caseProof :: Proof
+ } deriving (Show, Eq, Ord, Generic, NFData)
+
+data Calc
+ = Equation Expr (NonEmpty (Expr, Justification))
+ -- ^ A chain of equalities. Each claimed equality has a (potentially empty) justification.
+ -- For example: @a &= b \\explanation{by \\cref{a_eq_b}} &= c@
+ -- would be (modulo expr constructors)
+ -- @Equation "a" [("b", JustificationRef "a_eq_b"), ("c", JustificationEmpty)]@.
+ | Biconditionals Formula (NonEmpty (Formula, Justification))
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+
+data Abbreviation
+ = AbbreviationAdj VarSymbol (AdjOf VarSymbol) Stmt
+ | AbbreviationVerb VarSymbol (VerbOf VarSymbol) Stmt
+ | AbbreviationNoun VarSymbol (NounOf VarSymbol) Stmt
+ | AbbreviationRel VarSymbol RelationSymbol [VarSymbol] VarSymbol Stmt
+ | AbbreviationFun (FunOf VarSymbol) Term
+ | AbbreviationEq SymbolPattern Expr
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data Datatype
+ = Datatype
+ { datatypeHeadExpr :: Expr
+ , datatypeClauses :: NonEmpty DatatypeClause
+ }
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data DatatypeClause = DatatypeClause
+ { datatypeClauseConstructorExpr :: Expr
+ , datatypeClauseTargetExpr :: Expr
+ , datatypeClausePremises :: [(VarSymbol, Expr)]
+ }
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data Inductive = Inductive
+ { inductiveSymbolPattern :: SymbolPattern
+ , inductiveDomain :: Expr
+ , inductiveIntros :: NonEmpty IntroRule
+ }
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data IntroRule = IntroRule
+ { introConditions :: [Formula] -- The inductively defined set may only appear as an argument of monotone operations on the rhs.
+ , introResult :: Formula -- TODO Refine.
+ }
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+
+data SymbolPattern = SymbolPattern FunctionSymbol [VarSymbol]
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data Signature
+ = SignatureAdj VarSymbol (AdjOf VarSymbol)
+ -- The verb and noun forms are available to programmatic AST consumers but
+ -- have no concrete source syntax.
+ | SignatureVerb VarSymbol (VerbOf VarSymbol)
+ | SignatureNoun VarSymbol (NounOf VarSymbol)
+ | SignatureSymbolic SymbolPattern (NounPhrase Maybe)
+ -- ^ /@$\<symbol\>(\<vars\>)$ is a \<noun\>@/
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+
+data StructDefn = StructDefn
+ { structPhrase :: StructPhrase
+ -- ^ E.g.: @partial order@ or @abelian group@.\
+ , structParents :: [StructPhrase]
+ -- ^ Structural parents
+ , structLabel :: VarSymbol
+ , structFixes :: [StructSymbol]
+ -- ^ List of text for commands representing constants not inherited from its parents,
+ -- e.g.: @\sqsubseteq@ or @\inv@.
+ , structAssumes :: [(Marker, Stmt)]
+ }
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+newtype Marker = Marker Text
+ deriving stock (Show, Eq, Ord, Generic)
+
+deriving newtype instance Hashable Marker
+deriving newtype instance NFData Marker
+
+instance IsString Marker where
+ fromString str = Marker (Text.pack str)
+
+type BlockTitle = [Token]
+
+data ClaimKind
+ = Proposition
+ | Theorem
+ | Lemma
+ | Corollary
+ | PlainClaim
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+data Block
+ = BlockAxiom Location (Maybe BlockTitle) Marker Axiom
+ | BlockClaim ClaimKind Location (Maybe BlockTitle) Marker Claim
+ | BlockProof Location Proof Location -- ^ Proof start and ending location.
+ | BlockDefn Location (Maybe BlockTitle) Marker Defn
+ | BlockAbbr Location (Maybe BlockTitle) Marker Abbreviation
+ | BlockData Location (Maybe BlockTitle) Marker Datatype
+ | BlockInductive Location (Maybe BlockTitle) Marker Inductive
+ | BlockSig Location (Maybe BlockTitle) Marker [Asm] Signature
+ | BlockStruct Location (Maybe BlockTitle) Marker StructDefn
+ deriving (Show, Eq, Ord, Generic, NFData)
+
+instance Locatable Block where
+ locate = \case
+ BlockAxiom location _title _marker _axiom -> location
+ BlockClaim _kind location _title _marker _claim -> location
+ BlockProof location _proof _end -> location
+ BlockDefn location _title _marker _definition -> location
+ BlockAbbr location _title _marker _abbreviation -> location
+ BlockData location _title _marker _datatype -> location
+ BlockInductive location _title _marker _inductive -> location
+ BlockSig location _title _marker _assumptions _signature -> location
+ BlockStruct location _title _marker _structure -> location
diff --git a/source/Felix/Syntax/Adapt.hs b/source/Felix/Syntax/Adapt.hs
new file mode 100644
index 0000000..eb0cb6c
--- /dev/null
+++ b/source/Felix/Syntax/Adapt.hs
@@ -0,0 +1,928 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE ApplicativeDo #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Felix.Syntax.Adapt
+ ( FunctionPatternError(..)
+ , LexicalScanError(..)
+ , ScannedLexicalItem(..)
+ , scannedItemMarker
+ , canonicalScannedItem
+ , SyntaxMaterializationError(..)
+ , materializeSyntaxDelta
+ , scanChunk
+ ) where
+
+import Base
+import Felix.Syntax.Abstract
+import Felix.Syntax.Interface
+import Felix.Syntax.Lexicon
+import Felix.Report.Location
+
+import Control.Monad (foldM)
+import Data.Bifunctor qualified as Bifunctor
+import Data.Map.Strict qualified as Map
+import Data.Maybe (catMaybes)
+import Data.Set qualified as Set
+import Data.Sequence qualified as Seq
+import Data.Text qualified as Text
+import Numeric.Natural (Natural)
+import Text.Regex.Applicative qualified as RE
+import Text.Regex.Applicative (RE)
+
+data FunctionPatternError
+ = FunctionPatternEmpty
+ | FunctionPatternBareVariable
+ | FunctionPatternReservedApplication
+ deriving (Show, Eq)
+
+data LexicalScanError
+ = MalformedLexicalEnvironment !Location !Text
+ | InvalidFunctionPattern !Location !FunctionPatternError
+ | MalformedDatatype !Location !Text
+ deriving (Eq)
+
+instance Show LexicalScanError where
+ show = \case
+ MalformedLexicalEnvironment location environment ->
+ "could not find a lexical pattern in "
+ <> Text.unpack environment
+ <> " at "
+ <> prettyLocation location
+ InvalidFunctionPattern location problem ->
+ functionPatternErrorMessage problem
+ <> " at "
+ <> prettyLocation location
+ MalformedDatatype location problem ->
+ "malformed datatype declaration at "
+ <> prettyLocation location
+ <> ": "
+ <> Text.unpack problem
+
+scanChunk
+ :: [Located Token]
+ -> Either LexicalScanError [Located ScannedLexicalItem]
+scanChunk ltoks =
+ case ltoks of
+ first@Located{startPos = pos, unLocated = BeginEnv "definition"} : _ ->
+ locateAt first
+ <$> (matchOrErr (definition pos) "definition" pos >>= id)
+ first@Located{startPos = pos, unLocated = BeginEnv "signature"} : _ ->
+ locateAt first
+ <$> (matchOrErr (signatureExtension pos) "signature" pos >>= id)
+ first@Located{startPos = pos, unLocated = BeginEnv "abbreviation"} : _ ->
+ locateAt first
+ <$> (matchOrErr (abbreviation pos) "abbreviation" pos >>= id)
+ first@Located{startPos = pos, unLocated = BeginEnv "struct"} : _ ->
+ locateStructItems first ltoks
+ <$> matchOrErr structRE "struct definition" pos
+ first@Located{startPos = pos, unLocated = BeginEnv "inductive"} : _ ->
+ locateAt first
+ <$> (matchOrErr (inductive pos) "inductive definition" pos >>= id)
+ Located{startPos = pos, unLocated = BeginEnv "datatype"} : _ ->
+ scanDatatypeChunk pos ltoks
+ _ ->
+ Right []
+ where
+ toks = unLocated <$> ltoks
+
+ matchOrErr
+ :: RE Token a
+ -> Text
+ -> Location
+ -> Either LexicalScanError a
+ matchOrErr re environment location =
+ case RE.match re toks of
+ Nothing ->
+ Left
+ (MalformedLexicalEnvironment
+ location
+ environment)
+ Just result ->
+ Right result
+
+ locateAt first items =
+ [item <$ first | item <- items]
+
+data ScannedLexicalItem
+ = ScanAdj LexicalPhrase Marker
+ | ScanFun LexicalPhrase Marker
+ | ScanNoun LexicalPhrase Marker
+ | ScanStructNoun LexicalPhrase Marker
+ | ScanVerb LexicalPhrase Marker
+ | ScanRelationSymbol Token ParameterArity Marker
+ | ScanFunctionSymbol Pattern Marker
+ | ScanPrefixPredicate PrefixPredicate Marker
+ | ScanStructOp Text -- we an use the command text as export name.
+ deriving (Show, Eq, Ord)
+
+scannedItemMarker :: ScannedLexicalItem -> Marker
+scannedItemMarker = \case
+ ScanAdj _item marker ->
+ marker
+ ScanFun _item marker ->
+ marker
+ ScanNoun _item marker ->
+ marker
+ ScanStructNoun _item marker ->
+ marker
+ ScanVerb _item marker ->
+ marker
+ ScanRelationSymbol _token _arity marker ->
+ marker
+ ScanFunctionSymbol _pattern marker ->
+ marker
+ ScanPrefixPredicate _predicate marker ->
+ marker
+ ScanStructOp commandText ->
+ Marker commandText
+
+canonicalScannedItem
+ :: Fixity
+ -> ScannedLexicalItem
+ -> CanonicalLexicalEntry
+canonicalScannedItem fixity = \case
+ ScanAdj phrase marker
+ | isAdjR phrase ->
+ CanonicalRightAdjective
+ (patternFromHoley phrase)
+ marker
+ | otherwise ->
+ CanonicalLeftAdjective
+ (patternFromHoley phrase)
+ marker
+ ScanFun phrase marker ->
+ canonicalSgPl
+ CanonicalFunctionPhrase
+ (guessNounPlural phrase)
+ marker
+ ScanNoun phrase marker ->
+ canonicalSgPl
+ CanonicalNoun
+ (guessNounPlural phrase)
+ marker
+ ScanStructNoun phrase marker ->
+ canonicalSgPl
+ CanonicalStructureNoun
+ (guessNounPlural phrase)
+ marker
+ ScanVerb phrase marker ->
+ canonicalSgPl
+ CanonicalVerb
+ (guessVerbPlural phrase)
+ marker
+ ScanRelationSymbol token arity marker ->
+ CanonicalRelation token arity marker
+ ScanFunctionSymbol pat marker ->
+ CanonicalExpressionFunction
+ (if pat == tupleSurfacePattern
+ then mixfixPattern PairSymbol
+ else pat)
+ marker
+ fixity
+ ScanPrefixPredicate
+ (PrefixPredicate commandText arity)
+ marker ->
+ CanonicalPrefixPredicate
+ commandText
+ (fromIntegral arity)
+ marker
+ ScanStructOp commandText ->
+ CanonicalStructureOperation commandText
+ where
+ canonicalSgPl constructor phrases marker =
+ constructor
+ (patternFromHoley (sg phrases))
+ (patternFromHoley (pl phrases))
+ marker
+
+data SyntaxMaterializationError
+ = MaterializedSyntaxCollision !CanonicalSyntaxCollision
+ | PrefixPredicateArityOutOfRange !Natural
+ deriving (Show, Eq)
+
+materializeSyntaxDelta
+ :: CanonicalSyntaxDelta
+ -> Either SyntaxMaterializationError Lexicon
+materializeSyntaxDelta delta = do
+ combined <-
+ Bifunctor.first MaterializedSyntaxCollision
+ (canonicalSyntaxDelta
+ (fixedBaseSyntaxEntries
+ <> canonicalSyntaxDeltaEntries delta))
+ foldM
+ insertCanonicalEntry
+ builtins
+ [ entry
+ | entry <- canonicalSyntaxDeltaEntries combined
+ , entry `Set.notMember` fixedEntries
+ ]
+ where
+ fixedEntries =
+ Set.fromList fixedBaseSyntaxEntries
+
+insertCanonicalEntry
+ :: Lexicon
+ -> CanonicalLexicalEntry
+ -> Either SyntaxMaterializationError Lexicon
+insertCanonicalEntry lexicon@Lexicon{..} entry = do
+ extended <- case entry of
+ CanonicalLeftAdjective pat marker ->
+ pure
+ lexicon
+ { lexiconAdjLs =
+ lexicalItem pat marker : lexiconAdjLs
+ }
+ CanonicalRightAdjective pat marker ->
+ pure
+ lexicon
+ { lexiconAdjRs =
+ lexicalItem pat marker : lexiconAdjRs
+ }
+ CanonicalFunctionPhrase singular plural marker ->
+ pure
+ lexicon
+ { lexiconFuns =
+ lexicalItemSgPl singular plural marker
+ : lexiconFuns
+ }
+ CanonicalNoun singular plural marker ->
+ pure
+ lexicon
+ { lexiconNouns =
+ lexicalItemSgPl singular plural marker
+ : lexiconNouns
+ }
+ CanonicalStructureNoun singular plural marker ->
+ pure
+ lexicon
+ { lexiconStructNouns =
+ lexicalItemSgPl singular plural marker
+ : lexiconStructNouns
+ }
+ CanonicalVerb singular plural marker ->
+ pure
+ lexicon
+ { lexiconVerbs =
+ lexicalItemSgPl singular plural marker
+ : lexiconVerbs
+ }
+ CanonicalRelation token arity marker ->
+ pure
+ lexicon
+ { lexiconRelationSymbols =
+ RelationSymbol token arity marker
+ : lexiconRelationSymbols
+ }
+ CanonicalExpressionFunction
+ pat
+ marker
+ (Fixity associativity level) ->
+ pure
+ lexicon
+ { lexiconMixfixTable =
+ Seq.adjust
+ (Map.insert
+ pat
+ (MixfixItem
+ pat
+ marker
+ associativity))
+ (fromIntegral
+ (mixfixLevelValue level))
+ lexiconMixfixTable
+ }
+ CanonicalPrefixPredicate commandText arity marker -> do
+ runtimeArity <-
+ if arity <= fromIntegral (maxBound :: Int)
+ then pure (fromIntegral arity)
+ else
+ Left
+ (PrefixPredicateArityOutOfRange arity)
+ pure
+ lexicon
+ { lexiconPrefixPredicates =
+ (PrefixPredicate commandText runtimeArity, marker)
+ : lexiconPrefixPredicates
+ }
+ CanonicalStructureOperation commandText ->
+ pure
+ lexicon
+ { lexiconStructFun =
+ StructSymbol commandText : lexiconStructFun
+ }
+ pure extended
+ where
+ lexicalItem pat marker =
+ mkLexicalItem (patternToHoley pat) marker
+
+ lexicalItemSgPl singular plural marker =
+ mkLexicalItemSgPl
+ (SgPl
+ (patternToHoley singular)
+ (patternToHoley plural))
+ marker
+
+skipUntilNextLexicalEnv :: RE Token [Token]
+skipUntilNextLexicalEnv = many (RE.psym otherToken)
+ where
+ otherToken tok = tok /= BeginEnv "definition" && tok /= BeginEnv "struct" && tok /= BeginEnv "abbreviation"
+
+notEndOfLexicalEnvToken :: RE Token Token
+notEndOfLexicalEnvToken = RE.psym innerToken
+ where
+ innerToken tok = tok /= EndEnv "definition" && tok /= EndEnv "struct" && tok /= EndEnv "abbreviation"
+
+notEndOfSignatureSentenceToken :: RE Token Token
+notEndOfSignatureSentenceToken = RE.psym \case
+ Symbol "." -> False
+ EndEnv "signature" -> False
+ _ -> True
+
+definition
+ :: Location
+ -> RE Token (Either LexicalScanError [ScannedLexicalItem])
+definition location = do
+ RE.sym (BeginEnv "definition")
+ RE.few notEndOfLexicalEnvToken
+ m <- labelRE
+ RE.few RE.anySym
+ lexicalItem <- headRE location
+ RE.few RE.anySym
+ RE.sym (EndEnv "definition")
+ skipUntilNextLexicalEnv
+ pure ((: []) <$> lexicalItem m)
+
+abbreviation
+ :: Location
+ -> RE Token (Either LexicalScanError [ScannedLexicalItem])
+abbreviation location = do
+ RE.sym (BeginEnv "abbreviation")
+ RE.few RE.anySym
+ m <- labelRE
+ RE.few RE.anySym
+ lexicalItem <- headRE location
+ RE.few RE.anySym
+ RE.sym (EndEnv "abbreviation")
+ skipUntilNextLexicalEnv
+ pure ((: []) <$> lexicalItem m)
+
+signatureExtension
+ :: Location
+ -> RE Token (Either LexicalScanError [ScannedLexicalItem])
+signatureExtension location = do
+ RE.sym (BeginEnv "signature")
+ RE.few notEndOfLexicalEnvToken
+ m <- labelRE
+ RE.few RE.anySym
+ lexicalItem <- sigHeadRE location
+ RE.few notEndOfSignatureSentenceToken
+ RE.sym (Symbol ".")
+ RE.sym (EndEnv "signature")
+ skipUntilNextLexicalEnv
+ pure ((: []) <$> lexicalItem m)
+
+labelRE :: RE Token Marker
+labelRE = RE.msym \case
+ Label m -> Just (Marker m)
+ _ -> Nothing
+
+-- | 'RE' that matches the head of a definition.
+headRE
+ :: Location
+ -> RE Token (Marker -> Either LexicalScanError ScannedLexicalItem)
+-- Note that @<|>@ is left biased for 'RE', so we can just
+-- place 'adj' before 'verb' and do not have to worry about
+-- overlapping patterns.
+headRE location =
+ pureScan ScanNoun <$> nounRE
+ <|> pureScan ScanAdj <$> adjRE
+ <|> pureScan ScanVerb <$> verbRE
+ <|> pureScan ScanFun <$> funRE
+ <|> relationScan <$> relationSymbolRE
+ <|> functionScan <$> functionSymbolRE location
+ <|> pureScan ScanPrefixPredicate <$> prefixPredicate
+ where
+ pureScan constructor value marker =
+ Right (constructor value marker)
+
+ relationScan (token, arity) marker =
+ Right (ScanRelationSymbol token arity marker)
+
+ functionScan patternResult marker =
+ (`ScanFunctionSymbol` marker) <$> patternResult
+
+sigHeadRE
+ :: Location
+ -> RE Token (Marker -> Either LexicalScanError ScannedLexicalItem)
+sigHeadRE location =
+ asum
+ [ signatureHeadRE form
+ | form <- concreteSignatureHeadForms
+ ]
+ where
+ signatureHeadRE = \case
+ AdjectiveSignatureHead ->
+ pureScan ScanAdj <$> sigAdjectiveRE
+ SymbolicSignatureHead ->
+ functionScan <$> sigFunctionSymbolRE location
+
+ pureScan constructor value marker =
+ Right (constructor value marker)
+
+ functionScan patternResult marker =
+ (`ScanFunctionSymbol` marker) <$> patternResult
+
+sigAdjectiveRE :: RE Token LexicalPhrase
+sigAdjectiveRE =
+ toLexicalPhrase
+ <$> ( math var
+ *> RE.sym (Word "can")
+ *> RE.sym (Word "be")
+ *> RE.some
+ (RE.psym isLexicalPhraseToken <|> math var)
+ )
+
+sigFunctionSymbolRE :: Location -> RE Token (Either LexicalScanError Pattern)
+sigFunctionSymbolRE location = do
+ RE.sym (BeginEnv "math")
+ toks <- RE.few nonDefinitionKeyword
+ RE.sym (EndEnv "math")
+ pure (makeFunctionSymbol location toks)
+
+inductive
+ :: Location
+ -> RE Token (Either LexicalScanError [ScannedLexicalItem])
+inductive location = do
+ RE.sym (BeginEnv "inductive")
+ RE.few notEndOfLexicalEnvToken
+ m <- labelRE
+ RE.few RE.anySym
+ lexicalItem <- functionSymbolInductive location
+ RE.few RE.anySym
+ RE.sym (EndEnv "inductive")
+ skipUntilNextLexicalEnv
+ pure (((: []) . (`ScanFunctionSymbol` m)) <$> lexicalItem)
+
+scanDatatypeChunk
+ :: Location
+ -> [Located Token]
+ -> Either LexicalScanError [Located ScannedLexicalItem]
+scanDatatypeChunk = datatypeLexicalItems
+
+datatypeLexicalItems
+ :: Location
+ -> [Located Token]
+ -> Either LexicalScanError [Located ScannedLexicalItem]
+datatypeLexicalItems environmentLocation toks = do
+ marker <- requireDatatype
+ environmentLocation
+ "missing declaration label"
+ (findDatatypeLabel toks)
+ datatypeHeadToks <- requireDatatype
+ environmentLocation
+ "missing datatype head"
+ (findDatatypeHead toks)
+ constructorToks <- requireDatatype
+ environmentLocation
+ "missing constructor enumeration"
+ (findDatatypeConstructors toks)
+ let datatypeLocation =
+ maybe environmentLocation startPos (listToMaybe datatypeHeadToks)
+ datatypePattern <- makeFunctionSymbol
+ datatypeLocation
+ (unLocated <$> datatypeHeadToks)
+ constructorItems <- traverse makeConstructor constructorToks
+ pure
+ ((ScanFunctionSymbol datatypePattern marker
+ <$ locationTemplate datatypeLocation toks)
+ : constructorItems)
+ where
+ makeConstructor (itemLocation, raw) = do
+ constructorToks <- requireDatatype
+ itemLocation
+ "constructor item has no symbolic declaration"
+ (itemConstructorToks raw)
+ let stripped = stripOuterParens constructorToks
+ markerToken <- constructorMarker itemLocation stripped
+ constructorPattern <- makeFunctionSymbol
+ (startPos markerToken)
+ (unLocated <$> stripped)
+ pure
+ (ScanFunctionSymbol
+ constructorPattern
+ (markerFromToken (unLocated markerToken))
+ <$ markerToken)
+
+requireDatatype
+ :: Location
+ -> Text
+ -> Maybe a
+ -> Either LexicalScanError a
+requireDatatype location problem =
+ maybe (Left (MalformedDatatype location problem)) Right
+
+locationTemplate :: Location -> [Located Token] -> Located Token
+locationTemplate location = \case
+ token : _ ->
+ token{startPos = location}
+ [] ->
+ impossible "datatype scanner has no environment token"
+
+findDatatypeLabel :: [Located Token] -> Maybe Marker
+findDatatypeLabel = \case
+ [] -> Nothing
+ Located{unLocated = Label m} : _ -> Just (Marker m)
+ _ : toks -> findDatatypeLabel toks
+
+findDatatypeHead :: [Located Token] -> Maybe [Located Token]
+findDatatypeHead toks = do
+ afterLabel <- tailMay =<< dropUntil (isLabel . unLocated) toks
+ defineToks <- dropUntil ((== Word "define") . unLocated) afterLabel
+ case defineToks of
+ _define : Located{unLocated = BeginEnv "math"} : rest ->
+ takeUntilToken (EndEnv "math") rest
+ _ -> Nothing
+
+findDatatypeConstructors
+ :: [Located Token]
+ -> Maybe [(Location, [Located Token])]
+findDatatypeConstructors toks = do
+ afterEnumerate <- tailMay
+ =<< dropUntil ((== BeginEnv "enumerate") . unLocated) toks
+ enumerateBody <- takeUntilToken (EndEnv "enumerate") afterEnumerate
+ let items = splitDatatypeItems enumerateBody
+ guard (not (null items))
+ pure items
+
+splitDatatypeItems
+ :: [Located Token]
+ -> [(Location, [Located Token])]
+splitDatatypeItems = \case
+ [] ->
+ []
+ Located{startPos = itemLocation, unLocated = Command "item"} : rest ->
+ let (item, remaining) =
+ break ((== Command "item") . unLocated) rest
+ in (itemLocation, item) : splitDatatypeItems remaining
+ _ : rest ->
+ splitDatatypeItems rest
+
+itemConstructorToks :: [Located Token] -> Maybe [Located Token]
+itemConstructorToks toks = do
+ afterMath <- tailMay =<< dropUntil ((== BeginEnv "math") . unLocated) toks
+ mathBody <- takeUntilToken (EndEnv "math") afterMath
+ takeUntilToken (Command "in") mathBody
+
+constructorMarker
+ :: Location
+ -> [Located Token]
+ -> Either LexicalScanError (Located Token)
+constructorMarker fallback =
+ maybe
+ (Left
+ (MalformedDatatype
+ fallback
+ "constructor has no marker-bearing head token"))
+ Right
+ . find (isConstructorMarkerToken . unLocated)
+
+isConstructorMarkerToken :: Token -> Bool
+isConstructorMarkerToken = \case
+ Word _ -> True
+ Symbol _ -> True
+ Command _ -> True
+ Integer _ -> True
+ _ -> False
+
+stripOuterParens :: [Located Token] -> [Located Token]
+stripOuterParens toks
+ | hasOuterParens toks = case toks of
+ Located{unLocated = ParenL} : rest -> case reverse rest of
+ Located{unLocated = ParenR} : innerRev -> reverse innerRev
+ _ -> toks
+ _ -> toks
+ | otherwise = toks
+
+hasOuterParens :: [Located Token] -> Bool
+hasOuterParens = \case
+ Located{unLocated = ParenL} : rest -> go (1 :: Int) rest
+ _ -> False
+ where
+ go _ [] = False
+ go depth [Located{unLocated = ParenR}] = depth == 1
+ go depth (Located{unLocated = ParenL} : rest) =
+ go (depth + 1) rest
+ go depth (Located{unLocated = ParenR} : rest)
+ | depth <= 0 = False
+ | otherwise = go (depth - 1) rest
+ go depth (_ : rest) = go depth rest
+
+isLabel :: Token -> Bool
+isLabel = \case
+ Label _ -> True
+ _ -> False
+
+dropUntil :: (a -> Bool) -> [a] -> Maybe [a]
+dropUntil predicate = \case
+ [] -> Nothing
+ xs@(x : rest)
+ | predicate x -> Just xs
+ | otherwise -> dropUntil predicate rest
+
+takeUntilToken :: Token -> [Located Token] -> Maybe [Located Token]
+takeUntilToken stop = \case
+ [] -> Nothing
+ x : xs
+ | unLocated x == stop -> Just []
+ | otherwise -> (x :) <$> takeUntilToken stop xs
+
+tailMay :: [a] -> Maybe [a]
+tailMay = \case
+ [] -> Nothing
+ _ : xs -> Just xs
+
+structRE :: RE Token [ScannedLexicalItem]
+structRE = do
+ RE.sym (BeginEnv "struct")
+ RE.few RE.anySym
+ m <- labelRE
+ RE.few RE.anySym
+ lexicalItem <- ScanStructNoun . toLexicalPhrase <$> (an *> structPat <* math var)
+ RE.few RE.anySym
+ lexicalItems <- structOps <|> pure []
+ RE.sym (EndEnv "struct")
+ skipUntilNextLexicalEnv
+ pure (lexicalItem m : lexicalItems)
+
+structOps :: RE Token [ScannedLexicalItem]
+structOps = do
+ RE.sym (BeginEnv "enumerate")
+ lexicalItems <- many structOp
+ RE.sym (EndEnv "enumerate")
+ RE.few RE.anySym
+ pure lexicalItems
+
+structOp :: RE Token ScannedLexicalItem
+structOp = do
+ RE.sym (Command "item")
+ op <- math command
+ pure (ScanStructOp op)
+
+locateStructItems
+ :: Located Token
+ -> [Located Token]
+ -> [ScannedLexicalItem]
+ -> [Located ScannedLexicalItem]
+locateStructItems environmentToken toks = \case
+ [] ->
+ []
+ structureNoun : operations ->
+ (structureNoun <$ environmentToken)
+ : zipWith locateOperation operations operationTokens
+ where
+ operationTokens =
+ structOperationTokens toks
+ <> repeat environmentToken
+
+ locateOperation operation token =
+ operation <$ token
+
+structOperationTokens :: [Located Token] -> [Located Token]
+structOperationTokens = \case
+ Located{unLocated = Command "item"}
+ : Located{unLocated = BeginEnv "math"}
+ : commandToken@Located{unLocated = Command _}
+ : Located{unLocated = EndEnv "math"}
+ : rest ->
+ commandToken : structOperationTokens rest
+ _ : rest ->
+ structOperationTokens rest
+ [] ->
+ []
+
+nounRE :: RE Token LexicalPhrase
+nounRE = toLexicalPhrase <$> (math var *> is *> an *> patRE <* iff)
+
+adjRE :: RE Token LexicalPhrase
+adjRE = toLexicalPhrase <$> (math var *> is *> patRE <* iff)
+
+verbRE :: RE Token LexicalPhrase
+verbRE = toLexicalPhrase <$> (math var *> patRE <* iff)
+
+funRE :: RE Token LexicalPhrase
+funRE = toLexicalPhrase <$> (the *> patRE <* (is <|> comma))
+
+relationSymbolRE :: RE Token (Token, ParameterArity)
+relationSymbolRE = do
+ beginMath
+ var
+ rel <- symbol
+ k <- params
+ var
+ endMath
+ iff
+ pure (rel, k)
+ where
+ params :: RE Token ParameterArity
+ params = do
+ vars <- many (RE.sym InvisibleBraceL *> var <* RE.sym InvisibleBraceR)
+ pure (parameterArityOf vars)
+
+functionSymbolRE
+ :: Location
+ -> RE Token (Either LexicalScanError Pattern)
+functionSymbolRE location = do
+ RE.sym (BeginEnv "math")
+ toks <- RE.few nonDefinitionKeyword
+ RE.sym (Symbol "=")
+ pure (makeFunctionSymbol location toks)
+
+makeFunctionSymbol
+ :: Location
+ -> [Token]
+ -> Either LexicalScanError Pattern
+makeFunctionSymbol location = \case
+ [] ->
+ Left (InvalidFunctionPattern location FunctionPatternEmpty)
+ [Variable _] ->
+ Left (InvalidFunctionPattern location FunctionPatternBareVariable)
+ [Variable _, ParenL, Variable _, ParenR] ->
+ Left
+ (InvalidFunctionPattern
+ location
+ FunctionPatternReservedApplication)
+ toks ->
+ Right (patternFromHoley (fromToken <$> toks))
+ where
+ fromToken = \case
+ Variable _ -> Nothing -- Variables become slots.
+ tok -> Just tok -- Everything else is part of the pattern.
+
+functionPatternErrorMessage :: FunctionPatternError -> String
+functionPatternErrorMessage = \case
+ FunctionPatternEmpty ->
+ "malformed function pattern: no pattern"
+ FunctionPatternBareVariable ->
+ "malformed function pattern: a bare variable would cause infinite left recursion"
+ FunctionPatternReservedApplication ->
+ "malformed function pattern: _(_) is reserved for set-theoretic function application"
+
+functionSymbolInductive
+ :: Location
+ -> RE Token (Either LexicalScanError Pattern)
+functionSymbolInductive location = do
+ RE.sym (BeginEnv "math")
+ toks <- RE.few nonDefinitionKeyword
+ RE.sym (Command "subseteq")
+ pure (makeFunctionSymbol location toks)
+
+prefixPredicate :: RE Token PrefixPredicate
+prefixPredicate = math prfx <* iff
+ where
+ prfx = do
+ r <- command
+ args <- many (RE.sym InvisibleBraceL *> var <* RE.sym InvisibleBraceR)
+ pure (PrefixPredicate r (length args))
+
+
+command :: RE Token Text
+command = RE.msym \case
+ Command cmd -> Just cmd
+ _ -> Nothing
+
+var :: RE Token Token
+var = RE.psym isVar
+
+
+nonDefinitionKeyword :: RE Token Token
+nonDefinitionKeyword = RE.psym (`notElem` keywords)
+ where
+ keywords =
+ [ Word "if"
+ , Word "iff"
+ , Symbol "="
+ , Command "iff"
+ , BeginEnv "math"
+ , EndEnv "math"
+ ]
+
+
+patRE :: RE Token [Token]
+patRE = many (RE.psym isLexicalPhraseToken <|> math var)
+
+structPat :: RE Token [Token]
+structPat = many (RE.psym isLexicalPhraseToken)
+
+beginMath, endMath :: RE Token ()
+beginMath = void (RE.sym (BeginEnv "math"))
+endMath = void (RE.sym (EndEnv "math"))
+
+math :: RE Token a -> RE Token a
+math re = beginMath *> re <* endMath
+
+-- | We allow /conditional perfection/: the first /@if@/ in a definition is interpreted as /@iff@/.
+iff :: RE Token ()
+iff = void (RE.sym (Word "if")) -- Using @void@ is faster (only requires recognition).
+ <|> void (RE.sym (Word "iff"))
+ <|> void (RE.string [Word "if", Word "and", Word "only", Word "if"])
+ <|> void (RE.sym (Word "denote"))
+ <|> void (RE.sym (Word "stand") *> RE.sym (Word "for"))
+{-# INLINE iff #-}
+
+an :: RE Token ()
+an = void (RE.sym (Word "a"))
+ <|> void (RE.sym (Word "an"))
+{-# INLINE an #-}
+
+is :: RE Token ()
+is = void (RE.sym (Word "is") <|> RE.sym (Word "denotes"))
+{-# INLINE is #-}
+
+the :: RE Token ()
+the = void (RE.sym (Word "the"))
+{-# INLINE the #-}
+
+comma :: RE Token ()
+comma = void (RE.sym (Symbol ","))
+{-# INLINE comma #-}
+
+
+isVar :: Token -> Bool
+isVar = \case
+ Variable _ -> True
+ _token -> False
+
+isLexicalPhraseToken :: Token -> Bool
+isLexicalPhraseToken = \case
+ Word w -> w `Set.notMember` keywords
+ --
+ -- Simple commands (outside of math-mode) are allowed. This is useful
+ -- for defining lexical phrases containing symbolic expressions such as
+ -- `X is \Ttwo{}`, where `\Ttwo` is a macro that expands to `T_2`.
+ -- We also allow these macros to take arguments, hence the need to
+ -- allow grouping delimiters. They can also be used to escape the end
+ -- of the command for correct spacing, as in the above example.
+ --
+ Command _cmd -> True
+ InvisibleBraceL -> True
+ InvisibleBraceR -> True
+ --
+ -- No other tokens may occur in lexical phrases. In particular, no `_dot`
+ -- token may occur, limiting the lexical phrase to a single sentence.
+ -- Commas occurring in variable lists should be placed
+ -- within the math environment. Thus `$a,b$ are coprime iff`,
+ -- not `$a$,`$b$` are coprime iff`.
+ --
+ _token -> False
+ where
+ keywords = Set.fromList ["a", "an", "is", "are", "if", "iff", "denote", "stand", "let"]
+
+
+toLexicalPhrase :: [Token] -> LexicalPhrase
+toLexicalPhrase toks = component <$> toks
+ where
+ component = \case
+ Variable _ -> Nothing
+ tok -> Just tok
+
+
+symbol :: RE Token Token
+symbol = RE.msym $ \tok -> case tok of
+ Command _ -> Just tok
+ Symbol _ -> Just tok
+ _tok -> Nothing
+
+
+-- | Basic paradigms for pluralizations of nominals.
+guessNounPlural :: LexicalPhrase -> SgPl LexicalPhrase
+guessNounPlural item = SgPl item (pluralize item)
+ where
+ pluralize :: LexicalPhrase -> LexicalPhrase
+ pluralize = \case
+ Just (Word w) : pat'@(Just w' : _) | isPreposition w' -> Just (Word (Text.snoc w 's')) : pat'
+ tok : Just (Word w) : pat'@(Just w' : _) | isPreposition w' -> tok : Just (Word (Text.snoc w 's')) : pat'
+ tok1 : tok2 : Just (Word w) : pat'@(Just w' : _) | isPreposition w' -> tok1 : tok2 : Just (Word (Text.snoc w 's')) : pat'
+ [Just (Word w)] -> [Just (Word (Text.snoc w 's'))]
+ [tok, Just (Word w)] -> [tok, Just (Word (Text.snoc w 's'))]
+ [tok, tok', Just (Word w)] -> [tok, tok', Just (Word (Text.snoc w 's'))]
+ pat' -> pat'
+
+guessVerbPlural :: LexicalPhrase -> SgPl LexicalPhrase
+guessVerbPlural item = SgPl item itemPl
+ where
+ itemPl = case item of
+ Just (Word v) : rest -> case Text.unsnoc v of
+ Just (v', 's') -> Just (Word v') : rest
+ _ -> item
+ _ -> item
+
+isAdjR :: LexicalPhrase -> Bool
+isAdjR item = containsPreposition item || containsSlot item
+ where
+ containsPreposition, containsSlot :: LexicalPhrase -> Bool
+ containsPreposition = any isPreposition . catMaybes
+ containsSlot = (Nothing `elem`)
+
+isPreposition :: Token -> Bool
+isPreposition w = Set.member w (Set.map Word prepositions)
diff --git a/source/Felix/Syntax/Concrete.hs b/source/Felix/Syntax/Concrete.hs
new file mode 100644
index 0000000..8be8ab6
--- /dev/null
+++ b/source/Felix/Syntax/Concrete.hs
@@ -0,0 +1,1032 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE RecursiveDo #-}
+
+-- | Concrete syntax of the surface language.
+module Felix.Syntax.Concrete where
+
+import Base
+import Felix.Syntax.Abstract
+import Felix.Syntax.Concrete.Keywords
+import Felix.Syntax.Lexicon
+ ( Lexicon(..)
+ , SignatureHeadForm(..)
+ , concreteSignatureHeadForms
+ , lexiconAdjs
+ , splitOnVariableSlot
+ )
+import Felix.Syntax.Token
+import Felix.Report.Location
+
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Map.Strict qualified as Map
+import Text.Earley (Grammar, Prod, (<?>), rule, satisfy, terminal)
+import Felix.Syntax.Mixfix
+
+
+grammar :: Lexicon -> Grammar r (Prod r Text (Located Token) Block)
+grammar lexicon@Lexicon{..} = mdo
+ let patternToProd :: Pattern -> Holey (Prod r Text (Located Token) (Located Token))
+ patternToProd pat = map (fmap tokenLocated) (patternToHoley pat)
+ makeMixfixOp item = (patternToProd (mixfixPattern item), mixfixAssoc item, \parts args -> ExprOp (mixfixLoc parts args) item args)
+ mixfixItems = toList (Map.elems <$> lexiconMixfixTable)
+ mixfixOps = map (map makeMixfixOp) mixfixItems
+ makeConn (pat, assoc) = (map (fmap tokenLocated) pat, assoc)
+ conns = map (map makeConn) lexiconConnectives
+
+ integerWithLoc <- rule (terminal maybeIntTokenWithLoc <?> "integer")
+ relatorWithLoc <- rule $ asum
+ [ (,) <$> tokenPos (relationSymbolToken item) <*> pure item
+ | item <- lexiconRelationSymbols
+ ] <?> "relator"
+ relator <- rule (snd <$> relatorWithLoc)
+ varSymbol <- rule (terminal maybeVarToken <?> "variable")
+ varSymbols <- rule (commaList varSymbol)
+ cmd <- rule (terminal maybeCmdToken <?> "TEX command")
+--
+-- Formulas have three levels:
+--
+-- + Expressions: atoms or operators applied to atoms.
+-- + Chains: comma-lists of expressions, separated by relators.
+-- + Formulas: chains or connectives applied to chains.
+--
+-- For example, the formula @x, y < z \implies x, y < z + 1@ consist of the
+-- connective @\implies@ applied to two chains @x, y < z@ and @x, y < z + 1@.
+-- In turn, the chain @x, y < z + 1@ consist of three expressions,
+-- @x@, @y@, and @z + 1@. Finally, @z + 1@ consist the operator @+@
+-- applied to two atoms, the variable @z@ and the number literal @1@.
+--
+-- This split is due to the different behaviour of relators compared to
+-- operators and connectives. Relators can chain (@x < y < z@) and allow
+-- lists as arguments, as in the above example. Operators and connectives
+-- instead have precedence and fixity. The only syntactic difference between
+-- an operator and a connective is the relative precedence compared to relators.
+--
+ replaceBound <- rule $ (,) <$> varSymbol <* _in <*> expr
+ replaceBounds <- rule $ commaList replaceBound
+ comprStmt <- rule $ (StmtFormula <$> formula) <|> text stmt
+
+ let replaceFun = (\e bounds mstmt loc -> ExprReplace loc e bounds mstmt) <$> expr <* _pipe <*> replaceBounds <*> optional (_pipe *> comprStmt)
+ replacePredSymbolic = (\y x xBound st loc -> ExprReplacePred loc y x xBound st) <$> varSymbol <* _pipe <*> (command "exists" *> varSymbol) <* _in <*> expr <* _dot <*> (StmtFormula <$> formula)
+ replacePredText = (\y x xBound st loc -> ExprReplacePred loc y x xBound st) <$> varSymbol <* _pipe <*> (begin "text" *> _exists *> beginMath *> varSymbol <* _in) <*> expr <* endMath <* _suchThat <*> stmt <* end "text"
+ replacePred = replacePredSymbolic <|> replacePredText
+
+ let exprStructOpOf ann = foldr alg empty lexiconStructFun
+ where
+ alg s prod = prod <|> (uncurry ExprStructOp <$> structSymbolPos s <*> ann)
+
+ exprStructOp <- rule (exprStructOpOf (optional (bracket expr)))
+
+ let bracedArgs1 ar arg = count1 ar $ group arg
+ let prefixPredicateOf f arg symb@(PrefixPredicate c ar) = f <$> pure symb <* command c <*> bracedArgs1 ar arg
+
+
+ exprParen <- rule $ paren expr
+ exprInteger <- rule $ uncurry ExprInteger <$> integerWithLoc
+ exprVar <- rule $ ExprVar <$> varSymbol
+ exprTuple <- rule do
+ loc <- tokenPos ParenL
+ es <- commaList2 expr <* token ParenR
+ pure (makeTuple loc es)
+ exprSep <- rule do
+ loc <- tokenPos VisibleBraceL
+ x <- varSymbol <* _in
+ bound <- expr <* _pipe
+ phi <- comprStmt <* token VisibleBraceR
+ pure (ExprSep loc x bound phi)
+ exprReplace <- rule do
+ (\loc mk -> mk loc) <$> tokenPos VisibleBraceL <*> (replaceFun <|> replacePred) <* token VisibleBraceR
+ exprFinSet <- rule do
+ loc <- tokenPos VisibleBraceL
+ es <- exprs <* token VisibleBraceR
+ pure (ExprFiniteSet loc es)
+ exprBase <- rule $ asum [exprVar, exprInteger, exprStructOp, exprParen, exprTuple, exprSep, exprReplace, exprFinSet]
+ exprApp <- rule $ (\e1 e2 -> ExprApp (locate e1) e1 e2) <$> exprBase <*> (paren expr <|> exprTuple)
+ expr <- mixfixExpressionSeparate mixfixOps (exprBase <|> exprApp)
+ exprs <- rule $ commaList expr
+
+ relationSign <- rule $ pure Positive <|> (Negative <$ command "not")
+ relationExpr <- rule $ RelationExpr <$> command "mathrel" <*> group expr
+ relation <- rule $ (uncurry Relation <$> relatorWithLoc <*> many (group expr)) <|> relationExpr
+ chainBase <- rule $ (\es sign rel es' -> ChainBase es sign rel es') <$> exprs <*> relationSign <*> relation <*> exprs
+ chainCons <- rule $ (\es sign rel ch -> ChainCons es sign rel ch) <$> exprs <*> relationSign <*> relation <*> chain
+ chain <- rule $ chainCons <|> chainBase
+
+ formulaPredicate <- rule $ asum
+ [ (\loc es -> FormulaPredicate loc symb marker es) <$> command c <*> bracedArgs1 ar expr
+ | (symb@(PrefixPredicate c ar), marker) <- lexiconPrefixPredicates
+ ]
+ formulaChain <- rule $ FormulaChain <$> chain
+ formulaBottom <- rule $ PropositionalConstant <$> command "bot" <*> pure IsBottom <?> "\"\\bot\""
+ formulaTop <- rule $ PropositionalConstant <$> command "top" <*> pure IsTop <?> "\"\\top\""
+ formulaExists <- rule $ FormulaQuantified <$> command "exists" <*> pure Existentially <*> varSymbols <*> maybeBounded <* _dot <*> formula
+ formulaAll <- rule $ FormulaQuantified <$> command "forall" <*> pure Universally <*> varSymbols <*> maybeBounded <* _dot <*> formula
+ formulaQuantified <- rule $ formulaExists <|> formulaAll
+ formulaBase <- rule $ asum [formulaChain, formulaPredicate, formulaBottom, formulaTop, paren formula]
+ formulaConn <- mixfixExpression conns formulaBase makeConnective
+ formula <- rule $ formulaQuantified <|> formulaConn
+
+-- These are asymmetric formulas (only variables are allowed on one side).
+-- They express judgements.
+--
+ assignment <- rule $ (,) <$> varSymbol <* (_eq <|> _defeq) <*> expr
+ typing <- rule $ (,) <$> varSymbols <* (_in <|> _colon) <*> expr
+
+ adjL <- rule $ adjLOf lexicon term
+ adjR <- rule $ adjROf lexicon term
+ adj <- rule $ adjOf lexicon term
+ adjVar <- rule $ adjOf lexicon var
+
+ var <- rule $ math varSymbol
+ vars <- rule $ math varSymbols
+
+ verb <- rule $ verbOf lexicon sg term
+ verbPl <- rule $ verbOf lexicon pl term
+ verbVar <- rule $ verbOf lexicon sg var
+
+ let nounTrieSg = nounTrieOf sg lexiconNouns
+ nounTriePl = nounTrieOf pl lexiconNouns
+ structNounTrieSg = nounTrieOf sg lexiconStructNouns
+
+ noun <- rule $ nounOfTrie nounTrieSg term nounName -- Noun with optional variable name.
+ nounList <- rule $ nounOfTrie nounTrieSg term nounNames -- Noun with a list of names.
+ nounVar <- rule $ fst <$> nounOfTrie nounTrieSg var (pure Nameless) -- No names in defined nouns.
+ nounPl <- rule $ nounOfTrie nounTriePl term nounNames
+ nounPlMay <- rule $ nounOfTrie nounTriePl term nounName
+
+
+ structNoun <- rule $ structNounOfTrie structNounTrieSg var var
+ structNounNameless <- rule $ fst <$> structNounOfTrie structNounTrieSg var (pure Nameless)
+
+
+ fun <- rule $ funOf lexicon sg term
+ funVar <- rule $ funOf lexicon sg var
+
+ attrRThat <- rule $ AttrRThat <$> thatVerbPhrase
+ attrRThats <- rule $ ((:[]) <$> attrRThat) <|> ((\a a' -> [a,a']) <$> attrRThat <* _and <*> attrRThat) <|> pure []
+ attrRs <- rule $ ((:[]) <$> adjR) <|> ((\a a' -> [a,a']) <$> adjR <* _and <*> adjR) <|> pure []
+ attrRight <- rule $ (<>) <$> attrRs <*> attrRThats
+
+ verbPhraseVerbSg <- rule $ VPVerb <$> verb
+ verbPhraseVerbNotSg <- rule $ VPVerbNot <$> (_does *> _not *> verbPl)
+ verbPhraseAdjSg <- rule $ VPAdj . (:|[]) <$> (_is *> adj)
+ verbPhraseAdjAnd <- rule do {_is; a1 <- adj; _and; a2 <- adj; pure (VPAdj (a1 :| [a2]))}
+ verbPhraseAdjNotSg <- rule $ VPAdjNot . (:|[]) <$> (_is *> _not *> adj)
+ verbPhraseNotSg <- rule $ verbPhraseVerbNotSg <|> verbPhraseAdjNotSg
+ verbPhraseSg <- rule $ verbPhraseVerbSg <|> verbPhraseAdjSg <|> verbPhraseAdjAnd <|> verbPhraseNotSg
+
+ -- LATER can cause technical ambiguities? verbPhraseVerbPl <- rule $ VPVerb <$> verbPl
+ verbPhraseVerbNotPl <- rule $ VPVerbNot <$> (_do *> _not *> verbPl)
+ verbPhraseAdjPl <- rule $ VPAdj . (:|[]) <$> (_are *> adj)
+ verbPhraseAdjNotPl <- rule $ VPAdjNot . (:|[]) <$> (_are *> _not *> adj)
+ verbPhraseNotPl <- rule $ verbPhraseVerbNotPl <|> verbPhraseAdjNotPl
+ verbPhrasePl <- rule $ verbPhraseAdjPl <|> verbPhraseNotPl -- LATER <|> verbPhraseVerbPl
+
+
+
+ thatVerbPhrase <- rule $ _that *> verbPhraseSg
+
+ nounName <- rule $ optional (math varSymbol)
+ nounNames <- rule $ math (commaList_ varSymbol) <|> pure []
+ nounPhrase <- rule $ makeNounPhrase <$> many adjL <*> noun <*> attrRight <*> optional suchStmt
+ nounPhrase' <- rule $ makeNounPhrase <$> many adjL <*> nounList <*> attrRight <*> optional suchStmt
+ nounPhrasePl <- rule $ makeNounPhrase <$> many adjL <*> nounPl <*> attrRight <*> optional suchStmt
+ nounPhrasePlMay <- rule $ makeNounPhrase <$> many adjL <*> nounPlMay <*> attrRight <*> optional suchStmt
+ nounPhraseMay <- rule $ makeNounPhrase <$> many adjL <*> noun <*> attrRight <*> optional suchStmt
+
+ -- Quantification phrases for quantification and indfinite terms.
+ quantAll <- rule $ QuantPhrase Universally <$> (_forEvery *> nounPhrase' <|> _forAll *> nounPhrasePl)
+ quantSome <- rule $ QuantPhrase Existentially <$> (_some *> (nounPhrase' <|> nounPhrasePl))
+ quantNone <- rule $ QuantPhrase Nonexistentially <$> (_no *> (nounPhrase' <|> nounPhrasePl))
+ quant <- rule $ quantAll <|> quantSome <|> quantNone -- <|> quantUniq
+
+
+ termExpr <- rule $ math do
+ e <- expr
+ pure (TermExpr e)
+ termFun <- rule $ TermFun <$> (optional _the *> fun)
+ termIota <- rule $ TermIota <$> _the <*> var <* _suchThat <*> stmt
+ termAll <- rule $ TermQuantified Universally <$> _every <*> nounPhraseMay
+ termSome <- rule $ TermQuantified Existentially <$> _some <*> nounPhraseMay
+ termNo <- rule $ TermQuantified Nonexistentially <$> _no <*> nounPhraseMay
+ termQuantified <- rule $ termAll <|> termSome <|> termNo
+ term <- rule $ termExpr <|> termFun <|> termQuantified <|> termIota
+
+-- Basic statements @stmt'@ are statements without any conjunctions or quantifiers.
+--
+ let singletonTerm = (:| []) <$> term
+ nonemptyTerms = andList1 term
+ stmtVerbSg <- rule $ StmtVerbPhrase <$> singletonTerm <*> verbPhraseSg
+ stmtVerbPl <-rule $ StmtVerbPhrase <$> andList1 term <*> verbPhrasePl
+ stmtVerb <- rule $ stmtVerbSg <|> stmtVerbPl
+ stmtNounIs <- rule do
+ ts <- singletonTerm
+ np <- _is *> _an *> nounPhrase
+ pure (StmtNoun ts np)
+ stmtNounAre <- rule do
+ ts <- nonemptyTerms <* _are
+ np <- nounPhrasePlMay
+ pure (StmtNoun ts np)
+ stmtNounIsNot <- rule do
+ ts <- singletonTerm
+ np <- _is *> _not *> _an *> nounPhrase
+ pure let t :| _ = ts in (StmtNeg (locate t) (StmtNoun ts np))
+ stmtNounAreNot <- rule do
+ ts <- nonemptyTerms
+ np <- _are *> _not *> nounPhrasePlMay
+ pure let t :| _ = ts in (StmtNeg (locate t) (StmtNoun ts np))
+ stmtNoun <- rule $ stmtNounIs <|> stmtNounIsNot <|> stmtNounAre <|> stmtNounAreNot
+ stmtStruct <- rule do
+ t <- term
+ s <- _is *> _an *> structNounNameless
+ pure (StmtStruct t s)
+ stmtExists <- rule $ StmtExists <$> _exists <*> (_an *> nounPhrase')
+ stmtExist <- rule $ StmtExists <$> _exist <*> nounPhrasePl
+ stmtExistsNot <- rule do
+ p <- _exists *> _no
+ np <- nounPhrase'
+ pure (StmtNeg p (StmtExists p np))
+ stmtFormula <- rule $ math do
+ phi <- formula
+ pure (StmtFormula phi)
+ stmtFormualNeg <- rule do
+ loc <- _not
+ phi <- math formula
+ pure (StmtNeg loc (StmtFormula phi))
+ stmtAtom <- rule $
+ stmtVerb
+ <|> stmtNoun
+ <|> stmtStruct
+ <|> stmtFormula
+ <|> stmtFormualNeg
+ <|> paren stmt
+
+ -- Textual connectives use the same precedence and associativity as
+ -- symbolic connectives. Prefix negation and quantifiers scope over the
+ -- complete statement that follows them.
+ let connect conn lhs rhs =
+ StmtConnected conn Nothing lhs rhs
+ appendScoped conn lhs rhs scoped =
+ let connected = foldl' (connect conn) lhs rhs
+ in maybe connected (connect conn connected) scoped
+ stmtAnd <- rule do
+ lhs <- stmtAtom
+ rhs <- many (_and *> stmtAtom)
+ scoped <- optional (_and *> stmtScoped)
+ pure (appendScoped Conjunction lhs rhs scoped)
+ stmtXor <- rule $
+ StmtConnected ExclusiveOr
+ <$> (Just <$> _either)
+ <*> stmtAnd
+ <* _or
+ <*> stmtAnd
+ stmtNor <- rule $
+ StmtConnected NegatedDisjunction
+ <$> (Just <$> _neither)
+ <*> stmtAnd
+ <* _nor
+ <*> stmtAnd
+ stmtOrBase <- rule $ stmtXor <|> stmtNor <|> stmtAnd
+ stmtOr <- rule do
+ lhs <- stmtOrBase
+ rhs <- many (_or *> stmtOrBase)
+ scoped <- optional (_or *> stmtScoped)
+ pure (appendScoped Disjunction lhs rhs scoped)
+ stmtIf <- rule $
+ StmtConnected Implication
+ <$> (Just <$> _if)
+ <*> stmtIfAntecedent
+ <* optional _comma
+ <* _then
+ <*> stmtImpRhs
+ stmtImp <- rule $ stmtIf <|> stmtOr
+ stmtIff <- rule do
+ lhs <- stmtImp
+ rhs <- optional (_iff *> stmtImpRhs)
+ pure case rhs of
+ Nothing -> lhs
+ Just rhs' -> connect Equivalence lhs rhs'
+ stmtNeg <- rule $ StmtNeg <$> _itIsWrong <*> stmt
+
+ stmtQuantPhrase <- rule $ StmtQuantPhrase <$> _for <*> quant <* optional _comma <* optional _have <*> stmt
+
+ suchStmt <- rule $ _suchThat *> stmt <* optional _comma
+
+ -- Symbolic quantifications with or without generalized bounds.
+ symbolicForall <- rule do
+ p <- _forAll <|> _forEvery
+ xs <- beginMath *> varSymbols
+ b <- maybeBounded <* endMath
+ ms <- optional suchStmt
+ s <- optional _have *> stmt
+ pure (SymbolicForall p xs b ms s)
+ symbolicExists <- rule do
+ loc1 <- _exists <|> _exist
+ xs <- beginMath *> varSymbols
+ b <- maybeBounded
+ loc2 <- endMath
+ ms <- optional (_suchThat *> stmt)
+ pure (SymbolicExists loc1 xs b (ms ?? StmtFormula (PropositionalConstant loc2 IsTop)))
+ symbolicNotExists <- rule do
+ p <- _exists *> _no
+ xs <- beginMath *> varSymbols
+ b <- maybeBounded <* endMath
+ s <- _suchThat *> stmt
+ pure (makeSymbolicNotExists p xs b s)
+ symbolicBound <- rule $ (\sign rel e -> Bounded (locate rel) sign rel e) <$> relationSign <*> relation <*> expr
+ maybeBounded <- rule (pure Unbounded <|> symbolicBound)
+
+ symbolicQuantified <- rule $ symbolicForall <|> symbolicExists <|> symbolicNotExists
+
+ stmtScoped <- rule $
+ asum
+ [ stmtNeg
+ , stmtExists
+ , stmtExist
+ , stmtExistsNot
+ , stmtQuantPhrase
+ , symbolicQuantified
+ ]
+ stmtIfAntecedent <- rule $ stmtScoped <|> stmtOr
+ stmtImpRhs <- rule $ stmtScoped <|> stmtImp
+ stmt :: Prod r Text (Located Token) Stmt <- rule $
+ (stmtScoped <|> stmtIff) <?> "a statement"
+
+
+ asmLetIn <- rule $ uncurry AsmLetIn <$> (_let *> math typing)
+ asmLetNoun <- rule $ AsmLetNoun <$> (_let *> fmap pure var <* (_be <|> _denote) <* _an) <*> nounPhrase
+ asmLetNouns <- rule $ AsmLetNoun <$> (_let *> vars <* (_be <|> _denote)) <*> nounPhrasePlMay
+ asmLetEq <- rule $ uncurry AsmLetEq <$> (_let *> math assignment)
+ asmLetThe <- rule $ AsmLetThe <$> (_let *> var <* _be <* _the) <*> fun
+ asmLetStruct <- rule $ AsmLetStruct <$> (_let *> var <* _be <* _an) <*> structNounNameless
+ asmLet <- rule $ asmLetNoun <|> asmLetNouns <|> asmLetIn <|> asmLetEq <|> asmLetThe <|> asmLetStruct
+ asmSuppose <- rule $ AsmSuppose <$> (_suppose *> stmt)
+ asm <- rule $ andList1_ (asmLet <|> asmSuppose) <* _dot
+ asms <- rule $ concat <$> many asm
+
+ axiom <- rule $ Axiom <$> asms <* optional _then <*> stmt <* _dot
+
+ claim <- rule $ (,) <$> asms <* optional _then <*> stmt <* _dot
+
+ defnAdj <- rule $ DefnAdj <$> optional (_an *> nounPhrase) <*> var <* _is <*> adjVar
+ defnVerb <- rule $ DefnVerb <$> optional (_an *> nounPhrase) <*> var <*> verbVar
+ defnNoun <- rule $ DefnNoun <$> var <* _is <* _an <*> nounVar
+ defnRel <- rule $ DefnRel <$> (beginMath *> varSymbol) <*> relator <*> many (group varSymbol) <*> varSymbol <* endMath
+ defnSymbolicPredicate <- rule $ math $ asum $ do
+ (predi, marker) <- lexiconPrefixPredicates
+ pure (prefixPredicateOf (\predi' args -> DefnSymbolicPredicate predi' marker args) varSymbol predi)
+ defnHead <- rule $ optional _write *> asum [defnAdj, defnVerb, defnNoun, defnRel, defnSymbolicPredicate]
+
+ defnIf <- rule $ Defn <$> asms <*> defnHead <* (_iff <|> _if) <*> stmt <* _dot
+ defnFunSymb <- rule $ _comma *> termExpr <* _comma -- Optional symbolic equivalent.
+ defnFun <- rule $ DefnFun <$> asms <*> (optional _the *> funVar) <*> optional defnFunSymb <* _is <*> term <* _dot
+
+ symbolicPatternEqTerm <- rule do
+ pat <- beginMath *> symbolicPattern <* _eq
+ e <- expr <* endMath <* _dot
+ pure (pat, e)
+ defnOp <- rule $ uncurry DefnOp <$> symbolicPatternEqTerm
+
+ defn <- rule $ defnIf <|> defnFun <|> defnOp
+
+ abbreviationVerb <- rule $ AbbreviationVerb <$> var <*> verbVar <* (_iff <|> _if) <*> stmt <* _dot
+ abbreviationAdj <- rule $ AbbreviationAdj <$> var <* _is <*> adjVar <* (_iff <|> _if) <*> stmt <* _dot
+ abbreviationNoun <- rule $ AbbreviationNoun <$> var <* _is <* _an <*> nounVar <* (_iff <|> _if) <*> stmt <* _dot
+ abbreviationRel <- rule $ AbbreviationRel <$> (beginMath *> varSymbol) <*> relator <*> many (group varSymbol) <*> varSymbol <* endMath <* (_iff <|> _if) <*> stmt <* _dot
+ abbreviationFun <- rule $ AbbreviationFun <$> (_the *> funVar) <* (_is <|> _denotes) <*> term <* _dot
+ abbreviationEq <- rule $ uncurry AbbreviationEq <$> symbolicPatternEqTerm
+ abbreviation <- rule $ (abbreviationVerb <|> abbreviationAdj <|> abbreviationNoun <|> abbreviationRel <|> abbreviationFun <|> abbreviationEq)
+
+ datatypePremise <- rule $ math $ (,) <$> varSymbol <* _in <*> expr
+ datatypeClause <- rule $
+ (\(constructorExpr, targetExpr) premises -> DatatypeClause
+ { datatypeClauseConstructorExpr = constructorExpr
+ , datatypeClauseTargetExpr = targetExpr
+ , datatypeClausePremises = premises ?? []
+ }) <$> math ((,) <$> expr <* _in <*> expr)
+ <*> optional (_for *> andList1_ datatypePremise)
+ <* _dot
+ datatypeHead <- rule $ _define *> math expr <* optional _inductively <* optional _asFollows <* _dot
+ datatype <- rule $ Datatype <$> datatypeHead <*> enumerated1 datatypeClause
+
+ unconditionalIntro <- rule $ IntroRule [] <$> math formula
+ conditionalIntro <- rule $ IntroRule <$> (_if *> andList1_ (math formula)) <* _comma <* _then <*> math formula
+ inductiveIntro <- rule $ (unconditionalIntro <|> conditionalIntro) <* _dot
+ inductiveDomain <- rule $ math $ (,) <$> symbolicPattern <* _subseteq <*> expr
+ inductiveHead <- rule $ _define *> inductiveDomain <* optional _inductively <* optional _asFollows <* _dot
+ inductive <- rule $ uncurry Inductive <$> inductiveHead <*> enumerated1 inductiveIntro
+
+ signatureAdj <- rule $ SignatureAdj <$> var <* _can <* _be <*> adjOf lexicon var
+ symbolicPattern <- symbolicPatternOf mixfixItems varSymbol
+ signatureSymbolic <- rule $ SignatureSymbolic <$> math symbolicPattern <* _is <* _an <*> nounPhrase
+ signatureHead <- rule $ asum
+ [ case form of
+ AdjectiveSignatureHead -> signatureAdj
+ SymbolicSignatureHead -> signatureSymbolic
+ | form <- concreteSignatureHeadForms
+ ]
+ signature <- rule $
+ (,) <$> asms <* optional _then <*> signatureHead <* _dot
+
+ structFix <- rule do
+ beginMath
+ rawCmd <- cmd
+ endMath
+ pure (StructSymbol rawCmd)
+ structDefn <- rule $ do
+ _an
+ ~(structPhrase, structLabel) <- structNoun
+ _extends
+ structParents <- andList1_ (_an *> structNounNameless)
+ maybeFixes <- optional (_equipped *> enumerated structFix)
+ structAssumes <- (_suchThat *> enumeratedMarked (stmt <* _dot)) <|> ([] <$ _dot)
+ pure StructDefn
+ { structPhrase = structPhrase
+ , structLabel = structLabel
+ , structParents = structParents
+ , structFixes = maybeFixes ?? []
+ , structAssumes = structAssumes
+ }
+
+ justificationSet <- rule $ JustificationSetExt <$ _bySetExt
+ justificationRef <- rule $ JustificationRef <$> (_by *> ref)
+ justificationLocal <- rule $ JustificationLocal <$ (_by *> (_assumption <|> _definition))
+ justification <- rule (justificationSet <|> justificationRef <|> justificationLocal <|> pure JustificationEmpty)
+
+ trivial <- rule $ Qed . Just <$> _trivial <* _dot <*> pure JustificationEmpty
+ omitted <- rule $ Omitted <$> _omitted <* _dot
+ qedJustified <- rule $ Qed . Just <$> _follows <*> (justification <* _dot)
+ qed <- rule $ qedJustified <|> trivial <|> omitted <|> pure (Qed Nothing JustificationEmpty)
+ contradiction <- rule $ Contradiction <$> _contradiction <*> justification <* _dot
+
+ let alignedEq = symbol "&=" <?> "\"&=\""
+ explanation <- rule $ (text justification) <|> pure JustificationEmpty
+ equationItem <- rule $ (,) <$> (alignedEq *> expr) <*> explanation
+ equations <- rule $ Equation <$> expr <*> (many1 equationItem)
+
+ let alignedIff = symbol "&" *> command "iff" <?> "\"&\\iff\""
+ biconditionalItem <- rule $ (,) <$> (alignedIff *> formula) <*> explanation
+ biconditionals <- rule $ Biconditionals <$> formula <*> (many1 biconditionalItem) <* optional _dot
+
+
+ calcQuantifier <- rule do
+ loc <- _forAll <|> _forEvery
+ xs <- beginMath *> varSymbols
+ mb <- maybeBounded <* endMath
+ st <- optional suchStmt
+ optional _have
+ pure (loc, CalcQuantifier xs mb st)
+
+ calc <- rule do
+ mquant <- optional calcQuantifier
+ psteps <- align (equations <|> biconditionals)
+ pf <- proof
+ pure let (loc2, steps) = psteps in case mquant of
+ Nothing -> Calc loc2 Nothing steps pf
+ Just (loc, q) -> Calc loc (Just q) steps pf
+
+ caseOf <- rule $ command "caseOf" *> token InvisibleBraceL *> stmt <* _dot <* token InvisibleBraceR
+ byCases <- rule $ uncurry ByCase <$> envPos_ "byCase" (many1_ (Case <$> caseOf <*> proof))
+ byContradiction <- rule $ ByContradiction <$> _suppose <* _not <* _dot <*> proof
+ bySetInduction <- rule $ uncurry BySetInduction <$> proofBy (_in *> word "-induction" *> optional (word "on" *> term)) <*> proof
+ byOrdInduction <- rule $ ByOrdInduction . fst <$> proofBy (word "transfinite" *> word "induction") <*> proof
+ assume <- rule $ Assume <$> _suppose <*> (stmt <* _dot) <*> proof
+
+ fixSymbolic <- rule $ FixSymbolic <$> _fix <*> (beginMath *> varSymbols) <*> maybeBounded <* endMath <* _dot <*> proof
+ fixSuchThat <- rule $ FixSuchThat <$> _fix <*> math varSymbols <* _suchThat <*> stmt <* _dot <*> proof
+ fix <- rule $ fixSymbolic <|> fixSuchThat
+
+ takeVar <- rule $ TakeVar <$> _take <*> (beginMath *> varSymbols) <*> maybeBounded <* endMath <* _suchThat <*> stmt <*> justification <* _dot <*> proof
+ takeNoun <- rule $ TakeNoun <$> _take <*> (_an *> (nounPhrase' <|> nounPhrasePl)) <*> justification <* _dot <*> proof
+ take <- rule $ takeVar <|> takeNoun
+ suffices <- rule $ Suffices <$> _sufficesThat <*> stmt <*> (justification <* _dot) <*> proof
+ subclaim <- rule $ Subclaim <$> _show <*> (stmt <* _dot) <*> env_ "subproof" proof <*> proof
+ have <- rule do
+ msince <- optional ((,) <$> _since <*> stmt <* _comma <* _have)
+ mpos <- optional _haveIntro
+ s <- stmt
+ j <- justification <* _dot
+ pf <- proof
+ pure
+ let pos = case (msince, mpos) of
+ (Just (p, _), _) -> p
+ (_, Just p) -> p
+ _ -> locate s
+ in (Have pos (snd <$> msince) s j pf)
+
+
+ define <- rule $ Define <$> _let <*> (beginMath *> varSymbol <* _eq) <*> expr <* endMath <* _dot <*> proof
+ defineFunction <- rule $ DefineFunction <$> _let <*> (beginMath *> varSymbol) <*> paren varSymbol <* _eq <*> expr <* endMath <* _for <* beginMath <*> varSymbol <* _in <*> expr <* endMath <* _dot <*> proof
+
+ proof <- rule $ asum [byContradiction, byCases, bySetInduction, byOrdInduction, calc, subclaim, assume, fix, take, have, suffices, define, defineFunction, contradiction, qed]
+
+
+ blockAxiom <- rule $ (\(p, title, m, a) -> BlockAxiom p title m a) <$> envPos "axiom" axiom
+ blockClaim <- rule $ claimEnv claim
+ blockProof <- rule $ uncurry3 BlockProof <$> envStartEndLocation "proof" proof
+ blockDefn <- rule $ (\(p, title, m, d) -> BlockDefn p title m d) <$> envPos "definition" defn
+ blockAbbr <- rule $ (\(p, title, m, a) -> BlockAbbr p title m a) <$> envPos "abbreviation" abbreviation
+ blockData <- rule $ (\(p, title, m, d) -> BlockData p title m d) <$> envPos "datatype" datatype
+ blockInd <- rule $ (\(p, title, m, i) -> BlockInductive p title m i) <$> envPos "inductive" inductive
+ blockSig <- rule $ (\(p, title, m, (a, s)) -> BlockSig p title m a s) <$> envPos "signature" signature
+ blockStruct <- rule $ (\(p, title, m, s) -> BlockStruct p title m s) <$> envPos "struct" structDefn
+ block <- rule $ asum [blockAxiom, blockClaim, blockDefn, blockAbbr, blockData, blockInd, blockSig, blockStruct, blockProof]
+
+ -- Starting category.
+ pure block
+
+
+proofBy :: Prod r Text (Located Token) a -> Prod r Text (Located Token) (Location, a)
+proofBy method = bracket do
+ pos <- word "proof" *> word "by"
+ a <- method
+ pure (pos, a)
+
+claimEnv :: Prod r Text (Located Token) (([Asm], Stmt)) -> Prod r Text (Located Token) Block
+claimEnv content = asum
+ [ make Theorem <$> envPos "theorem" content
+ , make Lemma <$> envPos "lemma" content
+ , make Corollary <$> envPos "corollary" content
+ , make PlainClaim <$> envPos "claim" content
+ , make Proposition <$> envPos "proposition" content
+ ]
+ where
+ make kind = (\ (loc, title, m, (asms, stmt)) -> BlockClaim kind loc title m (Claim asms stmt))
+
+-- | A disjunctive list with at least two items:
+-- * 'a or b'
+-- * 'a, b, or c'
+-- * 'a, b, c, or d'
+--
+orList2 :: Prod r Text (Located Token) a -> Prod r Text (Located Token) (NonEmpty a)
+orList2 item = ((:|) <$> item <*> many (_commaOr *> item))
+ <|> ((\i j -> i:|[j]) <$> item <* _or <*> item)
+
+
+-- | Nonempty textual lists of the form "a, b, c, and d".
+-- The final comma is mandatory, 'and' is not.
+-- Also allows "a and b". Should therefore be avoided in contexts where
+-- a logical conjunction would also be possible.
+-- Currently also allows additional 'and's after each comma...
+--
+andList1 :: Prod r Text (Located Token) a -> Prod r Text (Located Token) (NonEmpty a)
+andList1 item = ((:|) <$> item <*> many (_commaAnd *> item))
+ <|> ((\i j -> i:|[j]) <$> item <* _and <*> item)
+
+-- | Like 'andList1', but drops the information about nonemptiness.
+andList1_ :: Prod r Text (Located Token) a -> Prod r Text (Located Token) [a]
+andList1_ item = NonEmpty.toList <$> andList1 item
+
+
+commaList :: Prod r Text (Located Token) a -> Prod r Text (Located Token) (NonEmpty a)
+commaList item = (:|) <$> item <*> many (_comma *> item)
+
+-- | Like 'commaList', but drops the information about nonemptiness.
+commaList_ :: Prod r Text (Located Token) a -> Prod r Text (Located Token) [a]
+commaList_ item = NonEmpty.toList <$> commaList item
+
+-- | Like 'commaList', but requires at least two items (and hence at least one comma).
+commaList2 :: Prod r Text (Located Token) a -> Prod r Text (Located Token) (NonEmpty a)
+commaList2 item = (:|) <$> item <* _comma <*> commaList_ item
+
+
+enumerated :: Prod r Text (Located Token) a -> Prod r Text (Located Token) [a]
+enumerated p = NonEmpty.toList <$> enumerated1 p
+
+enumerated1 :: Prod r Text (Located Token) a -> Prod r Text (Located Token) (NonEmpty a)
+enumerated1 p = begin "enumerate" *> many1 (command "item" *> p) <* end "enumerate" <?> "\"\\begin{enumerate} ...\""
+
+
+enumeratedMarked :: Prod r Text (Located Token) a -> Prod r Text (Located Token) [(Marker, a)]
+enumeratedMarked p = NonEmpty.toList <$> enumeratedMarked1 p
+
+enumeratedMarked1 :: Prod r Text (Located Token) a -> Prod r Text (Located Token) (NonEmpty (Marker, a))
+enumeratedMarked1 p = begin "enumerate" *> many1 ((,) <$> (command "item" *> label) <*> p) <* end "enumerate" <?> "\"\\begin{enumerate}\\item\\label{...}...\""
+
+
+
+-- This function could be rewritten, so that it can be used directly in the grammar,
+-- instead of with specialized variants.
+--
+phraseOf
+ :: forall pat a b r. Locatable a
+ => (Location -> pat -> [a] -> b)
+ -> Lexicon
+ -> (Lexicon -> [pat])
+ -> (pat -> LexicalPhrase)
+ -> Prod r Text (Located Token) a
+ -> Prod r Text (Located Token) b
+phraseOf constr lexicon selector proj arg =
+ uncurry3 constr <$> buildPhraseTrie arg trie
+ where
+ pats :: [pat]
+ pats = selector lexicon
+
+ trie :: Trie PhraseStep pat
+ trie = trieFromList
+ [ (phraseSteps (proj pat), pat)
+ | pat <- pats
+ ]
+
+adjLOf :: Locatable arg => Lexicon -> Prod r Text (Located Token) arg -> Prod r Text (Located Token) (AdjLOf arg)
+adjLOf lexicon arg = phraseOf AdjL lexicon lexiconAdjLs lexicalItemPhrase arg <?> "a left adjective"
+
+adjROf :: Locatable arg =>Lexicon -> Prod r Text (Located Token) arg -> Prod r Text (Located Token) (AdjROf arg)
+adjROf lexicon arg = phraseOf AdjR lexicon lexiconAdjRs lexicalItemPhrase arg <?> "a right adjective"
+
+adjOf :: Locatable arg =>Lexicon -> Prod r Text (Located Token) arg -> Prod r Text (Located Token) (AdjOf arg)
+adjOf lexicon arg = phraseOf Adj lexicon lexiconAdjs lexicalItemPhrase arg <?> "an adjective"
+
+verbOf
+ :: Locatable a => Lexicon
+ -> (SgPl LexicalPhrase -> LexicalPhrase)
+ -> Prod r Text (Located Token) a
+ -> Prod r Text (Located Token) (VerbOf a)
+verbOf lexicon proj arg = phraseOf Verb lexicon lexiconVerbs (proj . lexicalItemSgPlPhrase) arg
+
+funOf
+ :: Locatable a => Lexicon
+ -> (SgPl LexicalPhrase -> LexicalPhrase)
+ -> Prod r Text (Located Token) a
+ -> Prod r Text (Located Token) (FunOf a)
+funOf lexicon proj arg = phraseOf Fun lexicon lexiconFuns (proj . lexicalItemSgPlPhrase) arg <?> "functional phrase"
+
+
+-- | A noun with a @t VarSymbol@ as name(s).
+nounOf
+ :: Locatable arg => Lexicon
+ -> (SgPl LexicalPhrase -> LexicalPhrase)
+ -> Prod r Text (Located Token) arg
+ -> Prod r Text (Located Token) (t VarSymbol)
+ -> Prod r Text (Located Token) (NounOf arg, t VarSymbol)
+nounOf lexicon proj arg vars =
+ nounOfTrie (nounTrieOf proj (lexiconNouns lexicon)) arg vars
+
+nounOfTrie
+ :: Locatable arg => Trie NounStep LexicalItemSgPl
+ -> Prod r Text (Located Token) arg
+ -> Prod r Text (Located Token) (t VarSymbol)
+ -> Prod r Text (Located Token) (NounOf arg, t VarSymbol)
+nounOfTrie trie arg vars =
+ (\(loc, pat, args, xs) -> (Noun loc pat args, xs))
+ <$> buildNounTrie arg vars trie
+ <?> "a noun"
+
+nounTrieOf
+ :: (SgPl LexicalPhrase -> LexicalPhrase)
+ -> [LexicalItemSgPl]
+ -> Trie NounStep LexicalItemSgPl
+nounTrieOf proj pats = trieFromList
+ [ (nounStepsWithSlot (proj (lexicalItemSgPlPhrase pat)), pat)
+ | pat <- pats
+ ]
+
+structNounOfTrie
+ :: Locatable arg => Trie NounStep LexicalItemSgPl
+ -> Prod r Text (Located Token) arg
+ -> Prod r Text (Located Token) name
+ -> Prod r Text (Located Token) (StructPhrase, name)
+structNounOfTrie trie arg name =
+ (\(_loc, pat, _args, xs) -> (pat, xs))
+ <$> buildNounTrie arg name trie
+ <?> "a structure noun"
+
+structNounOf
+ :: Locatable arg => Lexicon
+ -> (SgPl LexicalPhrase -> LexicalPhrase)
+ -> Prod r Text (Located Token) arg
+ -> Prod r Text (Located Token) name
+ -> Prod r Text (Located Token) (StructPhrase, name)
+structNounOf lexicon proj arg name =
+ structNounOfTrie (nounTrieOf proj (lexiconStructNouns lexicon)) arg name
+
+-- Trie helpers for lexically-defined phrases.
+
+data PhraseStep
+ = PhraseTok Token
+ | PhraseHole
+ deriving (Eq, Ord)
+
+data NounStep
+ = NounTok Token
+ | NounHole
+ | NounVar
+ deriving (Eq, Ord)
+
+data Trie k v = Trie
+ { trieValues :: [v]
+ , trieEdges :: [(k, Trie k v)]
+ }
+
+emptyTrie :: Trie k v
+emptyTrie = Trie [] []
+
+insertTrie :: Eq k => [k] -> v -> Trie k v -> Trie k v
+insertTrie [] v Trie{trieValues = vs, trieEdges = es} =
+ Trie (vs <> [v]) es
+insertTrie (k:ks) v Trie{trieValues = vs, trieEdges = es} =
+ Trie vs (go es)
+ where
+ go = \case
+ [] -> [(k, insertTrie ks v emptyTrie)]
+ (k', child) : rest
+ | k == k' -> (k', insertTrie ks v child) : rest
+ | otherwise -> (k', child) : go rest
+
+trieFromList :: Eq k => [([k], v)] -> Trie k v
+trieFromList = foldl' (\tr (k, v) -> insertTrie k v tr) emptyTrie
+
+phraseSteps :: LexicalPhrase -> [PhraseStep]
+phraseSteps = map \case
+ Just tok -> PhraseTok tok
+ Nothing -> PhraseHole
+
+nounSteps :: LexicalPhrase -> [NounStep]
+nounSteps = map \case
+ Just tok -> NounTok tok
+ Nothing -> NounHole
+
+nounStepsWithSlot :: LexicalPhrase -> [NounStep]
+nounStepsWithSlot pat =
+ let (pat1, pat2) = splitOnVariableSlot pat
+ in nounSteps pat1 <> [NounVar] <> nounSteps pat2
+
+data PhraseAcc a = PhraseAcc
+ { phraseLoc :: Maybe Location
+ , phraseArgs :: [a] -> [a]
+ }
+
+emptyPhraseAcc :: PhraseAcc a
+emptyPhraseAcc = PhraseAcc Nothing id
+
+setPhraseLoc :: Location -> PhraseAcc a -> PhraseAcc a
+setPhraseLoc Nowhere acc = acc
+setPhraseLoc _loc acc@PhraseAcc{phraseLoc = Just _} = acc
+setPhraseLoc loc PhraseAcc{phraseLoc = Nothing, phraseArgs = args} =
+ PhraseAcc (Just loc) args
+
+addPhraseArg :: Locatable a => a -> PhraseAcc a -> PhraseAcc a
+addPhraseArg a acc@PhraseAcc{phraseLoc = loc, phraseArgs = args}
+ | locate a == Nowhere = acc{phraseArgs = args . (a :)}
+ | otherwise = PhraseAcc (loc <|> Just (locate a)) (args . (a :))
+
+finalizePhraseAcc :: PhraseAcc a -> (Location, [a])
+finalizePhraseAcc PhraseAcc{phraseLoc = Just loc, phraseArgs = args} =
+ (loc, args [])
+finalizePhraseAcc PhraseAcc{phraseLoc = Nothing} =
+ impossible "phraseOf: empty phrase"
+
+data NounAcc a name = NounAcc
+ { nounLoc :: Maybe Location
+ , nounArgs :: [a] -> [a]
+ , nounName :: Maybe name
+ }
+
+emptyNounAcc :: NounAcc a name
+emptyNounAcc = NounAcc Nothing id Nothing
+
+setNounLoc :: Location -> NounAcc a name -> NounAcc a name
+setNounLoc Nowhere acc = acc
+setNounLoc _loc acc@NounAcc{nounLoc = Just _} = acc
+setNounLoc loc NounAcc{nounLoc = Nothing, nounArgs = args, nounName = name} =
+ NounAcc (Just loc) args name
+
+addNounArg :: Locatable a => a -> NounAcc a name -> NounAcc a name
+addNounArg a acc@NounAcc{nounLoc = loc, nounArgs = args, nounName = name}
+ | locate a == Nowhere = acc{nounArgs = args . (a :)}
+ | otherwise = NounAcc (loc <|> Just (locate a)) (args . (a :)) name
+
+setNounName :: name -> NounAcc a name -> NounAcc a name
+setNounName name NounAcc{nounLoc = loc, nounArgs = args, nounName = Nothing} =
+ NounAcc loc args (Just name)
+setNounName _ acc@NounAcc{nounName = Just _} = acc
+
+finalizeNounAcc :: NounAcc a name -> (Location, [a], name)
+finalizeNounAcc NounAcc{nounLoc = Just loc, nounArgs = args, nounName = Just name} =
+ (loc, args [], name)
+finalizeNounAcc NounAcc{nounName = Nothing} =
+ impossible "nounOf: missing variable slot"
+finalizeNounAcc NounAcc{nounLoc = Nothing} =
+ impossible "nounOf: empty noun phrase"
+
+buildPhraseTrie
+ :: Locatable a
+ => Prod r Text (Located Token) a
+ -> Trie PhraseStep pat
+ -> Prod r Text (Located Token) (Location, pat, [a])
+buildPhraseTrie arg trie =
+ let stepParser = \case
+ PhraseTok tok -> setPhraseLoc <$> tokenPos tok
+ PhraseHole -> addPhraseArg <$> arg
+ finish f =
+ let (acc, pat) = f emptyPhraseAcc
+ (loc, args) = finalizePhraseAcc acc
+ in (loc, pat, args)
+ in finish <$> buildTrieProd stepParser trie
+
+buildTrieProd
+ :: (step -> Prod r Text (Located Token) (acc -> acc))
+ -> Trie step pat
+ -> Prod r Text (Located Token) (acc -> (acc, pat))
+buildTrieProd stepParser = go
+ where
+ go Trie{trieValues = pats, trieEdges = edges} =
+ let leafs = asum [pure (\acc -> (acc, pat)) | pat <- pats]
+ edgesProds = asum
+ [ liftA2 (\f g -> g . f) (stepParser step) (go sub)
+ | (step, sub) <- edges
+ ]
+ in leafs <|> edgesProds
+
+buildNounTrie
+ :: Locatable a
+ => Prod r Text (Located Token) a
+ -> Prod r Text (Located Token) name
+ -> Trie NounStep pat
+ -> Prod r Text (Located Token) (Location, pat, [a], name)
+buildNounTrie arg vars trie =
+ let stepParser = \case
+ NounTok tok -> setNounLoc <$> tokenPos tok
+ NounHole -> addNounArg <$> arg
+ NounVar -> setNounName <$> vars
+ finish f =
+ let (acc, pat) = f emptyNounAcc
+ (loc, args, name) = finalizeNounAcc acc
+ in (loc, pat, args, name)
+ in finish <$> buildTrieProd stepParser trie
+
+
+symbolicPatternOf
+ :: forall r. [[MixfixItem]]
+ -> Prod r Text (Located Token) VarSymbol
+ -> Grammar r (Prod r Text (Located Token) SymbolPattern)
+symbolicPatternOf ops varSymbol = rule $
+ (tuplePattern <|> asum
+ [ go item
+ | ops' <- ops
+ , item <- ops'
+ ]) <?> "a symbolic pattern"
+ where
+ tuplePattern = do
+ token ParenL
+ first <- varSymbol <* token (Symbol ",")
+ second <- varSymbol <* token ParenR
+ pure (SymbolPattern PairSymbol [first, second])
+
+ go :: MixfixItem -> Prod r Text (Located Token) SymbolPattern
+ go item = SymbolPattern item <$> parseVars (mixfixPattern item)
+
+ parseVars :: Pattern -> Prod r Text (Located Token) [VarSymbol]
+ parseVars = \case
+ End -> pure []
+ TokenCons tok pat -> token tok *> parseVars pat
+ HoleCons pat -> (:) <$> varSymbol <*> parseVars pat
+
+
+makeNounPhrase
+ :: [AdjL]
+ -> (Noun, t VarSymbol)
+ -> [AdjR]
+ -> Maybe Stmt
+ -> NounPhrase t
+makeNounPhrase ls (n, vs) rs ms = NounPhrase ls n vs rs ms
+
+
+
+
+begin, end :: Text -> Prod r Text (Located Token) Location
+begin kind = tokenPos (BeginEnv kind) <?> ("\"\\begin{" <> kind <> "}\"")
+end kind = tokenPos (EndEnv kind) <?> ("\"\\end{" <> kind <> "}\"")
+
+-- | Surround a production rule @body@ with an environment of a certain @kind@ requiring a marker specified in a @\\label@.
+envPos :: Text -> Prod r Text (Located Token) a -> Prod r Text (Located Token) (Location, Maybe [Token], Marker, a)
+envPos kind body = do
+ p <- begin kind <?> ("start of a \"" <> kind <> "\" environment")
+ mt <- optional title
+ m <- label
+ a <- body <* end kind
+ pure (p, mt, m, a)
+ where
+ title :: Prod r Text (Located Token) [Token]
+ title = bracket (many (unLocated <$> satisfy (\ltok -> unLocated ltok /= BracketR)))
+
+-- 'env_' is like 'env', but without allowing titles.
+--
+envPos_ :: Text -> Prod r Text (Located Token) a -> Prod r Text (Located Token) (Location, a)
+envPos_ kind body = (,) <$> begin kind <*> (optional label *> body) <* end kind
+
+envStartEndLocation :: Text -> Prod r Text (Located Token) a -> Prod r Text (Located Token) (Location, a, Location)
+envStartEndLocation kind body = (,,) <$> begin kind <*> (optional label *> body) <*> end kind
+
+env_ :: Text -> Prod r Text (Located Token) a -> Prod r Text (Located Token) a
+env_ kind body = begin kind *> optional label *> body <* end kind
+
+-- | A label specifying a marker for referencing via /@\\label{...}@/. Returns the marker text.
+label :: Prod r Text (Located Token) Marker
+label = label_ <?> "\"\\label{...}\""
+ where
+ label_ = terminal \ltok -> case unLocated ltok of
+ Label m -> Just (Marker m)
+ _tok -> Nothing
+
+-- | A reference via /@\\ref{...}@/. Returns the markers as text.
+ref :: Prod r Text (Located Token) (NonEmpty Marker)
+ref = terminal \ltok -> case unLocated ltok of
+ Ref ms -> Just (Marker <$> ms)
+ _tok -> Nothing
+
+math :: Prod r Text (Located Token) a -> Prod r Text (Located Token) a
+math body = beginMath *> body <* endMath
+
+mathPos :: Prod r Text (Located Token) a -> Prod r Text (Located Token) (Location, a)
+mathPos body = (,) <$> beginMath <*> body <* endMath
+
+text :: Prod r Text (Located Token) a -> Prod r Text (Located Token) a
+text body = begin "text" *> body <* end "text" <?> "\"\\text{...}\""
+
+beginMath, endMath :: Prod r Text (Located Token) Location
+beginMath = begin "math" <?> "start of a formula, e.g. \"$\""
+endMath = end "math" <?> "end of a formula, e.g. \"$\""
+
+paren :: Prod r Text (Located Token) a -> Prod r Text (Located Token) a
+paren body = token ParenL *> body <* token ParenR <?> "\"(...)\""
+
+bracket :: Prod r Text (Located Token) a -> Prod r Text (Located Token) a
+bracket body = token BracketL *> body <* token BracketR <?> "\"[...]\""
+
+brace :: Prod r Text (Located Token) a -> Prod r Text (Located Token) a
+brace body = token VisibleBraceL *> body <* token VisibleBraceR <?> "\"\\{...\\}\""
+
+group :: Prod r Text (Located Token) a -> Prod r Text (Located Token) a
+group body = token InvisibleBraceL *> body <* token InvisibleBraceR <?> "\"{...}\""
+
+align :: Prod r Text (Located Token) a -> Prod r Text (Located Token) (Location, a)
+align body = (,) <$> begin "align*" <*> body <* end "align*"
+
+cases :: Prod r Text (Located Token) a -> Prod r Text (Located Token) a
+cases body = begin "cases" *> body <* end "cases"
+
+
+maybeVarToken :: Located Token -> Maybe VarSymbol
+maybeVarToken ltok = case unLocated ltok of
+ Variable x -> Just (NamedVarAt (startPos ltok) x)
+ _tok -> Nothing
+
+maybeWordToken :: Located Token -> Maybe Text
+maybeWordToken ltok = case unLocated ltok of
+ Word n -> Just n
+ _tok -> Nothing
+
+maybeIntToken :: Located Token -> Maybe Int
+maybeIntToken ltok = case unLocated ltok of
+ Integer n -> Just n
+ _tok -> Nothing
+
+maybeIntTokenWithLoc :: Located Token -> Maybe (Location, Int)
+maybeIntTokenWithLoc ltok = case unLocated ltok of
+ Integer n -> Just (startPos ltok, n)
+ _tok -> Nothing
+
+maybeCmdToken :: Located Token -> Maybe Text
+maybeCmdToken ltok = case unLocated ltok of
+ Command n -> Just n
+ _tok -> Nothing
+
+structSymbol :: StructSymbol -> Prod r Text (Located Token) StructSymbol
+structSymbol s@(StructSymbol c) = terminal \ltok -> case unLocated ltok of
+ Command c' | c == c' -> Just s
+ _ -> Nothing
+
+structSymbolPos :: StructSymbol -> Prod r Text (Located Token) (Location, StructSymbol)
+structSymbolPos s@(StructSymbol c) = terminal \ltok -> case unLocated ltok of
+ Command c' | c == c' -> Just (startPos ltok, s)
+ _ -> Nothing
+
+-- | Tokens that are allowed to appear in labels of environments.
+maybeTagToken :: Located Token -> Maybe Text
+maybeTagToken ltok = case unLocated ltok of
+ Symbol "'" ->Just "'"
+ Symbol "-" -> Just ""
+ _ -> maybeWordToken ltok
+
+
+token :: Token -> Prod r Text (Located Token) Token
+token tok = terminal maybeToken <?> tokToText tok
+ where
+ maybeToken ltok = case unLocated ltok of
+ tok' | tok == tok' -> Just tok
+ _ -> Nothing
+
+tokenLocated :: Token -> Prod r Text (Located Token) (Located Token)
+tokenLocated tok = terminal maybeToken <?> tokToText tok
+ where
+ maybeToken ltok = case unLocated ltok of
+ tok' | tok == tok' -> Just ltok
+ _ -> Nothing
+
+tokenPos :: Token -> Prod r Text (Located Token) Location
+tokenPos tok = terminal maybeToken <?> tokToText tok
+ where
+ maybeToken ltok = case unLocated ltok of
+ tok' | tok == tok' -> Just (startPos ltok)
+ _ -> Nothing
diff --git a/source/Felix/Syntax/Concrete/Keywords.hs b/source/Felix/Syntax/Concrete/Keywords.hs
new file mode 100644
index 0000000..eb6d09c
--- /dev/null
+++ b/source/Felix/Syntax/Concrete/Keywords.hs
@@ -0,0 +1,228 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-|
+This module defines lots of keywords and various filler
+phrases. The prefix underscore indicates that we do not
+care about the parse result (analogous to discarding
+like @...; _ <- action; ...@ in do-notation). Moreover,
+this convention allows the use of short names that would
+otherwise be Haskell keywords or clash with other definitions.
+Care should be taken with introducing too many variants of
+a keyword, lest the grammar becomes needlessly ambiguous!
+
+The names are chosen using the following criteria:
+
+ * As short as possible (e.g.: @_since@ over @_because@).
+
+ * Sound like a keyword (e.g.: @_show@).
+
+This module also defines symbols that have special uses
+(such as @_colon@ for its use in type signatures).
+-}
+module Felix.Syntax.Concrete.Keywords where
+
+
+import Base
+import Felix.Syntax.Token
+import Felix.Report.Location
+
+import Text.Earley (Prod, (<?>), terminal)
+
+infixr 0 ?
+-- | Variant of '<?>' for annotating literal tokens.
+(?) :: Prod r Text t a -> Text -> Prod r Text t a
+p ? e = p <?> ("\"" <> e <> "\"")
+
+word :: Text -> Prod r Text (Located Token) Location
+word w = terminal maybeToken
+ where
+ maybeToken ltok = case unLocated ltok of
+ Word w' | w == w' -> Just (startPos ltok)
+ _ -> Nothing
+
+symbol :: Text -> Prod r Text (Located Token) Location
+symbol s = terminal maybeToken
+ where
+ maybeToken ltok = case unLocated ltok of
+ Symbol s' | s == s' -> Just (startPos ltok)
+ _ -> Nothing
+
+command :: Text -> Prod r Text (Located Token) Location
+command cmd = terminal maybeToken
+ where
+ maybeToken ltok = case unLocated ltok of
+ Command cmd' | cmd == cmd' -> Just (startPos ltok)
+ _ -> Nothing
+
+_arity :: Prod r Text (Located Token) Int
+_arity = asum
+ [ 1 <$ word "unary"
+ , 2 <$ word "binary"
+ , 3 <$ word "ternary"
+ , 4 <$ word "quaternary"
+ , 5 <$ word "quinary"
+ , 6 <$ word "senary"
+ , 7 <$ word "septenary"
+ , 8 <$ word "octonary"
+ , 9 <$ word "nonary"
+ , 10 <$ word "denary"
+ ] <?> "\"unary\", \"binary\', ..."
+
+-- * Keywords
+
+_an :: Prod r Text (Located Token) Location
+_an = word "a" <|> word "an" <?> "indefinite article"
+_and :: Prod r Text (Located Token) Location
+_and = word "and" ? "and"
+_are :: Prod r Text (Located Token) Location
+_are = word "are" ? "are"
+_asFollows :: Prod r Text (Located Token) Location
+_asFollows = word "as" <* word "follows" ? "as follows"
+_assumption :: Prod r Text (Located Token) Location
+_assumption = word "assumption" ? "assumption"
+_be :: Prod r Text (Located Token) Location
+_be = word "be" ? "be"
+_by :: Prod r Text (Located Token) Location
+_by = word "by" ? "by"
+_bySetExt :: Prod r Text (Located Token) Location
+_bySetExt = word "by" <* ((word "set" ? "set") <* word "extensionality") ? "by set extensionality"
+_can :: Prod r Text (Located Token) Location
+_can = word "can" ? "can"
+_consistsOf :: Prod r Text (Located Token) Location
+_consistsOf = word "consists" <* word "of" ? "consists of"
+_contradiction :: Prod r Text (Located Token) Location
+_contradiction = optional (word "a") *> word "contradiction" ? "a contradiction"
+_define :: Prod r Text (Located Token) Location
+_define = word "define" ? "define"
+_definition :: Prod r Text (Located Token) Location
+_definition = word "definition" ? "definition"
+_denote :: Prod r Text (Located Token) Location
+_denote = word "denote" <|> (word "stand" <* word "for") ? "denote"
+_denotes :: Prod r Text (Located Token) Location
+_denotes = word "denotes" ? "denotes"
+_do :: Prod r Text (Located Token) Location
+_do = word "do" ? "do"
+_does :: Prod r Text (Located Token) Location
+_does = word "does" ? "does"
+_either :: Prod r Text (Located Token) Location
+_either = word "either" ? "either"
+_equipped :: Prod r Text (Located Token) Location
+_equipped = (word "equipped" <|> word "together") <* word "with" ? "equipped with"
+_every :: Prod r Text (Located Token) Location
+_every = word "every" ? "every"
+_exist :: Prod r Text (Located Token) Location
+_exist = word "there" <* word "exist" ? "there exist"
+_exists :: Prod r Text (Located Token) Location
+_exists = word "there" <* word "exists" ? "there exists"
+_extends :: Prod r Text (Located Token) Location
+_extends = (_is) <|> (word "consists" <* word "of") ? "consists of"
+_fix :: Prod r Text (Located Token) Location
+_fix = word "fix" ? "fix"
+_follows :: Prod r Text (Located Token) Location
+_follows = word "follows" ? "follows"
+_for :: Prod r Text (Located Token) Location
+_for = word "for" ? "for"
+_forAll :: Prod r Text (Located Token) Location
+_forAll = (word "for" <* word "all") <|> word "all" ? "all"
+_forEvery :: Prod r Text (Located Token) Location
+_forEvery = (word "for" <* word "every") <|> word "every" ? "for every"
+_have :: Prod r Text (Located Token) Location
+_have = word "we" <* word "have" <* optional (word "that") ? "we have"
+_if :: Prod r Text (Located Token) Location
+_if = word "if" ? "if"
+_iff :: Prod r Text (Located Token) Location
+_iff = word "iff" <|> (word "if" <* word "and" <* word "only" <* word "if") ? "iff"
+_inductively :: Prod r Text (Located Token) Location
+_inductively = word "inductively" ? "inductively"
+_is :: Prod r Text (Located Token) Location
+_is = word "is" ? "is"
+_itIsWrong :: Prod r Text (Located Token) Location
+_itIsWrong = word "it" <* word "is" <* (word "not" <* word "the" <* word "case" <|> word "wrong") <* word "that" ? "it is wrong that"
+_let :: Prod r Text (Located Token) Location
+_let = word "let" ? "let"
+_neither :: Prod r Text (Located Token) Location
+_neither = word "neither" ? "neither"
+_no :: Prod r Text (Located Token) Location
+_no = word "no" ? "no"
+_nor :: Prod r Text (Located Token) Location
+_nor = word "nor" ? "nor"
+_not :: Prod r Text (Located Token) Location
+_not = word "not" ? "not"
+_omitted :: Prod r Text (Located Token) Location
+_omitted = word "omitted" ? "omitted"
+_on :: Prod r Text (Located Token) Location
+_on = word "on" ? "on"
+_oneOf :: Prod r Text (Located Token) Location
+_oneOf = word "one" <* word "of" ? "one of"
+_or :: Prod r Text (Located Token) Location
+_or = word "or" ? "or"
+_particularly :: Prod r Text (Located Token) Location
+_particularly = (word "particularly" <|> (word "in" *> word "particular")) <* _comma ? "particularly"
+_relation :: Prod r Text (Located Token) Location
+_relation = word "relation" ? "relation"
+_satisfying :: Prod r Text (Located Token) Location
+_satisfying = _suchThat <|> word "satisfying" ? "satisfying"
+_setOf :: Prod r Text (Located Token) Location
+_setOf = word "set" <* word "of" ? "set of"
+_now :: Prod r Text (Located Token) Location
+_now = (word "then" <|> word "next" <|> word "now" <|> word "first" <|> word "finally" <|> word "subsequently" <|> word "ultimately")
+_show :: Prod r Text (Located Token) Location
+_show = optional _now *> optional (word "we") *> word "show" <* optional (word "that")
+_since :: Prod r Text (Located Token) Location
+_since = word "since" <|> word "because" ? "since"
+_some :: Prod r Text (Located Token) Location
+_some = word "some" ? "some"
+_suchThat :: Prod r Text (Located Token) Location
+_suchThat = ((word "such" <* word "that") <|> (word "s" <* _dot <* word "t" <* _dot)) ? "such that"
+_sufficesThat :: Prod r Text (Located Token) Location
+_sufficesThat = word "it" <* word "suffices" <* word "to" <* word "show" <* word "that" ? "it suffices to show"
+_suppose :: Prod r Text (Located Token) Location
+_suppose = (word "suppose" <|> word "assume") <* optional (word "that") ? "assume"
+_take :: Prod r Text (Located Token) Location
+_take = optional _now *> (word "take" <|> word "consider") ? "take"
+_that :: Prod r Text (Located Token) Location
+_that = word "that" ? "that"
+_the :: Prod r Text (Located Token) Location
+_the = word "the" ? "the"
+_then :: Prod r Text (Located Token) Location
+_then = word "then" ? "then"
+_thus :: Prod r Text (Located Token) Location
+_thus = word "thus" <|> word "hence" <|> _now <|> word "therefore" ? "thus"
+_trivial :: Prod r Text (Located Token) Location
+_trivial = word "straightforward" <|> word "trivial" ? "trivial"
+_unique :: Prod r Text (Located Token) Location
+_unique = word "unique" ? "unique"
+_write :: Prod r Text (Located Token) Location
+_write = (optional (word "we") *> word "say" <* optional (word "that")) <|> (optional (word "we") *> word "write") ? "write"
+
+-- | Introducing plain claims in proofs.
+_haveIntro :: Prod r Text (Located Token) Location
+_haveIntro = _thus <|> _particularly <|> _have
+
+-- * Symbols
+
+_colon :: Prod r Text (Located Token) Location
+_colon = symbol ":" ? ":"
+_pipe :: Prod r Text (Located Token) Location
+_pipe = (optional (command "middle") *> symbol "|") <|> command "mid" ? "\\mid"
+_comma :: Prod r Text (Located Token) Location
+_comma = symbol "," ? ","
+_commaAnd :: Prod r Text (Located Token) Location
+_commaAnd = symbol "," <* optional (word "and") ? ", and"
+_commaOr :: Prod r Text (Located Token) Location
+_commaOr = symbol "," <* optional (word "or") ? ", or"
+_defeq :: Prod r Text (Located Token) Location
+_defeq = symbol ":=" ? ":=" -- Should use `\coloneq` from unicode-math as display.
+_dot :: Prod r Text (Located Token) Location
+_dot = symbol "." ? "."
+_eq :: Prod r Text (Located Token) Location
+_eq = symbol "=" ? "="
+_in :: Prod r Text (Located Token) Location
+_in = command "in" ? "\\in"
+_subseteq :: Prod r Text (Located Token) Location
+_subseteq = command "subseteq" ? "\\subseteq"
+_to :: Prod r Text (Located Token) Location
+_to = command "to" ? "\\to"
+_mapsto :: Prod r Text (Located Token) Location
+_mapsto = command "mapsto" ? "\\mapsto"
+_ampersand :: Prod r Text (Located Token) Location
+_ampersand = symbol "&" ? "&"
diff --git a/source/Felix/Syntax/Interface.hs b/source/Felix/Syntax/Interface.hs
new file mode 100644
index 0000000..1820705
--- /dev/null
+++ b/source/Felix/Syntax/Interface.hs
@@ -0,0 +1,887 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Syntax.Interface
+ ( MixfixLevel
+ , mixfixLevel
+ , mixfixLevelValue
+ , MixfixLevelError(..)
+ , Fixity(..)
+ , sourcePragmaFixity
+ , CanonicalLexicalEntry(..)
+ , canonicalLexicalSurfacePatterns
+ , eligibleExpressionPattern
+ , CanonicalSyntaxDelta
+ , canonicalSyntaxDelta
+ , canonicalSyntaxDeltaEntries
+ , canonicalSyntaxDeltaSize
+ , CanonicalSyntaxCollision
+ , canonicalCollisionPattern
+ , canonicalCollisionEntries
+ , CanonicalSyntaxDeltaId
+ , canonicalSyntaxDeltaId
+ , canonicalSyntaxDeltaIdDigest
+ , BaseSyntaxInterfaceId
+ , baseSyntaxInterfaceId
+ , baseSyntaxInterfaceIdDigest
+ , baseSyntaxManifest
+ , fixedBaseSyntaxEntries
+ , SyntaxInterfaceId
+ , syntaxInterfaceIdDigest
+ , ModuleSyntaxInterface
+ , moduleSyntaxInterface
+ , moduleSyntaxBase
+ , moduleSyntaxDirectInputs
+ , moduleSyntaxLocalDelta
+ , moduleSyntaxAssertedId
+ , SyntaxInterfaceError(..)
+ , validateModuleSyntaxInterface
+ , putCanonicalLexicalEntryCache
+ , getCanonicalLexicalEntryCache
+ , putPatternCache
+ , getPatternCache
+ , putTokenCache
+ , getTokenCache
+ , putCanonicalSyntaxDeltaCache
+ , getCanonicalSyntaxDeltaCache
+ , putModuleSyntaxInterfaceCache
+ , getModuleSyntaxInterfaceCache
+ , putBaseSyntaxInterfaceIdCache
+ , getBaseSyntaxInterfaceIdCache
+ , putSyntaxInterfaceIdCache
+ , getSyntaxInterfaceIdCache
+ ) where
+
+import Base
+
+import Felix.Cache.Codec
+import Felix.Syntax.Abstract
+import Felix.Syntax.Lexicon
+import Felix.Syntax.Pragma
+
+import Control.DeepSeq (NFData)
+import Control.Monad (unless)
+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.Word (Word8)
+import Numeric.Natural (Natural)
+
+
+newtype MixfixLevel = MixfixLevel Word8
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+data MixfixLevelError
+ = MixfixLevelOutOfRange !Word8
+ deriving stock (Show, Eq)
+
+mixfixLevel :: Word8 -> Either MixfixLevelError MixfixLevel
+mixfixLevel supplied
+ | supplied <= 9 =
+ Right (MixfixLevel supplied)
+ | otherwise =
+ Left (MixfixLevelOutOfRange supplied)
+
+mixfixLevelValue :: MixfixLevel -> Word8
+mixfixLevelValue (MixfixLevel level) =
+ level
+
+data Fixity = Fixity
+ { fixityAssociativity :: !Associativity
+ , fixityLevel :: !MixfixLevel
+ } deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+sourcePragmaFixity :: SyntaxPragma -> Fixity
+sourcePragmaFixity pragma =
+ Fixity
+ (syntaxPragmaAssociativity pragma)
+ (MixfixLevel
+ (sourceMixfixLevelValue
+ (syntaxPragmaLevel pragma)))
+
+-- | Complete origin-free lexical semantics produced by scanning.
+data CanonicalLexicalEntry
+ = CanonicalLeftAdjective !Pattern !Marker
+ | CanonicalRightAdjective !Pattern !Marker
+ | CanonicalFunctionPhrase !Pattern !Pattern !Marker
+ | CanonicalNoun !Pattern !Pattern !Marker
+ | CanonicalStructureNoun !Pattern !Pattern !Marker
+ | CanonicalVerb !Pattern !Pattern !Marker
+ | CanonicalRelation !Token !ParameterArity !Marker
+ | CanonicalExpressionFunction !Pattern !Marker !Fixity
+ | CanonicalPrefixPredicate !Text !Natural !Marker
+ | CanonicalStructureOperation !Text
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+-- | Every surface pattern through which the concrete parser can select an
+-- entry. Equal singular and plural surfaces are returned only once.
+canonicalLexicalSurfacePatterns
+ :: CanonicalLexicalEntry
+ -> NonEmpty Pattern
+canonicalLexicalSurfacePatterns =
+ deduplicatePatterns . \case
+ CanonicalLeftAdjective pat _marker ->
+ [pat]
+ CanonicalRightAdjective pat _marker ->
+ [pat]
+ CanonicalFunctionPhrase singular _plural _marker ->
+ [singular]
+ CanonicalNoun singular plural _marker ->
+ [singular, plural]
+ CanonicalStructureNoun singular _plural _marker ->
+ [singular]
+ CanonicalVerb singular plural _marker ->
+ [singular, plural]
+ CanonicalRelation token arity marker ->
+ [ relationSymbolPattern
+ (RelationSymbol token arity marker)
+ ]
+ CanonicalExpressionFunction pat _marker _fixity ->
+ [pat]
+ CanonicalPrefixPredicate command _arity _marker ->
+ [ prefixPredicatePattern
+ (PrefixPredicate command 0)
+ ]
+ CanonicalStructureOperation command ->
+ [structSymbolPattern (StructSymbol command)]
+ where
+ deduplicatePatterns patterns =
+ case NonEmpty.nonEmpty
+ (Set.toList (Set.fromList patterns)) of
+ Just nonempty ->
+ nonempty
+ Nothing ->
+ impossible
+ "canonical lexical entry has no parser surface"
+
+eligibleExpressionPattern :: Pattern -> Bool
+eligibleExpressionPattern pat =
+ case patternToHoley pat of
+ Nothing : rest ->
+ case reverse rest of
+ Nothing : middleReversed ->
+ let middle =
+ reverse middleReversed
+ in
+ countHoles middle == 0
+ && any isJust middle
+ _ ->
+ False
+ _ ->
+ False
+ where
+ countHoles =
+ length . List.filter isNothing
+
+newtype CanonicalSyntaxDelta =
+ CanonicalSyntaxDelta
+ [CanonicalLexicalEntry]
+ deriving stock (Show, Eq, Generic)
+ deriving anyclass (NFData)
+
+data CanonicalSyntaxCollision = CanonicalSyntaxCollision
+ { canonicalCollisionPattern :: !Pattern
+ , canonicalCollisionEntries
+ :: !(NonEmpty CanonicalLexicalEntry)
+ } deriving stock (Show, Eq)
+
+canonicalSyntaxDelta
+ :: [CanonicalLexicalEntry]
+ -> Either CanonicalSyntaxCollision CanonicalSyntaxDelta
+canonicalSyntaxDelta supplied =
+ case orderedCollisions of
+ [] ->
+ Right (CanonicalSyntaxDelta orderedEntries)
+ (_encodedPattern, pat, entries) : _ ->
+ Left
+ (CanonicalSyntaxCollision
+ pat
+ (case NonEmpty.nonEmpty
+ (canonicalEntryOrder
+ (Set.toList entries)) of
+ Just collisionEntries ->
+ collisionEntries
+ Nothing ->
+ impossible
+ "canonical syntax collision has no entries"))
+ where
+ orderedEntries =
+ canonicalEntryOrder
+ (Set.toList (Set.fromList supplied))
+
+ grouped =
+ foldl'
+ (\entries entry ->
+ foldl'
+ (\indexed pat ->
+ Map.insertWith
+ Set.union
+ pat
+ (Set.singleton entry)
+ indexed)
+ entries
+ (canonicalLexicalSurfacePatterns entry))
+ mempty
+ orderedEntries
+
+ orderedCollisions =
+ List.sortOn
+ (\(encodedPattern, _pattern, _entries) ->
+ encodedPattern)
+ [ ( encodeCache (putPatternCache pat)
+ , pat
+ , entries
+ )
+ | (pat, entries) <- Map.toList grouped
+ , Set.size entries > 1
+ ]
+
+canonicalSyntaxDeltaEntries
+ :: CanonicalSyntaxDelta
+ -> [CanonicalLexicalEntry]
+canonicalSyntaxDeltaEntries
+ (CanonicalSyntaxDelta entries) =
+ entries
+
+canonicalSyntaxDeltaSize :: CanonicalSyntaxDelta -> Int
+canonicalSyntaxDeltaSize
+ (CanonicalSyntaxDelta entries) =
+ length entries
+
+canonicalEntryOrder
+ :: [CanonicalLexicalEntry]
+ -> [CanonicalLexicalEntry]
+canonicalEntryOrder =
+ List.sortOn
+ (encodeCache . putCanonicalLexicalEntryCache)
+
+newtype CanonicalSyntaxDeltaId =
+ CanonicalSyntaxDeltaId CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+canonicalSyntaxDeltaId
+ :: CanonicalSyntaxDelta
+ -> CanonicalSyntaxDeltaId
+canonicalSyntaxDeltaId delta =
+ CanonicalSyntaxDeltaId
+ (hashCacheFields
+ "felix-syntax-delta-v1"
+ [encodeCache
+ (putCanonicalSyntaxDeltaCache delta)])
+
+canonicalSyntaxDeltaIdDigest
+ :: CanonicalSyntaxDeltaId
+ -> CacheDigest
+canonicalSyntaxDeltaIdDigest
+ (CanonicalSyntaxDeltaId digest) =
+ digest
+
+newtype BaseSyntaxInterfaceId =
+ BaseSyntaxInterfaceId CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+baseSyntaxInterfaceId :: BaseSyntaxInterfaceId
+baseSyntaxInterfaceId =
+ BaseSyntaxInterfaceId
+ (hashCacheFields
+ "felix-base-syntax-interface-v1"
+ [encodeCache
+ (putCacheList
+ putManifestRow
+ baseSyntaxManifest)])
+ where
+ putManifestRow =
+ putCacheList putCanonicalLexicalEntryCache
+ . canonicalEntryOrder
+
+baseSyntaxInterfaceIdDigest
+ :: BaseSyntaxInterfaceId
+ -> CacheDigest
+baseSyntaxInterfaceIdDigest
+ (BaseSyntaxInterfaceId digest) =
+ digest
+
+baseSyntaxManifest :: [[CanonicalLexicalEntry]]
+baseSyntaxManifest =
+ case traverse makeRow
+ (zip [0 :: Word8 ..] builtinMixfixLevels) of
+ Right rows
+ | length rows == 10 ->
+ rows
+ _ ->
+ impossible
+ "the fixed mixfix manifest does not have ten valid rows"
+ where
+ makeRow (row, entries) = do
+ level <- mixfixLevel row
+ pure
+ [ CanonicalExpressionFunction
+ pat
+ marker
+ (Fixity associativity level)
+ | MixfixItem pat marker associativity <-
+ entries
+ ]
+
+-- | Every fixed lexical entry consulted by the concrete parser. The base
+-- syntax identity above commits to the ten expression rows; the remaining
+-- fixed categories are compiler input covered by the cache epoch.
+fixedBaseSyntaxEntries :: [CanonicalLexicalEntry]
+fixedBaseSyntaxEntries =
+ case canonicalSyntaxDelta rawEntries of
+ Right delta ->
+ canonicalSyntaxDeltaEntries delta
+ Left collision ->
+ impossible
+ ("fixed base syntax contains a collision: "
+ <> show collision)
+ where
+ rawEntries =
+ concat baseSyntaxManifest
+ <> (canonicalAdjective CanonicalLeftAdjective
+ <$> lexiconAdjLs builtins)
+ <> (canonicalAdjective CanonicalRightAdjective
+ <$> lexiconAdjRs builtins)
+ <> (canonicalSgPl CanonicalFunctionPhrase
+ <$> lexiconFuns builtins)
+ <> (canonicalSgPl CanonicalNoun
+ <$> lexiconNouns builtins)
+ <> (canonicalSgPl CanonicalStructureNoun
+ <$> lexiconStructNouns builtins)
+ <> (canonicalSgPl CanonicalVerb
+ <$> lexiconVerbs builtins)
+ <> (canonicalRelation
+ <$> lexiconRelationSymbols builtins)
+ <> (canonicalPrefix
+ <$> lexiconPrefixPredicates builtins)
+ <> (canonicalStructure
+ <$> lexiconStructFun builtins)
+
+ canonicalAdjective constructor item =
+ constructor
+ (lexicalItemPattern item)
+ (lexicalItemMarker item)
+
+ canonicalSgPl constructor item =
+ let patterns =
+ lexicalItemSgPlPattern item
+ in
+ constructor
+ (sg patterns)
+ (pl patterns)
+ (lexicalItemSgPlMarker item)
+
+ canonicalRelation relation =
+ CanonicalRelation
+ (relationSymbolToken relation)
+ (relationSymbolParameterArity relation)
+ (relationSymbolMarker relation)
+
+ canonicalPrefix
+ (PrefixPredicate command arity, marker) =
+ CanonicalPrefixPredicate
+ command
+ (fromIntegral arity)
+ marker
+
+ canonicalStructure (StructSymbol command) =
+ CanonicalStructureOperation command
+
+newtype SyntaxInterfaceId =
+ SyntaxInterfaceId CacheDigest
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving newtype (Hashable, NFData)
+
+syntaxInterfaceIdDigest :: SyntaxInterfaceId -> CacheDigest
+syntaxInterfaceIdDigest (SyntaxInterfaceId digest) =
+ digest
+
+data ModuleSyntaxInterface = ModuleSyntaxInterface
+ !BaseSyntaxInterfaceId
+ ![SyntaxInterfaceId]
+ !CanonicalSyntaxDelta
+ !SyntaxInterfaceId
+ deriving stock (Show, Eq, Generic)
+ deriving anyclass (NFData)
+
+moduleSyntaxBase
+ :: ModuleSyntaxInterface
+ -> BaseSyntaxInterfaceId
+moduleSyntaxBase
+ (ModuleSyntaxInterface base _direct _delta _asserted) =
+ base
+
+moduleSyntaxDirectInputs
+ :: ModuleSyntaxInterface
+ -> [SyntaxInterfaceId]
+moduleSyntaxDirectInputs
+ (ModuleSyntaxInterface _base direct _delta _asserted) =
+ direct
+
+moduleSyntaxLocalDelta
+ :: ModuleSyntaxInterface
+ -> CanonicalSyntaxDelta
+moduleSyntaxLocalDelta
+ (ModuleSyntaxInterface _base _direct delta _asserted) =
+ delta
+
+moduleSyntaxAssertedId
+ :: ModuleSyntaxInterface
+ -> SyntaxInterfaceId
+moduleSyntaxAssertedId
+ (ModuleSyntaxInterface _base _direct _delta asserted) =
+ asserted
+
+data SyntaxInterfaceError
+ = DuplicateDirectSyntaxInterface !SyntaxInterfaceId
+ | UnexpectedBaseSyntaxInterface
+ !BaseSyntaxInterfaceId
+ !BaseSyntaxInterfaceId
+ | SyntaxInterfaceIdMismatch
+ !SyntaxInterfaceId
+ !SyntaxInterfaceId
+ deriving stock (Show, Eq)
+
+moduleSyntaxInterface
+ :: [SyntaxInterfaceId]
+ -> CanonicalSyntaxDelta
+ -> Either SyntaxInterfaceError ModuleSyntaxInterface
+moduleSyntaxInterface direct delta =
+ validateModuleSyntaxInterface
+ baseSyntaxInterfaceId
+ direct
+ delta
+ (computeSyntaxInterfaceId
+ baseSyntaxInterfaceId
+ direct
+ delta)
+
+validateModuleSyntaxInterface
+ :: BaseSyntaxInterfaceId
+ -> [SyntaxInterfaceId]
+ -> CanonicalSyntaxDelta
+ -> SyntaxInterfaceId
+ -> Either SyntaxInterfaceError ModuleSyntaxInterface
+validateModuleSyntaxInterface base direct delta asserted = do
+ unless
+ (base == baseSyntaxInterfaceId)
+ (Left
+ (UnexpectedBaseSyntaxInterface
+ base
+ baseSyntaxInterfaceId))
+ case firstDuplicate direct of
+ Just duplicate ->
+ Left
+ (DuplicateDirectSyntaxInterface duplicate)
+ Nothing ->
+ pure ()
+ let computed =
+ computeSyntaxInterfaceId base direct delta
+ unless
+ (asserted == computed)
+ (Left
+ (SyntaxInterfaceIdMismatch
+ asserted
+ computed))
+ Right
+ (ModuleSyntaxInterface
+ base
+ direct
+ delta
+ asserted)
+
+computeSyntaxInterfaceId
+ :: BaseSyntaxInterfaceId
+ -> [SyntaxInterfaceId]
+ -> CanonicalSyntaxDelta
+ -> SyntaxInterfaceId
+computeSyntaxInterfaceId base direct delta =
+ SyntaxInterfaceId
+ (hashCacheFields
+ "felix-syntax-interface-v1"
+ [ cacheDigestBytes
+ (baseSyntaxInterfaceIdDigest base)
+ , encodeCache
+ (putCacheList
+ putSyntaxInterfaceIdCache
+ direct)
+ , cacheDigestBytes
+ (canonicalSyntaxDeltaIdDigest
+ (canonicalSyntaxDeltaId delta))
+ ])
+
+firstDuplicate :: Ord value => [value] -> Maybe value
+firstDuplicate =
+ go mempty
+ where
+ go _seen [] =
+ Nothing
+ go seen (value : rest)
+ | value `Set.member` seen =
+ Just value
+ | otherwise =
+ go (Set.insert value seen) rest
+
+putCanonicalLexicalEntryCache
+ :: CanonicalLexicalEntry
+ -> CachePut
+putCanonicalLexicalEntryCache = \case
+ CanonicalLeftAdjective pat marker -> do
+ putCacheTag 0x00
+ putPatternCache pat
+ putMarkerCache marker
+ CanonicalRightAdjective pat marker -> do
+ putCacheTag 0x01
+ putPatternCache pat
+ putMarkerCache marker
+ CanonicalFunctionPhrase singular plural marker -> do
+ putCacheTag 0x02
+ putPatternCache singular
+ putPatternCache plural
+ putMarkerCache marker
+ CanonicalNoun singular plural marker -> do
+ putCacheTag 0x03
+ putPatternCache singular
+ putPatternCache plural
+ putMarkerCache marker
+ CanonicalStructureNoun singular plural marker -> do
+ putCacheTag 0x04
+ putPatternCache singular
+ putPatternCache plural
+ putMarkerCache marker
+ CanonicalVerb singular plural marker -> do
+ putCacheTag 0x05
+ putPatternCache singular
+ putPatternCache plural
+ putMarkerCache marker
+ CanonicalRelation token arity marker -> do
+ putCacheTag 0x06
+ putTokenCache token
+ putCacheNatural (parameterArityValue arity)
+ putMarkerCache marker
+ CanonicalExpressionFunction pat marker fixity -> do
+ putCacheTag 0x07
+ putPatternCache pat
+ putMarkerCache marker
+ putFixityCache fixity
+ CanonicalPrefixPredicate command arity marker -> do
+ putCacheTag 0x08
+ putCacheText command
+ putCacheNatural arity
+ putMarkerCache marker
+ CanonicalStructureOperation command -> do
+ putCacheTag 0x09
+ putCacheText command
+
+getCanonicalLexicalEntryCache
+ :: CacheGet CanonicalLexicalEntry
+getCanonicalLexicalEntryCache =
+ getCacheTag >>= \case
+ 0x00 ->
+ CanonicalLeftAdjective
+ <$> getPatternCache
+ <*> getMarkerCache
+ 0x01 ->
+ CanonicalRightAdjective
+ <$> getPatternCache
+ <*> getMarkerCache
+ 0x02 ->
+ CanonicalFunctionPhrase
+ <$> getPatternCache
+ <*> getPatternCache
+ <*> getMarkerCache
+ 0x03 ->
+ CanonicalNoun
+ <$> getPatternCache
+ <*> getPatternCache
+ <*> getMarkerCache
+ 0x04 ->
+ CanonicalStructureNoun
+ <$> getPatternCache
+ <*> getPatternCache
+ <*> getMarkerCache
+ 0x05 ->
+ CanonicalVerb
+ <$> getPatternCache
+ <*> getPatternCache
+ <*> getMarkerCache
+ 0x06 ->
+ CanonicalRelation
+ <$> getTokenCache
+ <*> (ParameterArity <$> getCacheNatural)
+ <*> getMarkerCache
+ 0x07 ->
+ CanonicalExpressionFunction
+ <$> getPatternCache
+ <*> getMarkerCache
+ <*> getFixityCache
+ 0x08 ->
+ CanonicalPrefixPredicate
+ <$> getCacheText
+ <*> getCacheNatural
+ <*> getMarkerCache
+ 0x09 ->
+ CanonicalStructureOperation
+ <$> getCacheText
+ tag ->
+ fail
+ ("unknown canonical lexical entry tag "
+ <> show tag)
+
+putCanonicalSyntaxDeltaCache
+ :: CanonicalSyntaxDelta
+ -> CachePut
+putCanonicalSyntaxDeltaCache
+ (CanonicalSyntaxDelta entries) =
+ putCacheList putCanonicalLexicalEntryCache entries
+
+getCanonicalSyntaxDeltaCache
+ :: CacheGet CanonicalSyntaxDelta
+getCanonicalSyntaxDeltaCache = do
+ supplied <- getCacheList getCanonicalLexicalEntryCache
+ case canonicalSyntaxDelta supplied of
+ Left collision ->
+ fail
+ ("colliding cached canonical syntax entries: "
+ <> show collision)
+ Right delta
+ | canonicalSyntaxDeltaEntries delta == supplied ->
+ pure delta
+ | otherwise ->
+ fail
+ "cached canonical syntax entries are not in canonical order"
+
+putModuleSyntaxInterfaceCache
+ :: ModuleSyntaxInterface
+ -> CachePut
+putModuleSyntaxInterfaceCache
+ (ModuleSyntaxInterface base direct delta asserted) = do
+ putBaseSyntaxInterfaceIdCache base
+ putCacheList putSyntaxInterfaceIdCache direct
+ putCanonicalSyntaxDeltaCache delta
+ putSyntaxInterfaceIdCache asserted
+
+getModuleSyntaxInterfaceCache
+ :: CacheGet ModuleSyntaxInterface
+getModuleSyntaxInterfaceCache = do
+ base <- getBaseSyntaxInterfaceIdCache
+ direct <- getCacheList getSyntaxInterfaceIdCache
+ delta <- getCanonicalSyntaxDeltaCache
+ asserted <- getSyntaxInterfaceIdCache
+ case
+ validateModuleSyntaxInterface
+ base
+ direct
+ delta
+ asserted of
+ Left err ->
+ fail
+ ("invalid module syntax interface: "
+ <> show err)
+ Right interface ->
+ pure interface
+
+putBaseSyntaxInterfaceIdCache
+ :: BaseSyntaxInterfaceId
+ -> CachePut
+putBaseSyntaxInterfaceIdCache
+ (BaseSyntaxInterfaceId digest) =
+ putCacheDigest digest
+
+getBaseSyntaxInterfaceIdCache
+ :: CacheGet BaseSyntaxInterfaceId
+getBaseSyntaxInterfaceIdCache =
+ BaseSyntaxInterfaceId <$> getCacheDigest
+
+putSyntaxInterfaceIdCache
+ :: SyntaxInterfaceId
+ -> CachePut
+putSyntaxInterfaceIdCache
+ (SyntaxInterfaceId digest) =
+ putCacheDigest digest
+
+getSyntaxInterfaceIdCache
+ :: CacheGet SyntaxInterfaceId
+getSyntaxInterfaceIdCache =
+ SyntaxInterfaceId <$> getCacheDigest
+
+putFixityCache :: Fixity -> CachePut
+putFixityCache (Fixity associativity level) = do
+ putAssociativityCache associativity
+ putCacheTag (mixfixLevelValue level)
+
+getFixityCache :: CacheGet Fixity
+getFixityCache = do
+ associativity <- getAssociativityCache
+ suppliedLevel <- getCacheTag
+ case mixfixLevel suppliedLevel of
+ Left err ->
+ fail ("invalid cached mixfix level: " <> show err)
+ Right level ->
+ pure (Fixity associativity level)
+
+putAssociativityCache :: Associativity -> CachePut
+putAssociativityCache =
+ putCacheTag . \case
+ LeftAssoc ->
+ 0x00
+ RightAssoc ->
+ 0x01
+ NonAssoc ->
+ 0x02
+
+getAssociativityCache :: CacheGet Associativity
+getAssociativityCache =
+ getCacheTag >>= \case
+ 0x00 ->
+ pure LeftAssoc
+ 0x01 ->
+ pure RightAssoc
+ 0x02 ->
+ pure NonAssoc
+ tag ->
+ fail
+ ("unknown associativity tag " <> show tag)
+
+putMarkerCache :: Marker -> CachePut
+putMarkerCache (Marker marker) =
+ putCacheText marker
+
+getMarkerCache :: CacheGet Marker
+getMarkerCache =
+ Marker <$> getCacheText
+
+putPatternCache :: Pattern -> CachePut
+putPatternCache = \case
+ End ->
+ putCacheTag 0x00
+ HoleCons rest -> do
+ putCacheTag 0x01
+ putPatternCache rest
+ TokenCons token rest -> do
+ putCacheTag 0x02
+ putTokenCache token
+ putPatternCache rest
+
+getPatternCache :: CacheGet Pattern
+getPatternCache =
+ getCacheTag >>= \case
+ 0x00 ->
+ pure End
+ 0x01 ->
+ HoleCons <$> getPatternCache
+ 0x02 ->
+ TokenCons
+ <$> getTokenCache
+ <*> getPatternCache
+ tag ->
+ fail
+ ("unknown lexical pattern tag " <> show tag)
+
+putTokenCache :: Token -> CachePut
+putTokenCache = \case
+ Word text -> do
+ putCacheTag 0x00
+ putCacheText text
+ Variable text -> do
+ putCacheTag 0x01
+ putCacheText text
+ Symbol text -> do
+ putCacheTag 0x02
+ putCacheText text
+ Integer integer -> do
+ putCacheTag 0x03
+ putCacheInteger (toInteger integer)
+ Command text -> do
+ putCacheTag 0x04
+ putCacheText text
+ Label text -> do
+ putCacheTag 0x05
+ putCacheText text
+ Ref references -> do
+ putCacheTag 0x06
+ putCacheList putCacheText (toList references)
+ BeginEnv text -> do
+ putCacheTag 0x07
+ putCacheText text
+ EndEnv text -> do
+ putCacheTag 0x08
+ putCacheText text
+ ParenL ->
+ putCacheTag 0x09
+ ParenR ->
+ putCacheTag 0x0a
+ BracketL ->
+ putCacheTag 0x0b
+ BracketR ->
+ putCacheTag 0x0c
+ VisibleBraceL ->
+ putCacheTag 0x0d
+ VisibleBraceR ->
+ putCacheTag 0x0e
+ InvisibleBraceL ->
+ putCacheTag 0x0f
+ InvisibleBraceR ->
+ putCacheTag 0x10
+
+getTokenCache :: CacheGet Token
+getTokenCache =
+ getCacheTag >>= \case
+ 0x00 ->
+ Word <$> getCacheText
+ 0x01 ->
+ Variable <$> getCacheText
+ 0x02 ->
+ Symbol <$> getCacheText
+ 0x03 ->
+ Integer <$> getCacheInt
+ 0x04 ->
+ Command <$> getCacheText
+ 0x05 ->
+ Label <$> getCacheText
+ 0x06 -> do
+ references <- getCacheList getCacheText
+ case NonEmpty.nonEmpty references of
+ Nothing ->
+ fail "cached reference token has no marker"
+ Just nonempty ->
+ pure (Ref nonempty)
+ 0x07 ->
+ BeginEnv <$> getCacheText
+ 0x08 ->
+ EndEnv <$> getCacheText
+ 0x09 ->
+ pure ParenL
+ 0x0a ->
+ pure ParenR
+ 0x0b ->
+ pure BracketL
+ 0x0c ->
+ pure BracketR
+ 0x0d ->
+ pure VisibleBraceL
+ 0x0e ->
+ pure VisibleBraceR
+ 0x0f ->
+ pure InvisibleBraceL
+ 0x10 ->
+ pure InvisibleBraceR
+ tag ->
+ fail ("unknown lexical token tag " <> show tag)
+
+getCacheInt :: CacheGet Int
+getCacheInt = do
+ integer <- getCacheInteger
+ if integer < toInteger (minBound :: Int)
+ || integer > toInteger (maxBound :: Int)
+ then
+ fail "cached integer token exceeds Int"
+ else
+ pure (fromInteger integer)
diff --git a/source/Felix/Syntax/Internal.hs b/source/Felix/Syntax/Internal.hs
new file mode 100644
index 0000000..d129947
--- /dev/null
+++ b/source/Felix/Syntax/Internal.hs
@@ -0,0 +1,830 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE ViewPatterns #-}
+
+-- | Data types for the internal (semantic) syntax tree.
+module Felix.Syntax.Internal
+ ( module Felix.Syntax.Internal
+ , module Felix.Syntax.Abstract
+ , module Felix.Syntax.LexicalPhrase
+ , module Felix.Syntax.Token
+ ) where
+
+
+import Base
+import Felix.Syntax.Lexicon
+ ( pattern PairSymbol
+ , pattern UnionsSymbol
+ , pattern UpairSymbol
+ )
+import Felix.Syntax.LexicalPhrase (unsafeReadPhrase, unsafeReadPhraseSgPl)
+import Felix.Syntax.Token (Token(..))
+import Felix.Report.Location
+
+import Felix.Syntax.Abstract
+ ( Chain(..)
+ , Associativity(..)
+ , Connective(..)
+ , VarSymbol(..)
+ , pattern NamedVar
+ , pattern FreshVar
+ , FunctionSymbol
+ , SymbolPattern(..)
+ , MixfixItem(..)
+ , Pattern(..)
+ , LexicalItem
+ , LexicalItemSgPl
+ , RelationSymbol(..)
+ , ParameterArity(..)
+ , PrefixPredicate(..)
+ , StructSymbol (..)
+ , Relation
+ , PropositionalConstant(..)
+ , StructPhrase
+ , Justification(..)
+ , Marker(..)
+ , markerFromToken
+ , lexicalItemMarker
+ , lexicalItemSgPlMarker
+ , mkLexicalItem
+ , mkLexicalItemSgPl
+ , relationSymbolMarker
+ , relationSymbolParameterArity
+ , relationSymbolToken
+ , parameterArityOf
+ , parameterArityValue
+ , zeroParameterArity
+ , mixfixMarker
+ , mkMixfixItem
+ , pattern CarrierSymbol, pattern ConsSymbol, pattern ElementSymbol
+ , pattern NotElementSymbol, pattern EqSymbol, pattern NeqSymbol, pattern SubseteqSymbol
+ )
+
+import Bound
+import Bound.Scope
+import Data.Deriving (deriveShow1, deriveEq1, deriveOrd1)
+import Data.Hashable.Lifted
+import Data.HashMap.Strict qualified as HM
+import Data.List qualified as List
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Set qualified as Set
+
+-- | 'Symbol's can be used as function and relation symbols.
+data Symbol
+ = SymbolMixfix FunctionSymbol
+ | SymbolFun LexicalItemSgPl
+ | SymbolInteger Int
+ | SymbolPredicate Predicate
+ deriving (Show, Eq, Ord, Generic, Hashable)
+
+
+data Predicate
+ = PredicateAdj LexicalItem
+ | PredicateVerb LexicalItemSgPl
+ | PredicateNoun LexicalItemSgPl -- ^ /@\<...\> is a \<...\>@/.
+ | PredicateRelation RelationSymbol
+ | PredicateSymbol Text
+ | PredicateNounStruct LexicalItemSgPl -- ^ /@\<...\> is a \<...\>@/.
+ deriving (Show, Eq, Ord, Generic, Hashable)
+
+
+-- | The object-language marker of an ownable symbol.
+objectSymbolMarker :: Symbol -> Maybe Marker
+objectSymbolMarker = \case
+ SymbolMixfix symbol ->
+ Just (mixfixMarker symbol)
+ SymbolFun symbol ->
+ Just (lexicalItemSgPlMarker symbol)
+ SymbolInteger{} ->
+ Nothing
+ SymbolPredicate predicate ->
+ Just (predicateObjectMarker predicate)
+
+-- | The object-language marker of a predicate.
+predicateObjectMarker :: Predicate -> Marker
+predicateObjectMarker = \case
+ PredicateAdj item ->
+ lexicalItemMarker item
+ PredicateVerb item ->
+ lexicalItemSgPlMarker item
+ PredicateNoun item ->
+ lexicalItemSgPlMarker item
+ PredicateRelation relation ->
+ relationSymbolMarker relation
+ PredicateSymbol text ->
+ Marker text
+ PredicateNounStruct item ->
+ lexicalItemSgPlMarker item
+
+
+data Quantifier
+ = Universally
+ | Existentially
+ deriving (Show, Eq, Ord, Generic, Hashable)
+
+type Formula = Term
+type Term = Expr
+type Expr = ExprOf VarSymbol
+
+
+-- | Internal higher-order expressions.
+data ExprOf a
+ = TermVar a
+ -- ^ Fresh constants disjoint from all user-named identifiers.
+ -- These can be used to eliminate higher-order constructs.
+ --
+ | TermSymbol Location Symbol [ExprOf a]
+ -- ^ Application of a symbol (including function and predicate symbols).
+ | TermSymbolStruct StructSymbol (Maybe (ExprOf a))
+ --
+ | Apply (ExprOf a) (NonEmpty (ExprOf a))
+ -- ^ Higher-order application.
+ --
+ | TermSep VarSymbol (ExprOf a) (Scope () ExprOf a)
+ -- ^ Set comprehension using seperation, e.g.: /@{ x ∈ X | P(x) }@/.
+ --
+ | ReplacePred VarSymbol VarSymbol (ExprOf a) (Scope ReplacementVar ExprOf a)
+ -- ^ Replacement for single-valued predicates. The concrete syntax for these
+ -- syntactically requires a bounded existential quantifier in the condition:
+ --
+ -- /@$\\{ y | \\exists x\\in A. P(x,y) \\}$@/
+ --
+ -- In definitions the single-valuedness of @P@ becomes a proof obligation.
+ -- In other cases we could instead add it as constraint
+ --
+ -- /@$b\\in \\{ y | \\exists x\\in A. P(x,y) \\}$@/
+ -- /@iff@/
+ -- /@$\\exists x\\in A. P(x,y)$ and $P$ is single valued@/
+ --
+ --
+ | ReplaceFun (NonEmpty (VarSymbol, ExprOf a)) (Scope VarSymbol ExprOf a) (Scope VarSymbol ExprOf a)
+ -- ^ Set comprehension using functional replacement,
+ -- e.g.: /@{ f(x, y) | x ∈ X; y ∈ Y; P(x, y) }@/.
+ -- The list of pairs gives the domains, the integers in the scope point to list indices.
+ -- The first scope is the lhs, the optional scope can be used for additional constraints
+ -- on the variables (i.e. implicit separation over the product of the domains).
+ -- An out-of-bound index is an error, since otherwise replacement becomes unsound.
+ --
+ | Connected Connective (ExprOf a) (ExprOf a)
+ | Lambda (Scope VarSymbol ExprOf a)
+ | Quantified Quantifier (Scope VarSymbol ExprOf a)
+ | PropositionalConstant PropositionalConstant
+ | Not Location (ExprOf a)
+ deriving (Functor, Foldable, Traversable)
+
+-- | Best source location carried by an elaborated expression.
+exprLocation :: Expr -> Location
+exprLocation = \case
+ TermVar variable -> locate variable
+ TermSymbol location _symbol _arguments -> location
+ TermSymbolStruct _symbol expression ->
+ maybe Nowhere exprLocation expression
+ Apply function _arguments -> exprLocation function
+ TermSep variable _bound _predicate -> locate variable
+ ReplacePred value _domain _bound _predicate -> locate value
+ ReplaceFun ((variable, _domain) :| _remaining) _value _condition ->
+ locate variable
+ Connected _connective left _right -> exprLocation left
+ Lambda{} -> Nowhere
+ Quantified{} -> Nowhere
+ PropositionalConstant{} -> Nowhere
+ Not location _term -> location
+
+data ReplacementVar = ReplacementDomVar | ReplacementRangeVar deriving (Show, Eq, Ord, Generic, Hashable)
+
+makeBound ''ExprOf
+
+deriveShow1 ''ExprOf
+deriveEq1 ''ExprOf
+deriveOrd1 ''ExprOf
+
+deriving instance Show a => Show (ExprOf a)
+deriving instance Eq a => Eq (ExprOf a)
+deriving instance Ord a => Ord (ExprOf a)
+
+deriving instance Generic (ExprOf a)
+deriving instance Generic1 ExprOf
+
+deriving instance Hashable1 ExprOf
+
+deriving instance Hashable a => Hashable (ExprOf a)
+
+mentionedSymbols :: ExprOf a -> Set Symbol
+mentionedSymbols = \case
+ TermVar{} ->
+ mempty
+ TermSymbol _loc symbol args ->
+ Set.insert symbol (Set.unions (mentionedSymbols <$> args))
+ TermSymbolStruct _symbol expr ->
+ maybe mempty mentionedSymbols expr
+ Apply expr args ->
+ mentionedSymbols expr <> Set.unions (mentionedSymbols <$> toList args)
+ TermSep _x bound scope ->
+ mentionedSymbols bound <> mentionedSymbols (fromScope scope)
+ ReplacePred _y _x bound scope ->
+ mentionedSymbols bound <> mentionedSymbols (fromScope scope)
+ ReplaceFun bounds lhs cond ->
+ Set.unions (mentionedSymbols . snd <$> toList bounds)
+ <> mentionedSymbols (fromScope lhs)
+ <> mentionedSymbols (fromScope cond)
+ Connected _conn left right ->
+ mentionedSymbols left <> mentionedSymbols right
+ Lambda scope ->
+ mentionedSymbols (fromScope scope)
+ Quantified _quant scope ->
+ mentionedSymbols (fromScope scope)
+ PropositionalConstant{} ->
+ mempty
+ Not _loc expr ->
+ mentionedSymbols expr
+
+abstractVarSymbol :: VarSymbol -> ExprOf VarSymbol -> Scope VarSymbol ExprOf VarSymbol
+abstractVarSymbol x = abstract (\y -> if x == y then Just x else Nothing)
+
+abstractVarSymbols :: Foldable t => t VarSymbol -> ExprOf VarSymbol -> Scope VarSymbol ExprOf VarSymbol
+abstractVarSymbols xs = abstract (\y -> if y `elem` xs then Just y else Nothing)
+
+
+forgetLocation :: forall a. ExprOf a -> ExprOf a
+forgetLocation = \case
+ TermVar a ->
+ TermVar a
+
+ TermSymbol _loc symb args ->
+ TermSymbol Nowhere symb (map forgetLocation args)
+
+ TermSymbolStruct ss me ->
+ TermSymbolStruct ss (forgetLocation <$> me)
+
+ Apply f args ->
+ Apply (forgetLocation f) (forgetLocation <$> args)
+
+ TermSep v dom sc ->
+ TermSep v (forgetLocation dom) (hoistScope forgetLocation sc)
+
+ ReplacePred v1 v2 dom sc ->
+ ReplacePred v1 v2 (forgetLocation dom) (hoistScope forgetLocation sc)
+
+ ReplaceFun doms lhs rhs ->
+ ReplaceFun
+ (fmap (fmap forgetLocation) doms)
+ (hoistScope forgetLocation lhs)
+ (hoistScope forgetLocation rhs)
+
+ Connected c e1 e2 ->
+ Connected c (forgetLocation e1) (forgetLocation e2)
+
+ Lambda sc ->
+ Lambda (hoistScope forgetLocation sc)
+
+ Quantified q sc ->
+ Quantified q (hoistScope forgetLocation sc)
+
+ PropositionalConstant pc ->
+ PropositionalConstant pc
+
+ Not _loc e ->
+ Not Nowhere (forgetLocation e)
+
+
+equivalent :: Eq a => ExprOf a -> ExprOf a -> Bool
+equivalent e1 e2 = forgetLocation e1 == forgetLocation e2
+
+-- | Use the given set of in scope structures to cast them to their carriers
+-- when occurring on the rhs of the element relation.
+-- Use the given 'Map' to annotate (unannotated) structure operations
+-- with the most recent inscope appropriate label.
+annotateWith :: Set VarSymbol -> HashMap StructSymbol VarSymbol -> Formula -> Formula
+annotateWith = go
+ where
+ go :: (Ord a) => Set a -> HashMap StructSymbol a -> ExprOf a -> ExprOf a
+ go labels ops = \case
+ TermSymbolStruct symb Nothing ->
+ -- TODO error if symbol is not instantiated, but only in theorems?
+ TermSymbolStruct symb (TermVar <$> HM.lookup symb ops)
+ TermSymbolStruct symb (Just e) ->
+ TermSymbolStruct symb (Just (go labels ops e))
+ IsElementOf loc1 a (TermVar x) | x `Set.member` labels ->
+ IsElementOf loc1 (go labels ops a) (TermSymbolStruct CarrierSymbol (Just (TermVar x)))
+ Not loc a ->
+ Not loc (go labels ops a)
+ Connected conn a b ->
+ Connected conn (go labels ops a) (go labels ops b)
+ Quantified quant body ->
+ Quantified quant (toScope (go (Set.map F labels) (F <$> ops) (fromScope body)))
+ e@TermVar{} -> e
+ TermSymbol loc symb args ->
+ TermSymbol loc symb (go labels ops <$> args)
+ Apply e1 args ->
+ Apply (go labels ops e1) (go labels ops <$> args)
+ TermSep vs e scope ->
+ TermSep vs (go labels ops e) (toScope (go (Set.map F labels) (F <$> ops) (fromScope scope)))
+ ReplacePred y x xB scope ->
+ ReplacePred y x (go labels ops xB) (toScope (go (Set.map F labels) (F <$> ops) (fromScope scope)))
+ ReplaceFun bounds ap cond ->
+ ReplaceFun
+ (fmap (\(x, e) -> (x, go labels ops e)) bounds)
+ (toScope (go (Set.map F labels) (F <$> ops) (fromScope ap)))
+ (toScope (go (Set.map F labels) (F <$> ops) (fromScope cond)))
+ Lambda body ->
+ Lambda (toScope (go (Set.map F labels) (F <$> ops) (fromScope body)))
+ e@PropositionalConstant{} -> e
+
+containsHigherOrderConstructs :: ExprOf a -> Bool
+containsHigherOrderConstructs = \case
+ TermSep {} -> True
+ ReplacePred{}-> True
+ ReplaceFun{}-> True
+ Lambda{} -> True
+ Apply{} -> False -- FIXME: this is a lie in general; we need to add sortchecking to determine this.
+ TermVar{} -> False
+ PropositionalConstant{} -> False
+ TermSymbol _loc _s es -> any containsHigherOrderConstructs es
+ Not _loc e -> containsHigherOrderConstructs e
+ Connected _ e1 e2 -> containsHigherOrderConstructs e1 || containsHigherOrderConstructs e2
+ Quantified _ scope -> containsHigherOrderConstructs (fromScope scope)
+ TermSymbolStruct _ _ -> False
+
+pattern TermOp :: Location -> FunctionSymbol -> [ExprOf a] -> ExprOf a
+pattern TermOp loc op es = TermSymbol loc (SymbolMixfix op) es
+
+pattern TermConst :: Location -> Token -> ExprOf a
+pattern TermConst loc c <- TermOp loc (MixfixItem (TokenCons c End) _ NonAssoc) []
+ where
+ TermConst loc c =
+ TermOp loc (MixfixItem (TokenCons c End) (markerFromToken c) NonAssoc) []
+
+pattern TermPair :: Location -> ExprOf a -> ExprOf a -> ExprOf a
+pattern TermPair loc e1 e2 = TermOp loc PairSymbol [e1, e2]
+
+pattern Atomic :: Location -> Predicate -> [ExprOf a] -> ExprOf a
+pattern Atomic loc symbol args = TermSymbol loc (SymbolPredicate symbol) args
+
+
+pattern FormulaAdj :: Location -> ExprOf a -> LexicalItem -> [ExprOf a] -> ExprOf a
+pattern FormulaAdj loc e adj es = Atomic loc (PredicateAdj adj) (e:es)
+
+pattern FormulaVerb :: Location -> ExprOf a -> LexicalItemSgPl -> [ExprOf a] -> ExprOf a
+pattern FormulaVerb loc e verb es = Atomic loc (PredicateVerb verb) (e:es)
+
+pattern FormulaNoun :: Location -> ExprOf a -> LexicalItemSgPl -> [ExprOf a] -> ExprOf a
+pattern FormulaNoun loc e noun es = Atomic loc (PredicateNoun noun) (e:es)
+
+relationNoun :: Location -> Expr -> Formula
+relationNoun loc arg = FormulaNoun loc arg (mkLexicalItemSgPl (unsafeReadPhraseSgPl "relation[/s]") "relation") []
+
+rightUniqueAdj :: Location -> Expr -> Formula
+rightUniqueAdj loc arg = FormulaAdj loc arg (mkLexicalItem (unsafeReadPhrase "right-unique") "rightunique") []
+
+-- | Untyped quantification.
+pattern Forall, Exists :: Scope VarSymbol ExprOf a -> ExprOf a
+pattern Forall scope = Quantified Universally scope
+pattern Exists scope = Quantified Existentially scope
+
+makeForall, makeExists :: Foldable t => t VarSymbol -> Formula -> Formula
+makeForall xs e = Quantified Universally (abstractVarSymbols xs e)
+makeExists xs e = Quantified Existentially (abstractVarSymbols xs e)
+
+instantiateSome :: NonEmpty VarSymbol -> Scope VarSymbol ExprOf VarSymbol -> Scope VarSymbol ExprOf VarSymbol
+instantiateSome xs scope = toScope (instantiateEither inst scope)
+ where
+ inst (Left x) | x `elem` xs = TermVar (F x)
+ inst (Left b) = TermVar (B b)
+ inst (Right fv) = TermVar (F fv)
+
+-- | Bind all free variables not occuring in the given set universally
+forallClosure :: Set VarSymbol -> Formula -> Formula
+forallClosure xs phi = if isClosed phi
+ then phi
+ else Quantified Universally (abstract isNamedVar phi)
+ where
+ isNamedVar :: VarSymbol -> Maybe VarSymbol
+ isNamedVar x = if x `Set.member` xs then Nothing else Just x
+
+freeVars :: ExprOf VarSymbol -> Set VarSymbol
+freeVars = Set.fromList . toList
+
+pattern And :: ExprOf a -> ExprOf a -> ExprOf a
+pattern And e1 e2 = Connected Conjunction e1 e2
+
+pattern Or :: ExprOf a -> ExprOf a -> ExprOf a
+pattern Or e1 e2 = Connected Disjunction e1 e2
+
+pattern Implies :: ExprOf a -> ExprOf a -> ExprOf a
+pattern Implies e1 e2 = Connected Implication e1 e2
+
+pattern Iff :: ExprOf a -> ExprOf a -> ExprOf a
+pattern Iff e1 e2 = Connected Equivalence e1 e2
+
+pattern Xor :: ExprOf a -> ExprOf a -> ExprOf a
+pattern Xor e1 e2 = Connected ExclusiveOr e1 e2
+
+
+pattern Bottom :: ExprOf a
+pattern Bottom = PropositionalConstant IsBottom
+
+pattern Top :: ExprOf a
+pattern Top = PropositionalConstant IsTop
+
+
+data RelationApplicationError
+ = RelationParameterArityMismatch
+ { relationApplicationLocation :: Location
+ , relationApplicationSymbol :: RelationSymbol
+ , relationApplicationExpectedParameters :: ParameterArity
+ , relationApplicationActualParameters :: ParameterArity
+ }
+ deriving (Show, Eq, Ord)
+
+checkRelationParameterArity
+ :: Foldable f
+ => Location
+ -> RelationSymbol
+ -> f a
+ -> Either RelationApplicationError ()
+checkRelationParameterArity loc relation parameters
+ | expected == actual =
+ Right ()
+ | otherwise =
+ Left RelationParameterArityMismatch
+ { relationApplicationLocation = loc
+ , relationApplicationSymbol = relation
+ , relationApplicationExpectedParameters = expected
+ , relationApplicationActualParameters = actual
+ }
+ where
+ expected = relationSymbolParameterArity relation
+ actual = parameterArityOf parameters
+
+makeRelationApplication
+ :: Location
+ -> RelationSymbol
+ -> [ExprOf a]
+ -> Either
+ RelationApplicationError
+ (ExprOf a -> ExprOf a -> ExprOf a)
+makeRelationApplication loc relation parameters = do
+ checkRelationParameterArity loc relation parameters
+ pure \left right ->
+ Atomic loc (PredicateRelation relation) (parameters <> [left, right])
+
+pattern Relation :: Location -> RelationSymbol -> [ExprOf a] -> ExprOf a
+pattern Relation loc rel es <- Atomic loc (PredicateRelation rel) es
+
+-- | Membership.
+pattern IsElementOf :: Location -> ExprOf a -> ExprOf a -> ExprOf a
+pattern IsElementOf loc e1 e2 =
+ Atomic loc (PredicateRelation ElementSymbol) [e1, e2]
+
+isElementOf :: ExprOf a -> ExprOf a -> ExprOf a
+isElementOf e1 e2 =
+ Atomic Nowhere (PredicateRelation ElementSymbol) [e1, e2]
+
+-- | Membership.
+isNotElementOf :: Location -> ExprOf a -> ExprOf a -> ExprOf a
+isNotElementOf loc e1 e2 = Not loc (IsElementOf loc e1 e2)
+
+-- | Subset relation (non-strict).
+pattern IsSubsetOf :: Location -> ExprOf a -> ExprOf a -> ExprOf a
+pattern IsSubsetOf loc e1 e2 = Atomic loc (PredicateRelation SubseteqSymbol) (e1 : [e2])
+
+ordinalNoun :: LexicalItemSgPl
+ordinalNoun = mkLexicalItemSgPl (unsafeReadPhraseSgPl "ordinal[/s]") "ordinal"
+
+isOrdinalNoun :: LexicalItemSgPl -> Bool
+isOrdinalNoun noun = noun == ordinalNoun
+
+-- | Ordinal predicate.
+pattern IsOrd :: Location -> ExprOf a -> ExprOf a
+pattern IsOrd loc e1 <- Atomic loc (PredicateNoun (isOrdinalNoun -> True)) [e1]
+ where
+ IsOrd loc e1 = Atomic loc (PredicateNoun ordinalNoun) [e1]
+
+-- | Equality.
+pattern Equals :: Location -> ExprOf a -> ExprOf a -> ExprOf a
+pattern Equals loc e1 e2 = Atomic loc (PredicateRelation EqSymbol) (e1 : [e2])
+
+equals :: ExprOf a -> ExprOf a -> ExprOf a
+equals e1 e2 = Atomic Nowhere (PredicateRelation EqSymbol) (e1 : [e2])
+
+-- | Disequality.
+pattern NotEquals :: Location -> ExprOf a -> ExprOf a -> ExprOf a
+pattern NotEquals loc e1 e2 = Atomic loc (PredicateRelation NeqSymbol) (e1 : [e2])
+
+pattern EmptySet :: Location -> ExprOf a
+pattern EmptySet loc =
+ TermSymbol loc
+ (SymbolMixfix (MixfixItem (TokenCons (Command "emptyset") End) "emptyset" NonAssoc))
+ []
+
+makeConjunction :: [ExprOf a] -> ExprOf a
+makeConjunction = \case
+ [] -> Top
+ es -> List.foldl1' And es
+
+makeDisjunction :: [ExprOf a] -> ExprOf a
+makeDisjunction = \case
+ [] -> Bottom
+ es -> List.foldl1' Or es
+
+makeIff :: [ExprOf a] -> ExprOf a
+makeIff = \case
+ [] -> Bottom
+ es -> List.foldl1' Iff es
+
+makeXor :: [ExprOf a] -> ExprOf a
+makeXor = \case
+ [] -> Bottom
+ es -> List.foldl1' Xor es
+
+-- | Source-ordered HOTG finite-set adjunction.
+--
+-- This deliberately uses only fixed operations. In particular, finite-set
+-- notation is independent of the ordinary source-owned 'ConsSymbol'.
+finiteSet :: Location -> NonEmpty (ExprOf a) -> ExprOf a
+finiteSet location = foldr insert (EmptySet location)
+ where
+ insert element set =
+ TermSymbol location (SymbolMixfix UnionsSymbol)
+ [ TermSymbol location (SymbolMixfix UpairSymbol)
+ [ TermSymbol location (SymbolMixfix UpairSymbol)
+ [element, element]
+ , set
+ ]
+ ]
+
+isPositive :: ExprOf a -> Bool
+isPositive = \case
+ Not _ _ -> False
+ _ -> True
+
+dual :: ExprOf a -> ExprOf a
+dual = \case
+ Not _loc f -> f
+ f -> Not Nowhere f
+
+
+
+-- | Local assumptions.
+data Asm
+ = Asm Formula
+ | AsmStruct VarSymbol StructPhrase
+
+
+deriving instance Show Asm
+deriving instance Eq Asm
+deriving instance Ord Asm
+
+data StructAsm
+ = StructAsm VarSymbol StructPhrase
+
+
+
+data Axiom = Axiom [Asm] Formula
+
+deriving instance Show Axiom
+deriving instance Eq Axiom
+deriving instance Ord Axiom
+
+
+data Lemma = Lemma [Asm] Formula
+
+deriving instance Show Lemma
+deriving instance Eq Lemma
+deriving instance Ord Lemma
+
+
+data Defn
+ = DefnPredicate [Asm] Predicate (NonEmpty VarSymbol) Formula
+ | DefnFun [Asm] LexicalItemSgPl [VarSymbol] Term
+ | DefnOp FunctionSymbol [VarSymbol] Term
+
+deriving instance Show Defn
+deriving instance Eq Defn
+deriving instance Ord Defn
+
+data Inductive = Inductive
+ { inductiveSymbol :: FunctionSymbol
+ , inductiveParams :: [VarSymbol]
+ , inductiveDomain :: Expr
+ , inductiveIntros :: NonEmpty IntroRule
+ }
+ deriving (Show, Eq, Ord)
+
+data IntroRule = IntroRule
+ { introConditions :: [Formula] -- The inductively defined set may only appear as an argument of monotone operations on the rhs.
+ , introResult :: Formula -- TODO Refine.
+ }
+ deriving (Show, Eq, Ord)
+
+data CalcQuantifier
+ = CalcForall (NonEmpty VarSymbol) (Maybe Formula)
+ | CalcUnquantified
+ deriving (Show, Eq, Ord)
+
+data Proof
+ = Omitted Location
+ -- ^ Ends a proof without further verification.
+ -- This results in a “gap” in the formalization.
+ | Qed {mloc :: Maybe Location, by :: Justification}
+ -- ^ Ends of a proof, leaving automation to discharge the current goal using the given justification.
+ | Contradiction Location Justification
+ -- ^ Ends a proof by deriving absurdity using the given justification.
+ | ByContradiction Location Proof
+ -- ^ Take the dual of the current goal as an assumption and
+ -- set the goal to absurdity.
+ | BySetInduction Location (Maybe Term) Proof
+ -- ^ ∈-induction.
+ | ByOrdInduction Location Proof
+ -- ^ Transfinite induction for ordinals.
+ | Assume Location Formula Proof
+ -- ^ Simplify goals that are implications or disjunctions.
+ | Fix Location (NonEmpty VarSymbol) Formula Proof
+ -- ^ Simplify universal goals (with an optional bound or such that statement)
+ | Take Location (NonEmpty VarSymbol) Formula Justification Proof
+ -- ^ Use existential assumptions.
+ | Suffices Location Formula Justification Proof
+ | ByCase Location [Case]
+ -- ^ Proof by case. Disjunction of the case hypotheses 'Case'
+ -- must hold for this step to succeed. Each case starts a subproof,
+ -- keeping the same goal but adding the case hypothesis as an assumption.
+ -- Often this will be a classical split between /@P@/ and /@not P@/, in
+ -- which case the proof that /@P or not P@/ holds is easy.
+ --
+ | Have Location Formula Justification Proof
+ -- ^ An affirmation, e.g.: /@We have \<stmt\> by \<ref\>@/.
+ --
+ | Calc Location CalcQuantifier Calc Proof
+ | Subclaim Location Formula Proof Proof
+ -- ^ A claim is a sublemma with its own proof:
+ --
+ -- /@Show \<goal stmt\>. \<steps\>. \<continue other proof\>.@/
+ --
+ -- A successful first proof adds the claimed formula as an assumption
+ -- for the remaining proof.
+ --
+ | Define Location VarSymbol Term Proof
+ | DefineFunction Location VarSymbol VarSymbol Term Term Proof
+
+ | DefineFunctionLocal Location VarSymbol VarSymbol VarSymbol Term (NonEmpty (Term, Formula)) Proof
+
+deriving instance Show Proof
+deriving instance Eq Proof
+deriving instance Ord Proof
+
+
+
+-- | A case of a case split.
+data Case = Case
+ { caseOf :: Formula
+ , caseProof :: Proof
+ }
+
+deriving instance Show Case
+deriving instance Eq Case
+deriving instance Ord Case
+
+-- | See 'Syntax.Abstract.Calc'.
+data Calc
+ = Equation Term (NonEmpty (Term, Justification))
+ | Biconditionals Term (NonEmpty (Term, Justification))
+
+deriving instance Show Calc
+deriving instance Eq Calc
+deriving instance Ord Calc
+
+calcQuant :: CalcQuantifier -> (Formula -> Formula)
+calcQuant = \case
+ CalcUnquantified -> id
+ CalcForall xs maySuchThat -> case maySuchThat of
+ Nothing -> makeForall xs
+ Just suchThat -> \phi -> makeForall xs (suchThat `Implies` phi)
+
+calcResult :: CalcQuantifier -> Calc -> ExprOf VarSymbol
+calcResult quant = \case
+ Equation e eqns -> calcQuant quant (Equals Nowhere e (fst (NonEmpty.last eqns)))
+ Biconditionals phi phis -> calcQuant quant (phi `Iff` fst (NonEmpty.last phis))
+
+calculation :: CalcQuantifier -> Calc -> [(ExprOf VarSymbol, Justification)]
+calculation quant = \case
+ Equation e1 eqns@((e2, jst) :| _) -> (calcQuant quant (Equals Nowhere e1 e2), jst) : collectEquations quant (toList eqns)
+ Biconditionals p1 ps@((p2, jst) :| _) -> (calcQuant quant (p1 `Iff` p2), jst) : collectBiconditionals quant (toList ps)
+
+
+collectEquations :: CalcQuantifier -> [(Formula, j)] -> [(Formula, j)]
+collectEquations quant = \case
+ (e1, _) : eqns'@((e2, jst) : _) -> (calcQuant quant (Equals Nowhere e1 e2), jst) : collectEquations quant eqns'
+ _ -> []
+
+collectBiconditionals :: CalcQuantifier -> [(Formula, j)] -> [(Formula, j)]
+collectBiconditionals quant = \case
+ (p1, _) : ps@((p2, jst) : _) -> (calcQuant quant (p1 `Iff` p2), jst) : collectBiconditionals quant ps
+ _ -> []
+
+
+data Datatype
+ = Datatype
+ { datatypeHead :: SymbolPattern
+ , datatypeClauses :: NonEmpty DatatypeClause
+ }
+ deriving (Show, Eq, Ord)
+
+data DatatypeClause = DatatypeClause
+ { datatypeClauseConstructor :: SymbolPattern
+ , datatypeClausePremises :: [(VarSymbol, Expr)]
+ }
+ deriving (Show, Eq, Ord)
+
+
+data Signature
+ = SignaturePredicate Predicate (NonEmpty VarSymbol)
+ | SignatureFormula Formula
+ -- TODO: This is a lossy encoding of a symbolic signature declaration.
+ -- The checker currently recovers the declared mixfix symbol heuristically
+ -- from the generated formula in order to assign ownership. Replace this
+ -- with a precise signature representation that carries the declared symbol
+ -- directly.
+
+deriving instance Show Signature
+deriving instance Eq Signature
+deriving instance Ord Signature
+
+data StructDefn = StructDefn
+ { structPhrase :: StructPhrase
+ -- ^ The noun phrase naming the structure, e.g.: @partial order@ or @abelian group@.
+ , structParents :: Set StructPhrase
+ , structDefnLabel :: VarSymbol
+ , structDefnFixes :: Set StructSymbol
+ -- ^ List of commands representing operations,
+ -- e.g.: @\\contained@ or @\\inv@. These are used as default operation names
+ -- in instantiations such as @Let $G$ be a group@.
+ -- The commands should be set up to handle an optional struct label
+ -- which would typically be rendered as a sub- or superscript, e.g.:
+ -- @\\contained[A]@ could render as ”⊑ᴬ“.
+ -- --
+ , structDefnAssumes :: [(Marker, Formula)]
+ -- ^ The assumption or axioms of the structure.
+ -- To be instantiate with the @structFixes@ of a given structure.
+ }
+
+deriving instance Show StructDefn
+deriving instance Eq StructDefn
+deriving instance Ord StructDefn
+
+
+data Abbreviation
+ = Abbreviation Symbol (Scope Int ExprOf Void)
+ deriving (Show, Eq, Ord)
+
+data Block
+ = BlockAxiom Location Marker Axiom
+ | BlockLemma Location Marker Lemma
+ | BlockProof Location Location Proof
+ | BlockDefn Location Marker Defn
+ | BlockAbbr Location Marker Abbreviation
+ | BlockStruct Location Marker StructDefn
+ | BlockInductive Location Marker Inductive
+ | BlockSig Location Marker [Asm] Signature
+ | BlockData Location Marker Datatype
+ deriving (Show, Eq, Ord)
+
+
+-- | Full boolean contraction.
+contraction :: ExprOf a -> ExprOf a
+contraction = \case
+ Connected conn f1 f2 -> atomicContraction (Connected conn (contraction f1) (contraction f2))
+ Quantified quant scope -> atomicContraction (Quantified quant (hoistScope contraction scope))
+ Not loc f -> Not loc (contraction f)
+ f -> f
+
+
+-- | Atomic boolean contraction.
+atomicContraction :: ExprOf a -> ExprOf a
+atomicContraction = \case
+ Top `Iff` f -> f
+ Bottom `Iff` f -> Not Nowhere f
+ f `Iff` Top -> f
+ f `Iff` Bottom -> Not Nowhere f
+
+ Top `Implies` f -> f
+ Bottom `Implies` _ -> Top
+ _ `Implies` Top -> Top
+ f `Implies` Bottom -> Not Nowhere f
+
+ Top `And` f -> f
+ Bottom `And` _ -> Bottom
+ f `And` Top -> f
+ _ `And` Bottom -> Bottom
+
+ phi@(Quantified _quant scope) -> case unscope scope of
+ Top -> Top
+ Bottom -> Bottom
+ _ -> phi
+
+ Not _ Top -> Bottom
+ Not _ Bottom -> Top
+
+ f -> f
diff --git a/source/Felix/Syntax/LexicalPhrase.hs b/source/Felix/Syntax/LexicalPhrase.hs
new file mode 100644
index 0000000..5eb5b18
--- /dev/null
+++ b/source/Felix/Syntax/LexicalPhrase.hs
@@ -0,0 +1,95 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Syntax.LexicalPhrase where
+
+
+import Base
+import Felix.Syntax.Token (Token(..))
+
+import Control.DeepSeq (NFData)
+import Data.Char (isAlpha)
+import Data.Text qualified as Text
+import Text.Earley.Mixfix (Holey)
+import Text.Earley (Grammar, Prod, (<?>), fullParses, parser, rule, token, satisfy)
+
+
+
+-- | 'LexicalPhrase's should be nonempty lists with at least one proper word token.
+-- Hyphens and quotes in words are treated as letters.
+-- Thus /@manifold-with-boundary@/ is a singleton lexical phrase (one word).
+--
+type LexicalPhrase = Holey Token
+
+-- MAYBE Add this instance by making LexicalPhrase a proper Type?
+-- Until then we can use the default instance for lists of prettyprintable things.
+--
+-- instance Pretty LexicalPhrase where
+-- pretty components = hsep (prettyComponent <$> components)
+-- where
+-- prettyComponent = \case
+-- Nothing -> "_"
+-- Just tok -> pretty tok
+
+
+
+-- | Split data by grammatical number (singular/plural).
+-- The 'Eq' and 'Ord' instances only consider the singular
+-- form so that we can prefer known irregular plurals over
+-- guessed irregular plurals when inserting items into
+-- the 'Lexicon'.
+data SgPl a
+ = SgPl {sg :: a, pl :: a}
+ deriving (Show, Functor, Generic, Hashable, NFData)
+
+instance Eq a => Eq (SgPl a) where (==) = (==) `on` sg
+instance Ord a => Ord (SgPl a) where compare = compare `on` sg
+
+
+-- These readers parse only Felix-owned lexical literals.
+unsafeReadPhrase :: String -> LexicalPhrase
+unsafeReadPhrase spec = case fst (fullParses (parser lexicalPhraseSpec) spec) of
+ pat : _ -> pat
+ _ -> error "unsafeReadPhrase failed"
+
+unsafeReadPhraseSgPl :: String -> SgPl LexicalPhrase
+unsafeReadPhraseSgPl spec = case fst (fullParses (parser lexicalPhraseSpecSgPl) spec) of
+ pat : _ -> pat
+ _ -> error "unsafeReadPhraseSgPl failed"
+
+
+lexicalPhraseSpec :: Grammar r (Prod r String Char LexicalPhrase)
+lexicalPhraseSpec = do
+ hole <- rule $ Nothing <$ token '?' <?> "hole"
+ word <- rule $ Just <$> many (satisfy (\c -> isAlpha c || c == '-'))
+ space <- rule $ Just . (:[]) <$> token ' '
+ segment <- rule $ hole <|> word
+ rule $ (\s ss -> makePhrase (s:ss)) <$> segment <*> many (space *> segment)
+ where
+ makePhrase :: [Maybe String] -> LexicalPhrase
+ makePhrase pat = fmap makeWord pat
+
+
+lexicalPhraseSpecSgPl :: Grammar r (Prod r String Char (SgPl LexicalPhrase))
+lexicalPhraseSpecSgPl = do
+ space <- rule $ Just . (:[]) <$> token ' '
+ hole <- rule $ (Nothing, Nothing) <$ token '?'<?> "hole"
+
+ word <- rule (many (satisfy isAlpha) <?> "word")
+ wordSgPl <- rule $ (,) <$> (token '[' *> word) <* token '/' <*> word <* token ']'
+ complexWord <- rule $ (\(a,b) -> (Just a, Just b)) . fuse <$>
+ many ((<>) <$> (dup <$> word) <*> wordSgPl) <?> "word"
+ segment <- rule (hole <|> (dup . Just <$> word) <|> complexWord )
+ rule $ (\s ss -> makePhrase (s:ss)) <$> segment <*> many (space *> segment)
+ where
+ dup x = (x,x)
+ fuse = \case
+ (a, b) : (c, d) : rest -> fuse ((a <> c, b <> d) : rest)
+ [(a, b)] -> (a, b)
+ _ -> error "Syntax.Abstract.fuse"
+
+ makePhrase :: [(Maybe String, Maybe String)] -> SgPl LexicalPhrase
+ makePhrase = (\(patSg, patPl) -> SgPl (fmap makeWord patSg) (fmap makeWord patPl)) . unzip
+
+makeWord :: Maybe String -> Maybe Token
+makeWord = fmap (Word . Text.pack)
diff --git a/source/Felix/Syntax/Lexicon.hs b/source/Felix/Syntax/Lexicon.hs
new file mode 100644
index 0000000..c3332c7
--- /dev/null
+++ b/source/Felix/Syntax/Lexicon.hs
@@ -0,0 +1,330 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | The 'Lexicon' describes the part of the grammar that extensible/dynamic.
+--
+-- The items of the 'Lexicon' are organized by their meaning and their
+-- syntactic behaviour. They are typically represented as some kind of
+-- pattern data which is then used to generate various production rules
+-- for the concrete grammar. This representation makes inspection and
+-- extension easier.
+--
+
+module Felix.Syntax.Lexicon
+ ( module Felix.Syntax.Lexicon
+ , pattern ConsSymbol
+ , pattern PairSymbol
+ , pattern UpairSymbol
+ , pattern UnionsSymbol
+ , pattern CarrierSymbol
+ , pattern ApplySymbol
+ , pattern DomSymbol
+ ) where
+
+
+import Base
+import Felix.Syntax.Abstract
+
+import Data.List qualified as List
+import Data.Sequence qualified as Seq
+import Data.Set qualified as Set
+import Data.Map.Strict qualified as Map
+import Data.Text qualified as Text
+import Felix.Syntax.Mixfix (Holey)
+
+
+data SignatureHeadForm
+ = AdjectiveSignatureHead
+ | SymbolicSignatureHead
+ deriving (Show, Eq, Ord)
+
+-- Adjective heads must precede symbolic heads because both start with a math
+-- variable.
+concreteSignatureHeadForms :: [SignatureHeadForm]
+concreteSignatureHeadForms =
+ [ AdjectiveSignatureHead
+ , SymbolicSignatureHead
+ ]
+
+data Lexicon = Lexicon
+ { lexiconMixfixTable :: Seq (Map Pattern MixfixItem)
+ , lexiconConnectives :: [[(Holey Token, Associativity)]]
+ , lexiconPrefixPredicates :: [(PrefixPredicate, Marker)]
+ , lexiconStructFun :: [StructSymbol]
+ , lexiconRelationSymbols :: [RelationSymbol]
+ , lexiconVerbs :: [LexicalItemSgPl]
+ , lexiconAdjLs :: [LexicalItem]
+ , lexiconAdjRs :: [LexicalItem]
+ , lexiconNouns :: [LexicalItemSgPl]
+ , lexiconStructNouns :: [LexicalItemSgPl]
+ , lexiconFuns :: [LexicalItemSgPl]
+ } deriving (Show, Eq)
+
+-- Projection returning the union of both left and right attributes.
+--
+lexiconAdjs :: Lexicon -> [LexicalItem]
+lexiconAdjs lexicon = lexiconAdjLs lexicon <> lexiconAdjRs lexicon
+
+
+builtins :: Lexicon
+builtins =
+ Lexicon
+ { lexiconMixfixTable = builtinMixfixTable
+ , lexiconPrefixPredicates = builtinPrefixPredicates
+ , lexiconStructFun = builtinStructOps
+ , lexiconConnectives = builtinConnectives
+ , lexiconRelationSymbols = builtinRelationSymbols
+ , lexiconAdjLs = []
+ , lexiconAdjRs = builtinAdjRs
+ , lexiconVerbs = builtinVerbs
+ , lexiconNouns = builtinNouns
+ , lexiconStructNouns = builtinStructNouns
+ , lexiconFuns = []
+ }
+
+prefixPredicatePattern :: PrefixPredicate -> Pattern
+prefixPredicatePattern (PrefixPredicate command _arity) =
+ TokenCons (Command command) End
+
+builtinMixfixTable :: Seq (Map Pattern MixfixItem)
+builtinMixfixTable = Seq.fromList $ Map.fromList . fmap toEntry <$> builtinMixfixLevels
+ where
+ toEntry item@(MixfixItem pat _ _) = (pat, item)
+
+-- INVARIANT: 10 precedence levels for now.
+builtinMixfixLevels :: [[MixfixItem]]
+builtinMixfixLevels =
+ [ []
+ , [binOp (Symbol "+") LeftAssoc "add", binOp (Command "union") LeftAssoc "union", binOp (Symbol "-") LeftAssoc "minus", binOp (Command "rminus") LeftAssoc "rminus", binOp (Command "monus") LeftAssoc "monus"]
+ , [binOp (Command "relcomp") LeftAssoc "relcomp"]
+ , [binOp (Command "circ") LeftAssoc "circ"]
+ , [binOp (Command "mul") LeftAssoc "mul", binOp (Command "inter") LeftAssoc "inter", binOp (Command "rmul") LeftAssoc "rmul"]
+ , [binOp (Command "setminus") LeftAssoc "setminus"]
+ , [binOp (Command "times") RightAssoc "times"]
+ , []
+ , prefixOps
+ , builtinIdentifiers
+ ]
+ where
+ builtinIdentifiers :: [MixfixItem]
+ builtinIdentifiers = identifier <$>
+ [ "emptyset"
+ , "naturals"
+ , "naturalsPlus"
+ , "integers"
+ , "rationals"
+ , "reals"
+ , "unit"
+ , "zero"
+ ]
+
+
+prefixOps :: [MixfixItem]
+prefixOps =
+ [ mkMixfixItem [Just (Command "rfrac"), Just InvisibleBraceL, Nothing, Just InvisibleBraceR, Just InvisibleBraceL, Nothing, Just InvisibleBraceR] "rfrac" NonAssoc
+ , mkMixfixItem [Just (Command "exp"), Just InvisibleBraceL, Nothing, Just InvisibleBraceR, Just InvisibleBraceL, Nothing, Just InvisibleBraceR] "exp" NonAssoc
+ , UnionsSymbol
+ , mkMixfixItem [Just (Command "cumul"), Just InvisibleBraceL, Nothing, Just InvisibleBraceR] "cumul" NonAssoc
+ , mkMixfixItem [Just (Command "fst"), Just InvisibleBraceL, Nothing, Just InvisibleBraceR] "fst" NonAssoc
+ , mkMixfixItem [Just (Command "snd"), Just InvisibleBraceL, Nothing, Just InvisibleBraceR] "snd" NonAssoc
+ , mkMixfixItem [Just (Command "pow"), Just InvisibleBraceL, Nothing, Just InvisibleBraceR] "pow" NonAssoc
+ , mkMixfixItem [Just (Command "neg"), Just InvisibleBraceL, Nothing, Just InvisibleBraceR] "neg" NonAssoc
+ , mkMixfixItem [Just (Command "inv"), Just InvisibleBraceL, Nothing, Just InvisibleBraceR] "inv" NonAssoc
+ , mkMixfixItem [Just (Command "abs"), Just InvisibleBraceL, Nothing, Just InvisibleBraceR] "abs" NonAssoc
+ , ConsSymbol
+ , PairSymbol
+ , UpairSymbol
+ -- NOTE Is now defined and hence no longer necessary , ApplySymbol
+ ]
+
+
+builtinStructOps :: [StructSymbol]
+builtinStructOps =
+ [ CarrierSymbol
+ ]
+
+identifier :: Text -> MixfixItem
+identifier cmd = mkMixfixItem [Just (Command cmd)] (Marker cmd) NonAssoc
+
+
+builtinRelationSymbols :: [RelationSymbol]
+builtinRelationSymbols =
+ [ RelationSymbol (Symbol "=") zeroParameterArity "eq"
+ , RelationSymbol (Command "rless") zeroParameterArity "rless"
+ , RelationSymbol (Command "neq") zeroParameterArity "neq"
+ , ElementSymbol
+ , NotElementSymbol -- Alternative to @\not\in@.
+ ]
+
+builtinPrefixPredicates :: [(PrefixPredicate, Marker)]
+builtinPrefixPredicates =
+ [ (PrefixPredicate "Cong" 4, "cong")
+ , (PrefixPredicate "Betw" 3, "betw")
+ ]
+
+
+builtinConnectives :: [[(Holey Token, Associativity)]]
+builtinConnectives =
+ [ [binOp' (Command "iff") NonAssoc]
+ , [binOp' (Command "implies") RightAssoc]
+ , [binOp' (Command "lor") LeftAssoc]
+ , [binOp' (Command "land") LeftAssoc]
+ , [([Just (Command "lnot"), Nothing], NonAssoc)]
+ ]
+
+
+binOp :: Token -> Associativity -> Marker -> MixfixItem
+binOp tok assoc m = mkMixfixItem [Nothing, Just tok, Nothing] m assoc
+
+binOp' :: Token -> Associativity -> (Holey Token, Associativity)
+binOp' tok assoc = ([Nothing, Just tok, Nothing], assoc)
+
+builtinAdjRs :: [LexicalItem]
+builtinAdjRs =
+ [ builtinEqualityRightAdjective
+ ]
+
+builtinEqualityRightAdjective :: LexicalItem
+builtinEqualityRightAdjective =
+ mkLexicalItem (unsafeReadPhrase "equal to ?") "eq"
+
+builtinVerbs :: [LexicalItemSgPl]
+builtinVerbs =
+ [ builtinEqualityVerb
+ ]
+
+builtinEqualityVerb :: LexicalItemSgPl
+builtinEqualityVerb =
+ mkLexicalItemSgPl (unsafeReadPhraseSgPl "equal[s/] ?") "eq"
+
+
+-- Some of these do/should correspond to mathlib structures,
+-- e.g.: lattice, complete lattice, ring, etc.
+--
+builtinNouns :: [LexicalItemSgPl]
+builtinNouns =
+ [ builtinSetNoun
+ , mkLexicalItemSgPl (unsafeReadPhraseSgPl "point[/s]") "point"
+ , builtinElementNoun
+ ]
+
+builtinSetNoun :: LexicalItemSgPl
+builtinSetNoun =
+ mkLexicalItemSgPl
+ (unsafeReadPhraseSgPl "set[/s]")
+ "set"
+
+builtinElementNoun :: LexicalItemSgPl
+builtinElementNoun =
+ mkLexicalItemSgPl
+ (unsafeReadPhraseSgPl "element[/s] of ?")
+ "elem"
+
+-- | Match the complete fixed-base identity, including the plural surface and
+-- authoritative marker. The ordinary 'Eq' instance intentionally compares
+-- only singular patterns.
+isBuiltinSetNoun :: LexicalItemSgPl -> Bool
+isBuiltinSetNoun item =
+ let actual = lexicalItemSgPlPattern item
+ expected = lexicalItemSgPlPattern builtinSetNoun
+ in sg actual == sg expected
+ && pl actual == pl expected
+ && lexicalItemSgPlMarker item
+ == lexicalItemSgPlMarker builtinSetNoun
+
+_Onesorted :: LexicalItemSgPl
+_Onesorted = mkLexicalItemSgPl (unsafeReadPhraseSgPl "onesorted structure[/s]") "onesorted_structure"
+
+builtinStructNouns :: [LexicalItemSgPl]
+builtinStructNouns = [_Onesorted]
+
+
+-- | Naïve splitting of lexical phrases to insert a variable slot for names in noun phrases,
+-- as in /@there exists a linear form $h$ on $E$@/, where the underlying pattern is
+-- /@linear form on ?@/. In this case we would get:
+--
+-- > splitOnVariableSlot (sg (unsafeReadPhraseSgPl "linear form[/s] on ?"))
+-- > ==
+-- > (unsafeReadPhrase "linear form", unsafeReadPhrase "on ?")
+--
+splitOnVariableSlot :: LexicalPhrase -> (LexicalPhrase, LexicalPhrase)
+splitOnVariableSlot pat = case prepositionIndices <> nonhyphenatedSlotIndices of
+ [] -> (pat, []) -- Place variable slot at the end.
+ is -> List.splitAt (minimum is) pat
+ where
+ prepositionIndices, slotIndices, nonhyphenatedSlotIndices :: [Int] -- Ascending.
+ prepositionIndices = List.findIndices isPreposition pat
+ slotIndices = List.findIndices isNothing pat
+ nonhyphenatedSlotIndices = [i | i <- slotIndices, noHyphen (nth (i + 1) pat)]
+
+ isPreposition :: Maybe Token -> Bool
+ isPreposition = \case
+ Just (Word w) -> w `Set.member` prepositions
+ _ -> False
+
+ noHyphen :: Maybe (Maybe Token) -> Bool
+ noHyphen = \case
+ Just (Just (Word w)) -> Text.head w /= '-'
+ -- If we arrive here, either the pattern is over (`Nothing`) or the next
+ -- part of the pattern is not a word that starts with a hyphen.
+ _ -> True
+
+
+-- Preposition are a closed class, but this list is not yet exhaustive.
+-- It can and should be extended when needed. The following list is a
+-- selection of the prepositions found at
+-- https://en.wikipedia.org/wiki/List_of_English_prepositions.
+--
+prepositions :: Set Text
+prepositions = Set.fromList
+ [ "about"
+ , "above"
+ , "across"
+ , "after"
+ , "against"
+ , "along", "alongside"
+ , "amid", "amidst"
+ , "among"
+ , "around"
+ , "as"
+ , "at"
+ , "atop"
+ , "before"
+ , "behind"
+ , "below"
+ , "beneath"
+ , "beside", "besides"
+ , "between"
+ , "beyond"
+ , "but"
+ , "by"
+ , "except"
+ , "for"
+ , "from"
+ , "in", "inside", "into"
+ , "like"
+ , "modulo", "mod"
+ , "near"
+ , "next"
+ , "of"
+ , "off"
+ , "on"
+ , "onto"
+ , "opposite"
+ , "out"
+ , "over"
+ , "past"
+ , "per"
+ , "sans"
+ , "till"
+ , "to"
+ , "under"
+ , "underneath"
+ , "unlike"
+ , "unto"
+ , "up", "upon"
+ , "versus"
+ , "via"
+ , "with"
+ , "within"
+ , "without"
+ ]
diff --git a/source/Felix/Syntax/Mixfix.hs b/source/Felix/Syntax/Mixfix.hs
new file mode 100644
index 0000000..a3ce4bb
--- /dev/null
+++ b/source/Felix/Syntax/Mixfix.hs
@@ -0,0 +1,139 @@
+{-# LANGUAGE RecursiveDo #-}
+
+module Felix.Syntax.Mixfix where
+
+{-
+Original code Copyright (c) 2014-2019, Olle Fredriksson
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * Neither the name of Olle Fredriksson nor the names of other
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-}
+
+
+import Base
+import Text.Earley
+import Data.Either
+import Felix.Syntax.Abstract
+
+
+replicateA :: Applicative f => Int -> f a -> f [a]
+replicateA n = sequenceA . replicate n
+
+consA :: Applicative f => f a -> f [a] -> f [a]
+consA p q = (:) <$> p <*> q
+
+
+-- | An identifier with identifier parts ('Just's), and holes ('Nothing's)
+-- representing the positions of its arguments.
+--
+-- Example (commonly written "if_then_else_"):
+-- @['Just' "if", 'Nothing', 'Just' "then", 'Nothing', 'Just' "else", 'Nothing'] :: 'Holey' 'String'@
+type Holey a = [Maybe a]
+
+
+-- | Create a grammar for parsing mixfix expressions.
+mixfixExpression
+ :: [[(Holey (Prod r e t ident), Associativity)]]
+ -- ^ A table of holey identifier parsers, with associativity information.
+ -- The identifiers should be in groups of precedence levels listed from
+ -- binding the least to the most tightly.
+ --
+ -- The associativity is taken into account when an identifier starts or ends
+ -- with holes, or both. Internal holes (e.g. after "if" in "if_then_else_")
+ -- start from the beginning of the table.
+ --
+ -- Note that this rule also applies to identifiers with multiple consecutive
+ -- holes, e.g. "if__" --- the associativity then applies to both holes.
+ -> Prod r e t expr
+ -- ^ An atom, i.e. what is parsed at the lowest level. This will
+ -- commonly be a (non-mixfix) identifier or a parenthesised expression.
+ -> (Holey ident -> [expr] -> expr)
+ -- ^ How to combine the successful application of a holey identifier to its
+ -- arguments into an expression.
+ -> Grammar r (Prod r e t expr)
+mixfixExpression table atom app = mixfixExpressionSeparate table' atom
+ where
+ table' = [[(holey, assoc, app) | (holey, assoc) <- row] | row <- table]
+
+-- | A version of 'mixfixExpression' with a separate semantic action for each
+-- individual 'Holey' identifier.
+mixfixExpressionSeparate
+ :: [[(Holey (Prod r e t ident), Associativity, Holey ident -> [expr] -> expr)]]
+ -- ^ A table of holey identifier parsers, with associativity information and
+ -- semantic actions. The identifiers should be in groups of precedence
+ -- levels listed from binding the least to the most tightly.
+ --
+ -- The associativity is taken into account when an identifier starts or ends
+ -- with holes, or both. Internal holes (e.g. after "if" in "if_then_else_")
+ -- start from the beginning of the table.
+ --
+ -- Note that this rule also applies to identifiers with multiple consecutive
+ -- holes, e.g. "if__" --- the associativity then applies to both holes.
+ -> Prod r e t expr
+ -- ^ An atom, i.e. what is parsed at the lowest level. This will
+ -- commonly be a (non-mixfix) identifier or a parenthesised expression.
+ -> Grammar r (Prod r e t expr)
+mixfixExpressionSeparate table atom = mdo
+ expr <- foldrM ($) atom $ map (level expr) table
+ return expr
+ where
+ level expr idents next = mdo
+ same <- rule $ asum $ next : map (mixfixIdent same) idents
+ return same
+ where
+ -- Group consecutive holes and ident parts.
+ grp [] = []
+ grp (Nothing:ps) = case grp ps of
+ Left n:rest -> (Left $! (n + 1)) : rest
+ rest -> Left 1 : rest
+ grp (Just p:ps) = case grp ps of
+ Right ps':rest -> Right (consA p ps') : rest
+ rest -> Right (consA p $ pure []) : rest
+
+ mixfixIdent same (ps, a, f) = f' <$> go (grp ps)
+ where
+ f' xs = f (concatMap (either (map $ const Nothing) $ map Just) xs)
+ $ concat $ lefts xs
+ go ps' = case ps' of
+ [] -> pure []
+ [Right p] -> pure . Right <$> p
+ Left n:rest -> consA
+ (Left <$> replicateA n (if a == RightAssoc then next
+ else same))
+ $ go rest
+ [Right p, Left n] -> consA
+ (Right <$> p)
+ $ pure . Left <$> replicateA n (if a == LeftAssoc then next
+ else same)
+ Right p:Left n:rest -> consA (Right <$> p)
+ $ consA (Left <$> replicateA n expr)
+ $ go rest
+ Right _:Right _:_ -> error
+ $ "Earley.mixfixExpression: The impossible happened. "
+ ++ "Please report this as a bug."
diff --git a/source/Felix/Syntax/Pragma.hs b/source/Felix/Syntax/Pragma.hs
new file mode 100644
index 0000000..97d1482
--- /dev/null
+++ b/source/Felix/Syntax/Pragma.hs
@@ -0,0 +1,250 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Syntax.Pragma
+ ( SourceMixfixLevel
+ , sourceMixfixLevelValue
+ , SyntaxPragma(..)
+ , SyntaxPragmaProblem(..)
+ , SyntaxPragmaError(..)
+ , renderSyntaxPragmaError
+ , extractSyntaxPragmas
+ ) where
+
+import Base
+
+import Felix.Report.Location
+import Felix.Syntax.Abstract (Associativity(..))
+
+import Control.DeepSeq (NFData)
+import Control.Monad (unless, when)
+import Data.Bifunctor (first)
+import Data.Char (ord)
+import Data.Text qualified as Text
+import Data.Word (Word8)
+
+
+newtype SourceMixfixLevel = SourceMixfixLevel Word8
+ deriving stock (Show, Eq, Ord, Generic)
+ deriving anyclass (NFData)
+
+sourceMixfixLevelValue :: SourceMixfixLevel -> Word8
+sourceMixfixLevelValue (SourceMixfixLevel level) =
+ level
+
+data SyntaxPragma = LocatedFixityPragma
+ { syntaxPragmaLocation :: !Location
+ , syntaxPragmaAssociativity :: !Associativity
+ , syntaxPragmaLevel :: !SourceMixfixLevel
+ } deriving stock (Show, Eq)
+
+instance Locatable SyntaxPragma where
+ locate = syntaxPragmaLocation
+
+data SyntaxPragmaProblem
+ = SyntaxPragmaMissingSpaceAfterPrefix
+ | SyntaxPragmaMissingKeyword
+ | SyntaxPragmaUnknownKeyword !Text
+ | SyntaxPragmaMissingLevel
+ | SyntaxPragmaInvalidLevel
+ | SyntaxPragmaLevelOutOfRange
+ | SyntaxPragmaTrailingContent
+ | SyntaxPragmaLoneCarriageReturn
+ deriving stock (Show, Eq)
+
+data SyntaxPragmaError
+ = InvalidSyntaxPragma !Location !SyntaxPragmaProblem
+ | SyntaxPragmaLocationOutOfRange !FilePath !Int !Int
+ deriving stock (Show, Eq)
+
+renderSyntaxPragmaError :: SyntaxPragmaError -> Text
+renderSyntaxPragmaError = \case
+ InvalidSyntaxPragma location problem ->
+ locationToText location
+ <> ": "
+ <> renderSyntaxPragmaProblem problem
+ SyntaxPragmaLocationOutOfRange file line column ->
+ Text.pack file
+ <> ": syntax pragma location is out of range at "
+ <> Text.pack (show line)
+ <> ":"
+ <> Text.pack (show column)
+
+renderSyntaxPragmaProblem :: SyntaxPragmaProblem -> Text
+renderSyntaxPragmaProblem = \case
+ SyntaxPragmaMissingSpaceAfterPrefix ->
+ "expected horizontal space after %!"
+ SyntaxPragmaMissingKeyword ->
+ "missing syntax pragma keyword"
+ SyntaxPragmaUnknownKeyword keyword ->
+ "unknown syntax pragma keyword " <> Text.pack (show keyword)
+ SyntaxPragmaMissingLevel ->
+ "missing syntax pragma level"
+ SyntaxPragmaInvalidLevel ->
+ "syntax pragma level must use ASCII decimal digits"
+ SyntaxPragmaLevelOutOfRange ->
+ "syntax pragma level must be between 0 and 7"
+ SyntaxPragmaTrailingContent ->
+ "unexpected trailing syntax pragma content"
+ SyntaxPragmaLoneCarriageReturn ->
+ "a syntax pragma line must end with LF, CRLF, or end of file"
+
+extractSyntaxPragmas
+ :: FileId
+ -> FilePath
+ -> Text
+ -> Either SyntaxPragmaError [SyntaxPragma]
+extractSyntaxPragmas fileId file =
+ go 1
+ where
+ go lineNumber source
+ | Text.null source =
+ Right []
+ | otherwise = do
+ let (rawLine, suffix) =
+ Text.break (== '\n') source
+ hasLineFeed =
+ not (Text.null suffix)
+ (line, lineEnding) =
+ if hasLineFeed && Text.isSuffixOf "\r" rawLine
+ then
+ (Text.dropEnd 1 rawLine, CrLf)
+ else if hasLineFeed
+ then
+ (rawLine, LineFeed)
+ else
+ (rawLine, EndOfFile)
+ remaining =
+ if hasLineFeed
+ then Text.drop 1 suffix
+ else ""
+ reserved =
+ Text.isPrefixOf "%!"
+ (Text.dropWhile isHorizontalSpace line)
+ pragma <-
+ if reserved
+ then Just <$> parsePragmaLine
+ fileId
+ file
+ lineNumber
+ lineEnding
+ line
+ else
+ Right Nothing
+ rest <- go (lineNumber + 1) remaining
+ pure (maybe rest (: rest) pragma)
+
+data LineEnding
+ = LineFeed
+ | CrLf
+ | EndOfFile
+ deriving stock (Show, Eq)
+
+parsePragmaLine
+ :: FileId
+ -> FilePath
+ -> Int
+ -> LineEnding
+ -> Text
+ -> Either SyntaxPragmaError SyntaxPragma
+parsePragmaLine fileId file lineNumber lineEnding rawLine = do
+ let horizontalPrefix =
+ Text.takeWhile isHorizontalSpace rawLine
+ column =
+ Text.length horizontalPrefix + 1
+ location <-
+ first
+ (const
+ (SyntaxPragmaLocationOutOfRange
+ file
+ lineNumber
+ column))
+ (mkLocationChecked fileId lineNumber column)
+ let invalid
+ :: SyntaxPragmaProblem
+ -> Either SyntaxPragmaError a
+ invalid =
+ Left . InvalidSyntaxPragma location
+ afterPrefix =
+ Text.drop 2
+ (Text.dropWhile isHorizontalSpace rawLine)
+ when
+ (lineEnding == EndOfFile
+ && Text.isSuffixOf "\r" rawLine)
+ (invalid SyntaxPragmaLoneCarriageReturn)
+ afterPrefixSpace <-
+ case Text.uncons afterPrefix of
+ Nothing ->
+ invalid SyntaxPragmaMissingKeyword
+ Just (char, _)
+ | not (isHorizontalSpace char) ->
+ invalid SyntaxPragmaMissingSpaceAfterPrefix
+ Just{} ->
+ Right (Text.dropWhile isHorizontalSpace afterPrefix)
+ when
+ (Text.null afterPrefixSpace)
+ (invalid SyntaxPragmaMissingKeyword)
+ let (keyword, afterKeyword) =
+ Text.break isHorizontalSpace afterPrefixSpace
+ associativity <-
+ case keyword of
+ "infixl" ->
+ Right LeftAssoc
+ "infixr" ->
+ Right RightAssoc
+ "infix" ->
+ Right NonAssoc
+ _ ->
+ invalid (SyntaxPragmaUnknownKeyword keyword)
+ afterKeywordSpace <-
+ case Text.uncons afterKeyword of
+ Nothing ->
+ invalid SyntaxPragmaMissingLevel
+ Just{} ->
+ Right (Text.dropWhile isHorizontalSpace afterKeyword)
+ when
+ (Text.null afterKeywordSpace)
+ (invalid SyntaxPragmaMissingLevel)
+ let (digits, trailing) =
+ Text.span isAsciiDigit afterKeywordSpace
+ when
+ (Text.null digits)
+ (invalid SyntaxPragmaInvalidLevel)
+ level <-
+ maybe
+ (invalid SyntaxPragmaLevelOutOfRange)
+ (Right . SourceMixfixLevel)
+ (sourceLevel digits)
+ unless
+ (Text.null (Text.dropWhile isHorizontalSpace trailing))
+ (invalid SyntaxPragmaTrailingContent)
+ pure
+ LocatedFixityPragma
+ { syntaxPragmaLocation = location
+ , syntaxPragmaAssociativity = associativity
+ , syntaxPragmaLevel = level
+ }
+
+isHorizontalSpace :: Char -> Bool
+isHorizontalSpace char =
+ char == ' ' || char == '\t'
+
+isAsciiDigit :: Char -> Bool
+isAsciiDigit char =
+ '0' <= char && char <= '9'
+
+sourceLevel :: Text -> Maybe Word8
+sourceLevel =
+ Text.foldl' step (Just 0)
+ where
+ step Nothing _ =
+ Nothing
+ step (Just current) char =
+ let next =
+ current * 10
+ + fromIntegral (ord char - ord '0')
+ in
+ if next <= 7
+ then Just next
+ else Nothing
diff --git a/source/Felix/Syntax/Token.hs b/source/Felix/Syntax/Token.hs
new file mode 100644
index 0000000..31d1d19
--- /dev/null
+++ b/source/Felix/Syntax/Token.hs
@@ -0,0 +1,633 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- |
+-- This module defines the lexer and its associated data types.
+-- The lexer takes `Text` as input and produces a stream of tokens
+-- annotated with positional information. This information is bundled
+-- together with the original raw input for producing error messages.
+--
+-- The lexer perfoms some normalizations to make describing the grammar easier.
+-- Words outside of math environments are case-folded. Some commands are analysed
+-- as variable tokens and are equivalent to their respective unicode variants
+-- (α, β, γ, ..., 𝔸, 𝔹, ℂ, ...). Similarly, @\\begin{...}@ and @\\end{...}@ commands
+-- are each parsed as single tokens.
+--
+module Felix.Syntax.Token
+ ( Token(..)
+ , VariableDisplay(..)
+ , VariableSuffix(..)
+ , displayVariable
+ , renderVariableText
+ , tokToString
+ , tokToText
+ , TokStream(..)
+ , Located(..)
+ , runLexer
+ , gatherImports
+ ) where
+
+
+import Base hiding (many)
+
+import Felix.Report.Location
+
+import Control.DeepSeq (NFData)
+import Control.Monad.Combinators
+import Control.Monad.State.Strict
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Text qualified as Text
+import Prettyprinter (Pretty(..))
+import Text.Megaparsec hiding (Token, Label, label)
+import Text.Megaparsec.Char qualified as Char
+import Text.Megaparsec.Char.Lexer qualified as Lexer
+import Tptp.UnsortedFirstOrder (isAsciiLetter, isAsciiAlphaNumOrUnderscore)
+
+
+runLexer :: FileId -> String -> Text -> Either (ParseErrorBundle Text Void) ([FilePath], [[Located Token]])
+runLexer fileId file raw = runParser (evalStateT document (initLexerState fileId)) file raw
+
+
+type Lexer = StateT LexerState (Parsec Void Text)
+
+
+data LexerState = LexerState
+ { frames :: !(NonEmpty Frame)
+ , currentFileId :: !FileId
+ } deriving (Show, Eq)
+
+data Frame
+ = TopText
+ | MathFrame !Text
+ | TextFrame !Int
+ deriving (Show, Eq)
+
+initLexerState :: FileId -> LexerState
+initLexerState fileId = LexerState (TopText :| []) fileId
+
+topFrameOf :: LexerState -> Frame
+topFrameOf LexerState{frames = frame :| _} = frame
+
+topFrame :: Lexer Frame
+topFrame = gets topFrameOf
+
+pushFrame :: Frame -> LexerState -> LexerState
+pushFrame frame st@LexerState{frames = top :| rest} =
+ st{frames = frame :| (top : rest)}
+
+popFrame :: LexerState -> LexerState
+popFrame st@LexerState{frames = _ :| []} = st
+popFrame st@LexerState{frames = _ :| (top : rest)} =
+ st{frames = top :| rest}
+
+modifyTopFrame :: (Frame -> Frame) -> LexerState -> LexerState
+modifyTopFrame f st@LexerState{frames = top :| rest} =
+ st{frames = f top :| rest}
+
+-- Token recognizers only emit tokens; this is the single place that changes
+-- lexical context.
+advance :: Token -> LexerState -> LexerState
+advance tok st =
+ case (topFrameOf st, tok) of
+ (TopText, BeginEnv "math") ->
+ pushFrame (MathFrame "math") st
+ (TextFrame{}, BeginEnv "math") ->
+ pushFrame (MathFrame "math") st
+ (TopText, BeginEnv "align*") ->
+ pushFrame (MathFrame "align*") st
+ (TextFrame{}, BeginEnv "align*") ->
+ pushFrame (MathFrame "align*") st
+ (MathFrame env, EndEnv env')
+ | env == env' ->
+ popFrame st
+ (MathFrame{}, BeginEnv "text") ->
+ pushFrame (TextFrame 1) st
+ (TextFrame n, InvisibleBraceL) ->
+ modifyTopFrame (const (TextFrame (n + 1))) st
+ (TextFrame n, InvisibleBraceR)
+ | n > 1 ->
+ modifyTopFrame (const (TextFrame (n - 1))) st
+ (TextFrame 1, EndEnv "text") ->
+ popFrame st
+ _ ->
+ st
+
+-- |
+-- A token stream as input stream for a parser. Contains the raw input
+-- before tokenization as 'Text' for showing error messages.
+--
+data TokStream = TokStream
+ { rawInput :: !Text
+ , unTokStream :: ![[Located Token]]
+ } deriving (Show, Eq)
+
+instance Semigroup TokStream where
+ TokStream raw1 toks1 <> TokStream raw2 toks2 = TokStream (raw1 <> raw2) (toks1 <> toks2)
+
+instance Monoid TokStream where
+ mempty = TokStream mempty mempty
+
+-- | A LaTeX token.
+-- Invisible delimiters 'InvisibleBraceL' and 'InvisibleBraceR' are
+-- unescaped braces used for grouping in TEX (@{@),
+-- visibles braces are escaped braces (@\\{@).
+data Token
+ = Word !Text
+ | Variable !Text
+ | Symbol !Text
+ | Integer !Int
+ | Command !Text
+ | Label Text -- ^ A /@\\label{...}@/ command (case-sensitive).
+ | Ref (NonEmpty Text) -- ^ A /@\\ref{...}@/ command (case-sensitive).
+ | BeginEnv !Text
+ | EndEnv !Text
+ | ParenL | ParenR
+ | BracketL | BracketR
+ | VisibleBraceL | VisibleBraceR
+ | InvisibleBraceL | InvisibleBraceR
+ deriving (Show, Eq, Ord, Generic, Hashable, NFData)
+
+instance IsString Token where
+ fromString w = Word (Text.pack w)
+
+data VariableDisplay = VariableDisplay
+ { variableBaseText :: !Text
+ , variableSuffix :: !(Maybe VariableSuffix)
+ } deriving (Show, Eq, Ord)
+
+data VariableSuffix
+ = VariableSubscript !Text
+ | VariableTicks !Int
+ deriving (Show, Eq, Ord)
+
+displayVariable :: Text -> VariableDisplay
+displayVariable rawName =
+ case splitVariableBase rawName of
+ Nothing ->
+ VariableDisplay rawName Nothing
+ Just (baseText, suffixText) ->
+ VariableDisplay baseText (displayVariableSuffix suffixText)
+
+renderVariableText :: Text -> Text
+renderVariableText rawName =
+ case displayVariable rawName of
+ VariableDisplay baseText Nothing ->
+ baseText
+ VariableDisplay baseText (Just (VariableTicks n)) ->
+ baseText <> Text.replicate n "'"
+ VariableDisplay baseText (Just (VariableSubscript subscriptText)) ->
+ baseText <> renderSubscriptText subscriptText
+
+splitVariableBase :: Text -> Maybe (Text, Text)
+splitVariableBase rawName =
+ matchBlackboardBase rawName <|> matchGreekBase rawName <|> matchSingleLetterBase rawName
+
+matchBlackboardBase :: Text -> Maybe (Text, Text)
+matchBlackboardBase rawName = do
+ suffixText <- Text.stripPrefix "bb" rawName
+ case Text.uncons suffixText of
+ Just (upper, rest)
+ | 'A' <= upper && upper <= 'Z' ->
+ Just ("bb" <> Text.singleton upper, rest)
+ _ ->
+ Nothing
+
+matchGreekBase :: Text -> Maybe (Text, Text)
+matchGreekBase rawName =
+ asum
+ [ (\suffixText -> (rendered, suffixText)) <$> Text.stripPrefix prefix rawName
+ | (prefix, rendered) <- greekVariables
+ ]
+
+matchSingleLetterBase :: Text -> Maybe (Text, Text)
+matchSingleLetterBase rawName = do
+ (baseChar, suffixText) <- Text.uncons rawName
+ pure (Text.singleton baseChar, suffixText)
+
+displayVariableSuffix :: Text -> Maybe VariableSuffix
+displayVariableSuffix suffixText
+ | Text.null suffixText =
+ Nothing
+ | Text.all (== '_') suffixText =
+ Just (VariableTicks (Text.length suffixText))
+ | otherwise =
+ Just (VariableSubscript (Text.replace "_" "'" suffixText))
+
+renderSubscriptText :: Text -> Text
+renderSubscriptText subscriptText
+ | Text.length subscriptText == 1 =
+ "_" <> subscriptText
+ | otherwise =
+ "_{" <> subscriptText <> "}"
+
+greekVariables :: [(Text, Text)]
+greekVariables =
+ [ ("alpha", "α"), ("beta", "β"), ("gamma", "γ"), ("delta", "δ")
+ , ("epsilon", "ε"), ("zeta", "ζ"), ("eta", "η"), ("theta", "θ")
+ , ("iota", "ι"), ("kappa", "κ"), ("lambda", "λ"), ("mu", "μ")
+ , ("nu", "ν"), ("xi", "ξ"), ("pi", "π"), ("rho", "ρ"), ("sigma", "σ")
+ , ("tau", "τ"), ("upsilon", "υ"), ("phi", "φ"), ("chi", "χ")
+ , ("psi", "ψ"), ("omega", "ω")
+ , ("Gamma", "Γ"), ("Delta", "Δ"), ("Theta", "Θ"), ("Lambda", "Λ")
+ , ("Xi", "Ξ"), ("Pi", "Π"), ("Sigma", "Σ"), ("Upsilon", "Υ")
+ , ("Phi", "Φ"), ("Psi", "Ψ"), ("Omega", "Ω")
+ ]
+
+tokToText :: Token -> Text
+tokToText = \case
+ Word w -> w
+ Variable v -> renderVariableText v
+ Symbol s -> s
+ Integer n -> Text.pack (show n)
+ Command cmd -> Text.cons '\\' cmd
+ Label m -> "\\label{" <> m <> "}"
+ Ref ms -> "\\ref{" <> Text.intercalate ", " (toList ms) <> "}"
+ BeginEnv "math" -> "$"
+ EndEnv "math" -> "$"
+ BeginEnv env -> "\\begin{" <> env <> "}"
+ EndEnv env -> "\\end{" <> env <> "}"
+ ParenL -> "("
+ ParenR -> ")"
+ BracketL -> "["
+ BracketR -> "]"
+ VisibleBraceL -> "\\{"
+ VisibleBraceR -> "\\}"
+ InvisibleBraceL -> "{"
+ InvisibleBraceR -> "}"
+
+tokToString :: Token -> String
+tokToString = Text.unpack . tokToText
+
+instance Pretty Token where
+ pretty = \case
+ Word w -> pretty w
+ Variable v -> pretty (renderVariableText v)
+ Symbol s -> pretty s
+ Integer n -> pretty n
+ Command cmd -> "\\" <> pretty cmd
+ Label m -> "\\label{" <> pretty m <> "}"
+ Ref m -> "\\ref{" <> pretty m <> "}"
+ BeginEnv env -> "\\begin{" <> pretty env <> "}"
+ EndEnv env -> "\\end{" <> pretty env <> "}"
+ ParenL -> "("
+ ParenR -> ")"
+ BracketL -> "["
+ BracketR -> "]"
+ VisibleBraceL -> "\\{"
+ VisibleBraceR -> "\\}"
+ InvisibleBraceL -> "{"
+ InvisibleBraceR -> "}"
+
+
+data Located a = Located
+ { startPos :: !Location
+ , unLocated :: !a
+ , postWhitespace :: Whitespace
+ } deriving (Show, Functor)
+
+data Whitespace = NoSpace | Space deriving (Show)
+
+collapseWhitespace :: [Whitespace] -> Whitespace
+collapseWhitespace = \case
+ Space : _ -> Space
+ NoSpace : ws -> collapseWhitespace ws
+ [] -> NoSpace
+
+instance Eq a => Eq (Located a) where (==) = (==) `on` unLocated
+instance Ord a => Ord (Located a) where compare = compare `on` unLocated
+
+
+document :: Lexer ([FilePath], [[Located Token]])
+document = do
+ is <- importBlock
+ es <- many environment
+ eof
+ return (unLocated <$> is, es)
+
+
+importBlock :: Lexer [Located FilePath]
+importBlock = do
+ void (skipManyTill skipChar importLineOrBeginEnvOrEof)
+ many importLine
+ where
+ -- When skipping to the import block, we first need to try parsing whitespace to properly handle comments and avoid picking up a commented import line at the start of the import block.
+ skipChar, importLineOrBeginEnvOrEof :: Lexer ()
+ skipChar = comment <|> void anySingle
+ importLineOrBeginEnvOrEof =
+ lookAhead
+ (void (Char.string "\\import{")
+ <|> void beginToplevelEnvironment)
+ <|> eof
+
+ importLine :: Lexer (Located FilePath) = lexeme do
+ Char.string "\\import{"
+ path <- some (satisfy isTheoryNameChar)
+ Char.char '}'
+ pure path
+
+ isTheoryNameChar :: Char -> Bool
+ isTheoryNameChar c =
+ c /= '}' && c /= '\n' && c /= '\r' && c /= '\0'
+
+-- | Scan only the leading import block. Source-graph construction uses this
+-- authority-free pass before parsing modules under their composed syntax.
+gatherImports
+ :: FileId
+ -> String
+ -> Text
+ -> Either (ParseErrorBundle Text Void) [Located FilePath]
+gatherImports fileId file =
+ runParser (evalStateT importBlock (initLexerState fileId)) file
+
+
+beginToplevelEnvironment :: Lexer (Located Text)
+beginToplevelEnvironment = lexeme do
+ Char.string "\\begin{"
+ env :: Text <- asum (Char.string <$> ["definition", "theorem", "lemma", "axiom", "proof", "corollary", "proposition", "claim", "abbreviation", "datatype", "inductive", "signature", "struct"])
+ Char.char '}'
+ pure env
+
+-- | Parses tokens, switching tokenizing frames when encountering math and text environments.
+environment :: Lexer [Located Token]
+environment = do
+ env <- skipManyTill (comment <|> void anySingle) beginToplevelEnvironment
+ lts <- go (unLocated env) id
+ pure ((BeginEnv <$> env) : lts)
+ where
+ go env f = do
+ frame <- topFrame
+ r <- optional (nextTokenFor frame)
+ case r of
+ Nothing ->
+ pure (f [])
+ Just t@Located{unLocated = EndEnv env'}
+ | frame == TopText && env == env' ->
+ pure (f [t])
+ Just t -> do
+ modify' (advance (unLocated t))
+ go env (f . (t:))
+{-# INLINE environment #-}
+
+nextTokenFor :: Frame -> Lexer (Located Token)
+nextTokenFor = \case
+ TopText -> normalToken
+ MathFrame{} -> mathToken
+ TextFrame n -> textToken n
+
+-- | Parses a single normal-mode token.
+normalToken :: Lexer (Located Token)
+normalToken =
+ word <|> symbol <|> beginMath <|> beginAlign <|> subEnvironment <|> opening <|> closing <|> label <|> ref <|> end <|> command
+
+-- | Parses a single math mode token.
+mathToken :: Lexer (Located Token)
+mathToken =
+ var <|> symbol <|> number <|> beginCases <|> endAlign <|> endCases <|> opening <|> closing <|> beginText <|> beginExplanation <|> endMath <|> command
+
+beginText :: Lexer (Located Token)
+beginText = lexeme do
+ Char.string "\\text{" <|> Char.string "\\textbox{"
+ pure (BeginEnv "text")
+
+-- | Same as text modulo spacing, so we treat it synonymously
+beginExplanation :: Lexer (Located Token)
+beginExplanation = lexeme do
+ Char.string "\\explanation{"
+ pure (BeginEnv "text")
+
+subEnvironment :: Lexer (Located Token)
+subEnvironment = beginOrEnd ["enumerate", "subproof", "byCase"]
+ where
+ beginOrEnd envs = asum [beginEnv env <|> endEnv env | env <- envs]
+ beginEnv env = lexeme do
+ Char.string ("\\begin{" <> env <> "}")
+ pure (BeginEnv env)
+ endEnv env = lexeme do
+ Char.string ("\\end{" <> env <> "}")
+ pure (EndEnv env)
+
+-- | Normal mode embedded into math mode via @\text{...}@.
+textToken :: Int -> Lexer (Located Token)
+textToken n = word <|> symbol <|> textEnd <|> beginMath <|> beginAlign <|> opening' <|> closing' <|> ref <|> command
+ where
+ textEnd = lexeme do
+ guard (n == 1)
+ Char.char '}'
+ pure (EndEnv "text")
+
+ opening' = lexeme (group <|> optional (Char.string "\\left") *> (brace <|> paren <|> bracket))
+ where
+ brace = VisibleBraceL <$ lexeme (Char.string "\\{")
+ group = InvisibleBraceL <$ lexeme (Char.char '{')
+ paren = ParenL <$ lexeme (Char.char '(')
+ bracket = BracketL <$ lexeme (Char.char '[')
+
+ closing' = lexeme (group <|> optional (Char.string "\\right") *> (brace <|> paren <|> bracket))
+ where
+ brace = VisibleBraceR <$ lexeme (Char.string "\\}")
+ group = InvisibleBraceR <$ lexeme (Char.char '}')
+ paren = ParenR <$ lexeme (Char.char ')')
+ bracket = BracketR <$ lexeme (Char.char ']')
+
+
+-- | Parses a single begin math token.
+beginMath :: Lexer (Located Token)
+beginMath = lexeme do
+ Char.string "\\(" <|> Char.string "\\[" <|> Char.string "$"
+ pure (BeginEnv "math")
+
+beginAlign :: Lexer (Located Token)
+beginAlign = lexeme do
+ Char.string "\\begin{align*}"
+ pure (BeginEnv "align*")
+
+beginCases :: Lexer (Located Token)
+beginCases = lexeme do
+ Char.string "\\begin{cases}"
+ pure (BeginEnv "cases")
+
+-- | Parses a single end math token.
+endMath :: Lexer (Located Token)
+endMath = lexeme do
+ Char.string "\\)" <|> Char.string "\\]" <|> Char.string "$"
+ pure (EndEnv "math")
+
+endAlign :: Lexer (Located Token)
+endAlign = lexeme do
+ Char.string "\\end{align*}"
+ pure (EndEnv "align*")
+
+endCases :: Lexer (Located Token)
+endCases = lexeme do
+ Char.string "\\end{cases}"
+ pure (EndEnv "cases")
+
+
+-- | Parses the end of an environment.
+-- Commits only after having seen "\end{".
+end :: Lexer (Located Token)
+end = lexeme do
+ notFollowedBy (Char.string "\\end{cases}")
+ Char.string "\\end{"
+ env <- some (Char.letterChar <|> Char.char '*')
+ Char.char '}'
+ pure (EndEnv (Text.pack env))
+
+
+
+-- | Parses a word. Words are returned casefolded, since we want to ignore their case later on.
+word :: Lexer (Located Token)
+word = lexeme do
+ w <- some (Char.letterChar <|> Char.char '\'' <|> Char.char '-')
+ let t = Word (Text.toCaseFold (Text.pack w))
+ pure t
+
+number :: Lexer (Located Token)
+number = lexeme $ Integer <$> Lexer.decimal
+
+
+var :: Lexer (Located Token)
+var = lexeme (fmap Variable var')
+ where
+ var' = do
+ alphabeticPart <- letter <|> bb <|> greek
+ variationPart <- subscript <|> ticked <|> pure ""
+ pure (alphabeticPart <> variationPart)
+
+ subscript :: Lexer Text
+ subscript = do
+ Char.char '_'
+ unbraced <|> braced <|> text
+ where
+ unbraced = Text.singleton <$> Char.alphaNumChar
+ braced = Text.pack <$> (Char.char '{' *> some (Char.alphaNumChar <|> tick) <* Char.char '}')
+ text = Char.string "\\text" *> braced -- for rendering the subscript in roman type
+
+ -- A bit of a hack to fit the TPTP format.
+ tick :: Lexer Char
+ tick = '_' <$ Char.char '\''
+
+ ticked :: Lexer Text
+ ticked = do
+ ticks <- some tick
+ pure (Text.pack ticks)
+
+ letter :: Lexer Text
+ letter = fmap Text.singleton Char.letterChar
+
+ greek :: Lexer Text
+ greek = try do
+ Char.char '\\'
+ l <- symbolParser greeks
+ notFollowedBy Char.letterChar
+ pure l
+
+ greeks :: [Text]
+ greeks =
+ [ "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta"
+ , "iota", "kappa", "lambda", "mu", "nu", "xi", "pi", "rho", "sigma"
+ , "tau", "upsilon", "phi", "chi", "psi", "omega"
+ , "Gamma", "Delta", "Theta", "Lambda", "Xi", "Pi", "Sigma", "Upsilon"
+ , "Phi", "Psi", "Omega"
+ ]
+
+ bb :: Lexer Text
+ bb = do
+ Char.string "\\mathbb{"
+ l <- symbolParser bbs
+ Char.char '}'
+ pure $ "bb" <> l
+
+ bbs :: [Text]
+ bbs = Text.singleton <$> ['A'..'Z']
+
+
+ symbolParser :: [Text] -> Lexer Text
+ symbolParser symbols = asum (fmap Char.string symbols)
+
+
+symbol :: Lexer (Located Token)
+symbol = lexeme do
+ symb <- some (satisfy (`elem` symbols))
+ pure (Symbol (Text.pack symb))
+ where
+ symbols :: [Char]
+ symbols = ".,:;!?@=≠+-/|^><≤≥*&≈⊂⊃⊆⊇∈“”‘’"
+
+-- | Parses a TEX-style command.
+command :: Lexer (Located Token)
+command = lexeme do
+ Char.char '\\'
+ cmd <- some Char.letterChar
+ pure (Command (Text.pack cmd))
+
+-- | Parses a label command and extracts its marker.
+label :: Lexer (Located Token)
+label = lexeme do
+ Char.string "\\label{"
+ m <- marker
+ Char.char '}'
+ pure (Label m)
+
+-- | Parses a label command and extracts its marker.
+ref :: Lexer (Located Token)
+ref = lexeme do
+ -- @\\cref@ is from @cleveref@ and @\\hyperref@ is from @hyperref@
+ cmd <- Char.string "\\ref{" <|> Char.string "\\cref{" <|> Char.string "\\hyperref["
+ ms <- NonEmpty.fromList <$> marker `sepBy1` Char.char ','
+ case cmd of
+ "\\hyperref[" -> Char.string "]{" *> some (satisfy (/= '}')) *> Char.char '}' *> pure (Ref ms)
+ _ -> Char.char '}' *> pure (Ref ms)
+
+marker :: Lexer Text
+marker = do
+ c <- satisfy isAsciiLetter
+ cs <- takeWhileP Nothing isAsciiAlphaNumOrUnderscore
+ pure (Text.cons c cs)
+
+-- | Parses an opening delimiter.
+opening :: Lexer (Located Token)
+opening = lexeme (group <|> optional (Char.string "\\left") *> (paren <|> brace <|> bracket))
+ where
+ brace = VisibleBraceL <$ lexeme (Char.string "\\{")
+ group = InvisibleBraceL <$ lexeme (Char.char '{')
+ paren = ParenL <$ lexeme (Char.char '(')
+ bracket = BracketL <$ lexeme (Char.char '[')
+
+-- | Parses a closing delimiter.
+closing :: Lexer (Located Token)
+closing = lexeme (group <|> optional (Char.string "\\right") *> (paren <|> brace <|> bracket))
+ where
+ brace = VisibleBraceR <$ lexeme (Char.string "\\}")
+ group = InvisibleBraceR <$ lexeme (Char.char '}')
+ paren = ParenR <$ lexeme (Char.char ')')
+ bracket = BracketR <$ lexeme (Char.char ']')
+
+-- | Turns a Lexer into one that tracks the source position of the token
+-- and consumes trailing whitespace.
+lexeme :: Lexer a -> Lexer (Located a)
+lexeme p = do
+ fileId <- gets currentFileId
+ start <- getSourcePos
+ location <-
+ either
+ (fail . show)
+ pure
+ (fromSourcePosChecked fileId start)
+ t <- p
+ w <- whitespace
+ pure (Located location t w)
+
+space :: Lexer Whitespace
+space = Space <$ (Char.char ' ' <|> Char.char '\n' <|> Char.char '\r')
+ <|> Space <$ (Char.string "\\ " <|> Char.string "\\\\" <|> Char.string "\\!" <|> Char.string "\\," <|> Char.string "\\:" <|> Char.string "\\;" <|> Char.string "\\;")
+
+whitespace :: Lexer Whitespace
+whitespace = do
+ ws <- many (spaces <|> NoSpace <$ comment)
+ pure (collapseWhitespace ws)
+ where
+ spaces = collapseWhitespace <$> some space
+
+comment :: Lexer ()
+comment = Lexer.skipLineComment "%"
diff --git a/source/Felix/Test/All.hs b/source/Felix/Test/All.hs
new file mode 100644
index 0000000..7cdc49c
--- /dev/null
+++ b/source/Felix/Test/All.hs
@@ -0,0 +1,14 @@
+module Felix.Test.All where
+
+
+import Base
+import Felix.Test.Golden
+import Felix.Test.Unit
+import Test.Tasty
+
+
+runTests :: IO ()
+runTests = defaultMain =<< tests
+
+tests :: IO TestTree
+tests = testGroup "all tests" <$> sequence [goldenTests, return unitTests]
diff --git a/source/Felix/Test/Golden.hs b/source/Felix/Test/Golden.hs
new file mode 100644
index 0000000..905ff73
--- /dev/null
+++ b/source/Felix/Test/Golden.hs
@@ -0,0 +1,86 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Felix.Test.Golden where
+
+
+import Base
+import Felix.Workspace qualified as Workspace
+
+import Data.Text.Lazy.IO qualified as LazyTextIO
+import System.Directory
+import System.FilePath
+import Test.Tasty
+import Test.Tasty.Golden (goldenVsFile, findByExtension)
+import Text.Pretty.Simple (pShowNoColor)
+import UnliftIO
+goldenTests :: IO TestTree
+goldenTests = goldenTestGroup
+
+goldenTestGroup :: MonadUnliftIO io => io TestTree
+goldenTestGroup = testGroup "golden tests" <$> sequence
+ [ tokenizing
+ , scanning
+ , parsing
+ ]
+
+
+-- | A testing triple consists of a an 'input' file, which is proccesed, resulting
+-- in 'output' file, which is then compared to a 'golden' file.
+data Triple = Triple
+ { input :: FilePath
+ , output :: FilePath
+ , golden :: FilePath
+ }
+ deriving (Show, Eq)
+
+
+-- | Gathers all the files for the test. We test all examples and everything in @test/pass/@.
+-- The golden files for all tests are stored in @test/pass/@, so we need to adjust the filepath
+-- of the files from @examples/@.
+gatherTriples :: MonadIO io => String -> io [Triple]
+gatherTriples stage = do
+ inputs <- liftIO (findByExtension [".tex"] "test/examples")
+ pure $
+ [ Triple{..}
+ | input <- inputs
+ , let input' = "test" </> "golden" </> takeBaseName input </> stage
+ , let golden = input' <.> "golden"
+ , let output = input' <.> "out"
+ ]
+
+createTripleDirectoriesIfMissing :: MonadIO io => Triple -> io ()
+createTripleDirectoriesIfMissing Triple{..} = liftIO $
+ createDirectoryIfMissing True (takeDirectory output)
+
+makeGoldenTest :: MonadUnliftIO io => String -> (Triple -> io ()) -> io TestTree
+makeGoldenTest stage action = do
+ triples <- gatherTriples stage
+ for triples createTripleDirectoriesIfMissing
+ runInIO <- askRunInIO
+ pure $ testGroup stage
+ [ goldenVsFile
+ (takeBaseName input) -- test name
+ golden
+ output
+ (runInIO (action triple))
+ | triple@Triple{..} <- triples
+ ]
+
+tokenizing :: MonadUnliftIO io => io TestTree
+tokenizing = makeGoldenTest "tokenizing" $ \Triple{..} -> do
+ tokenStream <- liftIO (Workspace.tokenize input)
+ liftIO
+ (LazyTextIO.writeFile output
+ (pShowNoColor (Workspace.simpleStream tokenStream)))
+
+
+scanning :: MonadUnliftIO io => io TestTree
+scanning = makeGoldenTest "scanning" $ \Triple{..} -> do
+ lexicalItems <- liftIO (Workspace.scan input)
+ liftIO (LazyTextIO.writeFile output (pShowNoColor lexicalItems))
+
+parsing :: MonadUnliftIO io => io TestTree
+parsing = makeGoldenTest "parsing" $ \Triple{..} -> do
+ parseResult <- liftIO (Workspace.parse input)
+ liftIO (LazyTextIO.writeFile output (pShowNoColor parseResult))
diff --git a/source/Felix/Test/Unit.hs b/source/Felix/Test/Unit.hs
new file mode 100644
index 0000000..f11d21f
--- /dev/null
+++ b/source/Felix/Test/Unit.hs
@@ -0,0 +1,52 @@
+module Felix.Test.Unit where
+
+
+import Felix.Test.Unit.Abstract qualified as Abstract
+import Felix.Test.Unit.Backend qualified as Backend
+import Felix.Test.Unit.CommandLine qualified as CommandLine
+import Felix.Test.Unit.Concrete qualified as Concrete
+import Felix.Test.Unit.Core qualified as Core
+import Felix.Test.Unit.Declaration qualified as Declaration
+import Felix.Test.Unit.Foundation qualified as Foundation
+import Felix.Test.Unit.Html qualified as Html
+import Felix.Test.Unit.HtmlLayout qualified as HtmlLayout
+import Felix.Test.Unit.HtmlOutput qualified as HtmlOutput
+import Felix.Test.Unit.Identity qualified as Identity
+import Felix.Test.Unit.Kernel qualified as Kernel
+import Felix.Test.Unit.Lexicon qualified as Lexicon
+import Felix.Test.Unit.Materialization qualified as Materialization
+import Felix.Test.Unit.Meaning qualified as Meaning
+import Felix.Test.Unit.Module qualified as Module
+import Felix.Test.Unit.OutputPlan qualified as OutputPlan
+import Felix.Test.Unit.Provers qualified as Provers
+import Felix.Test.Unit.Semantic qualified as Semantic
+import Felix.Test.Unit.Source qualified as Source
+import Felix.Test.Unit.Store qualified as Store
+import Felix.Test.Unit.Token qualified as Token
+import Test.Tasty
+
+unitTests :: TestTree
+unitTests = testGroup "unit tests"
+ [ Abstract.unitTests
+ , Backend.unitTests
+ , CommandLine.unitTests
+ , Concrete.unitTests
+ , Core.unitTests
+ , Declaration.unitTests
+ , Foundation.unitTests
+ , Identity.unitTests
+ , Html.unitTests
+ , HtmlLayout.unitTests
+ , HtmlOutput.unitTests
+ , Kernel.unitTests
+ , Lexicon.unitTests
+ , Meaning.unitTests
+ , Materialization.unitTests
+ , Module.unitTests
+ , OutputPlan.unitTests
+ , Provers.unitTests
+ , Semantic.unitTests
+ , Source.unitTests
+ , Store.unitTests
+ , Token.unitTests
+ ]
diff --git a/source/Felix/Test/Unit/Abstract.hs b/source/Felix/Test/Unit/Abstract.hs
new file mode 100644
index 0000000..c487c2a
--- /dev/null
+++ b/source/Felix/Test/Unit/Abstract.hs
@@ -0,0 +1,114 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.Abstract (unitTests) where
+
+import Base
+import Felix.Report.Location
+import Felix.Syntax.Abstract
+
+import Hedgehog
+import Hedgehog.Gen qualified as Gen
+import Test.Tasty
+import Test.Tasty.HUnit hiding (assert)
+import Test.Tasty.Hedgehog (testPropertyNamed)
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Abstract syntax"
+ [ testPropertyNamed
+ "noun phrase ordering obeys the Ord laws"
+ "prop_nounPhraseOrd"
+ prop_nounPhraseOrd
+ , testCase
+ "noun phrase fields survive ordered deduplication"
+ nounPhraseFieldsRemainDistinct
+ ]
+
+prop_nounPhraseOrd :: Property
+prop_nounPhraseOrd = withTests 100 . property $ do
+ x <- forAll nounPhrase
+ y <- forAll nounPhrase
+ z <- forAll nounPhrase
+
+ (compare x y == EQ) === (x == y)
+ compare x y === oppositeOrdering (compare y x)
+ assert (not (x <= y && y <= z) || x <= z)
+
+nounPhraseFieldsRemainDistinct :: Assertion
+nounPhraseFieldsRemainDistinct =
+ assertEqual
+ "one value per base and changed field"
+ 6
+ (length
+ (nubOrd
+ [ sampleNounPhrase False False False False False
+ , sampleNounPhrase True False False False False
+ , sampleNounPhrase False True False False False
+ , sampleNounPhrase False False True False False
+ , sampleNounPhrase False False False True False
+ , sampleNounPhrase False False False False True
+ ]))
+
+nounPhrase :: Gen (NounPhraseOf Maybe Int)
+nounPhrase =
+ sampleNounPhrase
+ <$> Gen.bool
+ <*> Gen.bool
+ <*> Gen.bool
+ <*> Gen.bool
+ <*> Gen.bool
+
+sampleNounPhrase
+ :: Bool
+ -> Bool
+ -> Bool
+ -> Bool
+ -> Bool
+ -> NounPhraseOf Maybe Int
+sampleNounPhrase hasLeft otherNoun hasName hasRight hasSuchThat =
+ NounPhrase
+ [AdjL Nowhere leftAdjective [1] | hasLeft]
+ (Noun
+ Nowhere
+ (if otherNoun then secondNoun else firstNoun)
+ [2])
+ (NamedVar "x" <$ guardMaybe hasName)
+ [AdjR Nowhere rightAdjective [3] | hasRight]
+ (truthStatement <$ guardMaybe hasSuchThat)
+ where
+ guardMaybe condition =
+ if condition then Just () else Nothing
+
+leftAdjective :: LexicalItem
+leftAdjective =
+ mkLexicalItem [Just (Word "left")] "left"
+
+rightAdjective :: LexicalItem
+rightAdjective =
+ mkLexicalItem [Just (Word "right")] "right"
+
+firstNoun :: LexicalItemSgPl
+firstNoun =
+ mkLexicalItemSgPl
+ (SgPl
+ [Just (Word "first")]
+ [Just (Word "firsts")])
+ "first"
+
+secondNoun :: LexicalItemSgPl
+secondNoun =
+ mkLexicalItemSgPl
+ (SgPl
+ [Just (Word "second")]
+ [Just (Word "seconds")])
+ "second"
+
+truthStatement :: Stmt
+truthStatement =
+ StmtFormula (PropositionalConstant Nowhere IsTop)
+
+oppositeOrdering :: Ordering -> Ordering
+oppositeOrdering = \case
+ LT -> GT
+ EQ -> EQ
+ GT -> LT
diff --git a/source/Felix/Test/Unit/Backend.hs b/source/Felix/Test/Unit/Backend.hs
new file mode 100644
index 0000000..384d21b
--- /dev/null
+++ b/source/Felix/Test/Unit/Backend.hs
@@ -0,0 +1,752 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.Backend (unitTests) where
+
+import Base hiding (Empty)
+import Felix.Checking.Backend.Problem
+import Felix.Checking.Backend.Tptp
+import Felix.Checking.Core
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Provers
+import Tptp.UnsortedFirstOrder qualified as Tptp
+
+import Data.Map.Strict qualified as Map
+import Data.Text qualified as Text
+import Data.Vector (Vector)
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+data TestGlobal
+ = FirstOrderPredicate
+ | HigherOrderPredicate
+ deriving (Show, Eq, Ord)
+
+testGlobalType :: TestGlobal -> Maybe CoreType
+testGlobalType = \case
+ FirstOrderPredicate ->
+ Just (TySet `TyArrow` TyProp)
+ HigherOrderPredicate ->
+ Just
+ ((TySet `TyArrow` TyProp)
+ `TyArrow` TyProp)
+
+data TestLocal
+ = ObjectLocal
+ | PredicateLocal
+ deriving (Show, Eq, Ord)
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Typed backend problem"
+ [ testCase
+ "projects proposition equality as equivalence"
+ classifiesPropositionEquality
+ , testCase
+ "projects exact ambient support"
+ projectsExactAmbientSupport
+ , testCase
+ "routes implicit, explicit, and local-only problems"
+ routesCompleteProblems
+ , testCase
+ "admits only checked implicit set constructions"
+ admitsImplicitSetConstructions
+ , testCase
+ "renders checked FOF and TH0 problems"
+ rendersCheckedProblems
+ ]
+
+classifiesPropositionEquality :: Assertion
+classifiesPropositionEquality = do
+ proposition <-
+ checkedProposition
+ Vector.empty
+ (CEq TyProp CFalsum CFalsum)
+ capability <-
+ either
+ (assertFailure . show)
+ pure
+ (classifySupportedProposition
+ testGlobalType
+ proposition)
+ case capability of
+ FofProjectable{} ->
+ pure ()
+ RequiresTh0 exclusions ->
+ assertFailure
+ ("proposition equality was not projected: "
+ <> show exclusions)
+
+projectsExactAmbientSupport :: Assertion
+projectsExactAmbientSupport = do
+ let term :: CanonicalTerm Void
+ term =
+ CForall TySet
+ (CEq TySet
+ (CBound 1)
+ (CBound 3))
+ scoped <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ (const Nothing)
+ [TySet, TySet, TySet]
+ term)
+ projected <-
+ either
+ (assertFailure . show)
+ pure
+ (projectSupportedProposition
+ (const Nothing)
+ (Vector.fromList
+ [ (0 :: Int, TySet)
+ , (1, TySet)
+ , (2, TySet)
+ ])
+ scoped)
+ assertEqual
+ "unused middle support is removed"
+ (Vector.fromList
+ [ (0 :: Int, TySet)
+ , (2, TySet)
+ ])
+ (supportedPropositionSupport projected)
+ assertEqual
+ "indices are remapped below nested binders"
+ (CForall TySet
+ (CEq TySet
+ (CBound 1)
+ (CBound 2)))
+ (supportedPropositionTerm projected)
+
+routesCompleteProblems :: Assertion
+routesCompleteProblems = do
+ fofFact <-
+ checkedBackendFact
+ (0 :: Int)
+ firstOrderClaim
+ th0Fact <-
+ checkedBackendFact
+ (1 :: Int)
+ higherOrderClaim
+ let fofFacts =
+ Vector.singleton fofFact
+ th0Facts =
+ Vector.singleton th0Fact
+ claim <-
+ checkedProposition
+ (Vector.singleton
+ (ObjectLocal, TySet))
+ (CApp
+ (CGlobal FirstOrderPredicate)
+ (CBound 0))
+ firstOrderLocal <-
+ checkedLocalPremise
+ 0
+ "first-order local"
+ claim
+ higherOrderLocalProposition <-
+ checkedProposition
+ (Vector.singleton
+ (PredicateLocal,
+ TySet `TyArrow` TyProp))
+ (CApp
+ (CGlobal HigherOrderPredicate)
+ (CBound 0))
+ higherOrderLocal <-
+ checkedLocalPremise
+ 1
+ "higher-order local"
+ higherOrderLocalProposition
+
+ implicit <-
+ planned
+ fofFacts
+ claim
+ [higherOrderLocal, firstOrderLocal]
+ []
+ FirstOrderLocals
+ ImplicitConstructionJustification
+ assertEqual "implicit route" RouteFof
+ (typedProblemRoute implicit)
+ assertEqual "implicit FOF globals" [0]
+ (typedBackendFactReference
+ <$> toList
+ (typedProblemGlobalPremises
+ implicit))
+ assertEqual "first-order local only" [0]
+ (localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList
+ (typedProblemLocalPremises
+ implicit))
+
+ explicitFof <-
+ planned
+ fofFacts
+ claim
+ [higherOrderLocal, firstOrderLocal]
+ []
+ FirstOrderLocals
+ ExplicitHigherOrderJustification
+ assertEqual "explicit FOF route" RouteFof
+ (typedProblemRoute explicitFof)
+ assertEqual "explicit FOF references retain only FOF locals" [0]
+ (localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList
+ (typedProblemLocalPremises explicitFof))
+
+ explicitTh0 <-
+ planned
+ th0Facts
+ claim
+ [higherOrderLocal, firstOrderLocal]
+ []
+ CompleteLocals
+ ExplicitHigherOrderJustification
+ assertEqual "explicit TH0 route" RouteTh0
+ (typedProblemRoute explicitTh0)
+ assertEqual "explicit TH0 references retain complete locals" [0, 1]
+ (localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList
+ (typedProblemLocalPremises explicitTh0))
+
+ localOnly <-
+ planned
+ Vector.empty
+ claim
+ [higherOrderLocal, firstOrderLocal]
+ []
+ CompleteLocals
+ ExplicitHigherOrderJustification
+ assertEqual "local-only TH0 route" RouteTh0
+ (typedProblemRoute localOnly)
+ assertEqual "local order restored" [0, 1]
+ (localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList
+ (typedProblemLocalPremises
+ localOnly))
+ assertEqual
+ "complete ambient local inventory"
+ (Map.fromList
+ [ (ObjectLocal, TySet)
+ , (PredicateLocal,
+ TySet `TyArrow` TyProp)
+ ])
+ (typedProblemLocalTypes localOnly)
+
+ case planTypedProblem
+ testGlobalType
+ Vector.empty
+ claim
+ [firstOrderLocal, firstOrderLocal]
+ []
+ CompleteLocals
+ ExplicitHigherOrderJustification of
+ Left
+ (TypedProblemDuplicateLocalPremiseOrdinal
+ duplicateOrdinal) ->
+ assertEqual
+ "duplicate local ordinal"
+ 0
+ (localPremiseOrdinalValue
+ duplicateOrdinal)
+ result ->
+ assertFailure
+ ("expected duplicate local ordinal error, got "
+ <> showProblemResult result)
+
+ higherOrderClaimProposition <-
+ checkedProposition
+ Vector.empty
+ higherOrderClaim
+ case planTypedProblem
+ testGlobalType
+ fofFacts
+ higherOrderClaimProposition
+ []
+ []
+ FirstOrderLocals
+ ImplicitConstructionJustification of
+ Left
+ TypedProblemExplicitHigherOrderJustificationRequired{} ->
+ pure ()
+ result ->
+ assertFailure
+ ("expected explicit higher-order error, got "
+ <> showProblemResult result)
+ checkedFoundationValue <-
+ either
+ (assertFailure . show)
+ pure
+ Foundation.checkedFoundation
+ case planTypedProblem
+ testGlobalType
+ fofFacts
+ claim
+ []
+ [typedFoundationAuxiliaryInput
+ checkedFoundationValue
+ Foundation.SeparationCharacteristic]
+ FirstOrderLocals
+ ImplicitConstructionJustification of
+ Left
+ TypedProblemExplicitHigherOrderJustificationRequired{} ->
+ pure ()
+ result ->
+ assertFailure
+ ("expected implicit higher-order auxiliary error, got "
+ <> showProblemResult result)
+ unusedContext <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ testGlobalType
+ [TySet `TyArrow` TyProp]
+ firstOrderClaim)
+ case supportedProposition
+ (Vector.singleton
+ (PredicateLocal,
+ TySet `TyArrow` TyProp))
+ unusedContext of
+ Left (UnusedSupportedLocal PredicateLocal) ->
+ pure ()
+ Left err ->
+ assertFailure
+ ("unexpected exact-support error: "
+ <> show err)
+ Right _ ->
+ assertFailure
+ "unused ambient local entered exact support"
+ where
+ planned selectedFacts claim locals auxiliaries localPolicy higherOrderPolicy =
+ either
+ (assertFailure . show)
+ pure
+ (planTypedProblem
+ testGlobalType
+ selectedFacts
+ claim
+ locals
+ auxiliaries
+ localPolicy
+ higherOrderPolicy)
+
+ showProblemResult = \case
+ Left err ->
+ show err
+ Right problem ->
+ show (typedProblemRoute problem)
+
+admitsImplicitSetConstructions :: Assertion
+admitsImplicitSetConstructions = do
+ checkedFoundationValue <-
+ either
+ (assertFailure . show)
+ pure
+ Foundation.checkedFoundation
+ let separation =
+ CApp
+ (CApp
+ (CIntrinsic Sep)
+ (CIntrinsic Empty))
+ (CLam TySet
+ (CApp
+ (CGlobal HigherOrderPredicate)
+ (CLam TySet CFalsum)))
+ separationClaim =
+ CEq TySet separation separation
+ filteredDomain =
+ CApp
+ (CApp
+ (CIntrinsic Sep)
+ (CBound 0))
+ (CLam TySet
+ (CEq TySet (CBound 0) (CBound 0)))
+ innerReplacement =
+ CApp
+ (CApp (CIntrinsic Repl) filteredDomain)
+ (CLam TySet (CBound 0))
+ functionalReplacement =
+ CApp
+ (CIntrinsic FamilyUnion)
+ (CApp
+ (CApp
+ (CIntrinsic Repl)
+ (CIntrinsic Empty))
+ (CLam TySet innerReplacement))
+ replacementClaim =
+ CEq TySet functionalReplacement functionalReplacement
+ replacementTags =
+ [ Foundation.FamilyUnionCharacteristic
+ , Foundation.SeparationCharacteristic
+ , Foundation.ReplacementCharacteristic
+ ]
+ auxiliary tag =
+ typedFoundationAuxiliaryInput
+ checkedFoundationValue tag
+ plan selected claim locals tags =
+ planTypedProblem
+ testGlobalType
+ selected
+ claim
+ locals
+ (auxiliary <$> tags)
+ FirstOrderLocals
+ ImplicitConstructionJustification
+
+ separationProposition <-
+ checkedProposition Vector.empty separationClaim
+ separationProblem <-
+ either
+ (assertFailure . show)
+ pure
+ (plan
+ Vector.empty
+ separationProposition
+ []
+ [Foundation.SeparationCharacteristic])
+ assertEqual "separation implicit route" RouteTh0
+ (typedProblemRoute separationProblem)
+ assertEqual "separation characteristic only"
+ [Foundation.SeparationCharacteristic]
+ (typedProblemAuxiliaryTag
+ <$> toList (typedProblemAuxiliaries separationProblem))
+ assertEqual "separation selects no global premise"
+ 0
+ (Vector.length (typedProblemGlobalPremises separationProblem))
+
+ replacementProposition <-
+ checkedProposition Vector.empty replacementClaim
+ replacementProblem <-
+ either
+ (assertFailure . show)
+ pure
+ (plan
+ Vector.empty
+ replacementProposition
+ []
+ replacementTags)
+ assertEqual "functional replacement implicit route" RouteTh0
+ (typedProblemRoute replacementProblem)
+ assertEqual "functional replacement exact helper set"
+ replacementTags
+ (typedProblemAuxiliaryTag
+ <$> toList (typedProblemAuxiliaries replacementProblem))
+
+ firstOrderProposition <-
+ checkedProposition Vector.empty firstOrderClaim
+ firstOrderLocal <-
+ checkedLocalPremise 0 "first-order" firstOrderProposition
+ separationLocal <-
+ checkedLocalPremise 2 "separation" separationProposition
+ unrelatedLocalProposition <-
+ checkedProposition
+ (Vector.singleton
+ (PredicateLocal, TySet `TyArrow` TyProp))
+ (CApp
+ (CGlobal HigherOrderPredicate)
+ (CBound 0))
+ unrelatedLocal <-
+ checkedLocalPremise 1 "unrelated higher-order" unrelatedLocalProposition
+ separationWithLocals <-
+ either
+ (assertFailure . show)
+ pure
+ (plan
+ Vector.empty
+ separationProposition
+ [unrelatedLocal, firstOrderLocal]
+ [Foundation.SeparationCharacteristic])
+ assertEqual "inline separation keeps unrelated HO local out" RouteTh0
+ (typedProblemRoute separationWithLocals)
+ assertEqual "inline separation retains only FOF local" [0]
+ ( localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList (typedProblemLocalPremises separationWithLocals)
+ )
+ assertEqual "excluded HO local adds no auxiliary"
+ [Foundation.SeparationCharacteristic]
+ (typedProblemAuxiliaryTag
+ <$> toList (typedProblemAuxiliaries separationWithLocals))
+ localProblem <-
+ either
+ (assertFailure . show)
+ pure
+ (plan
+ Vector.empty
+ firstOrderProposition
+ [unrelatedLocal, separationLocal, firstOrderLocal]
+ [])
+ assertEqual "implicit construction local remains excluded" RouteFof
+ (typedProblemRoute localProblem)
+ assertEqual "unrelated higher-order local remains unselected"
+ [0]
+ ( localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList (typedProblemLocalPremises localProblem)
+ )
+
+ higherOrderFact <- checkedBackendFact (1 :: Int) higherOrderClaim
+ expectImplicitHigherOrderRejection
+ "implicit higher-order global remains forbidden"
+ (plan
+ (Vector.singleton higherOrderFact)
+ separationProposition
+ []
+ [Foundation.SeparationCharacteristic])
+
+ ordinaryHigherOrder <-
+ checkedProposition Vector.empty
+ (CEq TySet
+ (CApp
+ (CIntrinsic SetChoose)
+ (CLam TySet CFalsum))
+ (CIntrinsic Empty))
+ expectImplicitHigherOrderRejection
+ "ordinary implicit higher-order target remains forbidden"
+ (plan Vector.empty ordinaryHigherOrder [] [])
+ mixedHigherOrder <-
+ checkedProposition Vector.empty
+ (CImp
+ separationClaim
+ (supportedPropositionTerm ordinaryHigherOrder))
+ expectImplicitHigherOrderRejection
+ "construction does not admit another higher-order intrinsic"
+ (plan
+ Vector.empty
+ mixedHigherOrder
+ []
+ [Foundation.SeparationCharacteristic])
+
+ expectImplicitHigherOrderRejection
+ "auxiliary tag alone grants no construction permission"
+ (plan
+ Vector.empty
+ firstOrderProposition
+ []
+ [Foundation.SeparationCharacteristic])
+ where
+ expectImplicitHigherOrderRejection label = \case
+ Left TypedProblemExplicitHigherOrderJustificationRequired{} ->
+ pure ()
+ Left err ->
+ assertFailure (label <> ": unexpected error " <> show err)
+ Right problem ->
+ assertFailure
+ (label <> ": unexpectedly routed "
+ <> show (typedProblemRoute problem))
+
+rendersCheckedProblems :: Assertion
+rendersCheckedProblems = do
+ fofFact <-
+ checkedBackendFact
+ (0 :: Int)
+ firstOrderClaim
+ th0Fact <-
+ checkedBackendFact
+ (1 :: Int)
+ higherOrderClaim
+ claim <-
+ checkedProposition
+ (Vector.singleton
+ (ObjectLocal, TySet))
+ (CApp
+ (CGlobal FirstOrderPredicate)
+ (CBound 0))
+ fofProblem <-
+ planned
+ (Vector.singleton fofFact)
+ claim
+ FirstOrderLocals
+ ImplicitConstructionJustification
+ th0Problem <-
+ planned
+ (Vector.singleton th0Fact)
+ claim
+ CompleteLocals
+ ExplicitHigherOrderJustification
+ preparedFof <-
+ either
+ (assertFailure . show)
+ pure
+ (prepareTypedTptpProblem
+ fofProblem)
+ preparedTh0 <-
+ either
+ (assertFailure . show)
+ pure
+ (prepareTypedTptpProblem
+ th0Problem)
+ proverTask <-
+ either
+ (assertFailure . show)
+ pure
+ (prepareTypedProverTask
+ DirectTask
+ th0Problem)
+ assertEqual "FOF route" RouteFof
+ (preparedTypedTptpRoute preparedFof)
+ assertBool "FOF formulas"
+ ("fof(tg_h0,axiom,"
+ `Text.isInfixOf`
+ preparedTypedTptpText
+ preparedFof)
+ assertBool "FOF has no TH0 declarations"
+ (not
+ ("thf("
+ `Text.isInfixOf`
+ preparedTypedTptpText
+ preparedFof))
+ assertEqual "TH0 route" RouteTh0
+ (preparedTypedTptpRoute preparedTh0)
+ assertEqual "TH0 request dialect"
+ VerificationTh0
+ (preparedVerificationDialect
+ (preparedTypedProverRequest
+ proverTask))
+ assertEqual "request preserves exact prepared text"
+ (preparedTypedTptpText preparedTh0)
+ (preparedVerificationText
+ (preparedTypedProverRequest
+ proverTask))
+ for_
+ [ "thf(tg_h0,axiom,"
+ , "^ [V0:$i]"
+ , "thf(tg_q0,conjecture,"
+ ]
+ \fragment ->
+ assertBool
+ ("TH0 contains " <> Text.unpack fragment)
+ (fragment
+ `Text.isInfixOf`
+ preparedTypedTptpText
+ preparedTh0)
+ for_
+ (Map.keys
+ (preparedTypedTptpNameOrigins
+ preparedTh0))
+ \target ->
+ assertBool
+ ("valid generated name " <> Text.unpack target)
+ (if "V" `Text.isPrefixOf` target
+ then Tptp.isProperVariable target
+ else Tptp.isProperAtomicWord target)
+ where
+ planned selectedFacts claim localPolicy higherOrderPolicy =
+ either
+ (assertFailure . show)
+ pure
+ (planTypedProblem
+ testGlobalType
+ selectedFacts
+ claim
+ []
+ []
+ localPolicy
+ higherOrderPolicy)
+
+firstOrderClaim :: CanonicalTerm TestGlobal
+firstOrderClaim =
+ CApp
+ (CGlobal FirstOrderPredicate)
+ (CIntrinsic Empty)
+
+higherOrderClaim :: CanonicalTerm TestGlobal
+higherOrderClaim =
+ CApp
+ (CGlobal HigherOrderPredicate)
+ (CLam TySet
+ (CApp
+ (CGlobal FirstOrderPredicate)
+ (CBound 0)))
+
+checkedProposition
+ :: Vector (TestLocal, CoreType)
+ -> CanonicalTerm TestGlobal
+ -> IO
+ (SupportedProposition
+ TestLocal
+ TestGlobal)
+checkedProposition support term = do
+ checked <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ testGlobalType
+ (snd <$> Vector.toList support)
+ term)
+ either
+ (assertFailure . show)
+ pure
+ (supportedProposition support checked)
+
+checkedClosedProposition
+ :: CanonicalTerm TestGlobal
+ -> IO
+ (SupportedProposition
+ Void
+ TestGlobal)
+checkedClosedProposition term = do
+ checked <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ testGlobalType
+ []
+ term)
+ either
+ (assertFailure . show)
+ pure
+ (supportedProposition
+ Vector.empty
+ checked)
+
+checkedBackendFact
+ :: ref
+ -> CanonicalTerm TestGlobal
+ -> IO (TypedBackendFact ref TestGlobal)
+checkedBackendFact reference term = do
+ proposition <-
+ checkedClosedProposition term
+ capability <-
+ either
+ (assertFailure . show)
+ pure
+ (classifySupportedProposition
+ testGlobalType
+ proposition)
+ pure
+ (typedBackendFact
+ reference
+ proposition
+ capability)
+
+checkedLocalPremise
+ :: Natural
+ -> Text
+ -> SupportedProposition TestLocal TestGlobal
+ -> IO
+ (TypedLocalPremise
+ TestLocal
+ Text
+ TestGlobal)
+checkedLocalPremise ordinal premiseOrigin proposition =
+ either
+ (assertFailure . show)
+ pure
+ (typedLocalPremise
+ testGlobalType
+ (localPremiseOrdinal ordinal)
+ premiseOrigin
+ proposition)
diff --git a/source/Felix/Test/Unit/CommandLine.hs b/source/Felix/Test/Unit/CommandLine.hs
new file mode 100644
index 0000000..f2a4a10
--- /dev/null
+++ b/source/Felix/Test/Unit/CommandLine.hs
@@ -0,0 +1,799 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.CommandLine (unitTests) where
+
+import Base
+import Felix.CommandLine
+import Felix.Output.Atomic qualified as Atomic
+import Felix.Source (safeRelativePath)
+import Felix.Store qualified as Store
+import Felix.Verification qualified as Verification
+import Felix.Provers qualified as Provers
+import Felix.Render.Html.Output qualified as HtmlOutput
+import Felix.Report.Location (pattern Nowhere)
+
+import Control.Exception (IOException, bracket)
+import Control.Exception qualified as Exception
+import Data.ByteString qualified as ByteString
+import Data.List qualified as List
+import Data.Text qualified as Text
+import Data.Text.Encoding qualified as TextEncoding
+import Options.Applicative (ParserResult(..))
+import Options.Applicative qualified as Options
+import System.Directory qualified as Directory
+import System.Environment (getEnvironment)
+import System.Exit (ExitCode(..))
+import System.FilePath.Posix ((</>))
+import System.Process
+ ( CreateProcess(..)
+ , proc
+ , readCreateProcessWithExitCode
+ )
+import Test.Tasty
+import Test.Tasty.HUnit
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Command line"
+ [ testCase "parses the closed command model"
+ parsesClosedCommands
+ , testCase "rejects conflicting command options"
+ rejectsConflictingOptions
+ , testCase "maps structured outcomes to process status" do
+ for_ outcomeCases \(outcome, expectedExitCode) ->
+ commandOutcomeExitCode outcome
+ `shouldBe` expectedExitCode
+ , testCase "reports the committed HTML prefix"
+ reportsCommittedHtmlPrefix
+ , testCase "removes an unpublished dump temporary"
+ removesFailedDumpTemporary
+ , testGroup "process boundary"
+ [ testCase "version needs no input or store"
+ versionNeedsNoInputOrStore
+ , testCase "parse-only uses no store or Vampire"
+ parseOnlyUsesNoAuthority
+ , testCase "malformed source has a stable failure class"
+ malformedSourceHasStableFailure
+ , testCase "invalid output needs no source pass or store startup"
+ invalidOutputPrecedesStoreStartup
+ , testCase "nested HTML routes fail before store startup"
+ nestedHtmlRoutesPrecedeStoreStartup
+ , testCase "verified theorem exits successfully" do
+ (exitCode, stdout, stderr) <- runCliWithFakeVampire
+ [ "printf '%s\\n' '% SZS status Theorem for cli'"
+ , "exit 0"
+ ]
+ exitCode `shouldBe` ExitSuccess
+ stdout `shouldBe` ""
+ stderr `shouldContain` "Verification successful."
+ , testCase "omitted proof reports a located explicit gap" do
+ (exitCode, stdout, stderr) <-
+ runCliWithSourceAndConfiguredVampire
+ cliGapSource
+ writeNonExecutableFile
+ exitCode `shouldBe` ExitSuccess
+ stdout `shouldBe` ""
+ stderr `shouldContain`
+ "Verification completed with explicit proof gaps."
+ stderr `shouldContain` "1 explicit proof gap"
+ stderr `shouldContain` "input.tex 5:5"
+ , testCase "countermodel exits as verification rejection" do
+ (exitCode, stdout, stderr) <- runCliWithFakeVampire
+ [ "printf '%s\\n' '% SZS status CounterSatisfiable for cli'"
+ , "exit 0"
+ ]
+ exitCode `shouldBe` ExitFailure 1
+ stdout `shouldBe` ""
+ stderr `shouldContain`
+ "Verification failed: prover found countermodel"
+ , testCase "failed prover exits as infrastructure failure" do
+ (exitCode, stdout, stderr) <- runCliWithFakeVampire
+ [ "printf '%s\\n' '% SZS status Theorem for cli'"
+ , "exit 7"
+ ]
+ exitCode `shouldBe` ExitFailure 2
+ stdout `shouldBe` ""
+ stderr `shouldContain`
+ "UnsuccessfulVampireExit (ExitFailure 7)"
+ , testCase "prover launch failure exits as infrastructure failure" do
+ (exitCode, stdout, stderr) <-
+ runCliWithConfiguredVampire writeNonExecutableFile
+ exitCode `shouldBe` ExitFailure 2
+ stdout `shouldBe` ""
+ stderr `shouldContain` "ProverLaunchFailed"
+ , testCase "dumps the exact executed request once"
+ dumpsExactExecutedRequest
+ , testCase "launch failure dumps no request"
+ launchFailureDumpsNoRequest
+ , testCase "failed verification retains its executed dump subset"
+ dumpsOnlyExecutedPrefix
+ , testCase "dump and HTML share one verification"
+ dumpAndHtmlVerifyOnce
+ , testCase "semantic failure publishes no HTML"
+ semanticFailurePublishesNoHtml
+ , testCase "fresh and cached presentation publish equal HTML"
+ freshAndCachedPresentationAgree
+ , testCase "missing renderer data is a typed failure"
+ missingRendererDataIsTyped
+ , testCase
+ "post-verification HTML failure retains authorization report"
+ htmlFailureRetainsAuthorizationReport
+ ]
+ ]
+
+parsesClosedCommands :: Assertion
+parsesClosedCommands = do
+ case parseCommandArguments ["--version"] of
+ Success Version ->
+ pure ()
+ other ->
+ assertFailure
+ ("unexpected version parse: " <> showParserResult other)
+ case parseCommandArguments ["input.tex", "--parseonly"] of
+ Success (ParseOnly (Input "input.tex")) ->
+ pure ()
+ other ->
+ assertFailure
+ ("unexpected parse-only parse: " <> showParserResult other)
+ case parseCommandArguments ["input.tex"] of
+ Success
+ (Verify
+ (Input "input.tex")
+ VerificationOptions
+ { verificationStoreSelection =
+ Store.DefaultStore
+ }) ->
+ pure ()
+ other ->
+ assertFailure
+ ("unexpected default verify parse: "
+ <> showParserResult other)
+ case parseCommandArguments ["input.tex", "--jobs", "3"] of
+ Success
+ (Verify
+ (Input "input.tex")
+ VerificationOptions
+ { verificationJobsOverride = Just jobs
+ }) ->
+ Provers.effectiveJobsValue jobs `shouldBe` 3
+ other ->
+ assertFailure
+ ("unexpected jobs parse: " <> showParserResult other)
+ case parseCommandArguments ["input.tex", "--fresh"] of
+ Success
+ (Verify
+ (Input "input.tex")
+ VerificationOptions
+ { verificationStoreSelection =
+ Store.FreshTemporaryStore
+ }) ->
+ pure ()
+ other ->
+ assertFailure
+ ("unexpected verify parse: " <> showParserResult other)
+
+rejectsConflictingOptions :: Assertion
+rejectsConflictingOptions = do
+ for_
+ [ ["input.tex", "--parseonly", "--fresh"]
+ , ["input.tex", "--parseonly", "--jobs", "2"]
+ , ["input.tex", "--parseonly", "--dump", "dump"]
+ , ["input.tex", "--parseonly", "--html"]
+ , ["input.tex", "--store", "store.sqlite", "--fresh"]
+ ]
+ \arguments ->
+ case parseCommandArguments arguments of
+ Failure _failure ->
+ pure ()
+ other ->
+ assertFailure
+ ("conflicting options were accepted: "
+ <> show arguments
+ <> " as "
+ <> showParserResult other)
+ case parseCommandArguments ["input.tex", "--jobs", "0"] of
+ Failure _failure -> pure ()
+ other ->
+ assertFailure
+ ("non-positive jobs were accepted as "
+ <> showParserResult other)
+
+showParserResult :: ParserResult Command -> String
+showParserResult = \case
+ Success selected ->
+ show selected
+ Failure failure ->
+ fst (Options.renderFailure failure "felix")
+ CompletionInvoked _completion ->
+ "completion invoked"
+
+versionNeedsNoInputOrStore :: Assertion
+versionNeedsNoInputOrStore =
+ withCliFixture cliSource \fixture -> do
+ (exitCode, stdout, stderr) <-
+ runCliFixture fixture ["--version"]
+ exitCode `shouldBe` ExitSuccess
+ stdout `shouldContain` "Version 0.3.0.0"
+ stderr `shouldBe` ""
+ assertNoDefaultStore fixture
+
+parseOnlyUsesNoAuthority :: Assertion
+parseOnlyUsesNoAuthority =
+ withCliFixture cliPreludeSyntaxSource \fixture -> do
+ writeNonExecutableFile (cliFixtureVampire fixture)
+ (exitCode, stdout, stderr) <-
+ runCliFixture
+ fixture
+ ["input.tex", "--parseonly"]
+ exitCode `shouldBe` ExitSuccess
+ stdout `shouldBe` ""
+ stderr `shouldBe` ""
+ assertNoDefaultStore fixture
+
+malformedSourceHasStableFailure :: Assertion
+malformedSourceHasStableFailure =
+ withCliFixture malformedCliSource \fixture -> do
+ (exitCode, stdout, stderr) <-
+ runCliFixture
+ fixture
+ ["input.tex", "--parseonly"]
+ exitCode `shouldBe` ExitFailure 1
+ stdout `shouldBe` ""
+ stderr `shouldContain` "Parsing failed: project:input.tex"
+ stderr `shouldContain` "input.tex 2:5"
+ stderr `shouldContain` "unconsumed word"
+ assertBool "does not print an internal error constructor"
+ (not ("SourceParseError" `List.isInfixOf` stderr))
+ assertNoDefaultStore fixture
+
+invalidOutputPrecedesStoreStartup :: Assertion
+invalidOutputPrecedesStoreStartup =
+ withCliFixture cliSource \fixture -> do
+ let dump = cliFixtureRoot fixture </> "dump"
+ Directory.removeFile
+ (cliFixtureRoot fixture </> "input.tex")
+ Directory.createDirectory dump
+ writeFile (dump </> "stale.p") "stale"
+ (exitCode, stdout, stderr) <-
+ runCliFixture fixture
+ ["input.tex", "--dump", "dump"]
+ exitCode `shouldBe` ExitFailure 2
+ stdout `shouldBe` ""
+ stderr `shouldContain` "Verification output preflight failed:"
+ stderr `shouldContain` (Text.pack (show dump))
+ stderr `shouldContain` "choose an absent or empty directory"
+ stderr `shouldContain` "stale.p"
+ assertNoDefaultStore fixture
+
+reportsCommittedHtmlPrefix :: Assertion
+reportsCommittedHtmlPrefix = do
+ first <- checkedRelative "a.html"
+ second <- checkedRelative "nested/b.html"
+ failed <- checkedRelative "nested/c.html"
+ assertEqual
+ "deterministic committed prefix"
+ [ "HTML publication failed at \"nested/c.html\": disk full"
+ , "HTML files published before the failure: \"a.html\", \"nested/b.html\""
+ ]
+ (HtmlOutput.renderHtmlPublicationError
+ (HtmlOutput.IncompleteHtmlPublication
+ [first, second]
+ failed
+ "disk full"))
+ where
+ checkedRelative path =
+ case safeRelativePath path of
+ Left problem ->
+ assertFailure
+ ("invalid test route " <> show path <> ": " <> show problem)
+ >> fail "unreachable"
+ Right relative ->
+ pure relative
+
+nestedHtmlRoutesPrecedeStoreStartup :: Assertion
+nestedHtmlRoutesPrecedeStoreStartup =
+ withCliFixture nestedHtmlRootSource \fixture -> do
+ let root = cliFixtureRoot fixture
+ nested = root </> "a.html"
+ Directory.createDirectory nested
+ writeFile (root </> "a.tex") cliSource
+ writeFile (nested </> "b.tex") cliSource
+ (exitCode, stdout, stderr) <-
+ runCliFixture fixture ["input.tex", "--html"]
+ exitCode `shouldBe` ExitFailure 2
+ stdout `shouldBe` ""
+ stderr `shouldContain` "HTML route planning failed:"
+ stderr `shouldContain` "\"a.html\""
+ stderr `shouldContain` "\"a.html/b.html\""
+ assertNoDefaultStore fixture
+
+removesFailedDumpTemporary :: Assertion
+removesFailedDumpTemporary =
+ withTemporaryDirectory "felix-dump-atomic" \root -> do
+ let destination = root </> "1.p"
+ Directory.createDirectory destination
+ result <- Exception.try
+ (Atomic.writeBytesAtomically
+ destination
+ (TextEncoding.encodeUtf8 "complete request"))
+ :: IO (Either IOException ())
+ case result of
+ Left _failure ->
+ pure ()
+ Right () ->
+ assertFailure "dump publication unexpectedly succeeded"
+ contents <- List.sort <$> Directory.listDirectory root
+ assertEqual
+ "only the pre-existing final target remains"
+ ["1.p"]
+ contents
+
+dumpsExactExecutedRequest :: Assertion
+dumpsExactExecutedRequest =
+ withCliFixture cliSource \fixture -> do
+ seedPackagedPreludeCache fixture
+ let captured = cliFixtureRoot fixture </> "captured.p"
+ writeExecutableScript
+ (cliFixtureVampire fixture)
+ [ "cat > " <> show captured
+ , "printf '%s\\n' '% SZS status Theorem for cli'"
+ ]
+ (exitCode, _stdout, stderr) <-
+ runCliFixture fixture
+ [ "input.tex"
+ , "--dump"
+ , "dump"
+ ]
+ exitCode `shouldBe` ExitSuccess
+ stderr `shouldContain` "Verification successful."
+ dumped <- ByteString.readFile
+ (cliFixtureRoot fixture </> "dump" </> "1-1.p")
+ sent <- ByteString.readFile captured
+ assertEqual "dump is the exact process input" sent dumped
+ assertBool "request is dumped only once"
+ . not
+ =<< Directory.doesPathExist
+ (cliFixtureRoot fixture </> "dump" </> "1-2.p")
+
+launchFailureDumpsNoRequest :: Assertion
+launchFailureDumpsNoRequest =
+ withCliFixture cliSource \fixture -> do
+ seedPackagedPreludeCache fixture
+ writeNonExecutableFile (cliFixtureVampire fixture)
+ (exitCode, _stdout, stderr) <-
+ runCliFixture fixture
+ [ "input.tex"
+ , "--dump"
+ , "dump"
+ ]
+ exitCode `shouldBe` ExitFailure 2
+ stderr `shouldContain` "ProverLaunchFailed"
+ assertBool "no request was dumped before process launch"
+ . not
+ =<< Directory.doesPathExist
+ (cliFixtureRoot fixture </> "dump" </> "1-1.p")
+
+dumpsOnlyExecutedPrefix :: Assertion
+dumpsOnlyExecutedPrefix =
+ withCliFixture cliTwoSource \fixture -> do
+ seedPackagedPreludeCache fixture
+ writeExecutableScript
+ (cliFixtureVampire fixture)
+ [ "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status CounterSatisfiable for cli'"
+ ]
+ (exitCode, _stdout, stderr) <-
+ runCliFixture fixture
+ [ "input.tex"
+ , "--dump"
+ , "dump"
+ ]
+ exitCode `shouldBe` ExitFailure 1
+ stderr `shouldContain` "prover found countermodel"
+ assertBool "executed request was dumped"
+ =<< Directory.doesFileExist
+ (cliFixtureRoot fixture </> "dump" </> "1-1.p")
+ -- Prospective execution may start a source-later request before the
+ -- admission cursor observes this first rejection. Dump ownership is
+ -- therefore the actual executed subset, not a semantic prefix.
+
+dumpAndHtmlVerifyOnce :: Assertion
+dumpAndHtmlVerifyOnce =
+ withCliFixture cliSource \fixture -> do
+ seedPackagedPreludeCache fixture
+ let countPath = cliFixtureRoot fixture </> "vampire-runs"
+ writeExecutableScript
+ (cliFixtureVampire fixture)
+ [ "cat >/dev/null"
+ , "printf '%s\\n' run >> " <> show countPath
+ , "printf '%s\\n' '% SZS status Theorem for cli'"
+ ]
+ (exitCode, _stdout, stderr) <-
+ runCliFixture fixture
+ [ "input.tex"
+ , "--dump"
+ , "dump"
+ , "--html"
+ ]
+ exitCode `shouldBe` ExitSuccess
+ stderr `shouldContain` "Verification successful."
+ runs <- List.lines <$> readFile countPath
+ assertEqual "one semantic verification" ["run"] runs
+ assertBool "request dump was published"
+ =<< Directory.doesFileExist
+ (cliFixtureRoot fixture </> "dump" </> "1-1.p")
+ assertBool "root HTML page was published"
+ =<< Directory.doesFileExist
+ (cliFixtureRoot fixture </> "html" </> "input.html")
+ assertBool "HTML support asset was published"
+ =<< Directory.doesFileExist
+ (cliFixtureRoot fixture
+ </> "html"
+ </> "_static"
+ </> "naproche-html.js")
+
+semanticFailurePublishesNoHtml :: Assertion
+semanticFailurePublishesNoHtml =
+ withCliFixture cliSource \fixture -> do
+ seedPackagedPreludeCache fixture
+ writeExecutableScript
+ (cliFixtureVampire fixture)
+ [ "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status CounterSatisfiable for cli'"
+ ]
+ (exitCode, _stdout, stderr) <-
+ runCliFixture fixture
+ [ "input.tex"
+ , "--dump"
+ , "dump"
+ , "--html"
+ ]
+ exitCode `shouldBe` ExitFailure 1
+ stderr `shouldContain` "prover found countermodel"
+ assertBool "semantic failure retains the executed request dump"
+ =<< Directory.doesFileExist
+ (cliFixtureRoot fixture </> "dump" </> "1-1.p")
+ assertBool "semantic failure publishes no HTML"
+ . not
+ =<< Directory.doesPathExist
+ (cliFixtureRoot fixture </> "html")
+
+freshAndCachedPresentationAgree :: Assertion
+freshAndCachedPresentationAgree =
+ withCliFixture cliSource \fixture -> do
+ seedPackagedPreludeCache fixture
+ writeExecutableScript
+ (cliFixtureVampire fixture)
+ [ "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for cli'"
+ ]
+ (freshExit, _freshStdout, freshStderr) <-
+ runCliFixture fixture ["input.tex", "--html"]
+ freshExit `shouldBe` ExitSuccess
+ freshStderr `shouldContain` "Verification successful."
+ let htmlRoot = cliFixtureRoot fixture </> "html"
+ page = htmlRoot </> "input.html"
+ support =
+ htmlRoot </> "_static" </> "naproche-html.js"
+ freshPage <- ByteString.readFile page
+ freshSupport <- ByteString.readFile support
+ Directory.removePathForcibly htmlRoot
+ writeNonExecutableFile (cliFixtureVampire fixture)
+ (warmExit, _warmStdout, warmStderr) <-
+ runCliFixture fixture ["input.tex", "--html"]
+ warmExit `shouldBe` ExitSuccess
+ warmStderr `shouldContain` "Verification successful."
+ warmPage <- ByteString.readFile page
+ warmSupport <- ByteString.readFile support
+ assertEqual "fresh/cache-hit page bytes" freshPage warmPage
+ assertEqual "fresh/cache-hit support bytes"
+ freshSupport warmSupport
+
+missingRendererDataIsTyped :: Assertion
+missingRendererDataIsTyped =
+ withCliFixture cliSource \fixture -> do
+ seedPackagedPreludeCache fixture
+ Directory.removeFile
+ (cliFixtureRoot fixture </> "library" </> "lexicon.tsv")
+ writeExecutableScript
+ (cliFixtureVampire fixture)
+ [ "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for cli'"
+ ]
+ (exitCode, stdout, stderr) <-
+ runCliFixture fixture ["input.tex", "--html"]
+ exitCode `shouldBe` ExitFailure 2
+ stdout `shouldBe` ""
+ stderr `shouldContain` "HTML preparation failed:"
+ stderr `shouldContain` "renderer data \"lexicon.tsv\" was not found"
+ assertBool "does not expose an ErrorCall"
+ (not ("ErrorCall" `List.isInfixOf` stderr))
+ assertBool "failed preparation publishes no HTML"
+ . not
+ =<< Directory.doesPathExist
+ (cliFixtureRoot fixture </> "html")
+
+htmlFailureRetainsAuthorizationReport :: Assertion
+htmlFailureRetainsAuthorizationReport =
+ withCliFixture cliGapSource \fixture -> do
+ seedPackagedPreludeCache fixture
+ Directory.removeFile
+ (cliFixtureRoot fixture </> "library" </> "lexicon.tsv")
+ writeNonExecutableFile (cliFixtureVampire fixture)
+ (exitCode, stdout, stderr) <-
+ runCliFixture fixture ["input.tex", "--html"]
+ exitCode `shouldBe` ExitFailure 2
+ stdout `shouldBe` ""
+ stderr `shouldContain`
+ "Verification succeeded, but HTML preparation failed:"
+ stderr `shouldContain`
+ "Direct source authorization summary: 0 source axioms, 1 explicit proof gap."
+ stderr `shouldContain` "Explicit proof gap at input.tex 5:5"
+ assertBool "failed output publishes no HTML"
+ . not
+ =<< Directory.doesPathExist
+ (cliFixtureRoot fixture </> "html")
+
+outcomeCases :: [(CommandOutcome, ExitCode)]
+outcomeCases =
+ [ (CommandCompleted, ExitSuccess)
+ , (VerificationSucceeded emptyReport emptySlowReport, ExitSuccess)
+ , (VerificationCompletedWithGaps emptyReport emptySlowReport, ExitSuccess)
+ , ( VerificationRejected
+ emptyReport
+ (Verification.FailedVerification
+ Nowhere
+ (Verification.CountermodelFailure ""))
+ emptySlowReport
+ , ExitFailure 1
+ )
+ , ( VerificationRejected
+ emptyReport
+ (Verification.FailedVerification
+ Nowhere
+ (Verification.IndeterminateFailure ""))
+ emptySlowReport
+ , ExitFailure 2
+ )
+ , ( VerificationCheckingRejected
+ emptyReport
+ (Verification.VerificationModuleSchedulerInvariant "test")
+ emptySlowReport
+ , ExitFailure 2
+ )
+ ]
+
+emptyReport :: Verification.VerificationReport
+emptyReport =
+ Verification.VerificationReport
+ { Verification.verificationDirectEscapes = []
+ }
+
+emptySlowReport :: Provers.SlowAtpReport
+emptySlowReport = Provers.SlowAtpReport 0 []
+
+runCliWithFakeVampire
+ :: [String]
+ -> IO (ExitCode, String, String)
+runCliWithFakeVampire scriptLines =
+ runCliWithConfiguredVampire \vampirePath -> do
+ writeExecutableScript vampirePath
+ (["cat >/dev/null"] <> scriptLines)
+
+runCliWithConfiguredVampire
+ :: (FilePath -> IO ())
+ -> IO (ExitCode, String, String)
+runCliWithConfiguredVampire =
+ runCliWithSourceAndConfiguredVampire cliSource
+
+runCliWithSourceAndConfiguredVampire
+ :: String
+ -> (FilePath -> IO ())
+ -> IO (ExitCode, String, String)
+runCliWithSourceAndConfiguredVampire source prepareVampire =
+ withCliFixture source \fixture -> do
+ seedPackagedPreludeCache fixture
+ prepareVampire (cliFixtureVampire fixture)
+ runCliFixture fixture ["input.tex"]
+
+data CliFixture = CliFixture
+ { cliFixtureRoot :: !FilePath
+ , cliFixtureExecutable :: !FilePath
+ , cliFixtureVampire :: !FilePath
+ , cliFixtureCacheRoot :: !FilePath
+ , cliFixtureEnvironment :: ![(String, String)]
+ }
+
+withCliFixture
+ :: String
+ -> (CliFixture -> IO value)
+ -> IO value
+withCliFixture source action =
+ withTemporaryDirectory "felix-cli" \temp -> do
+ felixExecutable <- requireFelixExecutable
+ repositoryRoot <- Directory.getCurrentDirectory
+ let sourcePath = temp </> "input.tex"
+ vampirePath = temp </> "vampire"
+ libraryPath = temp </> "library"
+ debugPath = temp </> "debug"
+ cacheRoot = temp </> "cache"
+ Directory.createDirectory libraryPath
+ Directory.createDirectory debugPath
+ Directory.createDirectory cacheRoot
+ writeFile sourcePath source
+ ByteString.readFile
+ (repositoryRoot </> "library" </> "lexicon.tsv")
+ >>= ByteString.writeFile
+ (libraryPath </> "lexicon.tsv")
+ inheritedEnvironment <- getEnvironment
+ let processEnvironment =
+ setEnvironmentVariable
+ "XDG_CACHE_HOME"
+ cacheRoot
+ (setEnvironmentVariable
+ "FELIX_VAMPIRE"
+ vampirePath
+ (setEnvironmentVariable
+ "NAPROCHE_LIB"
+ libraryPath
+ inheritedEnvironment))
+ action
+ CliFixture
+ { cliFixtureRoot = temp
+ , cliFixtureExecutable = felixExecutable
+ , cliFixtureVampire = vampirePath
+ , cliFixtureCacheRoot = cacheRoot
+ , cliFixtureEnvironment = processEnvironment
+ }
+
+runCliFixture
+ :: CliFixture
+ -> [String]
+ -> IO (ExitCode, String, String)
+runCliFixture fixture arguments =
+ readCreateProcessWithExitCode
+ ((proc
+ (cliFixtureExecutable fixture)
+ arguments)
+ { cwd = Just (cliFixtureRoot fixture)
+ , env = Just (cliFixtureEnvironment fixture)
+ })
+ ""
+
+-- | Populate only the packaged final-prelude root. Process-boundary tests
+-- can then exercise the requested ordinary module outcome without making
+-- their fake prover depend on the prelude's private obligation count.
+seedPackagedPreludeCache :: CliFixture -> Assertion
+seedPackagedPreludeCache fixture = do
+ let sourcePath = cliFixtureRoot fixture </> "input.tex"
+ original <- ByteString.readFile sourcePath
+ writeExecutableScript
+ (cliFixtureVampire fixture)
+ [ "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for prelude seed'"
+ ]
+ (exitCode, stdout, stderr) <-
+ (do
+ writeFile sourcePath "% cache the packaged final prelude\n"
+ runCliFixture fixture ["input.tex"])
+ `Exception.finally` ByteString.writeFile sourcePath original
+ exitCode `shouldBe` ExitSuccess
+ stdout `shouldBe` ""
+ stderr `shouldContain` "Verification successful."
+
+assertNoDefaultStore :: CliFixture -> Assertion
+assertNoDefaultStore fixture =
+ assertBool "default store was not created"
+ . not
+ =<< Directory.doesPathExist
+ (cliFixtureCacheRoot fixture </> "felix")
+
+writeExecutableScript :: FilePath -> [String] -> IO ()
+writeExecutableScript path scriptLines = do
+ writeFile path
+ (unlines (["#!/bin/sh"] <> scriptLines))
+ permissions <- Directory.getPermissions path
+ Directory.setPermissions path
+ (Directory.setOwnerExecutable True permissions)
+
+writeNonExecutableFile :: FilePath -> IO ()
+writeNonExecutableFile path = do
+ exists <- Directory.doesPathExist path
+ if exists
+ then Directory.removeFile path
+ else pure ()
+ writeFile path "not executable"
+
+requireFelixExecutable :: IO FilePath
+requireFelixExecutable :: IO FilePath
+ = do
+ executable <- Directory.findExecutable "felix"
+ case executable of
+ Just path ->
+ pure path
+ Nothing -> do
+ assertFailure "felix build tool is not available on PATH"
+ pure "felix"
+
+setEnvironmentVariable
+ :: String
+ -> String
+ -> [(String, String)]
+ -> [(String, String)]
+setEnvironmentVariable name value environment =
+ (name, value) : List.filter ((/= name) . fst) environment
+
+cliSource :: String
+cliSource =
+ unlines
+ [ "\\begin{proposition}\\label{cli_test}"
+ , " $\\forall x. x = x$."
+ , "\\end{proposition}"
+ ]
+
+cliPreludeSyntaxSource :: String
+cliPreludeSyntaxSource =
+ unlines
+ [ "\\begin{proposition}\\label{parse_prelude_syntax}"
+ , " For all $x$ we have $\\preludeSuccessor{x} = \\preludeSuccessor{x}$."
+ , "\\end{proposition}"
+ ]
+
+cliGapSource :: String
+cliGapSource =
+ unlines
+ [ "\\begin{proposition}\\label{cli_gap}"
+ , " $\\forall x. x = x$."
+ , "\\end{proposition}"
+ , "\\begin{proof}"
+ , " Omitted."
+ , "\\end{proof}"
+ ]
+
+cliTwoSource :: String
+cliTwoSource =
+ unlines
+ [ "\\begin{proposition}\\label{cli_first}"
+ , " $\\forall x. x = x$."
+ , "\\end{proposition}"
+ , "\\begin{proposition}\\label{cli_second}"
+ , " $\\forall y. y = y$."
+ , "\\end{proposition}"
+ ]
+
+malformedCliSource :: String
+malformedCliSource =
+ unlines
+ [ "\\begin{proposition}\\label{malformed}"
+ , " This is not a proposition."
+ , "\\end{proposition}"
+ ]
+
+nestedHtmlRootSource :: String
+nestedHtmlRootSource =
+ unlines
+ [ "\\import{a.tex}"
+ , "\\import{a.html/b.tex}"
+ ]
+
+withTemporaryDirectory :: String -> (FilePath -> IO a) -> IO a
+withTemporaryDirectory template =
+ bracket create Directory.removePathForcibly
+ where
+ create = do
+ systemTemp <- Directory.getTemporaryDirectory
+ (path, handle) <- openTempFile systemTemp template
+ hClose handle
+ Directory.removeFile path
+ Directory.createDirectory path
+ pure path
+
+shouldContain :: String -> Text -> Assertion
+shouldContain actual expected =
+ assertBool
+ ("expected " <> show actual <> " to contain " <> show expected)
+ (expected `Text.isInfixOf` Text.pack actual)
+
+shouldBe :: (Eq a, Show a, HasCallStack) => a -> a -> Assertion
+shouldBe =
+ flip (assertEqual "")
diff --git a/source/Felix/Test/Unit/Concrete.hs b/source/Felix/Test/Unit/Concrete.hs
new file mode 100644
index 0000000..7eac7df
--- /dev/null
+++ b/source/Felix/Test/Unit/Concrete.hs
@@ -0,0 +1,221 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.Concrete (unitTests) where
+
+import Base
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Concrete (grammar)
+import Felix.Syntax.Lexicon (builtins)
+import Felix.Syntax.Token (runLexer)
+
+import Data.Text qualified as Text
+import Test.Tasty
+import Test.Tasty.HUnit
+import Text.Earley (fullParses, parser)
+import Text.Megaparsec (errorBundlePretty)
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Parser"
+ [ testCase
+ "textual connectives follow symbolic precedence"
+ textualConnectivePrecedence
+ , testCase
+ "transfinite induction precedes its continuation"
+ transfiniteInductionContinuation
+ ]
+
+data StatementShape
+ = Truth
+ | Falsity
+ | Connected Raw.Connective StatementShape StatementShape
+ | Scoped StatementShape
+ deriving (Show, Eq)
+
+textualConnectivePrecedence :: Assertion
+textualConnectivePrecedence = do
+ for_ cases \(label, statement, expected) ->
+ assertEqual label (Right expected) (statementShape =<< parseStatement statement)
+
+ assertBool
+ "chained iff is rejected"
+ (case parseStatement "$\\top$ iff $\\bot$ iff $\\top$" of
+ Left _ -> True
+ Right _ -> False)
+
+ assertEqual
+ "textual and symbolic connective trees agree"
+ (statementShape
+ =<< parseStatement
+ "$\\top \\land \\bot \\lor \\top$")
+ (statementShape
+ =<< parseStatement
+ "$\\top$ and $\\bot$ or $\\top$")
+ where
+ cases =
+ [ ( "and binds tighter than following or"
+ , "$\\top$ and $\\bot$ or $\\top$"
+ , Connected Raw.Disjunction
+ (Connected Raw.Conjunction Truth Falsity)
+ Truth
+ )
+ , ( "and binds tighter than preceding or"
+ , "$\\top$ or $\\bot$ and $\\top$"
+ , Connected Raw.Disjunction
+ Truth
+ (Connected Raw.Conjunction Falsity Truth)
+ )
+ , ( "and associates left"
+ , "$\\top$ and $\\bot$ and $\\top$"
+ , Connected Raw.Conjunction
+ (Connected Raw.Conjunction Truth Falsity)
+ Truth
+ )
+ , ( "or associates left"
+ , "$\\top$ or $\\bot$ or $\\top$"
+ , Connected Raw.Disjunction
+ (Connected Raw.Disjunction Truth Falsity)
+ Truth
+ )
+ , ( "either is accepted after ordinary or"
+ , "$\\top$ or either $\\bot$ or $\\top$"
+ , Connected Raw.Disjunction
+ Truth
+ (Connected Raw.ExclusiveOr Falsity Truth)
+ )
+ , ( "implication associates right"
+ , "if $\\top$ then if $\\bot$ then $\\top$"
+ , Connected Raw.Implication
+ Truth
+ (Connected Raw.Implication Falsity Truth)
+ )
+ , ( "a quantified implication antecedent ends at then"
+ , "if for all $x$ we have $\\top$ then $\\bot$"
+ , Connected Raw.Implication
+ (Scoped Truth)
+ Falsity
+ )
+ , ( "parentheses override precedence"
+ , "($\\top$ or $\\bot$) and $\\top$"
+ , Connected Raw.Conjunction
+ (Connected Raw.Disjunction Truth Falsity)
+ Truth
+ )
+ , ( "a quantified right operand scopes over its continuation"
+ , "$\\top$ iff there exists $x$ such that $\\bot$ and $\\top$"
+ , Connected Raw.Equivalence
+ Truth
+ (Scoped
+ (Connected Raw.Conjunction Falsity Truth))
+ )
+ ]
+
+parseStatement :: Text -> Either String Raw.Stmt
+parseStatement statement = do
+ chunks <- case runLexer
+ (FileId 46)
+ "textual-connectives.tex"
+ (Text.unlines
+ [ "\\begin{axiom}\\label{textual_connectives}"
+ , statement <> "."
+ , "\\end{axiom}"
+ ]) of
+ Left err ->
+ Left (errorBundlePretty err)
+ Right (_imports, chunks') ->
+ Right chunks'
+ tokens <- case chunks of
+ [tokens'] ->
+ Right tokens'
+ _ ->
+ Left ("expected one source chunk, got " <> show (length chunks))
+ case fullParses (parser (grammar builtins)) tokens of
+ ( [Raw.BlockAxiom
+ _location
+ _title
+ _marker
+ (Raw.Axiom [] statement')]
+ , _report
+ ) ->
+ Right statement'
+ (blocks, report) ->
+ Left
+ ( "expected one axiom, got "
+ <> show blocks
+ <> " with "
+ <> show report
+ )
+
+statementShape :: Raw.Stmt -> Either String StatementShape
+statementShape = \case
+ Raw.StmtConnected conn _ left right ->
+ Connected conn
+ <$> statementShape left
+ <*> statementShape right
+ Raw.StmtFormula formula ->
+ formulaShape formula
+ Raw.SymbolicQuantified _ _ _ _ _ statement ->
+ Scoped <$> statementShape statement
+ statement ->
+ Left ("unsupported statement in precedence test: " <> show statement)
+
+formulaShape :: Raw.Formula -> Either String StatementShape
+formulaShape = \case
+ Raw.PropositionalConstant _ Raw.IsTop ->
+ Right Truth
+ Raw.PropositionalConstant _ Raw.IsBottom ->
+ Right Falsity
+ Raw.Connected _ conn left right ->
+ Connected conn
+ <$> formulaShape left
+ <*> formulaShape right
+ formula ->
+ Left ("unsupported formula in precedence test: " <> show formula)
+
+transfiniteInductionContinuation :: Assertion
+transfiniteInductionContinuation =
+ case runLexer
+ (FileId 45)
+ "transfinite-induction.tex"
+ sourceText of
+ Left err ->
+ assertFailure (errorBundlePretty err)
+ Right (_imports, [tokens]) ->
+ case fullParses (parser (grammar builtins)) tokens of
+ ( [ Raw.BlockProof
+ _start
+ (Raw.ByOrdInduction methodLocation
+ (Raw.Qed
+ (Just continuationLocation)
+ Raw.JustificationEmpty))
+ _end
+ ]
+ , _report
+ ) -> do
+ assertEqual
+ "method header line"
+ 2
+ (locLine methodLocation)
+ assertEqual
+ "continuation line"
+ 3
+ (locLine continuationLocation)
+ (blocks, report) ->
+ assertFailure
+ ( "expected one transfinite-induction proof, got "
+ <> show blocks
+ <> " with "
+ <> show report
+ )
+ Right (_imports, chunks) ->
+ assertFailure
+ ("expected one proof chunk, got " <> show (length chunks))
+ where
+ sourceText =
+ Text.unlines
+ [ "\\begin{proof}"
+ , "[proof by transfinite induction]"
+ , "Trivial."
+ , "\\end{proof}"
+ ]
diff --git a/source/Felix/Test/Unit/Core.hs b/source/Felix/Test/Unit/Core.hs
new file mode 100644
index 0000000..0388c7f
--- /dev/null
+++ b/source/Felix/Test/Unit/Core.hs
@@ -0,0 +1,670 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Test.Unit.Core (unitTests) where
+
+import Base hiding (Empty)
+import Felix.Checking.Core
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Checking.SetConstruction
+
+import Control.DeepSeq (NFData(..), force)
+import Hedgehog
+import Hedgehog.Gen qualified as Gen
+import Hedgehog.Range qualified as Range
+import Data.Set qualified as Set
+import Test.Tasty
+import Test.Tasty.HUnit hiding (assert)
+import Test.Tasty.Hedgehog (testPropertyNamed)
+
+
+data TestGlobal
+ = TestGlobal
+ | TestPairGlobal
+ deriving (Show, Eq, Ord)
+
+instance NFData TestGlobal where
+ rnf _global =
+ ()
+
+testGlobalType :: TestGlobal -> Maybe CoreType
+testGlobalType TestGlobal =
+ Just TySet
+testGlobalType TestPairGlobal =
+ Just (TySet `TyArrow` (TySet `TyArrow` TySet))
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Checked core"
+ [ testCase
+ "freezes explicit binder positions"
+ freezesExplicitBinderPositions
+ , testCase
+ "freezes generalized substitution without capture"
+ freezesGeneralizedSubstitution
+ , testCase
+ "rejects ill-typed and open terms"
+ rejectsInvalidTerms
+ , testCase
+ "rechecks canonical terms without unchecked construction"
+ rechecksCanonicalTerms
+ , testCase
+ "checks scoped canonical weakening and substitution"
+ checksScopedCanonicalOperations
+ , testCase
+ "builds set-induction hypotheses from the complete property"
+ buildsSetInductionHypotheses
+ , testCase
+ "specializes the checked separation characteristic"
+ specializesCheckedSeparationCharacteristic
+ , testCase
+ "specializes the checked replacement characteristic"
+ specializesCheckedReplacementCharacteristic
+ , testCase
+ "derives named construction views from checked components"
+ derivesNamedConstructionViews
+ , testCase
+ "thaws checked closed terms without changing them"
+ thawsCheckedClosedTerms
+ , testPropertyNamed
+ "optimized freeze agrees with bounded reference"
+ "prop_freezeAgreesWithReference"
+ prop_freezeAgreesWithReference
+ ]
+
+freezesExplicitBinderPositions :: Assertion
+freezesExplicitBinderPositions = do
+ let x = 0 :: Int
+ y = 1 :: Int
+ freeze body = do
+ checked <-
+ either
+ (assertFailure . show)
+ pure
+ (checkClosedCore
+ testGlobalType
+ (coreLambda TySet x
+ (coreLambda TyProp y body)))
+ either
+ (assertFailure . show)
+ (pure . frozenCoreTerm)
+ (freezeClosed checked)
+ nearest <-
+ freeze (coreLocal y)
+ outer <-
+ freeze (coreLocal x)
+ global <-
+ freeze (coreGlobal TestGlobal)
+ assertEqual
+ "nearest binder"
+ (CLam TySet (CLam TyProp (CBound 0)))
+ nearest
+ assertEqual
+ "outer binder"
+ (CLam TySet (CLam TyProp (CBound 1)))
+ outer
+ assertEqual
+ "global with both vacuous binders"
+ (CLam TySet (CLam TyProp (CGlobal TestGlobal)))
+ global
+
+freezesGeneralizedSubstitution :: Assertion
+freezesGeneralizedSubstitution = do
+ let outer = 0 :: Int
+ inner = 1 :: Int
+ placeholder = 2 :: Int
+ innerTerm =
+ coreLambda TySet inner (coreLocal placeholder)
+ substituted =
+ innerTerm >>= \local ->
+ if local == placeholder
+ then coreLocal outer
+ else coreLocal local
+ term =
+ coreLambda TyProp outer substituted
+ checked <-
+ either
+ (assertFailure . show)
+ pure
+ (checkClosedCore testGlobalType term)
+ optimized <-
+ either
+ (assertFailure . show)
+ pure
+ (freezeClosed checked)
+ reference <-
+ either
+ (assertFailure . show)
+ pure
+ (referenceFreezeClosed checked)
+ assertEqual "reference result" reference optimized
+ assertEqual
+ "outer variable remains outside the inner binder"
+ (CLam TyProp (CLam TySet (CBound 1)))
+ (frozenCoreTerm optimized)
+
+rejectsInvalidTerms :: Assertion
+rejectsInvalidTerms = do
+ assertEqual
+ "free local"
+ (Left UnboundCoreLocal)
+ (checkedCoreType
+ <$> checkClosedCore
+ testGlobalType
+ (coreLocal (0 :: Int)))
+ assertEqual
+ "application argument"
+ (Left
+ (ApplicationArgumentTypeMismatch
+ TySet
+ TyProp))
+ (checkedCoreType
+ <$> checkClosedCore
+ testGlobalType
+ (coreApply
+ (coreIntrinsic FamilyUnion)
+ coreFalsum))
+ assertEqual
+ "equality operand"
+ (Left
+ (EqualityOperandTypeMismatch
+ TySet
+ TyProp))
+ (checkedCoreType
+ <$> checkClosedCore
+ testGlobalType
+ (coreEquality
+ TySet
+ coreFalsum
+ (coreGlobal TestGlobal)))
+
+rechecksCanonicalTerms :: Assertion
+rechecksCanonicalTerms = do
+ let term =
+ CForall TySet
+ (CEq TySet
+ (CBound 0)
+ (CBound 0))
+ assertEqual
+ "well-typed canonical proposition"
+ (Right (TyProp, term))
+ ( (\checked ->
+ ( frozenCoreType checked
+ , frozenCoreTerm checked
+ ))
+ <$> checkCanonicalCore testGlobalType term
+ )
+ assertEqual
+ "out-of-scope de Bruijn index"
+ (Left (UnboundCoreIndex 1))
+ (frozenCoreType
+ <$> checkCanonicalCore
+ testGlobalType
+ (CForall TySet (CBound 1)))
+ assertEqual
+ "canonical application still checks argument types"
+ (Left
+ (ApplicationArgumentTypeMismatch
+ TySet
+ TyProp))
+ (frozenCoreType
+ <$> checkCanonicalCore
+ testGlobalType
+ (CApp
+ (CIntrinsic FamilyUnion)
+ CFalsum))
+
+checksScopedCanonicalOperations :: Assertion
+checksScopedCanonicalOperations = do
+ scoped <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ testGlobalType
+ [TySet]
+ (CBound 0))
+ weakened <-
+ either
+ (assertFailure . show)
+ pure
+ (weakenScopedCore
+ testGlobalType
+ TyProp
+ scoped)
+ assertEqual
+ "nearest binder insertion shifts the prior local"
+ ( [TyProp, TySet]
+ , TySet
+ , CBound 1
+ )
+ ( scopedCoreContext weakened
+ , scopedCoreType weakened
+ , scopedCoreTerm weakened
+ )
+ assertEqual
+ "top-level binder substitution removes its index"
+ (CEq TySet
+ (CIntrinsic Empty)
+ (CIntrinsic Empty))
+ (instantiateCanonical
+ (CIntrinsic Empty
+ :: CanonicalTerm TestGlobal)
+ (CEq TySet
+ (CBound 0)
+ (CBound 0)))
+ root <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ testGlobalType
+ []
+ (CGlobal TestGlobal))
+ assertEqual
+ "empty scoped context closes"
+ (Just
+ (TySet, CGlobal TestGlobal))
+ ( (\closed ->
+ ( frozenCoreType closed
+ , frozenCoreTerm closed
+ ))
+ <$> closeScopedCore root
+ )
+
+buildsSetInductionHypotheses :: Assertion
+buildsSetInductionHypotheses = do
+ let propertyTerm =
+ CImp
+ (CEq TySet
+ (CBound 0)
+ (CIntrinsic Empty))
+ (CEq TySet
+ (CBound 0)
+ (CBound 0))
+ claimProperty <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ testGlobalType
+ [TySet]
+ propertyTerm)
+ (predicate, hypothesis, step, result) <-
+ maybe
+ (assertFailure "set-induction instance was not constructed")
+ pure
+ (scopedSetInductionInstance 0 claimProperty)
+ let abstractedProperty =
+ CImp
+ (CEq TySet
+ (CBound 0)
+ (CIntrinsic Empty))
+ (CEq TySet
+ (CBound 0)
+ (CBound 0))
+ memberHypothesis =
+ CForall TySet
+ (CImp
+ (CApp
+ (CApp
+ (CIntrinsic Member)
+ (CBound 0))
+ (CBound 1))
+ abstractedProperty)
+ assertEqual
+ "set induction abstracts the selected property once"
+ (CLam TySet abstractedProperty)
+ (scopedCoreTerm predicate)
+ assertEqual
+ "antecedent and goal are both generalized over the member"
+ memberHypothesis
+ (scopedCoreTerm hypothesis)
+ assertEqual
+ "set-induction step owns its member-wise hypothesis"
+ (CForall TySet
+ (CImp memberHypothesis abstractedProperty))
+ (scopedCoreTerm step)
+ assertEqual
+ "set-induction result closes the complete property"
+ (CForall TySet abstractedProperty)
+ (scopedCoreTerm result)
+
+specializesCheckedSeparationCharacteristic :: Assertion
+specializesCheckedSeparationCharacteristic = do
+ foundation <-
+ either
+ (assertFailure . show)
+ pure
+ Foundation.checkedFoundation
+ let bound = CIntrinsic Empty
+ predicate =
+ CLam TySet
+ (CEq TySet (CBound 0) (CBound 0))
+ separation =
+ CApp
+ (CApp (CIntrinsic Sep) bound)
+ predicate
+ body <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ testGlobalType
+ []
+ separation)
+ definition <-
+ maybe
+ (assertFailure "separation did not form a set definition")
+ pure
+ (scopedSetDefinition
+ (Foundation.foundationAxiomFrozen
+ foundation
+ Foundation.SeparationCharacteristic)
+ body)
+ let generated =
+ instantiateCanonical
+ separation
+ (scopedCoreTerm definition)
+ expected =
+ betaNormalize
+ (specializeForall predicate
+ (specializeForall bound
+ (mapCanonicalGlobals absurd
+ (frozenCoreTerm
+ (Foundation.foundationAxiomFrozen
+ foundation
+ Foundation.SeparationCharacteristic)))))
+ assertEqual
+ "local characteristic is the checked rule specialization"
+ expected
+ generated
+
+specializesCheckedReplacementCharacteristic :: Assertion
+specializesCheckedReplacementCharacteristic = do
+ foundation <-
+ either
+ (assertFailure . show)
+ pure
+ Foundation.checkedFoundation
+ pair <- checked [] (CGlobal TestPairGlobal)
+ domain <- checked [] (CIntrinsic Empty)
+ value <- checked [TySet] (CBound 0)
+ (graph, checkedDomain, function) <-
+ maybe
+ (assertFailure "checked values did not form a replacement graph")
+ pure
+ (scopedReplacementGraph pair domain value)
+ definition <-
+ maybe
+ (assertFailure "replacement graph did not form a definition")
+ pure
+ (scopedCharacteristicDefinition
+ (Foundation.foundationAxiomFrozen
+ foundation
+ Foundation.ReplacementCharacteristic)
+ graph
+ (checkedDomain :| [function]))
+ let generated =
+ instantiateCanonical
+ (scopedCoreTerm graph)
+ (scopedCoreTerm definition)
+ expected =
+ betaNormalize
+ (specializeForall (scopedCoreTerm function)
+ (specializeForall (scopedCoreTerm checkedDomain)
+ (mapCanonicalGlobals absurd
+ (frozenCoreTerm
+ (Foundation.foundationAxiomFrozen
+ foundation
+ Foundation.ReplacementCharacteristic)))))
+ assertEqual
+ "local graph characteristic is the checked rule specialization"
+ expected
+ generated
+ where
+ checked context term =
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore testGlobalType context term)
+
+derivesNamedConstructionViews :: Assertion
+derivesNamedConstructionViews = do
+ foundation <-
+ either (assertFailure . show) pure Foundation.checkedFoundation
+ bound <- checked [] (CIntrinsic Empty)
+ predicate <- checked [TySet] (CEq TySet (CBound 0) (CBound 0))
+ separation <-
+ maybe
+ (assertFailure "checked separation descriptor failed")
+ pure
+ (checkedSeparationConstruction testGlobalType bound predicate)
+ (separationView, separationEquation) <-
+ maybe
+ (assertFailure "checked separation views failed")
+ pure
+ (namedSetConstructionLocalViews
+ (checkedFoundationSetConstruction foundation)
+ separation)
+ expectedSeparationView <-
+ maybe
+ (assertFailure "checked separation characteristic failed")
+ pure
+ (scopedSetDefinition
+ (Foundation.foundationAxiomFrozen foundation
+ Foundation.SeparationCharacteristic)
+ (namedSetConstructionTerm separation))
+ assertEqual "separation view is the checked specialization"
+ expectedSeparationView separationView
+ assertEqual "separation view is first-order"
+ (Set.singleton Foundation.EmptyCharacteristic)
+ (Foundation.foundationAxiomDependencies
+ (scopedCoreTerm separationView))
+ assertEqual "separation equation retains exact construction"
+ (Set.fromList
+ [ Foundation.EmptyCharacteristic
+ , Foundation.SeparationCharacteristic
+ ])
+ (Foundation.foundationAxiomDependencies
+ (scopedCoreTerm separationEquation))
+
+ firstDomain <- checked [] (CIntrinsic Empty)
+ singleValue <- checked [TySet] (CBound 0)
+ singleCondition <- checked [TySet]
+ (CEq TySet (CBound 0) (CBound 0))
+ singleReplacement <-
+ maybe
+ (assertFailure "checked one-domain replacement failed")
+ pure
+ (checkedFunctionalReplacementConstruction
+ testGlobalType
+ (firstDomain :| [])
+ singleValue
+ (Just singleCondition))
+ assertEqual "one-domain replacement has one canonical term"
+ (CApp
+ (CApp
+ (CIntrinsic Repl)
+ (CApp
+ (CApp (CIntrinsic Sep) (CIntrinsic Empty))
+ (CLam TySet
+ (CEq TySet (CBound 0) (CBound 0)))))
+ (CLam TySet (CBound 0)))
+ (scopedCoreTerm
+ (namedSetConstructionTerm singleReplacement))
+
+ secondDomain <- checked [TySet] (CBound 0)
+ value <- checked [TySet, TySet] (CBound 0)
+ condition <- checked [TySet, TySet]
+ (CEq TySet (CBound 0) (CBound 0))
+ replacement <-
+ maybe
+ (assertFailure "checked replacement descriptor failed")
+ pure
+ (checkedFunctionalReplacementConstruction
+ testGlobalType
+ (firstDomain :| [secondDomain])
+ value
+ (Just condition))
+ (replacementView, replacementEquation) <-
+ maybe
+ (assertFailure "checked replacement views failed")
+ pure
+ (namedSetConstructionLocalViews
+ (checkedFoundationSetConstruction foundation)
+ replacement)
+ let terminal =
+ andP
+ (CEq TySet (CBound 0) (CBound 0))
+ (CEq TySet (CBound 2) (CBound 0))
+ secondWitness =
+ existsP
+ (andP
+ (memberP (CBound 0) (CBound 1))
+ terminal)
+ firstWitness =
+ existsP
+ (andP
+ (memberP (CBound 0) (CIntrinsic Empty))
+ secondWitness)
+ expectedReplacementTerm =
+ CForall TySet
+ (CEq TyProp
+ (memberP (CBound 0) (CBound 1))
+ firstWitness)
+ expectedReplacementView <- checked [TySet] expectedReplacementTerm
+ assertEqual "replacement view preserves bounds and condition"
+ expectedReplacementView replacementView
+ assertEqual "flattened replacement view is first-order"
+ (Set.singleton Foundation.EmptyCharacteristic)
+ (Foundation.foundationAxiomDependencies
+ (scopedCoreTerm replacementView))
+ assertEqual "replacement equation retains every helper"
+ (Set.fromList
+ [ Foundation.FamilyUnionCharacteristic
+ , Foundation.EmptyCharacteristic
+ , Foundation.SeparationCharacteristic
+ , Foundation.ReplacementCharacteristic
+ ])
+ (Foundation.foundationAxiomDependencies
+ (scopedCoreTerm replacementEquation))
+ where
+ checked context term =
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore testGlobalType context term)
+
+ memberP element set =
+ CApp (CApp (CIntrinsic Member) element) set
+
+ notP proposition = CImp proposition CFalsum
+
+ andP left right =
+ notP (CImp left (notP right))
+
+ existsP proposition =
+ notP (CForall TySet (notP proposition))
+
+specializeForall
+ :: CanonicalTerm global
+ -> CanonicalTerm global
+ -> CanonicalTerm global
+specializeForall argument = \case
+ CForall _binderType body ->
+ instantiateCanonical argument body
+ _ ->
+ error "the checked characteristic lost a binder"
+
+betaNormalize :: CanonicalTerm global -> CanonicalTerm global
+betaNormalize = \case
+ CApp function argument ->
+ case betaNormalize function of
+ CLam _binderType body ->
+ betaNormalize
+ (instantiateCanonical
+ (betaNormalize argument)
+ body)
+ normalizedFunction ->
+ CApp normalizedFunction (betaNormalize argument)
+ CLam binderType body ->
+ CLam binderType (betaNormalize body)
+ CImp premise conclusion ->
+ CImp
+ (betaNormalize premise)
+ (betaNormalize conclusion)
+ CEq operandType left right ->
+ CEq operandType
+ (betaNormalize left)
+ (betaNormalize right)
+ CForall binderType body ->
+ CForall binderType (betaNormalize body)
+ term -> term
+
+thawsCheckedClosedTerms :: Assertion
+thawsCheckedClosedTerms = do
+ let source =
+ coreLambda TySet (0 :: Int)
+ (coreForall TySet 1
+ (coreEquality
+ TySet
+ (coreLocal 0)
+ (coreLocal 1)))
+ freeze syntax = do
+ checked <-
+ either
+ (assertFailure . show)
+ pure
+ (checkClosedCore testGlobalType syntax)
+ either
+ (assertFailure . show)
+ pure
+ (freezeClosed checked)
+ original <-
+ freeze source
+ roundTrip <-
+ freeze (thawFrozenCore original)
+ assertEqual "frozen term" original roundTrip
+ assertEqual
+ "global inventory"
+ mempty
+ (frozenCoreGlobals original)
+
+prop_freezeAgreesWithReference :: Property
+prop_freezeAgreesWithReference = property do
+ depth <-
+ forAll (Gen.int (Range.linear 1 10))
+ binderTypes <-
+ forAll
+ (Gen.list
+ (Range.singleton depth)
+ (Gen.element
+ [ TyProp
+ , TySet
+ , TySet `TyArrow` TySet
+ ]))
+ selected <-
+ forAll
+ (Gen.maybe
+ (Gen.int (Range.linear 0 (depth - 1))))
+ let binders =
+ zip [0 :: Int ..] binderTypes
+ body =
+ maybe
+ (coreGlobal TestGlobal)
+ coreLocal
+ selected
+ term =
+ foldr
+ (\(local, binderType) ->
+ coreLambda binderType local)
+ body
+ binders
+ checked <-
+ evalEither
+ (checkClosedCore testGlobalType term)
+ optimized <-
+ evalEither (force <$> freezeClosed checked)
+ reference <-
+ evalEither (force <$> referenceFreezeClosed checked)
+ optimized === reference
diff --git a/source/Felix/Test/Unit/Declaration.hs b/source/Felix/Test/Unit/Declaration.hs
new file mode 100644
index 0000000..f3d9be7
--- /dev/null
+++ b/source/Felix/Test/Unit/Declaration.hs
@@ -0,0 +1,3596 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Test.Unit.Declaration (unitTests) where
+
+import Base
+import Felix.Checking.Authority qualified as Authority
+import Felix.Checking.Backend.Problem qualified as Backend
+import Felix.Checking.Core qualified as Core
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Checking.Exact qualified as Exact
+import Felix.Checking.Exact.Vocabulary qualified as Vocabulary
+import Felix.Checking.Identity qualified as Identity
+import Felix.Checking.Kernel.Derivation qualified as Kernel
+import Felix.Checking.SetConstruction qualified as SetConstruction
+import Felix.Checking.Semantic qualified as Semantic
+import Felix.Checking.Typed.Inductive qualified as Typed
+import Felix.Math.Codec
+import Felix.Module
+import Felix.Source
+import Felix.Store qualified as Store
+import Felix.Meaning qualified as Meaning
+import Felix.Provers qualified as Provers
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Interface qualified as Syntax
+import Felix.Syntax.Internal qualified as Internal
+import Felix.Syntax.Lexicon qualified as Lexicon
+
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.IORef qualified as IORef
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+import Data.Text.Encoding qualified as TextEncoding
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+import Control.Exception (bracket)
+import Control.Exception qualified as Exception
+import Control.Monad.Except (runExceptT)
+import Control.Monad.State (evalState)
+import System.Directory qualified as Directory
+import System.FilePath.Posix qualified as Posix
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Typed declaration seam"
+ [ testCase "enforces staged candidate order"
+ enforcesStagedCandidateOrder
+ , testCase "retains only appended declaration prefixes"
+ retainsOnlyAppendedPrefixes
+ , testCase "makes declaration failures terminal"
+ makesDeclarationFailuresTerminal
+ , testCase "propagates unsafe authority through local claims"
+ propagatesUnsafeAuthorityThroughLocalClaims
+ , testCase "aggregates exact Vampire obligations"
+ aggregatesExactVampireObligations
+ , testCase "validates complete resolver batches before rejection"
+ validatesCompleteResolverBatchesBeforeRejection
+ , testCase "rejects retained-plan admission drift fatally"
+ rejectsRetainedPlanAdmissionDrift
+ , testCase "preserves source-axiom safety through Vampire validation"
+ preservesSourceAxiomSafetyThroughVampireValidation
+ , testCase "materializes a sealed import with fresh authority"
+ materializesSealedImport
+ , testCase "reconstructs exact imported global bindings"
+ reconstructsImportedGlobalBindings
+ , testCase "elaborates scoped exact propositions"
+ elaboratesScopedExactPropositions
+ , testCase "lowers fixed equality aliases without global support"
+ lowersFixedEqualityAliases
+ , testCase "scopes quantified proposition terms"
+ scopesQuantifiedPropositionTerms
+ , testCase "prepares exact claim envelopes"
+ preparesExactClaimEnvelopes
+ , testCase "lowers exact separation comprehensions"
+ lowersExactSeparationComprehensions
+ , testCase "lowers exact replacement telescopes"
+ lowersExactReplacementTelescopes
+ , testCase "lowers exact finite sets"
+ lowersExactFiniteSets
+ , testCase "lowers exact ordinary declarations"
+ lowersExactOrdinaryDeclarations
+ , testCase "folds transitive and diamond import evidence"
+ foldsTransitiveAndDiamondEvidence
+ , testCase "validates exact kernel construction descriptors"
+ validatesExactKernelConstructionDescriptors
+ , testCase "authorizes exact datatype compilation families"
+ authorizesExactDatatypeCompilationFamilies
+ , testCase "reuses exact compiled declaration validation"
+ reusesExactCompiledDeclarationValidation
+ , testCase "keeps fatal validation lookup failures out of declarations"
+ keepsFatalValidationLookupFailuresOutOfDeclarations
+ ]
+
+data FatalValidationLookup = FatalValidationLookup
+ deriving (Show)
+
+instance Exception.Exception FatalValidationLookup
+
+keepsFatalValidationLookupFailuresOutOfDeclarations :: Assertion
+keepsFatalValidationLookupFailuresOutOfDeclarations = do
+ fixture <- makeFixture
+ prepared <- makePreparedObligation
+ fixture
+ Foundation.EmptyCharacteristic
+ let lookup = proofOnlyValidationLookup
+ (const (Exception.throwIO FatalValidationLookup))
+ action = Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "fatal-validation-lookup") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "fatal-validation-lookup")
+ Declaration.authorizeVampireCandidate candidate
+ (Declaration.acceptVampireObligation prepared)
+ result <- Exception.try
+ (runDriverWithValidation fixture lookup action)
+ :: IO
+ (Either
+ FatalValidationLookup
+ (Declaration.DriverResult
+ Void
+ ((), Declaration.CommittedDeclarationBatch)))
+ case result of
+ Left FatalValidationLookup -> pure ()
+ Right _ ->
+ assertFailure "fatal validation lookup became a driver result"
+
+enforcesStagedCandidateOrder :: Assertion
+enforcesStagedCandidateOrder = do
+ fixture <- makeFixture
+ accepted <- runSuccessful fixture do
+ result <- Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId "staged-success") do
+ first <- Declaration.reserveCandidate
+ (factSpec fixture "first")
+ later <- Declaration.reserveCandidateBatch
+ ( factSpec fixture "later-a"
+ :| [factSpec fixture "later-b"]
+ )
+ Declaration.authorizeCompiledDeclaration do
+ Declaration.authorizeSourceAxiomCandidate first
+ traverse_
+ (\candidate ->
+ Declaration.authorizeKernelProofCandidate
+ candidate do
+ premise <-
+ Declaration.useStagedCandidate first
+ pure
+ (Kernel.importedFactDerivation premise))
+ later
+ pure result
+ let (_value, batch) = accepted
+ assertEqual
+ "all source-ordered candidates appended"
+ 3
+ (length
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch)))
+
+ provenance <- runDriver fixture (priorDeclarationUse fixture)
+ case provenance of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ (Declaration.CandidateOutsideDeclaration slot))
+ prefix -> do
+ assertEqual
+ "cross-declaration staged premise"
+ (localFact fixture 0)
+ slot
+ assertSingleCompletedPrefix prefix
+ _other ->
+ assertFailure
+ "cross-declaration staged provenance was not rejected"
+
+ traverse_
+ (\(label, action, expectedPremise, expectedCandidate) -> do
+ result <- runDriver fixture action
+ case result of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ (Declaration.StagedPremiseNotEarlier
+ premiseSlot premiseStage
+ candidateSlot candidateStage))
+ _prefix -> do
+ assertEqual (label <> " premise slot")
+ expectedPremise premiseSlot
+ assertEqual (label <> " candidate slot")
+ expectedCandidate candidateSlot
+ assertBool (label <> " rejected non-earlier stage")
+ (premiseStage >= candidateStage)
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed other)
+ _prefix ->
+ assertFailure
+ (label <> ": unexpected error " <> show other)
+ Declaration.DriverFailed
+ (Declaration.DriverActionFailed _failure)
+ _prefix ->
+ assertFailure
+ (label <> ": unexpected ordinary driver failure")
+ Declaration.DriverSucceeded{} ->
+ assertFailure (label <> ": invalid staged use succeeded")
+ Declaration.DriverSealFailed{} ->
+ assertFailure (label <> ": invalid staged use reached sealing"))
+ [ ( "self"
+ , selfUse fixture
+ , localFact fixture 0
+ , localFact fixture 0
+ )
+ , ( "same stage"
+ , sameStageUse fixture
+ , localFact fixture 1
+ , localFact fixture 0
+ )
+ , ( "forward"
+ , forwardUse fixture
+ , localFact fixture 1
+ , localFact fixture 0
+ )
+ ]
+ where
+ localFact fixture ordinal =
+ Semantic.factSlot
+ (fixtureOwner fixture)
+ (localFactOrdinal ordinal)
+
+ selfUse fixture =
+ Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId "self") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "self")
+ Declaration.authorizeCompiledDeclaration
+ (Declaration.authorizeKernelProofCandidate candidate do
+ premise <- Declaration.useStagedCandidate candidate
+ pure (Kernel.importedFactDerivation premise))
+
+ sameStageUse fixture =
+ Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId "same-stage") do
+ candidates <- Declaration.reserveCandidateBatch
+ ( factSpec fixture "same-a"
+ :| [factSpec fixture "same-b"]
+ )
+ let first = NonEmpty.head candidates
+ second = NonEmpty.last candidates
+ Declaration.authorizeCompiledDeclaration
+ (Declaration.authorizeKernelProofCandidate first do
+ premise <- Declaration.useStagedCandidate second
+ pure (Kernel.importedFactDerivation premise))
+
+ forwardUse fixture =
+ Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId "forward") do
+ first <- Declaration.reserveCandidate
+ (factSpec fixture "forward-a")
+ second <- Declaration.reserveCandidate
+ (factSpec fixture "forward-b")
+ Declaration.authorizeCompiledDeclaration
+ (Declaration.authorizeKernelProofCandidate first do
+ premise <- Declaration.useStagedCandidate second
+ pure (Kernel.importedFactDerivation premise))
+
+ priorDeclarationUse fixture = do
+ (premise, _batch) <- Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId "prior-stage") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "prior-stage")
+ Declaration.authorizeCompiledDeclaration
+ (Declaration.authorizeSourceAxiomCandidate candidate)
+ pure candidate
+ void
+ (Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId "later-stage") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "later-stage")
+ Declaration.authorizeCompiledDeclaration
+ (Declaration.authorizeKernelProofCandidate candidate do
+ imported <-
+ Declaration.useStagedCandidate premise
+ pure (Kernel.importedFactDerivation imported)))
+
+retainsOnlyAppendedPrefixes :: Assertion
+retainsOnlyAppendedPrefixes = do
+ fixture <- makeFixture
+ outcome <- runDriver fixture do
+ (_value, _firstBatch) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "accepted") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "accepted")
+ Declaration.authorizeSourceAxiomCandidate candidate
+ Declaration.failModuleDriver
+ ("later checker failure" :: Text)
+ case outcome of
+ Declaration.DriverFailed
+ (Declaration.DriverActionFailed reason) prefix -> do
+ assertEqual
+ "driver reports the later ordinary failure"
+ "later checker failure"
+ reason
+ assertSingleCompletedPrefix prefix
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed err) _prefix ->
+ assertFailure
+ ("unexpected declaration failure: " <> show err)
+ Declaration.DriverSucceeded{} ->
+ assertFailure "ordinary driver failure was lost"
+ Declaration.DriverSealFailed{} ->
+ assertFailure "ordinary driver failure became a seal failure"
+
+makesDeclarationFailuresTerminal :: Assertion
+makesDeclarationFailuresTerminal = do
+ fixture <- makeFixture
+ outcome <- runDriver fixture do
+ void
+ (Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "accepted-before-failure") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "accepted-before-failure")
+ Declaration.authorizeSourceAxiomCandidate candidate)
+ void
+ (Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId "rolled-back") do
+ void
+ (Declaration.reserveCandidate
+ (factSpec fixture "uncommitted")))
+ -- This declaration must be unreachable after the terminal failure.
+ void
+ (Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "must-not-publish") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "must-not-publish")
+ Declaration.authorizeSourceAxiomCandidate candidate)
+ case outcome of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ Declaration.DeclarationHasUnauthorizedCandidates)
+ prefix ->
+ assertSingleCompletedPrefix prefix
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed other) _prefix ->
+ assertFailure
+ ("unexpected declaration failure: " <> show other)
+ Declaration.DriverFailed
+ (Declaration.DriverActionFailed _failure) _prefix ->
+ assertFailure "unexpected ordinary driver failure"
+ Declaration.DriverSucceeded{} ->
+ assertFailure "failed declaration was skipped"
+ Declaration.DriverSealFailed{} ->
+ assertFailure "failed declaration reached sealing"
+
+assertSingleCompletedPrefix
+ :: Declaration.PendingModulePrefix
+ -> Assertion
+assertSingleCompletedPrefix prefix = do
+ let batches =
+ Declaration.pendingModulePrefixBatches prefix
+ assertEqual "one completed envelope survives" 1 (length batches)
+ case batches of
+ [batch] -> do
+ let expected =
+ Declaration.committedBatchNextPrefix batch
+ assertEqual
+ "failed declaration did not advance the prefix"
+ expected
+ (Declaration.pendingModulePrefixCurrent prefix)
+ assertEqual
+ "retained envelope ends at the exposed prefix"
+ expected
+ (Declaration.committedBatchNextPrefix batch)
+ _ -> pure ()
+
+propagatesUnsafeAuthorityThroughLocalClaims :: Assertion
+propagatesUnsafeAuthorityThroughLocalClaims = do
+ fixture <- makeFixture
+ result <- runSuccessful fixture do
+ (_sourceValue, sourceBatch) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "source-axiom") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "source")
+ Declaration.authorizeSourceAxiomCandidate candidate
+ sourceOccurrence <- requireSingleOccurrence sourceBatch
+ let
+ sourceFingerprint =
+ Semantic.semanticFactFingerprint sourceOccurrence
+ (_derivedValue, derivedBatch) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "local-claim") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "derived")
+ Declaration.authorizeKernelProofCandidate candidate do
+ sourcePremise <-
+ Declaration.useAuthorizedFact sourceFingerprint
+ claim <- Declaration.proveLocalKernelClaim
+ (fixtureProposition fixture)
+ (Kernel.importedFactDerivation sourcePremise)
+ claimPremise <- Declaration.useLocalClaim claim
+ pure (Kernel.importedFactDerivation claimPremise)
+ pure derivedBatch
+ occurrence <-
+ case Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta result) of
+ [single] -> pure single
+ facts ->
+ assertFailure
+ ("unexpected fact count: " <> show (length facts))
+ >> fail "unreachable"
+ let authority = Semantic.semanticFactAuthority occurrence
+ assertEqual
+ "local claim cannot erase source-axiom safety"
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind Authority.SourceAxiom))
+ (Authority.factAuthoritySafety authority)
+ case Declaration.committedBatchProofValidations result of
+ [record] ->
+ assertEqual
+ "local support is absent from the compact direct authorization"
+ (Authority.CheckedSourceProof [])
+ (Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate record))
+ records ->
+ assertFailure
+ ("unexpected proof validation count: "
+ <> show (length records))
+
+aggregatesExactVampireObligations :: Assertion
+aggregatesExactVampireObligations =
+ withTemporaryDirectory "felix-declaration-vampire" \root -> do
+ fixture <- makeFixture
+ let executable = root Posix.</> "vampire"
+ writeAcceptedVampire executable
+ first <- makePreparedObligation
+ fixture
+ Foundation.EmptyCharacteristic
+ second <- makePreparedObligation
+ fixture
+ Foundation.PairSetCharacteristic
+ let exactResolver = acceptedResolver executable
+ freshOutcome <-
+ (runDriverWithResolver fixture exactResolver do
+ (_value, committed) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "two-vampire-obligations") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "two-vampire-obligations")
+ Declaration.authorizeVampireCandidate candidate do
+ Declaration.acceptVampireObligation first
+ Declaration.acceptVampireObligation second
+ pure committed
+ :: IO
+ (Declaration.DriverResult Text
+ Declaration.CommittedDeclarationBatch))
+ (batch, freshPrefix) <-
+ case freshOutcome of
+ Declaration.DriverSucceeded value _ prefix _closure ->
+ pure (value, prefix)
+ Declaration.DriverFailed failure _ ->
+ assertFailure
+ ("fresh Vampire fixture failed: " <> show failure)
+ >> fail "unreachable"
+ Declaration.DriverSealFailed failure _ ->
+ assertFailure
+ ("fresh Vampire fixture did not seal: "
+ <> show failure)
+ >> fail "unreachable"
+ let expectedRequests =
+ Provers.preparedVerificationRequestId
+ (Provers.preparedTypedProverRequest first)
+ : [Provers.preparedVerificationRequestId
+ (Provers.preparedTypedProverRequest second)]
+ case Declaration.committedBatchProofValidations batch of
+ [record] ->
+ assertEqual
+ "accepted requests retain source order"
+ (Authority.CheckedSourceProof expectedRequests)
+ (Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate record))
+ records ->
+ assertFailure
+ ("unexpected proof validation count: "
+ <> show (length records))
+
+ cachedRecord <-
+ case Declaration.committedBatchProofValidations batch of
+ [record] -> pure record
+ records ->
+ assertFailure
+ ("unexpected cached proof records: "
+ <> show (length records))
+ >> fail "unreachable"
+ cachedLookupKey <- IORef.newIORef Nothing
+ cached <- runSuccessfulWithValidation fixture
+ (proofOnlyValidationLookup
+ (\key -> do
+ IORef.writeIORef cachedLookupKey (Just key)
+ pure (Just cachedRecord))) do
+ (_value, committed) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "two-vampire-obligations") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "two-vampire-obligations")
+ Declaration.authorizeVampireCandidate candidate do
+ Declaration.acceptVampireObligation first
+ Declaration.acceptVampireObligation second
+ pure committed
+ publishedRecord <-
+ case Declaration.committedBatchProofValidations cached of
+ [record] -> pure record
+ records ->
+ assertFailure
+ ("unexpected cached validation records: "
+ <> show (length records))
+ >> fail "unreachable"
+ assertEqual
+ "cached authorization preserves the exact direct proof"
+ (Authority.CheckedSourceProof expectedRequests)
+ (Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate publishedRecord))
+ assertEqual
+ "lookup and publication retain the same validation key"
+ (Semantic.proofValidationRecordKey cachedRecord)
+ (Semantic.proofValidationRecordKey publishedRecord)
+ assertEqual
+ "one proof syntax key governs lookup and publication"
+ (Just
+ (Semantic.proofValidationRecordKey cachedRecord))
+ =<< IORef.readIORef cachedLookupKey
+
+ missLookups <- IORef.newIORef (0 :: Int)
+ missRuns <- IORef.newIORef (0 :: Int)
+ let missLookup = proofOnlyValidationLookup \_key -> do
+ IORef.modifyIORef' missLookups (+ 1)
+ pure Nothing
+ missResolver = Declaration.vampireResolver \prepared -> do
+ IORef.modifyIORef' missRuns (+ 1)
+ resolveAccepted executable prepared
+ miss <- runDriverWithValidationAndResolver
+ fixture
+ missLookup
+ missResolver
+ do
+ (_value, committed) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "two-vampire-obligations") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "two-vampire-obligations")
+ Declaration.authorizeVampireCandidate candidate do
+ Declaration.acceptVampireObligation first
+ Declaration.acceptVampireObligation second
+ pure committed
+ case miss of
+ Declaration.DriverSucceeded{} -> pure ()
+ _ ->
+ assertFailure "warm miss failed"
+ assertEqual "warm miss performs one exact lookup"
+ 1
+ =<< IORef.readIORef missLookups
+ assertEqual "warm miss runs every reached Vampire request"
+ 2
+ =<< IORef.readIORef missRuns
+
+ mismatchRuns <- IORef.newIORef (0 :: Int)
+ let editedSyntax =
+ Semantic.proofSyntaxId "edited-proof-syntax"
+ cachedCertificate =
+ Semantic.proofValidationRecordCertificate cachedRecord
+ corruptedKey =
+ Semantic.proofValidationKey
+ (Identity.theoremId
+ (Authority.factAuthorityTheorem
+ (Authority.validationTarget
+ cachedCertificate)))
+ editedSyntax
+ (Declaration.committedBatchPreviousPrefix batch)
+ corruptedRecord =
+ Semantic.proofValidationRecord
+ corruptedKey
+ cachedCertificate
+ mismatchResolver = Declaration.vampireResolver \prepared -> do
+ IORef.modifyIORef' mismatchRuns (+ 1)
+ resolveAccepted executable prepared
+ mismatchingLookup = proofOnlyValidationLookup
+ (const (pure (Just corruptedRecord)))
+ mismatching <- Exception.try
+ (runDriverWithValidationAndResolver
+ fixture
+ mismatchingLookup
+ mismatchResolver
+ do
+ Declaration.commitProofDeclaration editedSyntax do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "two-vampire-obligations")
+ Declaration.authorizeVampireCandidate candidate
+ (Declaration.acceptVampireObligation first))
+ :: IO
+ (Either
+ Declaration.ValidationIntegrityError
+ (Declaration.DriverResult
+ Text
+ ((), Declaration.CommittedDeclarationBatch)))
+ case mismatching of
+ Left Declaration.CachedValidationIntegrityError{} ->
+ pure ()
+ Right _ ->
+ assertFailure "mismatching hit did not abort as corruption"
+ assertEqual "mismatching hit does not fall back to Vampire"
+ 0
+ =<< IORef.readIORef mismatchRuns
+
+ withOpenedStore fixture root \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store freshPrefix)
+ warmCalls <- IORef.newIORef (0 :: Int)
+ let storeLookup = proofOnlyValidationLookup \key -> do
+ IORef.modifyIORef' warmCalls (+ 1)
+ Store.loadProofValidation store key >>= expectRight
+ warm <- runSuccessfulWithValidation fixture
+ storeLookup
+ do
+ (_value, committed) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "two-vampire-obligations") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "two-vampire-obligations")
+ Declaration.authorizeVampireCandidate candidate do
+ Declaration.acceptVampireObligation first
+ Declaration.acceptVampireObligation second
+ pure committed
+ assertEqual "warm lookup executes once through the store"
+ 1
+ =<< IORef.readIORef warmCalls
+ assertEqual "warm path retains cached request IDs"
+ (Authority.CheckedSourceProof expectedRequests)
+ (case Declaration.committedBatchProofValidations warm of
+ [record] ->
+ Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate record)
+ records ->
+ error
+ ("unexpected warm validation records: "
+ <> show (length records)))
+
+ emptyProof <- runDriver fixture do
+ Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "empty-vampire-proof") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "empty-vampire-proof")
+ Declaration.authorizeVampireCandidate candidate
+ (pure ())
+ case emptyProof of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ Declaration.VampireProofHasNoAcceptedObligations)
+ prefix ->
+ assertEqual
+ "empty Vampire proof publishes no declaration"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed other) _prefix ->
+ assertFailure
+ ("unexpected empty-proof failure: " <> show other)
+ Declaration.DriverFailed
+ (Declaration.DriverActionFailed _failure) _prefix ->
+ assertFailure "unexpected ordinary driver failure"
+ Declaration.DriverSucceeded{} ->
+ assertFailure "empty Vampire proof was authorized"
+ Declaration.DriverSealFailed{} ->
+ assertFailure "empty Vampire proof reached sealing"
+
+ invalidClosure <- expectRight
+ (Identity.validateObjectClosure
+ (Identity.theoryId (fixtureFoundation fixture))
+ [])
+ invalidTarget <- expectRight
+ (Identity.validatePropositionContent
+ invalidClosure
+ (Core.CImp Core.CFalsum Core.CFalsum))
+ invalidCalls <- IORef.newIORef (0 :: Int)
+ let invalidResolver =
+ Declaration.vampireResolver \prepared -> do
+ IORef.modifyIORef' invalidCalls (+ 1)
+ resolveAccepted executable prepared
+ invalid <- runDriverWithResolver fixture invalidResolver do
+ Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "invalid-vampire-target") do
+ candidate <- Declaration.reserveCandidate
+ (Declaration.candidateSpec
+ invalidTarget
+ Semantic.SearchEligible
+ [Semantic.semanticName
+ "invalid-vampire-target"])
+ Declaration.authorizeVampireCandidate candidate
+ (Declaration.acceptVampireObligation first)
+ assertEqual
+ "invalid prepared problem does not invoke Vampire"
+ 0
+ =<< IORef.readIORef invalidCalls
+ case invalid of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ Declaration.VampireTargetMismatch)
+ prefix ->
+ assertEqual
+ "invalid prepared problem publishes no declaration"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed other) _prefix ->
+ assertFailure
+ ("unexpected prepared-problem failure: " <> show other)
+ Declaration.DriverFailed
+ (Declaration.DriverActionFailed _failure) _prefix ->
+ assertFailure "unexpected ordinary driver failure"
+ Declaration.DriverSucceeded{} ->
+ assertFailure "invalid prepared problem was authorized"
+ Declaration.DriverSealFailed{} ->
+ assertFailure "invalid prepared problem reached sealing"
+
+ let mismatchedResolver =
+ Declaration.vampireResolver \_prepared ->
+ resolveAccepted executable second
+ mismatch <- runDriverWithResolver fixture mismatchedResolver do
+ Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "mismatched-vampire-request") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "mismatched-vampire-request")
+ Declaration.authorizeVampireCandidate candidate
+ (Declaration.acceptVampireObligation first)
+ case mismatch of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ Declaration.VampireRequestMismatch)
+ prefix ->
+ assertEqual
+ "mismatch publishes no declaration"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed other) _prefix ->
+ assertFailure
+ ("unexpected mismatch failure: " <> show other)
+ Declaration.DriverFailed
+ (Declaration.DriverActionFailed _failure) _prefix ->
+ assertFailure "unexpected ordinary driver failure"
+ Declaration.DriverSucceeded{} ->
+ assertFailure "mismatched request was authorized"
+ Declaration.DriverSealFailed{} ->
+ assertFailure "mismatched request reached sealing"
+
+ unrecorded <- runDriver fixture do
+ Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "unrecorded-omission") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "unrecorded-omission")
+ Declaration.authorizeOmittedCandidate candidate
+ (pure ())
+ case unrecorded of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ Declaration.OmittedProofDidNotRecordUse)
+ prefix ->
+ assertEqual
+ "unrecorded omission publishes no declaration"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed other) _prefix ->
+ assertFailure
+ ("unexpected unrecorded-omission failure: "
+ <> show other)
+ Declaration.DriverFailed
+ (Declaration.DriverActionFailed _failure) _prefix ->
+ assertFailure "unexpected ordinary driver failure"
+ Declaration.DriverSucceeded{} ->
+ assertFailure "unrecorded omission was authorized"
+ Declaration.DriverSealFailed{} ->
+ assertFailure "unrecorded omission reached sealing"
+
+ calls <- IORef.newIORef (0 :: Int)
+ let countingResolver =
+ Declaration.vampireResolver \prepared -> do
+ IORef.modifyIORef' calls (+ 1)
+ resolveAccepted executable prepared
+ omittedBatch <- runSuccessfulWithResolver fixture countingResolver do
+ (_value, committed) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "omitted-after-obligations") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "omitted-after-obligations")
+ Declaration.authorizeOmittedCandidate candidate do
+ Declaration.acceptVampireObligation first
+ Declaration.acceptVampireObligation second
+ Declaration.recordOmittedUse
+ pure committed
+ assertEqual
+ "omitted proof still checks preceding obligations"
+ 2
+ =<< IORef.readIORef calls
+ case Declaration.committedBatchProofValidations omittedBatch of
+ [record] ->
+ assertEqual
+ "omitted direct authorization discards request IDs"
+ Authority.OmittedAuthorization
+ (Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate record))
+ records ->
+ assertFailure
+ ("unexpected omitted validation count: "
+ <> show (length records))
+
+validatesCompleteResolverBatchesBeforeRejection :: Assertion
+validatesCompleteResolverBatchesBeforeRejection =
+ withTemporaryDirectory "felix-declaration-batch-integrity" \root -> do
+ fixture <- makeFixture
+ let executable = root Posix.</> "vampire"
+ firstLocation = mkLocation (FileId 76) 1 1
+ secondLocation = mkLocation (FileId 76) 2 1
+ writeAcceptedVampire executable
+ mismatchedTask <-
+ makePreparedObligation
+ fixture
+ Foundation.EmptyCharacteristic
+ let integrityResolver =
+ Declaration.vampireBatchResolver \_tasks -> do
+ mismatched <- resolveAccepted executable mismatchedTask
+ pure
+ ( Right (Provers.CounterSatisfiable "earlier")
+ :| [mismatched]
+ )
+ integrityOutcome <-
+ (runDriverWithResolver fixture integrityResolver do
+ Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "batch-integrity-priority") do
+ candidates <- Declaration.reserveCandidateBatch
+ ( factSpec fixture "batch-integrity-first"
+ :| [factSpec fixture "batch-integrity-second"]
+ )
+ Declaration.authorizeVampireCandidateBatch
+ ( ( firstLocation
+ , NonEmpty.head candidates
+ , Declaration.prepareCurrentCandidateVampire
+ )
+ :| [ ( secondLocation
+ , NonEmpty.last candidates
+ , Declaration.prepareCurrentCandidateVampire
+ )
+ ]
+ )
+ :: IO
+ (Declaration.DriverResult Text
+ ((), Declaration.CommittedDeclarationBatch)))
+ case integrityOutcome of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ (Declaration.ProofObligationFailedAt
+ location
+ Declaration.VampireRequestMismatch))
+ prefix -> do
+ assertEqual
+ "later request mismatch retains its location"
+ secondLocation
+ location
+ assertEqual
+ "integrity failure rolls back the complete declaration"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("unexpected batch-integrity failure: " <> show failure)
+ Declaration.DriverSucceeded{} ->
+ assertFailure
+ "earlier ordinary rejection concealed no integrity failure"
+ Declaration.DriverSealFailed{} ->
+ assertFailure "invalid batch reached module sealing"
+
+ prepared <-
+ makePreparedObligation
+ fixture
+ Foundation.EmptyCharacteristic
+ let excessResolver =
+ Declaration.vampireBatchResolver \_tasks ->
+ pure
+ ( Right (Provers.CounterSatisfiable "first")
+ :| [Right (Provers.CounterSatisfiable "excess")]
+ )
+ excessOutcome <-
+ (runDriverWithResolver fixture excessResolver do
+ Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "singleton-excess-result") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "singleton-excess-result")
+ Declaration.authorizeVampireCandidate candidate
+ (Declaration.acceptVampireObligation prepared)
+ :: IO
+ (Declaration.DriverResult Text
+ ((), Declaration.CommittedDeclarationBatch)))
+ case excessOutcome of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ (Declaration.VampireResolverBatchSizeMismatch 1 2))
+ prefix ->
+ assertEqual
+ "malformed singleton response publishes no declaration"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("unexpected singleton-cardinality failure: "
+ <> show failure)
+ Declaration.DriverSucceeded{} ->
+ assertFailure "singleton resolver ignored an excess result"
+ Declaration.DriverSealFailed{} ->
+ assertFailure "malformed singleton reached module sealing"
+
+rejectsRetainedPlanAdmissionDrift :: Assertion
+rejectsRetainedPlanAdmissionDrift = do
+ fixture <- makeFixture
+ let checked =
+ Declaration.checkedProofDeclaration
+ (Semantic.proofSyntaxId "planned-admission-drift")
+ []
+ []
+ []
+ []
+ [ Declaration.checkedCandidate
+ (factSpec fixture "planned-admission-drift")
+ Declaration.checkedSourceAxiomPlanning
+ :| []
+ ]
+ ()
+ action = do
+ planned <-
+ Declaration.runProspectiveLoweringDriver
+ (Declaration.planCheckedDeclaration checked)
+ >>= either Declaration.failDeclarationDriver pure
+ Declaration.admitPlannedCheckedDeclaration planned
+ (\() stages ->
+ case concatMap toList stages of
+ [candidate] ->
+ Declaration.authorizeOmittedCandidate candidate
+ Declaration.recordOmittedUse
+ _ -> error "planned drift fixture candidate shape")
+ outcome <-
+ Exception.try (runDriver fixture action)
+ :: IO
+ (Either
+ Declaration.PlanningIntegrityError
+ (Declaration.DriverResult
+ Void
+ Declaration.CommittedDeclarationBatch))
+ case outcome of
+ Left (Declaration.PlanningIntegrityError diagnostic) ->
+ assertBool "fatal mismatch identifies prospective contract drift"
+ ("prospective contract" `Text.isInfixOf` diagnostic)
+ Right _ ->
+ assertFailure
+ "a changed admitted authority was accepted against its plan"
+
+preservesSourceAxiomSafetyThroughVampireValidation :: Assertion
+preservesSourceAxiomSafetyThroughVampireValidation =
+ withTemporaryDirectory "felix-declaration-source-axiom" \root -> do
+ fixture <- makeFixture
+ let executable = root Posix.</> "vampire"
+ writeAcceptedVampire executable
+ prepared <-
+ makePreparedObligationWithPremise
+ fixture
+ (fixtureSourceAxiomFingerprint fixture)
+ freshCalls <- IORef.newIORef (0 :: Int)
+ let freshResolver = Declaration.vampireResolver \task -> do
+ IORef.modifyIORef' freshCalls (+ 1)
+ resolveAccepted executable task
+ freshOutcome <-
+ (runDriverWithResolver fixture freshResolver
+ (sourceAxiomThenVampire fixture prepared)
+ :: IO
+ (Declaration.DriverResult Text
+ Declaration.CommittedDeclarationBatch))
+ assertEqual
+ "fresh source-axiom theorem invokes Vampire once"
+ 1
+ =<< IORef.readIORef freshCalls
+ freshBatch <-
+ case freshOutcome of
+ Declaration.DriverSucceeded batch _ _ _closure ->
+ pure batch
+ Declaration.DriverFailed failure _ ->
+ assertFailure
+ ("fresh source-axiom driver failed: "
+ <> show failure)
+ >> fail "unreachable"
+ Declaration.DriverSealFailed failure _ ->
+ assertFailure
+ ("fresh source-axiom driver did not seal: "
+ <> show failure)
+ >> fail "unreachable"
+ let freshRecord = singleProofValidation freshBatch
+ assertEqual
+ "fresh theorem retains source-axiom safety"
+ sourceAxiomSafety
+ (Authority.factAuthoritySafety
+ (Authority.validationTarget
+ (Semantic.proofValidationRecordCertificate freshRecord)))
+ separationCalls <- IORef.newIORef (0 :: Int)
+ let separationResolver = Declaration.vampireResolver \task -> do
+ IORef.modifyIORef' separationCalls (+ 1)
+ resolveAccepted executable task
+ separated <-
+ (runDriverWithResolver fixture separationResolver do
+ void
+ (Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "source-axiom") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "source-axiom")
+ Declaration.authorizeSourceAxiomCandidate candidate)
+ Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "atp-does-not-import") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "atp-does-not-import")
+ Declaration.authorizeKernelProofCandidate candidate do
+ Declaration.acceptVampireObligation prepared
+ pure
+ (Kernel.importedFactDerivation
+ (Kernel.importIx 0))
+ :: IO
+ (Declaration.DriverResult Text
+ ((), Declaration.CommittedDeclarationBatch)))
+ assertEqual "mixed proof executes its ATP obligation"
+ 1
+ =<< IORef.readIORef separationCalls
+ case separated of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ (Declaration.KernelCompletionFailed
+ (Kernel.KernelReplayImportOutOfBounds index)))
+ prefix -> do
+ assertEqual "ATP premise is absent from kernel imports"
+ (Kernel.importIx 0)
+ index
+ assertEqual "failed mixed proof retains only its prefix"
+ 1
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("unexpected mixed-proof failure: " <> show failure)
+ Declaration.DriverSucceeded{} ->
+ assertFailure "ATP premise entered the kernel import inventory"
+ Declaration.DriverSealFailed{} ->
+ assertFailure "mixed proof unexpectedly reached sealing"
+ withOpenedStore fixture root \store -> do
+ freshPrefix <-
+ case freshOutcome of
+ Declaration.DriverSucceeded _ _ prefix _closure ->
+ pure prefix
+ Declaration.DriverFailed failure _ ->
+ assertFailure
+ ("fresh source-axiom driver failed: "
+ <> show failure)
+ >> fail "unreachable"
+ Declaration.DriverSealFailed failure _ ->
+ assertFailure
+ ("fresh source-axiom driver did not seal: "
+ <> show failure)
+ >> fail "unreachable"
+ expectRightIO
+ (Store.writePendingModulePrefix store freshPrefix)
+ warmCalls <- IORef.newIORef (0 :: Int)
+ let storeLookup = proofOnlyValidationLookup \key -> do
+ IORef.modifyIORef' warmCalls (+ 1)
+ Store.loadProofValidation store key >>= expectRight
+ warmBatch <- runSuccessfulWithValidation fixture
+ storeLookup
+ (sourceAxiomThenVampire fixture prepared)
+ assertEqual
+ "warm source-axiom theorem performs one store lookup"
+ 1
+ =<< IORef.readIORef warmCalls
+ assertEqual
+ "warm theorem retains source-axiom safety"
+ sourceAxiomSafety
+ (Authority.factAuthoritySafety
+ (Authority.validationTarget
+ (Semantic.proofValidationRecordCertificate
+ (singleProofValidation warmBatch))))
+ where
+ sourceAxiomSafety =
+ Authority.authoritySafety
+ (Authority.singletonEscapeKind Authority.SourceAxiom)
+
+ singleProofValidation batch =
+ case Declaration.committedBatchProofValidations batch of
+ [record] -> record
+ records ->
+ error
+ ("unexpected proof validation count: "
+ <> show (length records))
+
+sourceAxiomThenVampire
+ :: Fixture
+ -> Provers.PreparedTypedProverTask
+ Semantic.SemanticFactOccurrenceFingerprint
+ Void
+ ()
+ Identity.ObjectId
+ -> Declaration.ModuleDriver failure
+ Declaration.CommittedDeclarationBatch
+sourceAxiomThenVampire fixture prepared = do
+ void
+ (Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "source-axiom") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "source-axiom")
+ Declaration.authorizeSourceAxiomCandidate candidate)
+ (_value, batch) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "source-axiom-vampire") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "source-axiom-vampire")
+ Declaration.authorizeVampireCandidate candidate
+ (Declaration.acceptVampireObligation prepared)
+ pure batch
+
+fixtureSourceAxiomFingerprint
+ :: Fixture
+ -> Semantic.SemanticFactOccurrenceFingerprint
+fixtureSourceAxiomFingerprint fixture =
+ Semantic.semanticFactOccurrenceFingerprint
+ (Semantic.factSlot
+ (fixtureOwner fixture)
+ (localFactOrdinal 0))
+ (Authority.factAuthority
+ (Identity.theoremRef
+ (Identity.theoryId (fixtureFoundation fixture))
+ (Identity.checkedPropositionId
+ (fixtureProposition fixture)))
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind Authority.SourceAxiom)))
+
+makePreparedObligationWithPremise
+ :: Fixture
+ -> Semantic.SemanticFactOccurrenceFingerprint
+ -> IO
+ (Provers.PreparedTypedProverTask
+ Semantic.SemanticFactOccurrenceFingerprint
+ Void
+ ()
+ Identity.ObjectId)
+makePreparedObligationWithPremise fixture fingerprint = do
+ claim <- expectRight
+ (Backend.supportedProposition
+ (Vector.empty :: Vector.Vector (Void, Core.CoreType))
+ (Core.embedClosedCore
+ []
+ (Identity.checkedPropositionTerm
+ (fixtureProposition fixture))))
+ factProposition <- expectRight
+ (Backend.supportedProposition
+ (Vector.empty :: Vector.Vector (Void, Core.CoreType))
+ (Core.embedClosedCore
+ []
+ (Identity.checkedPropositionTerm
+ (fixtureProposition fixture))))
+ capability <- expectRight
+ (Backend.classifySupportedProposition
+ (const Nothing)
+ factProposition)
+ problem <- expectRight
+ (Backend.planTypedProblem
+ (const Nothing)
+ (Vector.singleton
+ (Backend.typedBackendFact
+ fingerprint
+ factProposition
+ capability))
+ claim
+ []
+ []
+ Backend.FirstOrderLocals
+ Backend.ExplicitHigherOrderJustification)
+ expectRight
+ (Provers.prepareTypedProverTask
+ Provers.DirectTask
+ problem)
+
+makePreparedObligation
+ :: Fixture
+ -> Foundation.FoundationAxiomTag
+ -> IO
+ (Provers.PreparedTypedProverTask
+ Semantic.SemanticFactOccurrenceFingerprint
+ Void
+ ()
+ Identity.ObjectId)
+makePreparedObligation fixture tag = do
+ claim <- expectRight
+ (Backend.supportedProposition
+ (Vector.empty :: Vector.Vector (Void, Core.CoreType))
+ (Core.embedClosedCore
+ []
+ (Identity.checkedPropositionTerm
+ (fixtureProposition fixture))))
+ problem <- expectRight
+ (Backend.planTypedProblem
+ (const Nothing)
+ Vector.empty
+ claim
+ []
+ [Backend.typedFoundationAuxiliaryInput
+ (fixtureFoundation fixture)
+ tag]
+ Backend.CompleteLocals
+ Backend.ExplicitHigherOrderJustification)
+ expectRight
+ (Provers.prepareTypedProverTask
+ Provers.DirectTask
+ problem)
+
+acceptedResolver :: FilePath -> Declaration.VampireResolver
+acceptedResolver executable =
+ Declaration.vampireResolver (resolveAccepted executable)
+
+resolveAccepted
+ :: FilePath
+ -> Provers.PreparedTypedProverTask
+ ref local origin global
+ -> IO
+ (Either
+ Provers.ProverProcessError
+ Provers.ProverAnswer)
+resolveAccepted executable prepared =
+ Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared
+
+writeAcceptedVampire :: FilePath -> IO ()
+writeAcceptedVampire executable = do
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for typed'"
+ ])
+ permissions <- Directory.getPermissions executable
+ Directory.setPermissions executable
+ (Directory.setOwnerExecutable True permissions)
+
+validatesExactKernelConstructionDescriptors :: Assertion
+validatesExactKernelConstructionDescriptors = do
+ fixture <- makeFixture
+ proposition <- foundationProposition
+ fixture
+ Foundation.EmptyCharacteristic
+ let declaredObject = opaqueFixtureObject fixture
+ run descriptor =
+ runDriver fixture
+ (Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId
+ "kernel-construction-descriptor") do
+ Declaration.addDeclarationObject declaredObject
+ candidate <- Declaration.reserveCandidate
+ (Declaration.candidateSpec
+ proposition
+ Semantic.SearchEligible
+ [Semantic.semanticName "kernel-construction"])
+ Declaration.authorizeCompiledDeclaration
+ (Declaration.authorizeKernelConstructionCandidate
+ descriptor
+ candidate
+ (pure
+ (Kernel.foundationFactDerivation
+ Foundation.EmptyCharacteristic))))
+ success <- run
+ (Authority.FoundationLeaf
+ Foundation.EmptyCharacteristic)
+ case success of
+ Declaration.DriverSucceeded (_value, batch) _interface _prefix _closure -> do
+ assertEqual
+ "new object is included in the checked declaration batch"
+ 1
+ (length (Declaration.committedBatchObjects batch))
+ case Declaration.committedBatchDeclarationValidation batch of
+ Just record ->
+ case Semantic.declarationValidationRecordCertificates
+ record of
+ [certificate] ->
+ assertEqual
+ "exact kernel descriptor is retained"
+ (Authority.CheckedKernelConstruction
+ (Authority.FoundationLeaf
+ Foundation.EmptyCharacteristic))
+ (Authority.validationDirectAuthorization
+ certificate)
+ certificates ->
+ assertFailure
+ ("unexpected kernel certificate count: "
+ <> show (length certificates))
+ Nothing ->
+ assertFailure "missing declaration validation"
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed failure) _prefix ->
+ assertFailure
+ ("valid kernel descriptor failed: " <> show failure)
+ Declaration.DriverFailed
+ (Declaration.DriverActionFailed _failure) _prefix ->
+ assertFailure "unexpected ordinary driver failure"
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure (show failure)
+
+ traverse_
+ (\(label, descriptor) -> do
+ outcome <- run descriptor
+ case outcome of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ Declaration.KernelConstructionDescriptorMismatch)
+ prefix ->
+ assertEqual
+ (label <> " publishes no declaration")
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed other)
+ _prefix ->
+ assertFailure
+ (label <> ": unexpected error " <> show other)
+ Declaration.DriverFailed
+ (Declaration.DriverActionFailed _failure)
+ _prefix ->
+ assertFailure (label <> ": ordinary driver failure")
+ Declaration.DriverSucceeded{} ->
+ assertFailure (label <> ": mismatch was accepted")
+ Declaration.DriverSealFailed{} ->
+ assertFailure (label <> ": mismatch reached sealing"))
+ [ ( "wrong foundation leaf"
+ , Authority.FoundationLeaf
+ Foundation.PairSetCharacteristic
+ )
+ , ( "wrong construction family"
+ , Authority.GuardedFoundationRules
+ (Authority.guardedRuleSet
+ (Foundation.SetLfpBound :| []))
+ )
+ ]
+
+authorizesExactDatatypeCompilationFamilies :: Assertion
+authorizesExactDatatypeCompilationFamilies = do
+ fixture <- makeFixture
+ firstProposition <- foundationProposition
+ fixture
+ Foundation.EmptyCharacteristic
+ secondProposition <- foundationProposition
+ fixture
+ Foundation.PairSetCharacteristic
+ let carrier = datatypeFixtureObject fixture 0
+ constructor = datatypeFixtureObject fixture 1
+ carrierId = Identity.assertedObjectId carrier
+ constructorId = Identity.assertedObjectId constructor
+ references =
+ fmap
+ (Identity.theoremRef
+ (Identity.theoryId
+ (fixtureFoundation fixture))
+ . Identity.checkedPropositionId)
+ [firstProposition, secondProposition]
+ descriptor =
+ Authority.datatypeCompilationDescriptor
+ carrierId
+ (constructorId :| [])
+ references
+ action
+ :: Authority.DatatypeCompilationDescriptor
+ -> Declaration.ModuleDriver Text
+ ((), Declaration.CommittedDeclarationBatch)
+ action suppliedDescriptor =
+ Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId "datatype-compilation") do
+ traverse_ Declaration.addDeclarationObject
+ [carrier, constructor]
+ candidates <-
+ Declaration.reserveCandidateBatch
+ ( Declaration.candidateSpec
+ firstProposition
+ Semantic.SearchEligible
+ [Semantic.semanticName "datatype-first"]
+ :| [ Declaration.candidateSpec
+ secondProposition
+ Semantic.SearchEligible
+ [Semantic.semanticName "datatype-second"]
+ ]
+ )
+ Declaration.authorizeCompiledDeclaration
+ (Declaration.authorizeDatatypeCompilationCandidates
+ suppliedDescriptor
+ carrierId
+ (constructorId :| [])
+ candidates)
+
+ (_value, batch) <- runSuccessful fixture (action descriptor)
+ assertEqual "complete object family was published"
+ [carrier, constructor]
+ (Declaration.committedBatchObjects batch)
+ case Declaration.committedBatchDeclarationValidation batch of
+ Just record -> do
+ let certificates =
+ Semantic.declarationValidationRecordCertificates record
+ assertEqual "complete fact family was authorized" 2
+ (length certificates)
+ traverse_
+ (\certificate -> do
+ assertEqual "datatype authority is clean"
+ Authority.cleanAuthoritySafety
+ (Authority.factAuthoritySafety
+ (Authority.validationTarget certificate))
+ assertEqual "one descriptor protects every member"
+ (Authority.TrustedCompilation
+ (Authority.DatatypeCompilation descriptor))
+ (Authority.validationDirectAuthorization certificate))
+ certificates
+ Nothing ->
+ assertFailure "datatype compilation omitted validation"
+
+ let mismatched =
+ Authority.datatypeCompilationDescriptor
+ carrierId
+ (constructorId :| [])
+ (reverse references)
+ rejected <- runDriver fixture (action mismatched)
+ case rejected of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ Declaration.DatatypeCompilationDescriptorMismatch)
+ prefix ->
+ assertEqual "mismatched family publishes no declaration"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("unexpected datatype-family failure: " <> show failure)
+ Declaration.DriverSucceeded{} ->
+ assertFailure "mismatched datatype family was authorized"
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure
+ ("mismatched datatype family reached sealing: "
+ <> show failure)
+
+reusesExactCompiledDeclarationValidation :: Assertion
+reusesExactCompiledDeclarationValidation = do
+ fixture <- makeFixture
+ proposition <- foundationProposition
+ fixture
+ Foundation.EmptyCharacteristic
+ let declaredObject = opaqueFixtureObject fixture
+ syntax =
+ Semantic.declarationSyntaxId
+ "cached-kernel-construction"
+ action derivationTag =
+ Declaration.commitCompiledDeclaration syntax do
+ Declaration.addDeclarationObject declaredObject
+ candidate <- Declaration.reserveCandidate
+ (Declaration.candidateSpec
+ proposition
+ Semantic.SearchEligible
+ [Semantic.semanticName
+ "cached-kernel-construction"])
+ Declaration.authorizeCompiledDeclaration
+ (Declaration.authorizeKernelConstructionCandidate
+ (Authority.FoundationLeaf
+ Foundation.EmptyCharacteristic)
+ candidate
+ (pure
+ (Kernel.foundationFactDerivation
+ derivationTag)))
+ freshBatch <- runSuccessful fixture
+ (snd <$> action Foundation.EmptyCharacteristic)
+ freshRecord <-
+ case Declaration.committedBatchDeclarationValidation freshBatch of
+ Just record -> pure record
+ Nothing ->
+ assertFailure "fresh compiled declaration omitted validation"
+ >> fail "unreachable"
+
+ missLookups <- IORef.newIORef (0 :: Int)
+ missBatch <- runSuccessfulWithValidation fixture
+ (compiledOnlyValidationLookup \_key -> do
+ IORef.modifyIORef' missLookups (+ 1)
+ pure Nothing)
+ (snd <$> action Foundation.EmptyCharacteristic)
+ assertEqual "compiled warm miss performs one exact lookup"
+ 1
+ =<< IORef.readIORef missLookups
+ assertBool "compiled miss publishes fresh validation"
+ (isJust
+ (Declaration.committedBatchDeclarationValidation missBatch))
+
+ hitLookups <- IORef.newIORef []
+ hitBatch <- runSuccessfulWithValidation fixture
+ (compiledOnlyValidationLookup \key -> do
+ IORef.modifyIORef' hitLookups (key :)
+ pure (Just freshRecord))
+ (snd <$> action Foundation.PairSetCharacteristic)
+ assertEqual "compiled warm hit performs one exact lookup"
+ [Semantic.declarationValidationRecordKey freshRecord]
+ . reverse
+ =<< IORef.readIORef hitLookups
+ case Declaration.committedBatchDeclarationValidation hitBatch of
+ Just record ->
+ assertEqual
+ "compiled hit republishes the exact validation"
+ freshRecord
+ record
+ Nothing ->
+ assertFailure "compiled hit omitted validation"
+
+foundationProposition
+ :: Fixture
+ -> Foundation.FoundationAxiomTag
+ -> IO Identity.CheckedPropositionContent
+foundationProposition fixture tag = do
+ closure <- expectRight
+ (Identity.validateObjectClosure
+ (Identity.theoryId (fixtureFoundation fixture))
+ [])
+ expectRight
+ (Identity.validatePropositionContent
+ closure
+ (Core.frozenCoreTerm
+ (Core.mapFrozenGlobals
+ absurd
+ (Foundation.foundationAxiomFrozen
+ (fixtureFoundation fixture)
+ tag))))
+
+opaqueFixtureObject :: Fixture -> Identity.AssertedObject
+opaqueFixtureObject fixture =
+ let theory = Identity.theoryId (fixtureFoundation fixture)
+ seed =
+ Identity.opaqueDeclarationSeed
+ (fixtureOwner fixture)
+ (localDeclarationOrdinal 0)
+ SignatureDeclaration
+ (generatedObjectSlot 0)
+ identity =
+ Identity.opaqueObjectId
+ theory
+ seed
+ Core.TySet
+ in Identity.assertedObject
+ identity
+ (Identity.OpaqueObjectContent
+ theory
+ seed
+ Core.TySet)
+
+datatypeFixtureObject
+ :: Fixture
+ -> Natural
+ -> Identity.AssertedObject
+datatypeFixtureObject fixture slot =
+ let theory = Identity.theoryId (fixtureFoundation fixture)
+ seed =
+ Identity.opaqueDeclarationSeed
+ (fixtureOwner fixture)
+ (localDeclarationOrdinal 0)
+ DatatypeDeclaration
+ (generatedObjectSlot slot)
+ identity =
+ Identity.opaqueObjectId theory seed Core.TySet
+ in Identity.assertedObject
+ identity
+ (Identity.OpaqueObjectContent theory seed Core.TySet)
+
+
+data Fixture = Fixture
+ { fixtureFoundation :: !Foundation.CheckedFoundation
+ , fixtureOwner :: !ModuleName
+ , fixtureProposition :: !Identity.CheckedPropositionContent
+ }
+
+makeFixture :: IO Fixture
+makeFixture =
+ makeNamedFixture "root"
+
+makeNamedFixture :: Text -> IO Fixture
+makeNamedFixture name = do
+ foundation <- expectRight Foundation.checkedFoundation
+ namespaceDigest <- expectRight
+ (hashCanonicalFields
+ "declaration-test-namespace"
+ [TextEncoding.encodeUtf8 name])
+ relative <- expectRight
+ (safeRelativePath
+ (Text.unpack name <> ".tex"))
+ let theory = Identity.theoryId foundation
+ owner =
+ moduleNameFromParts
+ (sourceNamespaceIdFromDigest namespaceDigest)
+ relative
+ closure <- expectRight
+ (Identity.validateObjectClosure theory [])
+ proposition <- expectRight
+ (Identity.validatePropositionContent closure Core.CFalsum)
+ pure
+ Fixture
+ { fixtureFoundation = foundation
+ , fixtureOwner = owner
+ , fixtureProposition = proposition
+ }
+
+factSpec :: Fixture -> Text -> Declaration.CandidateSpec
+factSpec fixture alias =
+ Declaration.candidateSpec
+ (fixtureProposition fixture)
+ Semantic.SearchEligible
+ [Semantic.semanticName alias]
+
+runDriver
+ :: Fixture
+ -> Declaration.ModuleDriver failure value
+ -> IO (Declaration.DriverResult failure value)
+runDriver fixture =
+ runDriverWithResolver fixture unavailableVampireResolver
+
+runDriverWithResolver
+ :: Fixture
+ -> Declaration.VampireResolver
+ -> Declaration.ModuleDriver failure value
+ -> IO (Declaration.DriverResult failure value)
+runDriverWithResolver fixture resolver action = do
+ result <- Declaration.runModuleDriver
+ (fixtureFoundation fixture)
+ (fixtureOwner fixture)
+ []
+ resolver
+ Declaration.FreshValidation
+ action
+ expectRight result
+
+runDriverWithValidation
+ :: Fixture
+ -> Declaration.ValidationLookup
+ -> Declaration.ModuleDriver failure value
+ -> IO (Declaration.DriverResult failure value)
+runDriverWithValidation fixture lookup action = do
+ runDriverWithValidationAndResolver
+ fixture
+ lookup
+ unavailableVampireResolver
+ action
+
+runDriverWithValidationAndResolver
+ :: Fixture
+ -> Declaration.ValidationLookup
+ -> Declaration.VampireResolver
+ -> Declaration.ModuleDriver failure value
+ -> IO (Declaration.DriverResult failure value)
+runDriverWithValidationAndResolver fixture lookup resolver action = do
+ result <- Declaration.runModuleDriver
+ (fixtureFoundation fixture)
+ (fixtureOwner fixture)
+ []
+ resolver
+ (Declaration.WarmValidation lookup)
+ action
+ expectRight result
+
+proofOnlyValidationLookup
+ :: (Semantic.ProofValidationKey
+ -> IO (Maybe Semantic.ProofValidationRecord))
+ -> Declaration.ValidationLookup
+proofOnlyValidationLookup lookupProof =
+ Declaration.validationLookup
+ lookupProof
+ (const (pure Nothing))
+
+compiledOnlyValidationLookup
+ :: (Semantic.DeclarationValidationKey
+ -> IO (Maybe Semantic.DeclarationValidationRecord))
+ -> Declaration.ValidationLookup
+compiledOnlyValidationLookup lookupDeclaration =
+ Declaration.validationLookup
+ (const (pure Nothing))
+ lookupDeclaration
+
+runDriverWithDirect
+ :: Fixture
+ -> [Semantic.SemanticInterfaceId]
+ -> Declaration.ModuleDriver failure value
+ -> IO (Declaration.DriverResult failure value)
+runDriverWithDirect fixture direct action = do
+ result <- Declaration.runModuleDriver
+ (fixtureFoundation fixture)
+ (fixtureOwner fixture)
+ direct
+ unavailableVampireResolver
+ Declaration.FreshValidation
+ action
+ expectRight result
+
+unavailableVampireResolver :: Declaration.VampireResolver
+unavailableVampireResolver =
+ Declaration.vampireResolver \_prepared ->
+ pure
+ (Left
+ (Provers.ProverLaunchFailed
+ "unused"
+ "Vampire resolver was not expected"))
+
+runSuccessful
+ :: Fixture
+ -> Declaration.ModuleDriver failure value
+ -> IO value
+runSuccessful fixture =
+ runSuccessfulWithResolver fixture unavailableVampireResolver
+
+runSuccessfulWithResolver
+ :: Fixture
+ -> Declaration.VampireResolver
+ -> Declaration.ModuleDriver failure value
+ -> IO value
+runSuccessfulWithResolver fixture resolver action = do
+ outcome <- runDriverWithResolver fixture resolver action
+ case outcome of
+ Declaration.DriverSucceeded value _interface _prefix _closure ->
+ pure value
+ Declaration.DriverFailed _failure _prefix ->
+ assertFailure "unexpected driver failure" >> fail "unreachable"
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure (show failure) >> fail "unreachable"
+
+runSuccessfulWithValidation
+ :: Fixture
+ -> Declaration.ValidationLookup
+ -> Declaration.ModuleDriver failure value
+ -> IO value
+runSuccessfulWithValidation fixture lookup action = do
+ outcome <- runDriverWithValidation fixture lookup action
+ case outcome of
+ Declaration.DriverSucceeded value _interface _prefix _closure ->
+ pure value
+ Declaration.DriverFailed _failure _prefix ->
+ assertFailure "unexpected driver failure" >> fail "unreachable"
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure (show failure) >> fail "unreachable"
+
+materializesSealedImport :: Assertion
+materializesSealedImport = do
+ fixture <- makeFixture
+ producer <- runDriver fixture do
+ (_value, batch) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "sealed-import-producer") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "producer-fact")
+ Declaration.authorizeOmittedCandidate candidate
+ Declaration.recordOmittedUse
+ pure batch
+ (producerInterface, evidence, fingerprint) <-
+ case producer of
+ Declaration.DriverSucceeded _ interface prefix _closure ->
+ let imported =
+ Declaration.freshImportedModuleEvidence
+ [] interface prefix
+ in case concatMap
+ Semantic.declarationDeltaFacts
+ (Semantic.semanticInterfaceDeclarations interface) of
+ [occurrence] ->
+ pure
+ ( interface
+ , imported
+ , Semantic.semanticFactFingerprint occurrence
+ )
+ occurrences ->
+ assertFailure
+ ("unexpected producer facts: "
+ <> show (length occurrences))
+ >> fail "unreachable"
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("producer failed: "
+ <> show
+ (failure
+ :: Declaration.DriverFailure
+ Declaration.DeclarationError))
+ >> fail "unreachable"
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure ("producer did not seal: " <> show failure)
+ >> fail "unreachable"
+ consumerNamespace <- expectRight
+ (hashCanonicalFields
+ "declaration-import-consumer"
+ ["consumer"])
+ consumerPath <- expectRight (safeRelativePath "consumer.tex")
+ let consumerFixture =
+ fixture
+ { fixtureOwner =
+ moduleNameFromParts
+ (sourceNamespaceIdFromDigest consumerNamespace)
+ consumerPath
+ }
+ consumer <- runDriverWithDirect consumerFixture
+ [Semantic.semanticInterfaceAssertedId producerInterface]
+ do
+ (_value, committed) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "sealed-import-consumer") do
+ Declaration.importSealedModule evidence
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "consumer-fact")
+ Declaration.authorizeOmittedCandidate candidate do
+ _ <- Declaration.useAuthorizedFact fingerprint
+ Declaration.recordOmittedUse
+ pure committed
+ case consumer of
+ Declaration.DriverSucceeded committed _ _ _ -> do
+ case Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta committed) of
+ [localOccurrence] -> do
+ assertEqual
+ "the first local fact keeps ordinal zero"
+ (localFactOrdinal 0)
+ (Semantic.factSlotOrdinal
+ (Semantic.semanticFactSlot localOccurrence))
+ assertEqual
+ "the local fact belongs to the consumer"
+ (fixtureOwner consumerFixture)
+ (Semantic.factSlotModule
+ (Semantic.semanticFactSlot localOccurrence))
+ facts ->
+ assertFailure
+ ("unexpected consumer fact count: "
+ <> show (length facts))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("consumer failed: "
+ <> show
+ (failure
+ :: Declaration.DriverFailure
+ Declaration.DeclarationError))
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure ("consumer did not seal: " <> show failure)
+
+elaboratesScopedExactPropositions :: Assertion
+elaboratesScopedExactPropositions = do
+ fixture <- makeNamedFixture "exact-scoped-proposition"
+ let x = Raw.NamedVar "x"
+ y = Raw.NamedVar "y"
+ statement =
+ Raw.SymbolicQuantified
+ Nowhere
+ Raw.Universally
+ (x :| [y])
+ Raw.Unbounded
+ Nothing
+ (Raw.StmtFormula
+ (Raw.FormulaChain
+ (Raw.ChainBase
+ (Raw.ExprVar x :| [])
+ Raw.Positive
+ (Raw.Relation
+ Nowhere
+ Raw.EqSymbol
+ [])
+ (Raw.ExprVar y :| []))))
+ action
+ :: Declaration.ModuleDriver Text
+ (Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition)
+ action =
+ Declaration.runProspectiveLoweringDriver
+ (Exact.prepareExactProposition
+ Exact.emptyExactBinderContext
+ statement)
+ outcome <- runDriver fixture action
+ case outcome of
+ Declaration.DriverSucceeded (Right prepared) _interface _prefix _closure ->
+ assertEqual
+ "source-order universal binders"
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CEq Core.TySet
+ (Core.CBound 1)
+ (Core.CBound 0))))
+ (Core.scopedCoreTerm
+ (Exact.preparedExactPropositionCore prepared))
+ Declaration.DriverSucceeded (Left failure) _interface _prefix _closure ->
+ assertFailure
+ ("scoped exact elaboration failed: "
+ <> Text.unpack (Exact.renderExactCompileError failure))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure ("scoped exact driver failed: " <> show failure)
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure ("scoped exact driver did not seal: " <> show failure)
+
+lowersFixedEqualityAliases :: Assertion
+lowersFixedEqualityAliases = do
+ fixture <- makeNamedFixture "fixed-equality-aliases"
+ let x = Raw.NamedVar "x"
+ y = Raw.NamedVar "y"
+ z = Raw.NamedVar "z"
+ term variable = Raw.TermExpr (Raw.ExprVar variable)
+ equality left right =
+ Raw.StmtFormula
+ (Raw.FormulaChain
+ (Raw.ChainBase
+ (Raw.ExprVar left :| [])
+ Raw.Positive
+ (Raw.Relation Nowhere Raw.EqSymbol [])
+ (Raw.ExprVar right :| [])))
+ quantified variables statement =
+ Raw.SymbolicQuantified
+ Nowhere
+ Raw.Universally
+ variables
+ Raw.Unbounded
+ Nothing
+ statement
+ adjective =
+ Raw.Adj
+ Nowhere
+ Lexicon.builtinEqualityRightAdjective
+ [term y]
+ copular =
+ Raw.StmtVerbPhrase
+ (term x :| [])
+ (Raw.VPAdj (adjective :| []))
+ rightAttribute =
+ Raw.StmtNoun
+ (term x :| [])
+ (Raw.NounPhrase
+ []
+ (Raw.Noun Nowhere Lexicon.builtinSetNoun [])
+ Nothing
+ [ Raw.AdjR
+ Nowhere
+ Lexicon.builtinEqualityRightAdjective
+ [term y]
+ ]
+ Nothing)
+ rightAttributeExpected =
+ Raw.StmtNoun
+ (term x :| [])
+ (Raw.NounPhrase
+ []
+ (Raw.Noun Nowhere Lexicon.builtinSetNoun [])
+ Nothing
+ []
+ (Just (equality x y)))
+ verb argument =
+ Raw.Verb
+ Nowhere
+ Lexicon.builtinEqualityVerb
+ [term argument]
+ singular =
+ Raw.StmtVerbPhrase
+ (term x :| [])
+ (Raw.VPVerb (verb y))
+ negated =
+ Raw.StmtVerbPhrase
+ (term x :| [])
+ (Raw.VPVerbNot (verb y))
+ coordinated =
+ Raw.StmtVerbPhrase
+ (term x :| [term y])
+ (Raw.VPVerb (verb z))
+ coordinatedExpected =
+ Raw.StmtConnected
+ Raw.Conjunction
+ Nothing
+ (equality x z)
+ (equality y z)
+ comparisons =
+ [ ( "copular adjective"
+ , quantified (x :| [y]) copular
+ , quantified (x :| [y]) (equality x y)
+ )
+ , ( "right adjective"
+ , quantified (x :| [y]) rightAttribute
+ , quantified (x :| [y]) rightAttributeExpected
+ )
+ , ( "singular verb"
+ , quantified (x :| [y]) singular
+ , quantified (x :| [y]) (equality x y)
+ )
+ , ( "negated verb"
+ , quantified (x :| [y]) negated
+ , quantified
+ (x :| [y])
+ (Raw.StmtNeg Nowhere (equality x y))
+ )
+ , ( "quantified coordinated verb"
+ , quantified (x :| [y, z]) coordinated
+ , quantified (x :| [y, z]) coordinatedExpected
+ )
+ ]
+ action
+ :: Declaration.ModuleDriver Text
+ [ ( Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition
+ , Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition
+ )
+ ]
+ action =
+ Declaration.runProspectiveLoweringDriver
+ (traverse
+ (\(_label, alias, symbolic) ->
+ (,)
+ <$> Exact.prepareExactProposition
+ Exact.emptyExactBinderContext alias
+ <*> Exact.prepareExactProposition
+ Exact.emptyExactBinderContext symbolic)
+ comparisons)
+ runDriver fixture action >>= \case
+ Declaration.DriverSucceeded results _interface _prefix _closure ->
+ for_ (zip comparisons results) \((label, _alias, _symbolic), result) ->
+ case result of
+ (Right alias, Right symbolic) -> do
+ let aliasTerm =
+ Core.scopedCoreTerm
+ (Exact.preparedExactPropositionCore alias)
+ symbolicTerm =
+ Core.scopedCoreTerm
+ (Exact.preparedExactPropositionCore symbolic)
+ assertEqual
+ (label <> " checked core")
+ symbolicTerm
+ aliasTerm
+ assertEqual
+ (label <> " global support")
+ Set.empty
+ (Core.canonicalTermGlobals aliasTerm)
+ assertEqual
+ (label <> " foundation support")
+ Set.empty
+ (Foundation.foundationAxiomDependencies aliasTerm)
+ (Left failure, _) ->
+ assertFailure
+ (label <> " alias failed: "
+ <> Text.unpack
+ (Exact.renderExactCompileError failure))
+ (_, Left failure) ->
+ assertFailure
+ (label <> " symbolic comparison failed: "
+ <> Text.unpack
+ (Exact.renderExactCompileError failure))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure ("fixed equality driver failed: " <> show failure)
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure ("fixed equality driver did not seal: " <> show failure)
+
+ let internalEquality =
+ Internal.FormulaVerb
+ Nowhere
+ (Internal.EmptySet Nowhere)
+ Lexicon.builtinEqualityVerb
+ [Internal.EmptySet Nowhere]
+ internalResult
+ :: Either
+ Typed.TypedInductiveError
+ (Core.FrozenCheckedCore Void)
+ internalResult =
+ Typed.prepareTypedClosedFormula
+ absurd
+ (const Nothing)
+ internalEquality
+ case internalResult of
+ Right checked -> do
+ assertEqual
+ "internal fixed verb core"
+ (Core.CEq
+ Core.TySet
+ (Core.CIntrinsic Core.Empty)
+ (Core.CIntrinsic Core.Empty))
+ (Core.frozenCoreTerm checked)
+ assertEqual
+ "internal fixed verb global support"
+ Set.empty
+ (Core.frozenCoreGlobals checked)
+ Left failure ->
+ assertFailure
+ ("internal fixed verb failed: " <> show failure)
+
+scopesQuantifiedPropositionTerms :: Assertion
+scopesQuantifiedPropositionTerms = do
+ fixture <- makeNamedFixture "quantified-proposition-terms"
+ let x = Raw.NamedVar "x"
+ y = Raw.NamedVar "y"
+ term variable = Raw.TermExpr (Raw.ExprVar variable)
+ zero = Raw.TermExpr (Raw.ExprInteger Nowhere 0)
+ setNoun = Raw.Noun Nowhere Lexicon.builtinSetNoun []
+ setPhrase named = Raw.NounPhrase [] setNoun named [] Nothing
+ quantified quantifier variable =
+ Raw.TermQuantified
+ quantifier Nowhere (setPhrase (Just variable))
+ equalityVerb argument =
+ Raw.Verb Nowhere Lexicon.builtinEqualityVerb [argument]
+ equalityAdjective argument =
+ Raw.Adj
+ Nowhere Lexicon.builtinEqualityRightAdjective [argument]
+ equality left right = Core.CEq Core.TySet left right
+ notP proposition = Core.CImp proposition Core.CFalsum
+ andP left right = notP (Core.CImp left (notP right))
+ existsP body = notP (Core.CForall Core.TySet (notP body))
+ truth = Core.CImp Core.CFalsum Core.CFalsum
+ member left right =
+ Core.CApp
+ (Core.CApp (Core.CIntrinsic Core.Member) left)
+ right
+ soleSubject =
+ Raw.StmtNoun
+ (quantified Raw.Universally x :| [])
+ (setPhrase Nothing)
+ explicitSubject =
+ Raw.SymbolicQuantified
+ Nowhere Raw.Universally (x :| []) Raw.Unbounded Nothing
+ (Raw.StmtNoun (term x :| []) (setPhrase Nothing))
+ multipleSubjects =
+ Raw.StmtVerbPhrase
+ ( quantified Raw.Universally x
+ :| [quantified Raw.Existentially y]
+ )
+ (Raw.VPVerb (equalityVerb zero))
+ adjectiveArgument =
+ Raw.StmtVerbPhrase
+ (zero :| [])
+ (Raw.VPAdj
+ (equalityAdjective
+ (quantified Raw.Universally x) :| []))
+ nounArgument =
+ Raw.StmtNoun
+ (zero :| [])
+ (Raw.NounPhrase
+ []
+ (Raw.Noun
+ Nowhere Lexicon.builtinElementNoun
+ [quantified Raw.Universally x])
+ Nothing [] Nothing)
+ negatedSubject =
+ Raw.StmtVerbPhrase
+ (quantified Raw.Universally x :| [])
+ (Raw.VPVerbNot (equalityVerb zero))
+ negatedArgument =
+ Raw.StmtVerbPhrase
+ (zero :| [])
+ (Raw.VPVerbNot
+ (equalityVerb (quantified Raw.Universally x)))
+ nonexistentialArgument =
+ Raw.StmtVerbPhrase
+ (zero :| [])
+ (Raw.VPVerb
+ (equalityVerb (quantified Raw.Nonexistentially x)))
+ negatedStatement =
+ Raw.StmtNeg Nowhere soleSubject
+ siblingConstraints =
+ Raw.StmtNoun
+ (zero :| [])
+ (Raw.NounPhrase
+ []
+ (Raw.Noun
+ Nowhere Lexicon.builtinElementNoun
+ [quantified Raw.Universally x])
+ Nothing
+ [Raw.AdjR
+ Nowhere Lexicon.builtinEqualityRightAdjective
+ [quantified Raw.Universally y]]
+ Nothing)
+ constrainedSubject =
+ Raw.TermQuantified Raw.Universally Nowhere
+ (Raw.NounPhrase
+ []
+ (Raw.Noun
+ Nowhere Lexicon.builtinElementNoun [term x])
+ (Just x)
+ [Raw.AdjR
+ Nowhere Lexicon.builtinEqualityRightAdjective [term x]]
+ (Just
+ (Raw.StmtVerbPhrase
+ (term x :| [])
+ (Raw.VPVerb (equalityVerb (term x))))))
+ constrainedStatement =
+ Raw.StmtVerbPhrase
+ (constrainedSubject :| [])
+ (Raw.VPVerb (equalityVerb (term x)))
+ xEqualsX = equality (Core.CBound 0) (Core.CBound 0)
+ cases =
+ [ ( "sole quantified subject"
+ , soleSubject
+ , Core.CForall Core.TySet truth
+ )
+ , ( "explicit sole quantified subject"
+ , explicitSubject
+ , Core.CForall Core.TySet truth
+ )
+ , ( "multiple quantified subjects"
+ , multipleSubjects
+ , Core.CForall Core.TySet
+ (existsP
+ (andP
+ (equality
+ (Core.CBound 1) (Core.COpaqueInteger 0))
+ (equality
+ (Core.CBound 0) (Core.COpaqueInteger 0))))
+ )
+ , ( "quantified adjective argument"
+ , adjectiveArgument
+ , Core.CForall Core.TySet
+ (equality (Core.COpaqueInteger 0) (Core.CBound 0))
+ )
+ , ( "quantified noun argument"
+ , nounArgument
+ , Core.CForall Core.TySet
+ (member (Core.COpaqueInteger 0) (Core.CBound 0))
+ )
+ , ( "quantified subject outside negation"
+ , negatedSubject
+ , Core.CForall Core.TySet
+ (notP
+ (equality
+ (Core.CBound 0) (Core.COpaqueInteger 0)))
+ )
+ , ( "quantified argument inside negation"
+ , negatedArgument
+ , notP
+ (Core.CForall Core.TySet
+ (equality
+ (Core.COpaqueInteger 0) (Core.CBound 0)))
+ )
+ , ( "nonexistential quantified verb argument"
+ , nonexistentialArgument
+ , notP
+ (existsP
+ (equality
+ (Core.COpaqueInteger 0)
+ (Core.CBound 0)))
+ )
+ , ( "statement recursion bounds a quantified subject"
+ , negatedStatement
+ , notP (Core.CForall Core.TySet truth)
+ )
+ , ( "sibling constraints own their argument quantifiers"
+ , siblingConstraints
+ , andP
+ (Core.CForall Core.TySet
+ (member
+ (Core.COpaqueInteger 0)
+ (Core.CBound 0)))
+ (Core.CForall Core.TySet
+ (equality
+ (Core.COpaqueInteger 0)
+ (Core.CBound 0)))
+ )
+ , ( "quantified noun constraints share their binder"
+ , constrainedStatement
+ , Core.CForall Core.TySet
+ (Core.CImp
+ (andP
+ (member (Core.CBound 0) (Core.CBound 0))
+ (andP xEqualsX xEqualsX))
+ xEqualsX)
+ )
+ ]
+ prepare context statement =
+ Exact.prepareExactProposition context statement
+ activeContext <- expectRight
+ (Exact.extendExactBinderContext
+ ((Exact.exactLocalId 0, x) :| [])
+ Exact.emptyExactBinderContext)
+ let action
+ :: Declaration.ModuleDriver Text
+ ( [ Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition
+ ]
+ , Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition
+ )
+ action =
+ Declaration.runProspectiveLoweringDriver do
+ compiled <- traverse
+ (\(_label, statement, _expected) ->
+ prepare Exact.emptyExactBinderContext statement)
+ cases
+ collision <- prepare activeContext soleSubject
+ pure (compiled, collision)
+ runDriver fixture action >>= \case
+ Declaration.DriverSucceeded
+ (compiled, collision) _interface _prefix _closure -> do
+ for_ (zip cases compiled) \
+ ((label, _statement, expected), result) ->
+ case result of
+ Right prepared ->
+ assertEqual label expected
+ (Core.scopedCoreTerm
+ (Exact.preparedExactPropositionCore prepared))
+ Left failure ->
+ assertFailure
+ (label <> " failed: "
+ <> Text.unpack
+ (Exact.renderExactCompileError failure))
+ case collision of
+ Left (Exact.ExactDuplicateLocalBinder _location variable) ->
+ assertEqual "quantified binder collision" x variable
+ Left failure ->
+ assertFailure
+ ("unexpected quantified-binder collision: "
+ <> Text.unpack
+ (Exact.renderExactCompileError failure))
+ Right{} ->
+ assertFailure "an active quantified binder was shadowed"
+ case compiled of
+ Right sole : Right explicit : _ ->
+ assertEqual
+ "sole-subject lowering remains byte-for-byte identical"
+ (Exact.preparedExactPropositionCore sole)
+ (Exact.preparedExactPropositionCore explicit)
+ _ ->
+ assertFailure
+ "sole-subject equality comparison did not compile"
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("quantified proposition-term driver failed: " <> show failure)
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure
+ ("quantified proposition-term driver did not seal: "
+ <> show failure)
+
+preparesExactClaimEnvelopes :: Assertion
+preparesExactClaimEnvelopes = do
+ fixture <- makeNamedFixture "exact-claim-envelope"
+ let bLocation = mkLocation (FileId 78) 2 11
+ aLocation = mkLocation (FileId 78) 2 15
+ xLocation = mkLocation (FileId 78) 3 9
+ b = Raw.NamedVarAt bLocation "b"
+ a = Raw.NamedVarAt aLocation "a"
+ x = Raw.NamedVarAt xLocation "x"
+ c = Raw.NamedVarAt Nowhere "c"
+ d = Raw.NamedVarAt Nowhere "d"
+ z = Raw.NamedVarAt Nowhere "z"
+ equality left right =
+ Raw.StmtFormula
+ (Raw.FormulaChain
+ (Raw.ChainBase
+ (Raw.ExprVar left :| [])
+ Raw.Positive
+ (Raw.Relation Nowhere Raw.EqSymbol [])
+ (Raw.ExprVar right :| [])))
+ quantified variable body =
+ Raw.SymbolicQuantified
+ (locate variable)
+ Raw.Universally
+ (variable :| [])
+ Raw.Unbounded
+ Nothing
+ body
+ sourceAssumptions = [Raw.AsmSuppose (equality b a)]
+ sourceConclusion = quantified x (equality x b)
+ alphaAssumptions = [Raw.AsmSuppose (equality c d)]
+ alphaConclusion = quantified z (equality z c)
+ action
+ :: Declaration.ModuleDriver Text
+ ( Either
+ Exact.ExactCompileError
+ Exact.PreparedExactClaimEnvelope
+ , Either
+ Exact.ExactCompileError
+ Exact.PreparedExactClaimEnvelope
+ )
+ action =
+ Declaration.runProspectiveLoweringDriver do
+ source <- Exact.prepareExactClaimEnvelope
+ sourceAssumptions sourceConclusion
+ alpha <- Exact.prepareExactClaimEnvelope
+ alphaAssumptions alphaConclusion
+ pure (source, alpha)
+ expected =
+ Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (Core.CEq Core.TySet
+ (Core.CBound 1)
+ (Core.CBound 0))
+ (Core.CForall Core.TySet
+ (Core.CEq Core.TySet
+ (Core.CBound 0)
+ (Core.CBound 2)))))
+ runDriver fixture action >>= \case
+ Declaration.DriverSucceeded
+ (Right source, Right alpha)
+ _interface _prefix _closure -> do
+ let sourceTarget = Exact.preparedExactClaimTarget source
+ alphaTarget = Exact.preparedExactClaimTarget alpha
+ assertEqual "closed claim envelope core"
+ expected
+ (Core.scopedCoreTerm sourceTarget)
+ assertEqual "claim envelope is closed"
+ []
+ (Core.scopedCoreContext sourceTarget)
+ assertEqual "first semantic occurrence binder order"
+ [bLocation, aLocation]
+ (locate <$> Exact.preparedExactClaimVariables source)
+ assertEqual "explicit binders are not generalized"
+ 2
+ (length (Exact.preparedExactClaimVariables source))
+ assertEqual "header antecedent count"
+ 1
+ (Exact.preparedExactClaimAntecedentCount source)
+ assertEqual "alpha-renaming preserves the checked target"
+ sourceTarget alphaTarget
+ assertEqual "alpha-renaming preserves proposition identity"
+ (Identity.propositionIdOf
+ (Core.scopedCoreTerm sourceTarget))
+ (Identity.propositionIdOf
+ (Core.scopedCoreTerm alphaTarget))
+ Declaration.DriverSucceeded result _interface _prefix _closure ->
+ assertFailure
+ ("exact claim envelope preparation failed: "
+ <> case result of
+ (Left failure, _) ->
+ Text.unpack
+ (Exact.renderExactCompileError failure)
+ (_, Left failure) ->
+ Text.unpack
+ (Exact.renderExactCompileError failure))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure ("claim envelope driver failed: " <> show failure)
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure
+ ("claim envelope driver did not seal: " <> show failure)
+
+lowersExactSeparationComprehensions :: Assertion
+lowersExactSeparationComprehensions = do
+ fixture <- makeNamedFixture "exact-separation-comprehension"
+ let binderLocation = mkLocation (FileId 73) 2 7
+ ambientLocation = mkLocation (FileId 73) 2 18
+ boundOccurrenceLocation = mkLocation (FileId 73) 3 14
+ x = Raw.NamedVarAt binderLocation "x"
+ a = Raw.NamedVarAt ambientLocation "A"
+ equality left right =
+ Raw.StmtFormula
+ (Raw.FormulaChain
+ (Raw.ChainBase
+ (left :| [])
+ Raw.Positive
+ (Raw.Relation Nowhere Raw.EqSymbol [])
+ (right :| [])))
+ separation bound =
+ Raw.ExprSep
+ binderLocation
+ x
+ bound
+ (equality (Raw.ExprVar x) (Raw.ExprVar a))
+ validStatement =
+ Raw.SymbolicQuantified
+ Nowhere
+ Raw.Universally
+ (a :| [])
+ Raw.Unbounded
+ Nothing
+ (equality
+ (separation (Raw.ExprVar a))
+ (Raw.ExprVar a))
+ boundOccurrence =
+ Raw.NamedVarAt boundOccurrenceLocation "x"
+ invalidStatement =
+ equality
+ (separation (Raw.ExprVar boundOccurrence))
+ (Raw.ExprVar a)
+ action
+ :: Declaration.ModuleDriver Text
+ ( Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition
+ , Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition
+ )
+ action =
+ Declaration.runProspectiveLoweringDriver do
+ valid <- Exact.prepareExactProposition
+ Exact.emptyExactBinderContext
+ validStatement
+ invalid <- Exact.prepareExactProposition
+ Exact.emptyExactBinderContext
+ invalidStatement
+ pure (valid, invalid)
+ outcome <- runDriver fixture action
+ case outcome of
+ Declaration.DriverSucceeded
+ (Right prepared, Left failure) _interface _prefix _closure -> do
+ assertEqual
+ "separation comprehension core"
+ (Core.CForall Core.TySet
+ (Core.CEq Core.TySet
+ (Core.CApp
+ (Core.CApp
+ (Core.CIntrinsic Core.Sep)
+ (Core.CBound 0))
+ (Core.CLam Core.TySet
+ (Core.CEq Core.TySet
+ (Core.CBound 0)
+ (Core.CBound 1))))
+ (Core.CBound 0)))
+ (Core.scopedCoreTerm
+ (Exact.preparedExactPropositionCore prepared))
+ assertEqual
+ "separation proposition type"
+ Core.TyProp
+ (Core.scopedCoreType
+ (Exact.preparedExactPropositionCore prepared))
+ assertEqual
+ "the separation binder is unavailable in its bound"
+ (Exact.ExactFreeVariable
+ boundOccurrenceLocation
+ boundOccurrence)
+ failure
+ Declaration.DriverSucceeded result _interface _prefix _closure ->
+ case result of
+ (Left validFailure, _) ->
+ assertFailure
+ ("valid separation failed: "
+ <> Text.unpack
+ (Exact.renderExactCompileError validFailure))
+ (_, Right{}) ->
+ assertFailure "invalid separation was accepted"
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure ("separation exact driver failed: " <> show failure)
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure ("separation exact driver did not seal: " <> show failure)
+
+lowersExactReplacementTelescopes :: Assertion
+lowersExactReplacementTelescopes = do
+ fixture <- makeNamedFixture "exact-replacement-telescope"
+ let location = mkLocation (FileId 74) 2 1
+ futureOccurrenceLocation = mkLocation (FileId 74) 7 19
+ a = Raw.NamedVarAt location "A"
+ x = Raw.NamedVarAt location "x"
+ y = Raw.NamedVarAt location "y"
+ futureY = Raw.NamedVarAt futureOccurrenceLocation "y"
+ equality left right =
+ Raw.StmtFormula
+ (Raw.FormulaChain
+ (Raw.ChainBase
+ (left :| [])
+ Raw.Positive
+ (Raw.Relation Nowhere Raw.EqSymbol [])
+ (right :| [])))
+ replacement firstDomain =
+ Raw.ExprReplace
+ location
+ (Raw.ExprVar y)
+ ( (x, firstDomain) :|
+ [(y, Raw.ExprVar x)]
+ )
+ (Just (equality (Raw.ExprVar x) (Raw.ExprVar y)))
+ validStatement =
+ Raw.SymbolicQuantified
+ Nowhere
+ Raw.Universally
+ (a :| [])
+ Raw.Unbounded
+ Nothing
+ (equality
+ (replacement (Raw.ExprVar a))
+ (Raw.ExprVar a))
+ invalidStatement =
+ equality
+ (replacement (Raw.ExprVar futureY))
+ (Raw.ExprInteger Nowhere 0)
+ predicateReplacementLocation = mkLocation (FileId 74) 9 3
+ predicateReplacementStatement =
+ equality
+ (Raw.ExprReplacePred
+ predicateReplacementLocation
+ y
+ x
+ (Raw.ExprInteger Nowhere 0)
+ (equality (Raw.ExprVar x) (Raw.ExprVar y)))
+ (Raw.ExprInteger Nowhere 0)
+ namedPredicateReplacement =
+ Raw.ExprReplacePred
+ predicateReplacementLocation
+ y
+ x
+ (Raw.ExprVar a)
+ (equality (Raw.ExprVar x) (Raw.ExprVar y))
+ app1 intrinsic argument =
+ Core.CApp (Core.CIntrinsic intrinsic) argument
+ app2 intrinsic first second =
+ Core.CApp (app1 intrinsic first) second
+ expected =
+ Core.CForall Core.TySet $
+ Core.CEq Core.TySet
+ (app1 Core.FamilyUnion $
+ app2 Core.Repl (Core.CBound 0) $
+ Core.CLam Core.TySet $
+ app2 Core.Repl
+ (app2 Core.Sep
+ (Core.CBound 0)
+ (Core.CLam Core.TySet $
+ Core.CEq Core.TySet
+ (Core.CBound 1)
+ (Core.CBound 0)))
+ (Core.CLam Core.TySet
+ (Core.CBound 0)))
+ (Core.CBound 0)
+ action
+ :: Declaration.ModuleDriver Text
+ ( Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition
+ , Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition
+ , Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition
+ , Either
+ Exact.ExactCompileError
+ Exact.PreparedExactSetExpression
+ )
+ action =
+ Declaration.runProspectiveLoweringDriver do
+ valid <- Exact.prepareExactProposition
+ Exact.emptyExactBinderContext
+ validStatement
+ invalid <- Exact.prepareExactProposition
+ Exact.emptyExactBinderContext
+ invalidStatement
+ predicateReplacement <- Exact.prepareExactProposition
+ Exact.emptyExactBinderContext
+ predicateReplacementStatement
+ namedContext <-
+ either
+ (impossible
+ . Text.unpack
+ . Exact.renderExactCompileError)
+ pure
+ (Exact.extendExactBinderContext
+ ((Exact.exactLocalId 0, a) :| [])
+ Exact.emptyExactBinderContext)
+ named <- Exact.prepareExactSetExpression
+ namedContext namedPredicateReplacement
+ pure (valid, invalid, predicateReplacement, named)
+ runDriver fixture action >>= \case
+ Declaration.DriverSucceeded
+ ( Right prepared
+ , Left failure
+ , Left predicateReplacementFailure
+ , Right named
+ ) _interface _prefix _closure -> do
+ assertEqual
+ "dependent replacement core"
+ expected
+ (Core.scopedCoreTerm
+ (Exact.preparedExactPropositionCore prepared))
+ assertEqual
+ "future replacement binder location"
+ (Exact.ExactFreeVariable futureOccurrenceLocation futureY)
+ failure
+ assertEqual
+ "predicate replacement remains unsupported at its location"
+ (Exact.ExactRelationalReplacementRequiresNamedDefinition
+ predicateReplacementLocation)
+ predicateReplacementFailure
+ case Exact.preparedExactSetExpressionConstruction named of
+ Just (Exact.PreparedRelationalSetConstruction construction) -> do
+ assertEqual "relational replacement canonical term"
+ expectedRelationalTerm
+ (Core.scopedCoreTerm
+ (SetConstruction.relationalSetConstructionTerm
+ construction))
+ assertEqual "relational replacement functionality"
+ expectedFunctionality
+ (Core.scopedCoreTerm
+ (SetConstruction.relationalSetConstructionFunctionality
+ construction))
+ let relationalObject =
+ Identity.assertedObjectId
+ (opaqueFixtureObject fixture)
+ closedFunctionality =
+ SetConstruction.relationalSetConstructionClosedFunctionality
+ construction
+ relationalFact <-
+ maybe
+ (assertFailure
+ "exact functionality did not unlock relational extensionality"
+ >> fail "unreachable")
+ pure
+ (SetConstruction.relationalSetConstructionObjectFact
+ (SetConstruction.checkedFoundationSetConstruction
+ (fixtureFoundation fixture))
+ relationalObject
+ construction
+ closedFunctionality)
+ assertEqual
+ "relational replacement flattened extensional proposition"
+ (expectedRelationalExtensional relationalObject)
+ (Core.frozenCoreTerm
+ (SetConstruction.relationalSetConstructionFactProposition
+ relationalFact))
+ assertEqual
+ "unrelated functionality cannot unlock the relational view"
+ Nothing
+ (SetConstruction.relationalSetConstructionLocalViews
+ (SetConstruction.checkedFoundationSetConstruction
+ (fixtureFoundation fixture))
+ construction
+ (Core.falsumScopedCore [Core.TySet]))
+ wrongClosed <- expectRight
+ (Core.checkCanonicalCore
+ (const Nothing)
+ Core.CFalsum)
+ assertBool
+ "malformed relational authority is rejected"
+ (isNothing
+ (SetConstruction.relationalSetConstructionObjectFact
+ (SetConstruction.checkedFoundationSetConstruction
+ (fixtureFoundation fixture))
+ relationalObject
+ construction
+ wrongClosed))
+ _ ->
+ assertFailure
+ "named predicate replacement lost its relational construction"
+ Declaration.DriverSucceeded
+ (Left validFailure, _, _, _) _interface _prefix _closure ->
+ assertFailure
+ ("valid replacement failed: "
+ <> Text.unpack
+ (Exact.renderExactCompileError validFailure))
+ Declaration.DriverSucceeded
+ (_, Right{}, _, _) _interface _prefix _closure ->
+ assertFailure "invalid replacement was accepted"
+ Declaration.DriverSucceeded
+ (_, _, Right{}, _) _interface _prefix _closure ->
+ assertFailure "predicate replacement was accepted"
+ Declaration.DriverSucceeded
+ (_, _, _, Left failure) _interface _prefix _closure ->
+ assertFailure
+ ("named predicate replacement failed: "
+ <> Text.unpack (Exact.renderExactCompileError failure))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("replacement driver failed: " <> show failure)
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure
+ ("replacement driver did not seal: " <> show failure)
+ where
+ relApp1 intrinsic argument =
+ Core.CApp (Core.CIntrinsic intrinsic) argument
+ relApp2 intrinsic first second =
+ Core.CApp (relApp1 intrinsic first) second
+ notP proposition = Core.CImp proposition Core.CFalsum
+ andP left right = notP (Core.CImp left (notP right))
+ existsP body = notP (Core.CForall Core.TySet (notP body))
+ relation = Core.CEq Core.TySet (Core.CBound 1) (Core.CBound 0)
+ restricted =
+ relApp2 Core.Sep (Core.CBound 0)
+ (Core.CLam Core.TySet (existsP relation))
+ expectedRelationalTerm =
+ relApp2 Core.Repl restricted
+ (Core.CLam Core.TySet
+ (relApp1 Core.SetChoose (Core.CLam Core.TySet relation)))
+ expectedFunctionality =
+ Core.CForall Core.TySet
+ (Core.CImp
+ (relApp2 Core.Member (Core.CBound 0) (Core.CBound 1))
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (andP
+ (Core.CEq Core.TySet
+ (Core.CBound 2) (Core.CBound 1))
+ (Core.CEq Core.TySet
+ (Core.CBound 2) (Core.CBound 0)))
+ (Core.CEq Core.TySet
+ (Core.CBound 1) (Core.CBound 0))))))
+ expectedRelationalExtensional object =
+ Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CEq Core.TyProp
+ (relApp2 Core.Member
+ (Core.CBound 0)
+ (Core.CApp
+ (Core.CGlobal object)
+ (Core.CBound 1)))
+ (existsP
+ (andP
+ (relApp2 Core.Member
+ (Core.CBound 0)
+ (Core.CBound 2))
+ (Core.CEq Core.TySet
+ (Core.CBound 0)
+ (Core.CBound 1))))))
+
+lowersExactFiniteSets :: Assertion
+lowersExactFiniteSets = do
+ fixture <- makeNamedFixture "exact-finite-set"
+ let location = mkLocation (FileId 75) 2 1
+ a = Raw.NamedVarAt location "a"
+ b = Raw.NamedVarAt location "b"
+ equality left right =
+ Raw.StmtFormula
+ (Raw.FormulaChain
+ (Raw.ChainBase
+ (left :| [])
+ Raw.Positive
+ (Raw.Relation Nowhere Raw.EqSymbol [])
+ (right :| [])))
+ statement =
+ Raw.SymbolicQuantified
+ Nowhere
+ Raw.Universally
+ (a :| [b])
+ Raw.Unbounded
+ Nothing
+ (equality
+ (Raw.ExprFiniteSet
+ location
+ (Raw.ExprVar a :| [Raw.ExprVar b]))
+ (Raw.ExprVar a))
+ app1 intrinsic argument =
+ Core.CApp (Core.CIntrinsic intrinsic) argument
+ app2 intrinsic first second =
+ Core.CApp (app1 intrinsic first) second
+ insert element rest =
+ app1 Core.FamilyUnion
+ (app2 Core.PairSet
+ (app2 Core.PairSet element element)
+ rest)
+ expected =
+ Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CEq Core.TySet
+ (insert
+ (Core.CBound 1)
+ (insert
+ (Core.CBound 0)
+ (Core.CIntrinsic Core.Empty)))
+ (Core.CBound 1)))
+ action
+ :: Declaration.ModuleDriver Text
+ (Either
+ Exact.ExactCompileError
+ Exact.PreparedExactProposition)
+ action =
+ Declaration.runProspectiveLoweringDriver
+ (Exact.prepareExactProposition
+ Exact.emptyExactBinderContext
+ statement)
+ internal <-
+ expectRight
+ (evalState
+ (runExceptT (Meaning.glossStmt statement))
+ Meaning.initialGlossState)
+ reusable <-
+ expectRight
+ (Typed.prepareTypedClosedFormula
+ absurd
+ (const Nothing)
+ internal
+ :: Either
+ Typed.TypedInductiveError
+ (Core.FrozenCheckedCore Void))
+ assertEqual
+ "raw and reusable finite-set lowering"
+ expected
+ (Core.frozenCoreTerm reusable)
+ let internalSymbols = Internal.mentionedSymbols internal
+ assertBool
+ "finite-set meaning has no source-owned cons dependency"
+ (Internal.SymbolMixfix Raw.ConsSymbol
+ `Set.notMember` internalSymbols)
+ assertBool
+ "finite-set meaning retains fixed adjunction operations"
+ ( Set.fromList
+ [ Internal.SymbolMixfix Raw.UnionsSymbol
+ , Internal.SymbolMixfix Raw.UpairSymbol
+ ]
+ `Set.isSubsetOf` internalSymbols
+ )
+ case Vocabulary.classifyExactSymbol
+ (Internal.SymbolMixfix Raw.ConsSymbol) of
+ Vocabulary.ExactSourceGlobal{} -> pure ()
+ classification ->
+ assertFailure
+ ("explicit cons did not retain source ownership: "
+ <> show classification)
+ runDriver fixture action >>= \case
+ Declaration.DriverSucceeded
+ (Right prepared) _interface _prefix _closure ->
+ assertEqual
+ "source-order finite-set core"
+ expected
+ (Core.scopedCoreTerm
+ (Exact.preparedExactPropositionCore prepared))
+ Declaration.DriverSucceeded
+ (Left failure) _interface _prefix _closure ->
+ assertFailure
+ ("valid finite set failed: "
+ <> Text.unpack
+ (Exact.renderExactCompileError failure))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("finite-set driver failed: " <> show failure)
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure
+ ("finite-set driver did not seal: " <> show failure)
+
+lowersExactOrdinaryDeclarations :: Assertion
+lowersExactOrdinaryDeclarations = do
+ fixture <- makeNamedFixture "exact-lowering"
+ level <- expectRight (Syntax.mixfixLevel 2)
+ let makeSymbol command marker =
+ Raw.mkMixfixItem
+ [ Just (Raw.Command command)
+ , Just Raw.InvisibleBraceL
+ , Nothing
+ , Just Raw.InvisibleBraceR
+ ]
+ (Raw.Marker marker)
+ Raw.NonAssoc
+ opaqueSymbol = makeSymbol "phasefiveopaque" "opaque-label"
+ aliasSymbol = makeSymbol "phasefivealias" "alias-label"
+ definitionSymbol = makeSymbol "phasefivedef" "definition-label"
+ entry symbol =
+ Syntax.CanonicalExpressionFunction
+ (Raw.mixfixPattern symbol)
+ (Raw.mixfixMarker symbol)
+ (Syntax.Fixity Raw.NonAssoc level)
+ parameter = Raw.NamedVar "x"
+ exactSet =
+ Raw.NounPhrase
+ []
+ (Raw.Noun Nowhere Lexicon.builtinSetNoun [])
+ Nothing
+ []
+ Nothing
+ signature =
+ Raw.BlockSig
+ Nowhere Nothing (Raw.Marker "opaque-declaration") []
+ (Raw.SignatureSymbolic
+ (Raw.SymbolPattern opaqueSymbol [parameter])
+ exactSet)
+ application symbol =
+ Raw.ExprOp Nowhere symbol [Raw.ExprVar parameter]
+ abbreviation =
+ Raw.BlockAbbr
+ Nowhere Nothing (Raw.Marker "alias-declaration")
+ (Raw.AbbreviationEq
+ (Raw.SymbolPattern aliasSymbol [parameter])
+ (application opaqueSymbol))
+ definition =
+ Raw.BlockDefn
+ Nowhere Nothing (Raw.Marker "definition-declaration")
+ (Raw.DefnOp
+ (Raw.SymbolPattern definitionSymbol [parameter])
+ (application aliasSymbol))
+ compile block lexicalEntry = do
+ Declaration.runProspectiveLoweringDriver
+ (Exact.prepareExactDeclaration block [lexicalEntry]) >>= \case
+ Left failure ->
+ Declaration.failModuleDriver
+ (Exact.renderExactCompileError failure)
+ Right prepared -> pure prepared
+ admit prepared = do
+ lowered <-
+ Declaration.runProspectiveLoweringDriver
+ (Exact.lowerPreparedExactBinding prepared)
+ checked <-
+ either Declaration.failDeclarationDriver pure lowered
+ void
+ (Declaration.admitCheckedDeclaration
+ checked
+ Exact.authorizeCheckedExactBinding)
+ outcome <- runFixtureDriver fixture [] do
+ preparedSignature <-
+ compile signature (entry opaqueSymbol)
+ admit preparedSignature
+ preparedAbbreviation <-
+ compile abbreviation (entry aliasSymbol)
+ admit preparedAbbreviation
+ preparedDefinition <-
+ compile definition (entry definitionSymbol)
+ admit preparedDefinition
+ pure
+ ( preparedSignature
+ , preparedAbbreviation
+ , preparedDefinition
+ )
+ case outcome of
+ Declaration.DriverSucceeded
+ (preparedSignature, preparedAbbreviation, preparedDefinition)
+ interface prefix _closure -> do
+ assertEqual "three committed declarations"
+ 3
+ (length (Semantic.semanticInterfaceDeclarations interface))
+ assertEqual "three committed batches"
+ 3
+ (length (Declaration.pendingModulePrefixBatches prefix))
+ assertEqual "opaque signature family"
+ Identity.OpaqueObject
+ (Identity.objectIdFamily
+ (Exact.preparedExactObjectId preparedSignature))
+ assertEqual "transparent abbreviation family"
+ Identity.TransparentObject
+ (Identity.objectIdFamily
+ (Exact.preparedExactObjectId preparedAbbreviation))
+ assertEqual "transparent definition family"
+ Identity.TransparentObject
+ (Identity.objectIdFamily
+ (Exact.preparedExactObjectId preparedDefinition))
+ assertEqual "expanded definition coalesces with abbreviation"
+ (Exact.preparedExactObjectId preparedAbbreviation)
+ (Exact.preparedExactObjectId preparedDefinition)
+ case Exact.preparedExactObject preparedAbbreviation of
+ Just object ->
+ case Identity.assertedObjectContent object of
+ Identity.TransparentObjectContent
+ _theory coreType body -> do
+ assertEqual "definition type"
+ (Core.TyArrow Core.TySet Core.TySet)
+ coreType
+ assertEqual "expanded body retains opaque seed"
+ (Core.CLam Core.TySet
+ (Core.CApp
+ (Core.CGlobal
+ (Exact.preparedExactObjectId
+ preparedSignature))
+ (Core.CBound 0)))
+ body
+ content ->
+ assertFailure
+ ("unexpected definition content: " <> show content)
+ Nothing ->
+ assertFailure "new abbreviation object was not prepared"
+ assertEqual "coalesced definition adds no object"
+ Nothing
+ (Exact.preparedExactObject preparedDefinition)
+ case reverse (Declaration.pendingModulePrefixBatches prefix) of
+ definitionBatch : _ -> do
+ case Declaration.committedBatchDeclarationValidation
+ definitionBatch of
+ Just record ->
+ case Semantic.declarationValidationRecordCertificates
+ record of
+ [certificate] -> do
+ assertEqual "definition authority"
+ (Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation
+ (Exact.preparedExactObjectId
+ preparedDefinition)))
+ (Authority.validationDirectAuthorization
+ certificate)
+ assertEqual "definition authority is clean"
+ Authority.cleanAuthoritySafety
+ (Authority.factAuthoritySafety
+ (Authority.validationTarget
+ certificate))
+ certificates ->
+ assertFailure
+ ("unexpected definition certificate count: "
+ <> show (length certificates))
+ Nothing ->
+ assertFailure "definition has no declaration validation"
+ [] -> assertFailure "definition batch is absent"
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure ("exact lowering failed: " <> show failure)
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure ("exact lowering did not seal: " <> show failure)
+
+ let theory = Identity.theoryId (fixtureFoundation fixture)
+ mismatchBody = Core.COpaqueInteger 0
+ mismatchType = Core.TySet
+ mismatchId =
+ Identity.transparentObjectId theory mismatchType mismatchBody
+ mismatchObject =
+ Identity.assertedObject
+ mismatchId
+ (Identity.TransparentObjectContent
+ theory mismatchType mismatchBody)
+ let mismatchAction
+ :: Declaration.ModuleDriver Text
+ ((), Declaration.CommittedDeclarationBatch)
+ mismatchAction =
+ Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId
+ "mismatched-definition-equation") do
+ Declaration.addDeclarationObject mismatchObject
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "not-a-definition-equation")
+ Declaration.authorizeCompiledDeclaration
+ (Declaration.authorizeDefinitionEquationCandidate
+ mismatchId
+ candidate)
+ mismatch <- runDriver fixture mismatchAction
+ case mismatch of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ Declaration.DefinitionEquationCandidateMismatch)
+ prefix ->
+ assertEqual "mismatched equation publishes no batch"
+ 0
+ (length (Declaration.pendingModulePrefixBatches prefix))
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("unexpected mismatched-equation failure: " <> show failure)
+ Declaration.DriverSucceeded{} ->
+ assertFailure "mismatched definition equation was authorized"
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure
+ ("mismatched definition equation reached sealing: "
+ <> show failure)
+
+reconstructsImportedGlobalBindings :: Assertion
+reconstructsImportedGlobalBindings = do
+ producerFixture <- makeNamedFixture "global-producer"
+ consumerFixture <- makeNamedFixture "global-consumer"
+ conflictFixture <- makeNamedFixture "global-conflict"
+ rootFixture <- makeNamedFixture "global-root"
+ let key =
+ Semantic.SemanticExpressionFunction
+ (Raw.TokenCons (Raw.Command "phasefive") Raw.End)
+ asserted = opaqueFixtureObject producerFixture
+ target = Identity.assertedObjectId asserted
+ publishWith targetMode fixture object = do
+ (batch, sealed) <- sealFixture fixture [] do
+ (_value, committed) <-
+ Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId "global-binding") do
+ Declaration.addDeclarationObject object
+ Declaration.stageSemanticGlobalBinding
+ key
+ (targetMode
+ (Identity.assertedObjectId object))
+ Declaration.authorizeCompiledDeclaration (pure ())
+ pure committed
+ pure (batch, sealed)
+ (producerBatch, freshProducer) <-
+ publishWith Semantic.GlobalReference producerFixture asserted
+ let FixtureSealed producerInterface _freshEvidence = freshProducer
+ objects = Declaration.committedBatchObjects producerBatch
+ cachedEvidence <- expectRight
+ (Declaration.validateImportedModuleEvidence
+ (Identity.theoryId
+ (fixtureFoundation producerFixture))
+ []
+ producerInterface
+ objects
+ [])
+ let cachedProducer = FixtureSealed producerInterface cachedEvidence
+ resolveThrough label parent = do
+ outcome <- runFixtureDriver consumerFixture [parent] do
+ fst <$> Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId
+ (TextEncoding.encodeUtf8 (Text.pack label))) do
+ found <- Declaration.resolveVisibleGlobal key
+ Declaration.authorizeCompiledDeclaration (pure ())
+ pure found
+ case outcome of
+ Declaration.DriverSucceeded found _interface _prefix _closure ->
+ assertEqual label
+ (Just
+ ( Semantic.GlobalReference target
+ , Core.TySet
+ ))
+ found
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("global binding consumer failed: " <> show failure)
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure
+ ("global binding consumer did not seal: " <> show failure)
+ resolveThrough "fresh-global-binding" freshProducer
+ resolveThrough "cached-global-binding" cachedProducer
+
+ missingEnvironment <- expectRight
+ (Semantic.semanticEnvironmentDelta
+ [Semantic.semanticGlobalBinding
+ key
+ (Semantic.GlobalReference target)])
+ missingDelta <- expectRight
+ (Semantic.declarationInterfaceDelta
+ (Semantic.declarationSlot
+ (fixtureOwner producerFixture)
+ (localDeclarationOrdinal 0))
+ []
+ []
+ []
+ []
+ missingEnvironment)
+ missingInterface <- expectRight
+ (Semantic.semanticInterface
+ (fixtureOwner producerFixture)
+ []
+ [missingDelta])
+ case Declaration.validateImportedModuleEvidence
+ (Identity.theoryId
+ (fixtureFoundation producerFixture))
+ []
+ missingInterface
+ []
+ [] of
+ Left (Declaration.ImportedGlobalTargetInvalid
+ actualKey actualTarget
+ (Semantic.SemanticGlobalTargetMissing missingTarget)) -> do
+ assertEqual "missing target key" key actualKey
+ assertEqual "missing target mode"
+ (Semantic.GlobalReference target)
+ actualTarget
+ assertEqual "missing target object" target missingTarget
+ Left failure ->
+ assertFailure
+ ("unexpected missing-target failure: " <> show failure)
+ Right _evidence ->
+ assertFailure "cached evidence accepted a missing target object"
+
+ expansionEnvironment <- expectRight
+ (Semantic.semanticEnvironmentDelta
+ [Semantic.semanticGlobalBinding
+ key
+ (Semantic.TransparentExpansion target)])
+ expansionDelta <- expectRight
+ (Semantic.declarationInterfaceDelta
+ (Semantic.declarationSlot
+ (fixtureOwner producerFixture)
+ (localDeclarationOrdinal 0))
+ [] [] [target] [] expansionEnvironment)
+ expansionInterface <- expectRight
+ (Semantic.semanticInterface
+ (fixtureOwner producerFixture) [] [expansionDelta])
+ case Declaration.validateImportedModuleEvidence
+ (Identity.theoryId
+ (fixtureFoundation producerFixture))
+ []
+ expansionInterface
+ [asserted]
+ [] of
+ Left (Declaration.ImportedGlobalTargetInvalid
+ actualKey actualTarget
+ (Semantic.SemanticGlobalExpansionNotTransparent
+ invalidTarget)) -> do
+ assertEqual "nontransparent target key" key actualKey
+ assertEqual "nontransparent target mode"
+ (Semantic.TransparentExpansion target)
+ actualTarget
+ assertEqual "nontransparent target object" target invalidTarget
+ Left failure ->
+ assertFailure
+ ("unexpected nontransparent-target failure: "
+ <> show failure)
+ Right _evidence ->
+ assertFailure "cached evidence accepted a nontransparent expansion"
+
+ let intrinsicTarget =
+ Identity.intrinsicObjectId
+ (Identity.theoryId
+ (fixtureFoundation producerFixture))
+ Core.Empty
+ Core.TySet
+ intrinsicObject =
+ Identity.assertedObject
+ intrinsicTarget
+ (Identity.IntrinsicObjectContent
+ (Identity.theoryId
+ (fixtureFoundation producerFixture))
+ Core.Empty
+ Core.TySet)
+ intrinsicFailure <- runFixtureDriver producerFixture [] do
+ Declaration.commitCompiledDeclaration
+ (Semantic.declarationSyntaxId "intrinsic-global-binding") do
+ Declaration.addDeclarationObject intrinsicObject
+ Declaration.stageSemanticGlobalBinding
+ key
+ (Semantic.GlobalReference intrinsicTarget)
+ Declaration.authorizeCompiledDeclaration (pure ())
+ case intrinsicFailure of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ (Declaration.DeclarationGlobalTargetInvalid
+ actualKey actualTarget
+ (Semantic.SemanticGlobalTargetIsIntrinsic
+ invalidTarget)))
+ _prefix -> do
+ assertEqual "intrinsic target key" key actualKey
+ assertEqual "intrinsic target mode"
+ (Semantic.GlobalReference intrinsicTarget)
+ actualTarget
+ assertEqual "intrinsic target object"
+ intrinsicTarget invalidTarget
+ other ->
+ assertFailure
+ (case other of
+ Declaration.DriverSucceeded{} ->
+ "ordinary binding accepted an intrinsic target"
+ Declaration.DriverFailed failure _prefix ->
+ "unexpected intrinsic-target failure: " <> show failure
+ Declaration.DriverSealFailed failure _prefix ->
+ "intrinsic target reached sealing: " <> show failure)
+
+ conflictObject <- pure (opaqueFixtureObject conflictFixture)
+ (_conflictBatch, conflicting) <-
+ publishWith Semantic.GlobalReference conflictFixture conflictObject
+ collision <- runFixtureDriver rootFixture
+ [freshProducer, conflicting]
+ (pure ())
+ case collision of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ (Declaration.ImportedGlobalCollision
+ actualKey firstTarget secondTarget))
+ _prefix -> do
+ assertEqual "colliding global key" key actualKey
+ assertEqual "first imported target"
+ (Semantic.GlobalReference target)
+ firstTarget
+ assertEqual "second imported target"
+ (Semantic.GlobalReference
+ (Identity.assertedObjectId conflictObject))
+ secondTarget
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("unexpected imported collision: " <> show failure)
+ Declaration.DriverSucceeded{} ->
+ assertFailure "unequal imported bindings did not collide"
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure
+ ("global collision reached sealing: " <> show failure)
+
+ let theory = Identity.theoryId (fixtureFoundation producerFixture)
+ transparentBody = Core.COpaqueInteger 0
+ transparentTarget =
+ Identity.transparentObjectId
+ theory Core.TySet transparentBody
+ transparentObject =
+ Identity.assertedObject
+ transparentTarget
+ (Identity.TransparentObjectContent
+ theory Core.TySet transparentBody)
+ (_referenceBatch, referenceProducer) <-
+ publishWith
+ Semantic.GlobalReference
+ producerFixture
+ transparentObject
+ (_expansionBatch, expansionProducer) <-
+ publishWith
+ Semantic.TransparentExpansion
+ conflictFixture
+ transparentObject
+ modeCollision <- runFixtureDriver rootFixture
+ [referenceProducer, expansionProducer]
+ (pure ())
+ case modeCollision of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ (Declaration.ImportedGlobalCollision
+ actualKey firstTarget secondTarget))
+ _prefix -> do
+ assertEqual "mode collision key" key actualKey
+ assertEqual "reference target"
+ (Semantic.GlobalReference transparentTarget)
+ firstTarget
+ assertEqual "expansion target"
+ (Semantic.TransparentExpansion transparentTarget)
+ secondTarget
+ other ->
+ assertFailure
+ (case other of
+ Declaration.DriverSucceeded{} ->
+ "different global target modes did not collide"
+ Declaration.DriverFailed failure _prefix ->
+ "unexpected mode-collision failure: " <> show failure
+ Declaration.DriverSealFailed failure _prefix ->
+ "mode collision reached sealing: " <> show failure)
+
+data FixtureSealed = FixtureSealed
+ !Semantic.SemanticInterface
+ !Declaration.ImportedModuleEvidence
+
+foldsTransitiveAndDiamondEvidence :: Assertion
+foldsTransitiveAndDiamondEvidence = do
+ baseFixture <- makeNamedFixture "base"
+ middleFixture <- makeNamedFixture "middle"
+ transitiveFixture <- makeNamedFixture "transitive"
+ leftFixture <- makeNamedFixture "left"
+ rightFixture <- makeNamedFixture "right"
+ diamondFixture <- makeNamedFixture "diamond"
+ conflictLeftFixture <- makeNamedFixture "conflict-left"
+ conflictRightFixture <- makeNamedFixture "conflict-right"
+ conflictRootFixture <- makeNamedFixture "conflict-root"
+
+ (base, baseFingerprint) <-
+ sealFactFixture baseFixture [] "transitive-shared"
+ middle <- snd <$> sealFixture middleFixture [base] (pure ())
+ transitive <- sealUsingImportedFact
+ transitiveFixture [middle] baseFingerprint
+ assertLocalFactOrdinalZero "transitive importer" transitive
+
+ left <- snd <$> sealFixture leftFixture [base] (pure ())
+ right <- snd <$> sealFixture rightFixture [base] (pure ())
+ diamond <- sealUsingImportedFact
+ diamondFixture [left, right] baseFingerprint
+ assertLocalFactOrdinalZero "diamond importer" diamond
+
+ (conflictLeft, leftFingerprint) <-
+ sealFactFixture conflictLeftFixture [] "diamond-conflict"
+ (conflictRight, rightFingerprint) <-
+ sealFactFixture conflictRightFixture [] "diamond-conflict"
+ conflict <- runFixtureDriver
+ conflictRootFixture
+ [conflictLeft, conflictRight]
+ (pure ())
+ case conflict of
+ Declaration.DriverFailed
+ (Declaration.DriverDeclarationFailed
+ (Declaration.ImportedAliasCollision
+ alias
+ (Declaration.ImportedAliasOrigin
+ _leftSlot firstTarget)
+ (Declaration.ImportedAliasOrigin
+ _rightSlot secondTarget)))
+ _prefix -> do
+ assertEqual "conflicting alias"
+ (Semantic.semanticName "diamond-conflict")
+ alias
+ assertEqual "first alias origin"
+ leftFingerprint
+ firstTarget
+ assertEqual "second alias origin"
+ rightFingerprint
+ secondTarget
+ _ ->
+ assertFailure "conflicting diamond alias was not rejected"
+ where
+ sealUsingImportedFact fixture parents fingerprint = do
+ (batch, _sealed) <- sealFixture fixture parents do
+ (_value, committed) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "use-transitive-import") do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture "local-after-import")
+ Declaration.authorizeOmittedCandidate candidate do
+ void (Declaration.useAuthorizedFact fingerprint)
+ Declaration.recordOmittedUse
+ pure committed
+ pure batch
+
+ assertLocalFactOrdinalZero label batch =
+ case Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch) of
+ [occurrence] ->
+ assertEqual label
+ (localFactOrdinal 0)
+ (Semantic.factSlotOrdinal
+ (Semantic.semanticFactSlot occurrence))
+ facts ->
+ assertFailure
+ (label <> ": unexpected fact count "
+ <> show (length facts))
+
+sealFactFixture
+ :: Fixture
+ -> [FixtureSealed]
+ -> Text
+ -> IO
+ ( FixtureSealed
+ , Semantic.SemanticFactOccurrenceFingerprint
+ )
+sealFactFixture fixture parents alias = do
+ (batch, sealed) <- sealFixture fixture parents do
+ (_value, committed) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId
+ (TextEncoding.encodeUtf8 alias)) do
+ candidate <- Declaration.reserveCandidate
+ (factSpec fixture alias)
+ Declaration.authorizeSourceAxiomCandidate candidate
+ pure committed
+ case Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch) of
+ [occurrence] ->
+ pure
+ ( sealed
+ , Semantic.semanticFactFingerprint occurrence
+ )
+ facts ->
+ assertFailure
+ ("unexpected sealed fact count: " <> show (length facts))
+ >> fail "unreachable"
+
+sealFixture
+ :: Fixture
+ -> [FixtureSealed]
+ -> Declaration.ModuleDriver Text value
+ -> IO (value, FixtureSealed)
+sealFixture fixture parents action = do
+ outcome <- runFixtureDriver fixture parents action
+ case outcome of
+ Declaration.DriverSucceeded value interface prefix _closure ->
+ pure
+ ( value
+ , FixtureSealed
+ interface
+ (Declaration.freshImportedModuleEvidence
+ [ evidence
+ | FixtureSealed _interface evidence <- parents
+ ]
+ interface
+ prefix)
+ )
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure ("fixture failed: " <> show failure)
+ >> fail "unreachable"
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure ("fixture did not seal: " <> show failure)
+ >> fail "unreachable"
+
+runFixtureDriver
+ :: Fixture
+ -> [FixtureSealed]
+ -> Declaration.ModuleDriver Text value
+ -> IO (Declaration.DriverResult Text value)
+runFixtureDriver fixture parents action = do
+ result <- Declaration.runModuleDriver
+ (fixtureFoundation fixture)
+ (fixtureOwner fixture)
+ [ Semantic.semanticInterfaceAssertedId interface
+ | FixtureSealed interface _evidence <- parents
+ ]
+ unavailableVampireResolver
+ Declaration.FreshValidation
+ do
+ traverse_
+ (\(FixtureSealed _interface evidence) ->
+ Declaration.importSealedModuleDriver evidence)
+ parents
+ action
+ expectRight result
+
+requireSingleOccurrence
+ :: Declaration.CommittedDeclarationBatch
+ -> Declaration.ModuleDriver
+ Declaration.DeclarationError
+ Semantic.SemanticFactOccurrence
+requireSingleOccurrence batch =
+ case Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch) of
+ [occurrence] ->
+ pure occurrence
+ _ ->
+ Declaration.failModuleDriver
+ Declaration.ProofDeclarationMustProduceOneFact
+
+expectRight :: Show error => Either error value -> IO value
+expectRight = \case
+ Left err ->
+ assertFailure (show err) >> fail "unreachable"
+ Right value ->
+ pure value
+
+expectRightIO :: Show error => IO (Either error value) -> IO value
+expectRightIO action =
+ action >>= expectRight
+
+withOpenedStore
+ :: Fixture
+ -> FilePath
+ -> (Store.Store -> IO value)
+ -> IO value
+withOpenedStore fixture root action =
+ bracket
+ (expectRightIO
+ (Store.openStore
+ (root Posix.</> "store.sqlite")
+ (Identity.theoryId (fixtureFoundation fixture))))
+ (Store.closeStore . snd)
+ (action . snd)
+
+withTemporaryDirectory :: String -> (FilePath -> IO a) -> IO a
+withTemporaryDirectory template =
+ bracket create Directory.removePathForcibly
+ where
+ create = do
+ root <- Directory.getTemporaryDirectory
+ (path, handle) <- openTempFile root template
+ hClose handle
+ Directory.removeFile path
+ Directory.createDirectory path
+ pure path
diff --git a/source/Felix/Test/Unit/Foundation.hs b/source/Felix/Test/Unit/Foundation.hs
new file mode 100644
index 0000000..18ead04
--- /dev/null
+++ b/source/Felix/Test/Unit/Foundation.hs
@@ -0,0 +1,284 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Test.Unit.Foundation (unitTests) where
+
+import Base
+import Felix.Checking.Core
+import Felix.Checking.Foundation
+
+import Data.List qualified as List
+import Data.Set qualified as Set
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Foundation manifest"
+ [ testCase
+ "accepts the exact compiled foundation"
+ acceptsCompiledFoundation
+ , testCase
+ "rejects incomplete and altered manifests"
+ rejectsManifestMutations
+ , testCase
+ "classifies the exact UnivOf schemas"
+ classifiesUnivOfSchemas
+ , testCase
+ "selects only intrinsic characteristic dependencies"
+ selectsOnlyIntrinsicCharacteristicDependencies
+ ]
+
+acceptsCompiledFoundation :: Assertion
+acceptsCompiledFoundation = do
+ foundation <-
+ either
+ (assertFailure . show)
+ pure
+ checkedFoundation
+ assertEqual
+ "intrinsic coverage"
+ [minBound .. maxBound]
+ (fst <$> compiledFoundationIntrinsicRows)
+ assertEqual
+ "guarded-rule coverage"
+ [minBound .. maxBound]
+ [ tag
+ | FoundationRuleInput tag _signature <-
+ compiledFoundationRuleRows
+ ]
+ for_ [minBound .. maxBound] \tag ->
+ assertEqual
+ ("closed proposition type for " <> show tag)
+ TyProp
+ (frozenCoreType
+ (foundationAxiomFrozen foundation tag))
+
+selectsOnlyIntrinsicCharacteristicDependencies :: Assertion
+selectsOnlyIntrinsicCharacteristicDependencies = do
+ let separation =
+ CApp
+ (CApp (CIntrinsic Sep) (CBound 0))
+ (CLam TySet CFalsum)
+ underUniverse =
+ CApp (CIntrinsic UnivOf) separation
+ assertEqual
+ "separation is found recursively without a universe bundle"
+ (Set.singleton SeparationCharacteristic)
+ (foundationAxiomDependencies underUniverse)
+
+rejectsManifestMutations :: Assertion
+rejectsManifestMutations = do
+ let withoutMinimal =
+ List.filter
+ (\case
+ FoundationAxiomInput
+ UnivOfMinimal
+ _syntax
+ _backendClass ->
+ False
+ _ ->
+ True)
+ compiledFoundationAxiomRows
+ wrongUnivType =
+ [ if tag == UnivOf
+ then (tag, TySet)
+ else row
+ | row@(tag, _coreType) <-
+ compiledFoundationIntrinsicRows
+ ]
+ duplicatedEmpty =
+ case findAxiomInput EmptyCharacteristic of
+ Just row ->
+ row : compiledFoundationAxiomRows
+ Nothing ->
+ impossible
+ "compiled manifest omitted EmptyCharacteristic"
+ alteredEmpty =
+ replaceAxiomInput
+ EmptyCharacteristic
+ (FoundationAxiomInput
+ EmptyCharacteristic
+ (coreOpaqueInteger 0)
+ FoundationFofProjectable)
+ alteredExtensionality =
+ replaceAxiomInput
+ SetExtensionality
+ (FoundationAxiomInput
+ SetExtensionality
+ coreFalsum
+ FoundationFofProjectable)
+ misclassifiedEmpty =
+ case findAxiomInput EmptyCharacteristic of
+ Just
+ (FoundationAxiomInput
+ tag
+ syntax
+ _backendClass) ->
+ replaceAxiomInput
+ tag
+ (FoundationAxiomInput
+ tag
+ syntax
+ (FoundationRequiresTh0
+ (HigherOrderLambda :| [])))
+ Nothing ->
+ impossible
+ "compiled manifest omitted EmptyCharacteristic"
+ withoutLeast =
+ [ row
+ | row@(FoundationRuleInput tag _signature) <-
+ compiledFoundationRuleRows
+ , tag /= SetLfpLeast
+ ]
+ alteredInductSignature =
+ [ if tag == SetLfpInduct
+ then
+ FoundationRuleInput
+ tag
+ (KernelRuleSignature [TySet] 0)
+ else row
+ | row@(FoundationRuleInput tag _signature) <-
+ compiledFoundationRuleRows
+ ]
+ assertAuditContains
+ (== MissingFoundationAxiom UnivOfMinimal)
+ (auditFoundationManifest
+ compiledFoundationIntrinsicRows
+ compiledFoundationRuleRows
+ withoutMinimal)
+ assertAuditContains
+ (== FoundationIntrinsicTypeMismatch
+ UnivOf
+ (TySet `TyArrow` TySet)
+ TySet)
+ (auditFoundationManifest
+ wrongUnivType
+ compiledFoundationRuleRows
+ compiledFoundationAxiomRows)
+ assertAuditContains
+ (== MissingFoundationRule SetLfpLeast)
+ (auditFoundationManifest
+ compiledFoundationIntrinsicRows
+ withoutLeast
+ compiledFoundationAxiomRows)
+ assertAuditContains
+ (\case
+ FoundationRuleSignatureMismatch
+ SetLfpInduct
+ _expected
+ (KernelRuleSignature [TySet] 0) ->
+ True
+ _ ->
+ False)
+ (auditFoundationManifest
+ compiledFoundationIntrinsicRows
+ alteredInductSignature
+ compiledFoundationAxiomRows)
+ assertAuditContains
+ (== DuplicateFoundationAxiom EmptyCharacteristic)
+ (auditFoundationManifest
+ compiledFoundationIntrinsicRows
+ compiledFoundationRuleRows
+ duplicatedEmpty)
+ assertAuditContains
+ (\case
+ FoundationAxiomIllTyped
+ EmptyCharacteristic
+ (ExpectedCoreType TyProp TySet) ->
+ True
+ _ ->
+ False)
+ (auditFoundationManifest
+ compiledFoundationIntrinsicRows
+ compiledFoundationRuleRows
+ alteredEmpty)
+ assertAuditContains
+ (== FoundationAxiomStatementMismatch
+ SetExtensionality)
+ (auditFoundationManifest
+ compiledFoundationIntrinsicRows
+ compiledFoundationRuleRows
+ alteredExtensionality)
+ assertAuditContains
+ (== FoundationAxiomBackendClassMismatch
+ EmptyCharacteristic
+ (FoundationRequiresTh0
+ (HigherOrderLambda :| []))
+ FoundationFofProjectable)
+ (auditFoundationManifest
+ compiledFoundationIntrinsicRows
+ compiledFoundationRuleRows
+ misclassifiedEmpty)
+
+classifiesUnivOfSchemas :: Assertion
+classifiesUnivOfSchemas = do
+ foundation <-
+ either
+ (assertFailure . show)
+ pure
+ checkedFoundation
+ for_
+ [ UnivOfContains
+ , UnivOfTransitive
+ , UnivOfFamilyUnionClosed
+ , UnivOfPowerSetClosed
+ ]
+ \tag ->
+ assertEqual
+ (show tag)
+ FoundationFofProjectable
+ (foundationAxiomBackendClass foundation tag)
+ for_
+ [ UnivOfReplacementClosed
+ , UnivOfMinimal
+ ]
+ \tag ->
+ case foundationAxiomBackendClass foundation tag of
+ FoundationRequiresTh0 exclusions ->
+ assertBool
+ (show tag <> " has a structural exclusion")
+ (not (null exclusions))
+ FoundationFofProjectable ->
+ assertFailure
+ (show tag <> " was classified as FOF")
+
+findAxiomInput
+ :: FoundationAxiomTag
+ -> Maybe FoundationAxiomInput
+findAxiomInput wanted =
+ List.find
+ (\case
+ FoundationAxiomInput tag _syntax _backendClass ->
+ tag == wanted)
+ compiledFoundationAxiomRows
+
+replaceAxiomInput
+ :: FoundationAxiomTag
+ -> FoundationAxiomInput
+ -> [FoundationAxiomInput]
+replaceAxiomInput wanted replacement =
+ fmap
+ (\row ->
+ case row of
+ FoundationAxiomInput tag _syntax _backendClass
+ | tag == wanted ->
+ replacement
+ _ ->
+ row)
+ compiledFoundationAxiomRows
+
+assertAuditContains
+ :: (FoundationManifestError -> Bool)
+ -> Either
+ (NonEmpty FoundationManifestError)
+ FoundationManifestAudit
+ -> Assertion
+assertAuditContains predicate = \case
+ Left errors ->
+ assertBool
+ ("expected error not found in " <> show errors)
+ (any predicate errors)
+ Right _audit ->
+ assertFailure
+ "expected foundation-manifest audit to fail"
diff --git a/source/Felix/Test/Unit/Html.hs b/source/Felix/Test/Unit/Html.hs
new file mode 100644
index 0000000..c8395ec
--- /dev/null
+++ b/source/Felix/Test/Unit/Html.hs
@@ -0,0 +1,225 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.Html (unitTests) where
+
+import Base
+import Felix.Parse qualified as Parse
+import Felix.Source
+import Felix.Source.Graph
+import Felix.Workspace qualified as Workspace
+import Felix.Render.Html qualified as Html
+import Felix.Render.Html.Context
+import Felix.Render.Html.Layout
+import Felix.Report.Location (Location, pattern Nowhere)
+import Felix.Syntax.Abstract
+
+import Data.Text qualified as Text
+import Data.Text.IO qualified as TextIO
+import Data.List.NonEmpty qualified as NonEmpty
+import System.Directory qualified as Directory
+import Test.Tasty
+import Test.Tasty.HUnit
+
+unitTests :: TestTree
+unitTests = testGroup "HTML renderer"
+ [ testCase "one shared index resolves local and cross-page previews" referencePreviews
+ , testCase "missing reference preview data falls back to readable text" missingReferenceFallback
+ , testCase "datatype rendering omits unchecked derived facts" datatypeDerivedFactsAreOmitted
+ ]
+
+referencePreviews :: Assertion
+referencePreviews = do
+ environment <- expectRight =<< Workspace.prepareDefaultWorkspaceEnvironment
+ graph <-
+ expectRight =<<
+ Workspace.prepareDefaultSourceGraph
+ "test/html-fixtures/root-preview.tex"
+ workspace <-
+ expectRight =<< Parse.parseResolvedSourceGraph graph
+ hints <- TextIO.readFile "library/lexicon.tsv"
+ layout <-
+ expectRight
+ (layoutHtmlSourceGraph
+ (Workspace.workspaceHtmlMountPrefixes environment)
+ graph)
+ let nodes = Parse.parsedWorkspaceImportedBeforeImporter workspace
+ sourceBlocks =
+ (\node ->
+ ( Parse.parsedModuleResolved node
+ , Parse.parsedModuleBlocks node
+ ))
+ <$> nodes
+ (renderIndex, pages) =
+ Html.buildRenderIndex sourceBlocks
+ rootPage = NonEmpty.last pages
+ context <-
+ expectRight
+ (htmlRenderContext
+ layout
+ (Html.htmlPagePresentationSource rootPage))
+ html <-
+ expectRight
+ (Html.renderDocument
+ context
+ hints
+ renderIndex
+ rootPage)
+ let supportScript = Html.supportScriptAssetContents
+ assertContains "local references keep page anchors" "href=\"#local_prop\"" html
+ assertContains "local references point previews at the visible target block" "data-reference-label=\"local_prop\" data-preview-target-id=\"local_prop\"" html
+ assertNotContains "local references do not use hidden preview ids" "data-reference-label=\"local_prop\" data-preview-id=\"" html
+ assertContains "visible target blocks expose preview metadata" "id=\"local_prop\" data-preview-kind=\"Proposition\" data-preview-label=\"local_prop\"" html
+ assertContains "imported references get preview metadata" "data-reference-label=\"imported_prop\"" html
+ assertContains "imported references link to their relative encoded theory route" "href=\"imported-preview#imported_prop\"" html
+ assertContains "imported references still use hidden preview templates" "data-reference-label=\"imported_prop\" data-preview-id=\"reference-preview-" html
+ assertNotContains "imported references do not get broken page anchors" "href=\"#imported_prop\"" html
+ assertNotContains "imported references are not non-clickable spans" "<span class=\"ref-badge has-preview\" data-reference-label=\"imported_prop\"" html
+ assertCount "hidden preview store only contains the imported fixture preview" 1 "class=\"reference-preview-template\"" html
+ assertContains "page grid is scoped to an explicit shell" "class=\"page-layout\"" html
+ assertNotContains "page grid does not apply to every body div" "body > div" html
+ assertContains "page heading uses the selected mounted source" "<h1>project:test/html-fixtures/root-preview.tex</h1>" html
+ assertContains "imported preview records its mounted source" "project:test/html-fixtures/imported-preview.tex" html
+ assertContains "imported preview source links to the same route" "href=\"imported-preview\"><code>project:test/html-fixtures/imported-preview.tex</code>" html
+ assertContains "imported source renders on its own line" "class=\"reference-preview-source\"" html
+ assertContains "multi-reference rendering preserves the comma separator" ", <a href=\"imported-preview#imported_prop\" class=\"ref-badge has-preview\" data-reference-label=\"imported_prop\"" html
+ assertContains "calculation justifications also use target previews" "Step 2: by <a href=\"#local_prop\" class=\"ref-badge has-preview\" data-reference-label=\"local_prop\" data-preview-target-id=\"local_prop\"" html
+ assertContains "large reference lists collapse to an ellipsis trigger" "Follows by <span class=\"ref-badge has-preview ref-badge-group\" data-preview-group=\"true\" data-reference-label=\"5 references\"" html
+ assertContains "collapsed references keep current-page item metadata" "data-reference-label=\"group_source\" data-preview-link=\"#group_source\" data-preview-target-id=\"group_source\"" html
+ assertContains "collapsed references keep imported item metadata" "data-reference-label=\"imported_prop\" data-preview-link=\"imported-preview#imported_prop\" data-preview-id=\"reference-preview-" html
+ assertNotContains "collapsed references do not inline the long list" "Follows by <a href=\"#local_prop\" class=\"ref-badge has-preview\" data-reference-label=\"local_prop\" data-preview-target-id=\"local_prop\" aria-describedby=\"reference-preview-popup\">local_prop</a>, <a href=\"#uses_refs\"" html
+ assertContains "collapsed tooltips compose full preview templates" "const preview = cloneHiddenPreview(item) || buildCurrentPreview(item) || buildMissingPreview(item);" supportScript
+ assertContains "collapsed tooltip labels become links" "template.append(linkGroupHeading(item, preview));" supportScript
+ assertContains "collapsed tooltip labels use generated reference links" "link.href = href;" supportScript
+ assertContains "collapsed tooltip heading links have hover affordance" ".reference-preview-heading a:hover," html
+ assertContains "collapsed tooltips use stacked preview sections" "className = 'reference-preview-group-template'" supportScript
+ assertContains "visible preview popup accepts pointer interaction" "pointer-events: auto;" html
+ assertContains "preview popup uses a wider bounded layout" "width: 44rem;" html
+ assertContains "preview popup uses a taller bounded layout" "max-height: min(34rem, calc(100vh - 2rem));" html
+ assertContains "behavior loads from the shared external script asset" "src=\"../../_static/naproche-html.js\"" html
+ assertNotContains "inline script bundles are not emitted" "<script type=\"text/javascript\">" html
+ assertContains "preview popup cancels delayed hide on pointer entry" "popup.addEventListener('pointerenter', clearHideTimer);" supportScript
+ assertContains "preview popup schedules delayed hide on pointer exit" "popup.addEventListener('pointerleave', scheduleHide);" supportScript
+ assertContains "group click pins the preview popup" "showPreview(trigger, event, true);" supportScript
+ assertContains "group keyboard activation pins the preview popup" "showPreview(trigger, null, true);" supportScript
+ assertContains "preview statements use a full-width paragraph" "class=\"reference-preview-statement\"" html
+ assertContains "preview popup is emitted once" "id=\"reference-preview-popup\"" html
+
+missingReferenceFallback :: Assertion
+missingReferenceFallback = do
+ let proof = Qed (Just Nowhere) (JustificationRef ("missing_ref" :| []))
+ blocks = [BlockProof Nowhere proof Nowhere]
+ html <- renderSynthetic blocks
+ assertContains "missing references remain visible" "missing_ref" html
+ assertNotContains "missing references do not claim preview content" "data-preview-id=" html
+
+datatypeDerivedFactsAreOmitted :: Assertion
+datatypeDerivedFactsAreOmitted = do
+ let blocks =
+ [ propformDatatypeBlock Nowhere
+ , referenceClaimBlock "uses_datatype_fact"
+ , referenceProofBlock "propform_induct"
+ ]
+ html <- renderSynthetic blocks
+ assertContains "datatype declarations remain visible" "Datatype of " html
+ assertContains "derived fact references remain readable" "propform_induct" html
+ assertNotContains "unchecked datatype facts are not rendered" "<summary>Derived facts</summary>" html
+ assertNotContains "unchecked datatype facts do not become preview targets" "data-preview-label=\"propform_induct\"" html
+
+renderSynthetic :: [Block] -> IO Text
+renderSynthetic blocks = do
+ currentDirectory <- Directory.getCurrentDirectory
+ mounts <-
+ expectRight =<<
+ prepareSourceMounts
+ [(sourceMountId "project", currentDirectory)]
+ request <-
+ expectRight
+ (searchedRoot "test/html-fixtures/root-preview.tex")
+ graph <-
+ expectRight =<<
+ buildResolvedSourceGraph mounts request
+ layout <-
+ expectRight
+ (layoutHtmlSourceGraph
+ [(sourceMountId "project", [])]
+ graph)
+ context <-
+ expectRight
+ (htmlRenderContext
+ layout
+ (sourceGraphRootSource graph))
+ let (renderIndex, page :| _remainingPages) =
+ Html.buildRenderIndex
+ ((sourceGraphRootSource graph, blocks) :| [])
+ expectRight
+ (Html.renderDocument
+ context
+ ""
+ renderIndex
+ page)
+
+expectRight :: (Show e, HasCallStack) => Either e a -> IO a
+expectRight = \case
+ Left err ->
+ assertFailure ("expected Right, got Left " <> show err)
+ Right value ->
+ pure value
+
+propformDatatypeBlock :: Location -> Block
+propformDatatypeBlock blockLoc =
+ BlockData blockLoc Nothing "propform" propformDatatype
+
+propformDatatype :: Datatype
+propformDatatype =
+ Datatype
+ { datatypeHeadExpr = ExprOp Nowhere (constSymbol "propform") []
+ , datatypeClauses =
+ DatatypeClause (ExprOp Nowhere (constSymbol "propbot") []) (ExprOp Nowhere (constSymbol "propform") []) [] :|
+ [ DatatypeClause (ExprOp Nowhere (unarySymbol "propvar") [ExprVar "n"]) (ExprOp Nowhere (constSymbol "propform") []) [("n", ExprOp Nowhere (constSymbol "naturals") [])]
+ , DatatypeClause
+ (ExprOp Nowhere (infixSymbol "propto") [ExprVar "p", ExprVar "q"])
+ (ExprOp Nowhere (constSymbol "propform") [])
+ [ ("p", ExprOp Nowhere (constSymbol "propform") [])
+ , ("q", ExprOp Nowhere (constSymbol "propform") [])
+ ]
+ ]
+ }
+
+referenceClaimBlock :: Marker -> Block
+referenceClaimBlock marker =
+ BlockClaim Proposition Nowhere Nothing marker (Claim [] (StmtFormula (PropositionalConstant Nowhere IsTop)))
+
+referenceProofBlock :: Marker -> Block
+referenceProofBlock marker =
+ BlockProof Nowhere (Qed (Just Nowhere) (JustificationRef (marker :| []))) Nowhere
+
+constSymbol :: Text -> FunctionSymbol
+constSymbol name =
+ mkMixfixItem [Just (Command name)] (Marker name) NonAssoc
+
+unarySymbol :: Text -> FunctionSymbol
+unarySymbol name =
+ mkMixfixItem [Just (Command name), Just InvisibleBraceL, Nothing, Just InvisibleBraceR] (Marker name) NonAssoc
+
+infixSymbol :: Text -> FunctionSymbol
+infixSymbol name =
+ mkMixfixItem [Nothing, Just (Command name), Nothing] (Marker name) NonAssoc
+
+assertContains :: HasCallStack => String -> Text -> Text -> Assertion
+assertContains label needle haystack =
+ assertBool
+ (label <> "\nExpected to find: " <> Text.unpack needle)
+ (needle `Text.isInfixOf` haystack)
+
+assertNotContains :: HasCallStack => String -> Text -> Text -> Assertion
+assertNotContains label needle haystack =
+ assertBool
+ (label <> "\nDid not expect to find: " <> Text.unpack needle)
+ (not (needle `Text.isInfixOf` haystack))
+
+assertCount :: HasCallStack => String -> Int -> Text -> Text -> Assertion
+assertCount label expected needle haystack =
+ assertEqual
+ (label <> "\nExpected count for: " <> Text.unpack needle)
+ expected
+ (Text.count needle haystack)
diff --git a/source/Felix/Test/Unit/HtmlLayout.hs b/source/Felix/Test/Unit/HtmlLayout.hs
new file mode 100644
index 0000000..a3d701f
--- /dev/null
+++ b/source/Felix/Test/Unit/HtmlLayout.hs
@@ -0,0 +1,478 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.HtmlLayout (unitTests) where
+
+import Base
+import Felix.Source
+import Felix.Source.Graph
+import Felix.Render.Html.Layout
+
+import Control.Exception (bracket)
+import Data.List qualified as List
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Text qualified as Text
+import System.Directory qualified as Directory
+import System.FilePath.Posix qualified as Posix
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "HTML layout"
+ [ testCase
+ "encodes URL segments canonically"
+ encodesUrlSegments
+ , testCase
+ "renders relative typed URLs"
+ rendersRelativeUrls
+ , testCase
+ "separates mounted route namespaces"
+ separatesMountedNamespaces
+ , testCase
+ "routes searched and exact roots identically"
+ routesRootFormsIdentically
+ , testCase
+ "requires an explicit mount for an external root"
+ requiresExternalMount
+ , testCase
+ "reports URL and destination collisions independently"
+ reportsRouteCollisions
+ , testCase
+ "rejects ancestor and descendant destinations"
+ rejectsNestedDestinations
+ , testCase
+ "is independent of graph and configuration traversal order"
+ isTraversalOrderIndependent
+ ]
+
+
+encodesUrlSegments :: Assertion
+encodesUrlSegments = do
+ let accepted =
+ [ ("AZaz09-._~", "AZaz09-._~")
+ , ("#?% \\", "%23%3F%25%20%5C")
+ , ("über", "%C3%BCber")
+ , ("%2f", "%252f")
+ ]
+ for_ accepted \(decoded, expected) ->
+ assertEqual
+ ("encoded segment " <> Text.unpack decoded)
+ (Right expected)
+ (renderUrlSegment <$> urlSegment decoded)
+ let rejected =
+ [ ("", EmptyUrlSegment)
+ , (".", DotUrlSegment ".")
+ , ("..", DotUrlSegment "..")
+ , ("a/b", UrlSegmentContainsSeparator "a/b")
+ ]
+ for_ rejected \(decoded, expected) ->
+ assertEqual
+ ("rejected segment " <> Text.unpack decoded)
+ (Left expected)
+ (urlSegment decoded)
+ assertEqual
+ "fragment encoding"
+ "#name%23%C3%BC"
+ (renderUrlFragment "name#ü")
+
+rendersRelativeUrls :: Assertion
+rendersRelativeUrls = do
+ let cases =
+ [ ( ["library", "nested", "über"]
+ , ["_static", "naproche-html.js"]
+ , "../../_static/naproche-html.js"
+ )
+ , ( ["first", "entry"]
+ , ["second", "entry"]
+ , "../second/entry"
+ )
+ , ( ["mount", "entry"]
+ , ["mount", "a?b"]
+ , "a%3Fb"
+ )
+ ]
+ for_ cases \(currentSegments, targetSegments, expected) -> do
+ current <- expectRight (urlPath currentSegments)
+ target <- expectRight (urlPath targetSegments)
+ assertEqual
+ "relative URL"
+ expected
+ (renderRelativeUrlPath current target)
+ current <- expectRight (urlPath ["mount", "entry"])
+ target <- expectRight (urlPath ["mount", "ü"])
+ assertEqual
+ "encoded path and fragment remain separate"
+ "%C3%BC#part%23%3F"
+ ( renderRelativeUrlPath current target
+ <> renderUrlFragment "part#?"
+ )
+
+separatesMountedNamespaces :: Assertion
+separatesMountedNamespaces =
+ withTemporaryDirectory "felix-html-layout-mounts" \temp -> do
+ let mountSpecifications =
+ [ ("project", [])
+ , ("library", ["library"])
+ , ("debug", ["debug"])
+ , ("external", ["external"])
+ ]
+ roots <- for mountSpecifications \(ident, _prefix) -> do
+ let root = temp Posix.</> Text.unpack ident
+ Directory.createDirectory root
+ writeTheory (root Posix.</> "entry.tex") []
+ pure (sourceMountId ident, root)
+ mounts <- expectRight =<< prepareSourceMounts roots
+ routes <- for mountSpecifications \(ident, prefix) -> do
+ let sourcePath =
+ temp
+ Posix.</> Text.unpack ident
+ Posix.</> "entry.tex"
+ request <- expectRight =<< existingRoot sourcePath
+ graph <- expectRight =<< buildResolvedSourceGraph mounts request
+ layout <-
+ expectRight
+ (layoutHtmlSourceGraph
+ [ (sourceMountId configuredId, configuredPrefix)
+ | (configuredId, configuredPrefix) <-
+ mountSpecifications
+ ]
+ graph)
+ route <- requireRootRoute graph layout
+ pure
+ ( ident
+ , renderUrlPath (routeUrlPath route)
+ , safeRelativePathFilePath
+ (routeDestination route)
+ , prefix
+ )
+ assertEqual
+ "mount URLs"
+ [ ("project", "/entry")
+ , ("library", "/library/entry")
+ , ("debug", "/debug/entry")
+ , ("external", "/external/entry")
+ ]
+ [ (ident, url) | (ident, url, _destination, _prefix) <- routes ]
+ assertEqual
+ "mount destinations"
+ [ ("project", "entry.html")
+ , ("library", "library/entry.html")
+ , ("debug", "debug/entry.html")
+ , ("external", "external/entry.html")
+ ]
+ [ (ident, destination)
+ | (ident, _url, destination, _prefix) <- routes
+ ]
+
+routesRootFormsIdentically :: Assertion
+routesRootFormsIdentically =
+ withTemporaryDirectory "felix-html-layout-root-forms" \temp -> do
+ let libraryRoot = temp Posix.</> "library"
+ entry = libraryRoot Posix.</> "entry.tex"
+ Directory.createDirectory libraryRoot
+ writeTheory entry []
+ mounts <- expectRight =<< prepareSourceMounts
+ [ (sourceMountId "project", temp)
+ , (sourceMountId "library", libraryRoot)
+ ]
+ searched <- expectRight (searchedRoot "library/entry.tex")
+ exact <- expectRight =<< existingRoot entry
+ searchedGraph <-
+ expectRight =<< buildResolvedSourceGraph mounts searched
+ exactGraph <-
+ expectRight =<< buildResolvedSourceGraph mounts exact
+ let configuration =
+ [ (sourceMountId "project", [])
+ , (sourceMountId "library", ["library"])
+ ]
+ searchedLayout <-
+ expectRight
+ (layoutHtmlSourceGraph configuration searchedGraph)
+ exactLayout <-
+ expectRight
+ (layoutHtmlSourceGraph configuration exactGraph)
+ searchedRoute <-
+ requireRootRoute searchedGraph searchedLayout
+ exactRoute <-
+ requireRootRoute exactGraph exactLayout
+ assertEqual "selected route" searchedRoute exactRoute
+ assertEqual
+ "most-specific URL"
+ "/library/entry"
+ (renderUrlPath (routeUrlPath searchedRoute))
+
+requiresExternalMount :: Assertion
+requiresExternalMount =
+ withTemporaryDirectory "felix-html-layout-external" \temp -> do
+ let projectRoot = temp Posix.</> "project"
+ externalRoot = temp Posix.</> "vendor"
+ externalEntry = externalRoot Posix.</> "entry.tex"
+ Directory.createDirectory projectRoot
+ Directory.createDirectory externalRoot
+ writeTheory externalEntry []
+ request <- expectRight =<< existingRoot externalEntry
+ projectMounts <- expectRight =<< prepareSourceMounts
+ [(sourceMountId "project", projectRoot)]
+ outsideResult <-
+ buildResolvedSourceGraph projectMounts request
+ case outsideResult of
+ Left RootOutsideConfiguredMount{} ->
+ pure ()
+ result ->
+ assertFailure
+ ("expected external root rejection, got "
+ <> show result)
+ mounted <- expectRight =<< prepareSourceMounts
+ [ (sourceMountId "project", projectRoot)
+ , (sourceMountId "external", externalRoot)
+ ]
+ graph <- expectRight =<< buildResolvedSourceGraph mounted request
+ layout <-
+ expectRight
+ (layoutHtmlSourceGraph
+ [ (sourceMountId "project", [])
+ , (sourceMountId "external", ["vendor"])
+ ]
+ graph)
+ route <- requireRootRoute graph layout
+ assertEqual
+ "external URL"
+ "/vendor/entry"
+ (renderUrlPath (routeUrlPath route))
+
+reportsRouteCollisions :: Assertion
+reportsRouteCollisions = do
+ reportsPageCollisions
+ reportsAssetUrlCollision
+
+reportsPageCollisions :: Assertion
+reportsPageCollisions =
+ withTemporaryDirectory "felix-html-layout-page-collision" \temp -> do
+ let firstRoot = temp Posix.</> "first"
+ secondRoot = temp Posix.</> "second"
+ firstEntry = firstRoot Posix.</> "two" Posix.</> "a.tex"
+ Directory.createDirectory firstRoot
+ Directory.createDirectory secondRoot
+ Directory.createDirectory (firstRoot Posix.</> "two")
+ writeTheory (secondRoot Posix.</> "a.tex") []
+ writeTheory firstEntry ["a.tex"]
+ mounts <- expectRight =<< prepareSourceMounts
+ [ (sourceMountId "first", firstRoot)
+ , (sourceMountId "second", secondRoot)
+ ]
+ request <- expectRight =<< existingRoot firstEntry
+ graph <- expectRight =<< buildResolvedSourceGraph mounts request
+ let configuration =
+ [ (sourceMountId "first", ["one"])
+ , (sourceMountId "second", ["one", "two"])
+ ]
+ case layoutHtmlSourceGraph configuration graph of
+ Left
+ (CollidingHtmlRoutes
+ [HtmlUrlRouteCollision url owners]
+ [HtmlDestinationRouteCollision destination
+ destinationOwners]) -> do
+ assertEqual
+ "canonical URL collision"
+ "/one/two/a"
+ (renderUrlPath url)
+ assertEqual
+ "URL owners"
+ (NonEmpty.toList owners)
+ (NonEmpty.toList destinationOwners)
+ assertEqual
+ "destination collision"
+ "one/two/a.html"
+ (safeRelativePathFilePath destination)
+ result ->
+ assertFailure
+ ("expected paired route collisions, got "
+ <> show result)
+
+reportsAssetUrlCollision :: Assertion
+reportsAssetUrlCollision =
+ withTemporaryDirectory "felix-html-layout-asset-collision" \temp -> do
+ let entry =
+ temp
+ Posix.</> "_static"
+ Posix.</> "naproche-html.js.tex"
+ Directory.createDirectory (temp Posix.</> "_static")
+ writeTheory entry []
+ mounts <- expectRight =<< prepareSourceMounts
+ [(sourceMountId "project", temp)]
+ request <-
+ expectRight
+ (searchedRoot "_static/naproche-html.js.tex")
+ graph <- expectRight =<< buildResolvedSourceGraph mounts request
+ case
+ layoutHtmlSourceGraph
+ [(sourceMountId "project", [])]
+ graph of
+ Left
+ (CollidingHtmlRoutes
+ [HtmlUrlRouteCollision url _owners]
+ []) ->
+ assertEqual
+ "page/asset URL collision"
+ "/_static/naproche-html.js"
+ (renderUrlPath url)
+ result ->
+ assertFailure
+ ("expected URL-only asset collision, got "
+ <> show result)
+
+rejectsNestedDestinations :: Assertion
+rejectsNestedDestinations =
+ withTemporaryDirectory "felix-html-layout-nested" \temp -> do
+ let nestedDirectory = temp Posix.</> "a.html"
+ Directory.createDirectory nestedDirectory
+ writeTheory (temp Posix.</> "a.tex") []
+ writeTheory (nestedDirectory Posix.</> "b.tex") []
+ writeTheory
+ (temp Posix.</> "entry.tex")
+ ["a.tex", "a.html/b.tex"]
+ mounts <- expectRight =<< prepareSourceMounts
+ [(sourceMountId "project", temp)]
+ request <- expectRight (searchedRoot "entry.tex")
+ graph <- expectRight =<< buildResolvedSourceGraph mounts request
+ case layoutHtmlSourceGraph
+ [(sourceMountId "project", [])]
+ graph of
+ Left
+ (CollidingHtmlRoutes
+ []
+ [NestedHtmlDestinationRouteCollision
+ ancestor
+ ancestorOwner
+ descendant
+ descendantOwner]) -> do
+ assertEqual
+ "ancestor destination"
+ "a.html"
+ (safeRelativePathFilePath ancestor)
+ assertEqual
+ "ancestor owner"
+ "a.tex"
+ (pageOwnerPath ancestorOwner)
+ assertEqual
+ "descendant destination"
+ "a.html/b.html"
+ (safeRelativePathFilePath descendant)
+ assertEqual
+ "descendant owner"
+ "a.html/b.tex"
+ (pageOwnerPath descendantOwner)
+ result ->
+ assertFailure
+ ("expected nested destination collision, got "
+ <> show result)
+ where
+ pageOwnerPath = \case
+ HtmlPage source ->
+ safeRelativePathFilePath
+ (resolvedSourceRelativePath source)
+ HtmlSupportScript ->
+ "<support script>"
+
+isTraversalOrderIndependent :: Assertion
+isTraversalOrderIndependent =
+ withTemporaryDirectory "felix-html-layout-order" \temp -> do
+ writeTheory (temp Posix.</> "a.tex") []
+ writeTheory (temp Posix.</> "b.tex") []
+ let root = temp Posix.</> "entry.tex"
+ writeTheory root ["a.tex", "b.tex"]
+ mounts <- expectRight =<< prepareSourceMounts
+ [(sourceMountId "project", temp)]
+ request <- expectRight (searchedRoot "entry.tex")
+ firstGraph <-
+ expectRight =<< buildResolvedSourceGraph mounts request
+ writeTheory root ["b.tex", "a.tex"]
+ secondGraph <-
+ expectRight =<< buildResolvedSourceGraph mounts request
+ let firstConfiguration =
+ [ (sourceMountId "unused", ["unused"])
+ , (sourceMountId "project", [])
+ ]
+ firstLayout <-
+ expectRight
+ (layoutHtmlSourceGraph
+ firstConfiguration
+ firstGraph)
+ for_
+ (zip
+ (cycle [firstGraph, secondGraph])
+ (List.permutations firstConfiguration))
+ \(orderedGraph, configuration) -> do
+ layout <-
+ expectRight
+ (layoutHtmlSourceGraph
+ configuration
+ orderedGraph)
+ assertEqual
+ "route table"
+ firstLayout
+ layout
+
+ let collidingConfiguration =
+ [ (sourceMountId "z", ["same"])
+ , (sourceMountId "a", ["same"])
+ , (sourceMountId "project", [])
+ ]
+ expectedCollision =
+ layoutHtmlSourceGraph
+ collidingConfiguration
+ firstGraph
+ for_
+ (zip
+ (cycle [firstGraph, secondGraph])
+ (List.permutations collidingConfiguration))
+ \(orderedGraph, configuration) ->
+ assertEqual
+ "collision diagnostic"
+ expectedCollision
+ (layoutHtmlSourceGraph
+ configuration
+ orderedGraph)
+
+
+requireRootRoute
+ :: ResolvedSourceGraph
+ -> HtmlLayout
+ -> IO HtmlRoute
+requireRootRoute graph layout =
+ case htmlPageRoute layout (sourceGraphRootSource graph) of
+ Just route ->
+ pure route
+ Nothing -> do
+ assertFailure "layout omitted the root source"
+ pure (impossible "requireRootRoute: assertFailure returned")
+
+writeTheory :: FilePath -> [FilePath] -> IO ()
+writeTheory path imports =
+ writeFile path
+ (unlines
+ (["\\import{" <> imported <> "}" | imported <- imports]
+ <> [ "\\begin{axiom}\\label{route_fixture}"
+ , " $x = x$."
+ , "\\end{axiom}"
+ ]))
+
+expectRight :: (Show e, HasCallStack) => Either e a -> IO a
+expectRight = \case
+ Left err ->
+ assertFailure ("expected Right, got Left " <> show err)
+ Right value ->
+ pure value
+
+withTemporaryDirectory :: String -> (FilePath -> IO a) -> IO a
+withTemporaryDirectory template =
+ bracket create Directory.removePathForcibly
+ where
+ create = do
+ systemTemp <- Directory.getTemporaryDirectory
+ (path, handle) <- openTempFile systemTemp template
+ hClose handle
+ Directory.removeFile path
+ Directory.createDirectory path
+ pure path
diff --git a/source/Felix/Test/Unit/HtmlOutput.hs b/source/Felix/Test/Unit/HtmlOutput.hs
new file mode 100644
index 0000000..34fe2e7
--- /dev/null
+++ b/source/Felix/Test/Unit/HtmlOutput.hs
@@ -0,0 +1,560 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.HtmlOutput (unitTests) where
+
+import Base
+import Felix.Parse qualified as Parse
+import Felix.Source
+import Felix.Source.Graph qualified as SourceGraph
+import Felix.Render.Html qualified as Html
+import Felix.Render.Html.Export
+import Felix.Render.Html.Output
+
+import Control.Exception (bracket)
+import Data.ByteString qualified as ByteString
+import Data.List qualified as List
+import Data.Text qualified as Text
+import Data.Text.Encoding qualified as TextEncoding
+import System.Directory qualified as Directory
+import System.FilePath.Posix ((</>))
+import System.Posix.Files qualified as PosixFiles
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "HTML output"
+ [ testCase
+ "publishes a mounted multi-page UTF-8 export"
+ publishesMountedExport
+ , testCase
+ "writes prepared strict bytes"
+ writesPreparedBytes
+ , testCase
+ "attaches bytes only to reserved routes"
+ attachesBytesToReservedRoutes
+ , testCase
+ "rejects a final symlink before publication"
+ rejectsFinalSymlink
+ , testCase
+ "replaces hard-linked targets without changing peers"
+ replacesHardLinkedTarget
+ , testCase
+ "rejects a FIFO before publication"
+ rejectsFifo
+ , testCase
+ "reports and cleans the source-order publication prefix"
+ reportsIncompletePublication
+ ]
+
+
+publishesMountedExport :: Assertion
+publishesMountedExport =
+ withTemporaryDirectory "felix-html-export" \temp -> do
+ let projectRoot = temp </> "project"
+ libraryRoot = temp </> "library"
+ projectDirectory = projectRoot </> "docs"
+ libraryDirectory = libraryRoot </> "shared"
+ rootSource = projectDirectory </> "über #.tex"
+ importedSource =
+ libraryDirectory </> "sets #.tex"
+ outputRoot = temp </> "html"
+ rootPage = outputRoot </> "docs" </> "über #.html"
+ importedPage =
+ outputRoot
+ </> "library"
+ </> "shared"
+ </> "sets #.html"
+ supportAsset =
+ outputRoot
+ </> "_static"
+ </> "naproche-html.js"
+ configuration =
+ [ (sourceMountId "project", [])
+ , (sourceMountId "library", ["library"])
+ ]
+ hints =
+ "relation\teq\t0\t<mo>=</mo>\n"
+ Directory.createDirectory projectRoot
+ Directory.createDirectory libraryRoot
+ Directory.createDirectory projectDirectory
+ Directory.createDirectory libraryDirectory
+ writeImportedTheory importedSource
+ writeRootTheory rootSource
+ mounts <-
+ expectRight =<<
+ prepareSourceMounts
+ [ (sourceMountId "project", projectRoot)
+ , (sourceMountId "library", libraryRoot)
+ ]
+ searched <-
+ expectRight
+ (searchedRoot "docs/über #.tex")
+ exact <-
+ expectRight =<< existingRoot rootSource
+ searchedExport <-
+ expectRight =<<
+ prepareTestHtmlExport
+ configuration
+ mounts
+ searched
+ hints
+ exactExport <-
+ expectRight =<<
+ prepareTestHtmlExport
+ configuration
+ mounts
+ exact
+ hints
+ assertEqual
+ "root-form-independent destinations"
+ (preparedHtmlArtifactDestination <$> searchedExport)
+ (preparedHtmlArtifactDestination <$> exactExport)
+
+ let artifacts = searchedExport
+ rejectsArtifactEscape temp artifacts
+
+ plan <-
+ requirePlan =<<
+ planHtmlOutput outputRoot artifacts
+ outputExistsBeforePublication <-
+ Directory.doesPathExist outputRoot
+ assertBool
+ "preflight created the output root"
+ (not outputExistsBeforePublication)
+ requirePublication =<< writeHtmlOutput plan
+
+ rootText <- readUtf8 rootPage
+ importedText <- readUtf8 importedPage
+ supportText <- readUtf8 supportAsset
+ assertContains
+ "root heading"
+ "<h1>project:docs/über #.tex</h1>"
+ rootText
+ assertContains
+ "imported heading"
+ "<h1>library:shared/sets #.tex</h1>"
+ importedText
+ assertContains
+ "encoded imported reference"
+ "href=\"../library/shared/sets%20%23#imported_prop\""
+ rootText
+ assertContains
+ "encoded imported source link"
+ "href=\"../library/shared/sets%20%23\"><code>library:shared/sets #.tex</code>"
+ rootText
+ assertContains
+ "root support route"
+ "src=\"../_static/naproche-html.js\""
+ rootText
+ assertContains
+ "imported support route"
+ "src=\"../../_static/naproche-html.js\""
+ importedText
+ for_ [rootText, importedText] \document ->
+ assertContains
+ "UTF-8 declaration"
+ "<meta charset=\"utf-8\">"
+ document
+ assertEqual
+ "support asset"
+ Html.supportScriptAssetContents
+ supportText
+
+rejectsArtifactEscape
+ :: FilePath
+ -> [PreparedHtmlArtifact]
+ -> Assertion
+rejectsArtifactEscape temp artifacts = do
+ let outputRoot = temp </> "escape-html"
+ outsideRoot = temp </> "outside"
+ outsideMarker = outsideRoot </> "unchanged"
+ Directory.createDirectory outputRoot
+ Directory.createDirectory outsideRoot
+ ByteString.writeFile outsideMarker "outside"
+ Directory.createDirectoryLink
+ outsideRoot
+ (outputRoot </> "docs")
+ result <- planHtmlOutput outputRoot artifacts
+ case result of
+ Left
+ (HtmlOutputParentEscapesRoot
+ _parent
+ canonicalParent) -> do
+ expectedOutside <-
+ Directory.canonicalizePath outsideRoot
+ assertEqual
+ "escaping route target"
+ expectedOutside
+ canonicalParent
+ other ->
+ assertFailure
+ ("expected output-root escape rejection, got "
+ <> showPlanResult other)
+ outsideBytes <- ByteString.readFile outsideMarker
+ assertEqual
+ "escape planning changed the outside tree"
+ "outside"
+ outsideBytes
+ supportExists <-
+ Directory.doesPathExist
+ (outputRoot </> "_static")
+ assertBool
+ "escape preflight created another destination"
+ (not supportExists)
+
+writesPreparedBytes :: Assertion
+writesPreparedBytes =
+ withTemporaryDirectory "felix-html-output-bytes" \temp -> do
+ let outputRoot = temp </> "html"
+ pageText = "∀ café"
+ supportText = "const π = 3;"
+ expectedPageBytes =
+ ByteString.pack
+ [ 0xe2, 0x88, 0x80
+ , 0x20
+ , 0x63, 0x61, 0x66
+ , 0xc3, 0xa9
+ ]
+ artifacts <-
+ makeArtifacts
+ [ ( "nested/über.html"
+ , TextEncoding.encodeUtf8 pageText
+ )
+ , ( "_static/naproche-html.js"
+ , TextEncoding.encodeUtf8 supportText
+ )
+ ]
+ publishArtifacts outputRoot artifacts
+ pageBytes <-
+ ByteString.readFile
+ (outputRoot </> "nested" </> "über.html")
+ supportBytes <-
+ ByteString.readFile
+ (outputRoot
+ </> "_static"
+ </> "naproche-html.js")
+ assertEqual
+ "exact page UTF-8 bytes"
+ expectedPageBytes
+ pageBytes
+ assertEqual
+ "exact support bytes"
+ (TextEncoding.encodeUtf8 supportText)
+ supportBytes
+
+attachesBytesToReservedRoutes :: Assertion
+attachesBytesToReservedRoutes =
+ withTemporaryDirectory "felix-html-output-routes" \temp -> do
+ let outputRoot = temp </> "html"
+ reserved <- expectRight
+ (traverse safeRelativePath
+ ["page.html", "_static/naproche-html.js"])
+ routes <- requireRoutePlan =<<
+ planHtmlRoutes outputRoot reserved
+ matching <- makeArtifacts
+ [ ("page.html", "page")
+ , ("_static/naproche-html.js", "support")
+ ]
+ case planHtmlOutputAgainst routes matching of
+ Right _ ->
+ pure ()
+ Left failure ->
+ assertFailure (show failure)
+ mismatched <- makeArtifacts
+ [ ("other.html", "other")
+ , ("_static/naproche-html.js", "support")
+ ]
+ case planHtmlOutputAgainst routes mismatched of
+ Left HtmlOutputRouteMismatch{} ->
+ pure ()
+ Left failure ->
+ assertFailure
+ ("unexpected route mismatch: " <> show failure)
+ Right _ ->
+ assertFailure "unreserved HTML route was accepted"
+
+rejectsFinalSymlink :: Assertion
+rejectsFinalSymlink =
+ withTemporaryDirectory "felix-html-output-final-link" \temp -> do
+ let outputRoot = temp </> "html"
+ supportDirectory = outputRoot </> "_static"
+ page = outputRoot </> "page.html"
+ support =
+ supportDirectory </> "naproche-html.js"
+ outsideAsset = temp </> "outside.js"
+ Directory.createDirectory outputRoot
+ Directory.createDirectory supportDirectory
+ ByteString.writeFile page "old page"
+ ByteString.writeFile outsideAsset "outside asset"
+ Directory.createFileLink outsideAsset support
+ artifacts <-
+ makeArtifacts
+ [ ("page.html", "new page")
+ , ("_static/naproche-html.js", "new support")
+ ]
+ result <- planHtmlOutput outputRoot artifacts
+ case result of
+ Left (HtmlOutputTargetIsSymbolicLink target) ->
+ assertEqual "rejected target" support target
+ other ->
+ assertFailure
+ ("expected final symlink rejection, got "
+ <> showPlanResult other)
+ pageBytes <- ByteString.readFile page
+ outsideBytes <- ByteString.readFile outsideAsset
+ supportIsLink <-
+ Directory.pathIsSymbolicLink support
+ assertEqual
+ "page changed before complete preflight"
+ "old page"
+ pageBytes
+ assertEqual
+ "symlink referent changed"
+ "outside asset"
+ outsideBytes
+ assertBool "final symlink was replaced" supportIsLink
+
+replacesHardLinkedTarget :: Assertion
+replacesHardLinkedTarget =
+ withTemporaryDirectory "felix-html-output-hard-link" \temp -> do
+ let outputRoot = temp </> "html"
+ page = outputRoot </> "page.html"
+ outsidePage = temp </> "outside.html"
+ Directory.createDirectory outputRoot
+ ByteString.writeFile outsidePage "outside page"
+ PosixFiles.createLink outsidePage page
+ artifacts <-
+ makeArtifacts [("page.html", "new page")]
+ publishArtifacts outputRoot artifacts
+ outsideBytes <- ByteString.readFile outsidePage
+ pageBytes <- ByteString.readFile page
+ assertEqual
+ "outside hard-link peer changed"
+ "outside page"
+ outsideBytes
+ assertEqual "page was not replaced" "new page" pageBytes
+
+rejectsFifo :: Assertion
+rejectsFifo =
+ withTemporaryDirectory "felix-html-output-fifo" \temp -> do
+ let outputRoot = temp </> "html"
+ page = outputRoot </> "page.html"
+ Directory.createDirectory outputRoot
+ PosixFiles.createNamedPipe page PosixFiles.ownerModes
+ artifacts <-
+ makeArtifacts [("page.html", "page")]
+ result <- planHtmlOutput outputRoot artifacts
+ case result of
+ Left (HtmlOutputTargetNotRegularFile target) ->
+ assertEqual "rejected target" page target
+ other ->
+ assertFailure
+ ("expected FIFO rejection, got "
+ <> showPlanResult other)
+ pageStatus <-
+ PosixFiles.getSymbolicLinkStatus page
+ assertBool
+ "FIFO target was replaced"
+ (PosixFiles.isNamedPipe pageStatus)
+
+reportsIncompletePublication :: Assertion
+reportsIncompletePublication =
+ withTemporaryDirectory "felix-html-output-incomplete" \temp -> do
+ let outputRoot = temp </> "html"
+ first = outputRoot </> "z.html"
+ blocked = outputRoot </> "b.html"
+ unpublished = outputRoot </> "a.html"
+ artifacts <-
+ makeArtifacts
+ [ ("z.html", "first")
+ , ("b.html", "blocked")
+ , ("a.html", "unpublished")
+ ]
+ plan <-
+ requirePlan =<<
+ planHtmlOutput outputRoot artifacts
+ Directory.createDirectory outputRoot
+ Directory.createDirectory blocked
+ result <- writeHtmlOutput plan
+ case result of
+ Left
+ IncompleteHtmlPublication
+ { committedHtmlDestinations
+ , failedHtmlDestination
+ } -> do
+ expectedFirst <-
+ expectRight
+ (safeRelativePath "z.html")
+ expectedBlocked <-
+ expectRight
+ (safeRelativePath "b.html")
+ assertEqual
+ "committed destinations"
+ [expectedFirst]
+ committedHtmlDestinations
+ assertEqual
+ "failed destination"
+ expectedBlocked
+ failedHtmlDestination
+ Right () ->
+ assertFailure
+ "expected incomplete publication"
+ firstBytes <- ByteString.readFile first
+ unpublishedExists <-
+ Directory.doesPathExist unpublished
+ blockedIsDirectory <-
+ Directory.doesDirectoryExist blocked
+ outputEntries <-
+ Directory.listDirectory outputRoot
+ assertEqual "first artifact" "first" firstBytes
+ assertBool
+ "later artifact was published"
+ (not unpublishedExists)
+ assertBool
+ "injected blocker was replaced"
+ blockedIsDirectory
+ assertBool
+ "unpublished temporary files remain"
+ (not
+ (any
+ (List.isInfixOf ".tmp")
+ outputEntries))
+
+
+publishArtifacts
+ :: FilePath
+ -> [PreparedHtmlArtifact]
+ -> IO ()
+publishArtifacts outputRoot artifacts = do
+ plan <-
+ requirePlan =<< planHtmlOutput outputRoot artifacts
+ requirePublication =<< writeHtmlOutput plan
+
+makeArtifacts
+ :: [(FilePath, ByteString.ByteString)]
+ -> IO [PreparedHtmlArtifact]
+makeArtifacts artifacts =
+ for artifacts \(path, bytes) -> do
+ relative <- expectRight (safeRelativePath path)
+ pure (preparedHtmlArtifact relative (Right bytes))
+
+requirePlan
+ :: Either HtmlOutputError HtmlOutputPlan
+ -> IO HtmlOutputPlan
+requirePlan =
+ expectRight
+
+requireRoutePlan
+ :: Either HtmlOutputError HtmlRoutePlan
+ -> IO HtmlRoutePlan
+requireRoutePlan =
+ expectRight
+
+requirePublication
+ :: Either HtmlPublicationError ()
+ -> IO ()
+requirePublication =
+ expectRight
+
+showPlanResult
+ :: Either HtmlOutputError HtmlOutputPlan
+ -> String
+showPlanResult = \case
+ Left err ->
+ show err
+ Right _plan ->
+ "successful output plan"
+
+readUtf8 :: FilePath -> IO Text
+readUtf8 path = do
+ bytes <- ByteString.readFile path
+ case TextEncoding.decodeUtf8' bytes of
+ Left err ->
+ assertFailure
+ ("invalid UTF-8 output: " <> show err)
+ Right text ->
+ pure text
+
+prepareTestHtmlExport
+ :: [(SourceMountId, [Text])]
+ -> SourceMounts
+ -> RootRequest
+ -> Text
+ -> IO (Either HtmlExportError [PreparedHtmlArtifact])
+prepareTestHtmlExport configuration mounts request hints = do
+ graph <-
+ expectRight =<<
+ SourceGraph.buildResolvedSourceGraph mounts request
+ workspace <-
+ expectRight =<< Parse.parseResolvedSourceGraph graph
+ pure
+ (prepareHtmlExport
+ configuration
+ (htmlPresentationFromParsedWorkspace workspace)
+ hints)
+
+writeImportedTheory :: FilePath -> IO ()
+writeImportedTheory path =
+ writeFile path
+ (unlines
+ [ "\\begin{proposition}\\label{imported_prop}"
+ , " $i = i$."
+ , "\\end{proposition}"
+ ])
+
+writeRootTheory :: FilePath -> IO ()
+writeRootTheory path =
+ writeFile path
+ (unlines
+ [ "\\import{shared/sets #.tex}"
+ , "\\begin{proposition}\\label{local_prop}"
+ , " $a = a$."
+ , "\\end{proposition}"
+ , "\\begin{proposition}\\label{uses_import}"
+ , " $b = b$."
+ , "\\end{proposition}"
+ , "\\begin{proof}"
+ , " Follows by \\cref{imported_prop}."
+ , "\\end{proof}"
+ ])
+
+assertContains
+ :: String
+ -> Text
+ -> Text
+ -> Assertion
+assertContains description needle haystack =
+ assertBool
+ (description <> ": missing " <> show needle)
+ (needle `Text.isInfixOf` haystack)
+
+expectRight
+ :: (Show e, HasCallStack)
+ => Either e a
+ -> IO a
+expectRight = \case
+ Left err ->
+ assertFailure
+ ("expected Right, got Left " <> show err)
+ Right value ->
+ pure value
+
+withTemporaryDirectory
+ :: String
+ -> (FilePath -> IO a)
+ -> IO a
+withTemporaryDirectory template =
+ bracket create Directory.removePathForcibly
+ where
+ create = do
+ systemTemp <-
+ Directory.getTemporaryDirectory
+ (path, handle) <-
+ openTempFile systemTemp template
+ hClose handle
+ Directory.removeFile path
+ Directory.createDirectory path
+ pure path
diff --git a/source/Felix/Test/Unit/Identity.hs b/source/Felix/Test/Unit/Identity.hs
new file mode 100644
index 0000000..7e1e73f
--- /dev/null
+++ b/source/Felix/Test/Unit/Identity.hs
@@ -0,0 +1,802 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Test.Unit.Identity (unitTests) where
+
+import Base
+import Felix.Checking.Authority qualified as Authority
+import Felix.Checking.Core qualified as Core
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Checking.Identity qualified as Identity
+import Felix.Cache.Codec
+import Felix.Math.Codec
+import Felix.Module
+import Felix.Source
+
+import Control.Exception (bracket)
+import Data.ByteString qualified as ByteString
+import Data.Either (isLeft)
+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 Numeric.Natural (Natural)
+import System.Directory qualified as Directory
+import System.FilePath.Posix qualified as Posix
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Content identities"
+ [ testCase "uses the frozen mathematical hash framing"
+ hashesCanonicalFields
+ , testCase "orders exhaustive foundation rows by stable tags"
+ ordersFoundationManifestTags
+ , testCase "uses the frozen source path codecs"
+ encodesSourcePaths
+ , testCase "separates durable namespaces from mount labels"
+ separatesModuleOwnership
+ , testCase "rejects duplicate canonical collection encodings"
+ rejectsDuplicateCanonicalCollections
+ , testCase "matches every frozen mathematical identity vector"
+ matchesMathematicalIdentityVectors
+ , testCase "validates recursive transparent object content"
+ validatesTransparentObjectClosure
+ , testCase "rejects cyclic and mismatched object content"
+ rejectsInvalidObjectContent
+ , testCase "validates proposition content and theorem closure"
+ validatesPropositionAndTheorem
+ , testCase "round-trips deterministic epoch cache values"
+ roundTripsEpochCacheValues
+ , testCase "validates compact fact authority"
+ validatesCompactFactAuthority
+ , testCase "propagates candidate safety through local claims"
+ propagatesCandidateSafety
+ ]
+
+ordersFoundationManifestTags :: Assertion
+ordersFoundationManifestTags = do
+ let (intrinsics, rules, axioms) =
+ Identity.foundationManifestTags
+ assertEqual
+ "intrinsic stable-tag order"
+ [ Core.Member
+ , Core.Empty
+ , Core.PairSet
+ , Core.FamilyUnion
+ , Core.PowerSet
+ , Core.Sep
+ , Core.Repl
+ , Core.SetChoose
+ , Core.UnivOf
+ , Core.ISetLfp
+ ]
+ intrinsics
+ assertEqual
+ "kernel-rule stable-tag order"
+ [ Foundation.SetLfpBound
+ , Foundation.SetLfpLeast
+ , Foundation.SetLfpFixed
+ , Foundation.SetLfpInduct
+ ]
+ rules
+ assertEqual
+ "foundation-axiom stable-tag order"
+ [ Foundation.EmptyCharacteristic
+ , Foundation.PairSetCharacteristic
+ , Foundation.FamilyUnionCharacteristic
+ , Foundation.PowerSetCharacteristic
+ , Foundation.SeparationCharacteristic
+ , Foundation.ReplacementCharacteristic
+ , Foundation.SetChooseWitness
+ , Foundation.SetExtensionality
+ , Foundation.SetInduction
+ , Foundation.PropositionalExtensionality
+ , Foundation.DoubleNegationElim
+ , Foundation.UnivOfContains
+ , Foundation.UnivOfTransitive
+ , Foundation.UnivOfFamilyUnionClosed
+ , Foundation.UnivOfPowerSetClosed
+ , Foundation.UnivOfReplacementClosed
+ , Foundation.UnivOfMinimal
+ ]
+ axioms
+
+hashesCanonicalFields :: Assertion
+hashesCanonicalFields = do
+ let vectors =
+ [ ( []
+ , "09ace37213e33d80b79e5f21fd60d03f25855e528cc2f25da4762be81d34a8c2"
+ )
+ , ( [ByteString.empty]
+ , "cd166f5b566ebd02f00f202792699803df09e9a020afbaaa987f5001cb1d095e"
+ )
+ , (["a", "bc"]
+ , "70c43385ae5b28bb862bc461a3c8d85ab94fd616528e7d94a9e7a52217b5c657"
+ )
+ , (["ab", "c"]
+ , "d3588d1b26aac958d9f393d8528ad68ed34354aee41d7e405a156b3ffff20c90"
+ )
+ ]
+ traverse_
+ (\(fields, expected) -> do
+ digest <- expectRight
+ (hashCanonicalFields "felix-test-v1" fields)
+ assertEqual
+ ("fields " <> show fields)
+ expected
+ (mathematicalDigestHex digest))
+ vectors
+
+encodesSourcePaths :: Assertion
+encodesSourcePaths = do
+ let absoluteVectors =
+ [ ([]
+ , "000000000000002266656c69782d6162736f6c7574652d736f757263652d726f6f742d706174682d763100000000"
+ )
+ , (["a"]
+ , "000000000000002266656c69782d6162736f6c7574652d736f757263652d726f6f742d706174682d763100000001000000000000000161"
+ )
+ , (["a", "b"]
+ , "000000000000002266656c69782d6162736f6c7574652d736f757263652d726f6f742d706174682d763100000002000000000000000161000000000000000162"
+ )
+ ]
+ relativeVectors =
+ [ (["a"]
+ , "000000000000001b66656c69782d736166652d72656c61746976652d706174682d763100000001000000000000000161"
+ )
+ , (["a", "b"]
+ , "000000000000001b66656c69782d736166652d72656c61746976652d706174682d763100000002000000000000000161000000000000000162"
+ )
+ ]
+ traverse_
+ (\(components, expected) -> do
+ encoded <- expectRight
+ (encodeCanonicalPathRecord
+ "felix-absolute-source-root-path-v1"
+ components)
+ assertEqual
+ (show components)
+ expected
+ (hex encoded))
+ absoluteVectors
+ traverse_
+ (\(components, expected) -> do
+ encoded <- expectRight
+ (encodeCanonicalPathRecord
+ "felix-safe-relative-path-v1"
+ components)
+ assertEqual
+ (show components)
+ expected
+ (hex encoded))
+ relativeVectors
+
+separatesModuleOwnership :: Assertion
+separatesModuleOwnership =
+ withTemporaryDirectory "felix-module-owner" \root -> do
+ writeFile (root Posix.</> "b.tex") ""
+ mounts <- expectRight
+ =<< prepareSourceMounts
+ [(sourceMountId "display-only", root)]
+ request <- expectRight (searchedRoot "b.tex")
+ source <- expectRight =<< resolveRoot mounts request
+ mount <- case sourceMountList mounts of
+ [only] ->
+ pure only
+ _ ->
+ assertFailure "expected one prepared mount"
+ >> fail "unreachable"
+ relative <- expectRight (safeRelativePath "b.tex")
+ let owner =
+ moduleName (resolvedSourceAddress source)
+ assertEqual
+ "relative owner"
+ relative
+ (moduleNameRelativePath owner)
+ assertEqual
+ "namespace derives from the canonical root"
+ (sourceNamespaceId (sourceMountRoot mount))
+ (moduleNameNamespace owner)
+
+rejectsDuplicateCanonicalCollections :: Assertion
+rejectsDuplicateCanonicalCollections = do
+ assertEqual
+ "set duplicate"
+ (Left (DuplicateCanonicalSetElement "a"))
+ (encodeCanonicalSet ["b", "a", "a"])
+ assertEqual
+ "map duplicate"
+ (Left (DuplicateCanonicalMapKey "a"))
+ (encodeCanonicalMap [("a", "first"), ("a", "second")])
+
+matchesMathematicalIdentityVectors :: Assertion
+matchesMathematicalIdentityVectors = do
+ fixture <- makeIdentityFixture
+ let vectors =
+ [ ( "theory"
+ , Identity.theoryIdDigest
+ (fixtureTheory fixture)
+ , "46665f15f80ad52d319188de307471f34905ec3849b84b9d6d0d5a584a90eb62"
+ )
+ , ( "intrinsic Empty"
+ , Identity.objectIdDigest
+ (fixtureIntrinsic fixture)
+ , "a11f641738714ac806f3c3b902841b3178d32fff38fea353aac409e3cc1a8efc"
+ )
+ , ( "transparent Empty"
+ , Identity.objectIdDigest
+ (fixtureTransparent fixture)
+ , "49dfb3c96f0db2cc81703bbed82c08e9d2eda7d4a2274f0529d22c592003d4d1"
+ )
+ , ( "opaque signature"
+ , Identity.objectIdDigest
+ (fixtureOpaque fixture)
+ , "8723488e60ed09ffad378bc6d9916d07c348d12d01548a7b482726e6e18b2c8d"
+ )
+ , ( "proposition"
+ , Identity.propositionIdDigest
+ (Identity.checkedPropositionId
+ (fixtureProposition fixture))
+ , "c40b403f0422f4125065d83b5eba64e4b0c4b24fb1559967ef986f5265829b65"
+ )
+ , ( "theorem"
+ , Identity.theoremIdDigest
+ (fixtureTheorem fixture)
+ , "6a328a4142fde3c9851186ea00f54d8978d2640dcbe51421536c9726f4f717d2"
+ )
+ ]
+ traverse_
+ (\(description, digest, expected) ->
+ assertEqual
+ description
+ expected
+ (mathematicalDigestHex digest))
+ vectors
+ assertEqual
+ "opaque declaration seed"
+ "27697d641220b8bb63b32631492646277b28e70c8b7149206580337254ecc123"
+ (mathematicalDigestHex
+ (Identity.opaqueDeclarationSeedDigest
+ (fixtureOpaqueSeed fixture)))
+ assertEqual
+ "family domains remain distinct"
+ (length vectors)
+ (Set.size
+ (Set.fromList
+ [ digest
+ | (_description, digest, _expected) <- vectors
+ ]))
+
+validatesTransparentObjectClosure :: Assertion
+validatesTransparentObjectClosure = do
+ fixture <- makeIdentityFixture
+ let theory = fixtureTheory fixture
+ child = fixtureTransparent fixture
+ parentBody = Core.CGlobal child
+ parent =
+ Identity.transparentObjectId
+ theory
+ Core.TySet
+ parentBody
+ assertions =
+ [ Identity.assertedObject
+ parent
+ (Identity.TransparentObjectContent
+ theory
+ Core.TySet
+ parentBody)
+ , Identity.assertedObject
+ child
+ (Identity.TransparentObjectContent
+ theory
+ Core.TySet
+ (Core.CIntrinsic Core.Empty))
+ , Identity.assertedObject
+ (fixtureIntrinsic fixture)
+ (Identity.IntrinsicObjectContent
+ theory
+ Core.Empty
+ Core.TySet)
+ ]
+ closure <- expectRight
+ (Identity.validateObjectClosure theory assertions)
+ assertEqual
+ "all recursively checked objects"
+ (Set.fromList
+ [ fixtureIntrinsic fixture
+ , child
+ , parent
+ ])
+ (Identity.checkedObjectIds closure)
+ assertEqual
+ "parent type"
+ (Just Core.TySet)
+ (Identity.lookupCheckedObjectType parent closure)
+
+rejectsInvalidObjectContent :: Assertion
+rejectsInvalidObjectContent = do
+ fixture <- makeIdentityFixture
+ firstDigest <- expectRight
+ (hashCanonicalFields "felix-invalid-object-a" [])
+ secondDigest <- expectRight
+ (hashCanonicalFields "felix-invalid-object-b" [])
+ mismatchDigest <- expectRight
+ (hashCanonicalFields "felix-invalid-object-mismatch" [])
+ let theory = fixtureTheory fixture
+ first =
+ Identity.objectId
+ Identity.TransparentObject
+ firstDigest
+ second =
+ Identity.objectId
+ Identity.TransparentObject
+ secondDigest
+ cycleAssertions =
+ [ Identity.assertedObject
+ first
+ (Identity.TransparentObjectContent
+ theory
+ Core.TySet
+ (Core.CGlobal second))
+ , Identity.assertedObject
+ second
+ (Identity.TransparentObjectContent
+ theory
+ Core.TySet
+ (Core.CGlobal first))
+ ]
+ case Identity.validateObjectClosure theory cycleAssertions of
+ Left (Identity.TransparentObjectCycle path) -> do
+ assertEqual
+ "cycle closes"
+ (NonEmpty.head path)
+ (NonEmpty.last path)
+ assertEqual
+ "cycle members"
+ (Set.fromList [first, second])
+ (Set.fromList (NonEmpty.toList path))
+ Left other ->
+ assertFailure
+ ("expected a transparent cycle, got " <> show other)
+ Right _ ->
+ assertFailure "expected a transparent cycle, got Right"
+ let mismatched =
+ Identity.objectId
+ Identity.TransparentObject
+ mismatchDigest
+ content =
+ Identity.TransparentObjectContent
+ theory
+ Core.TySet
+ (Core.CIntrinsic Core.Empty)
+ case
+ Identity.validateObjectClosure
+ theory
+ [Identity.assertedObject mismatched content] of
+ Left
+ (Identity.ObjectIdPayloadMismatch
+ supplied
+ computed) -> do
+ assertEqual "supplied ID" mismatched supplied
+ assertEqual
+ "computed ID"
+ (fixtureTransparent fixture)
+ computed
+ Left other ->
+ assertFailure
+ ("expected object ID disagreement, got " <> show other)
+ Right _ ->
+ assertFailure "expected object ID disagreement, got Right"
+
+validatesPropositionAndTheorem :: Assertion
+validatesPropositionAndTheorem = do
+ fixture <- makeIdentityFixture
+ let proposition =
+ fixtureProposition fixture
+ reference =
+ fixtureTheoremRef fixture
+ assertEqual
+ "theorem retains its theory"
+ (fixtureTheory fixture)
+ (Identity.theoremRefTheory reference)
+ assertEqual
+ "theorem retains its proposition"
+ (Identity.checkedPropositionId proposition)
+ (Identity.theoremRefProposition reference)
+ falsum <- expectRight
+ (Identity.validatePropositionContent
+ (fixtureClosure fixture)
+ Core.CFalsum)
+ case
+ Identity.validateAssertedPropositionContent
+ (fixtureClosure fixture)
+ (Identity.checkedPropositionId falsum)
+ (Core.frozenCoreTerm
+ (Identity.checkedPropositionTerm proposition)) of
+ Left
+ (Identity.PropositionIdPayloadMismatch
+ supplied
+ computed) -> do
+ assertEqual
+ "supplied proposition ID"
+ (Identity.checkedPropositionId falsum)
+ supplied
+ assertEqual
+ "computed proposition ID"
+ (Identity.checkedPropositionId proposition)
+ computed
+ Left other ->
+ assertFailure
+ ("expected proposition ID disagreement, got "
+ <> show other)
+ Right _ ->
+ assertFailure
+ "expected proposition ID disagreement, got Right"
+
+roundTripsEpochCacheValues :: Assertion
+roundTripsEpochCacheValues = do
+ fixture <- makeIdentityFixture
+ let theory = fixtureTheory fixture
+ contents =
+ [ Identity.IntrinsicObjectContent
+ theory
+ Core.Empty
+ Core.TySet
+ , Identity.TransparentObjectContent
+ theory
+ Core.TySet
+ (Core.CIntrinsic Core.Empty)
+ , Identity.OpaqueObjectContent
+ theory
+ (fixtureOpaqueSeed fixture)
+ Core.TySet
+ ]
+ traverse_
+ (\content ->
+ assertEqual
+ "object-content cache round trip"
+ (Right content)
+ (decodeCache
+ Identity.getObjectContentCache
+ (encodeCache
+ (Identity.putObjectContentCache
+ content))))
+ contents
+ assertEqual
+ "constructive theorem reference cache round trip"
+ (Right (fixtureTheoremRef fixture))
+ (decodeCache
+ Identity.getTheoremRefCache
+ (encodeCache
+ (Identity.putTheoremRefCache
+ (fixtureTheoremRef fixture))))
+ assertBool
+ "cache bytes are not mathematical theorem-reference bytes"
+ ( encodeCache
+ (Identity.putTheoremRefCache
+ (fixtureTheoremRef fixture))
+ /= Identity.encodeTheoremRef
+ (fixtureTheoremRef fixture)
+ )
+ let ascending =
+ Map.fromList [("a", 1 :: Natural), ("b", 2)]
+ putMap =
+ putCanonicalCacheMap
+ putCacheText
+ putCacheNatural
+ assertEqual
+ "canonical cache map round trip"
+ (Right ascending)
+ (decodeCache
+ (getCanonicalCacheMap
+ getCacheText
+ getCacheNatural)
+ (encodeCache (putMap ascending)))
+
+validatesCompactFactAuthority :: Assertion
+validatesCompactFactAuthority = do
+ fixture <- makeIdentityFixture
+ let reference = fixtureTheoremRef fixture
+ sourceKinds =
+ Authority.singletonEscapeKind Authority.SourceAxiom
+ bothKinds =
+ Authority.escapeKinds
+ [Authority.Omitted, Authority.SourceAxiom]
+ sourceTarget =
+ Authority.factAuthority
+ reference
+ (Authority.authoritySafety sourceKinds)
+ bothTarget =
+ Authority.factAuthority
+ reference
+ (Authority.authoritySafety bothKinds)
+ requests =
+ [ Authority.preparedRequestId
+ Authority.PreparedRequestFof
+ Authority.PreparedRequestDirect
+ "first"
+ , Authority.preparedRequestId
+ Authority.PreparedRequestFof
+ Authority.PreparedRequestDirect
+ "first"
+ , Authority.preparedRequestId
+ Authority.PreparedRequestFof
+ Authority.PreparedRequestDirect
+ "second"
+ ]
+ directRequest =
+ Authority.preparedRequestId
+ Authority.PreparedRequestFof
+ Authority.PreparedRequestDirect
+ "same bytes"
+ indirectRequest =
+ Authority.preparedRequestId
+ Authority.PreparedRequestFof
+ Authority.PreparedRequestIndirect
+ "same bytes"
+ directAuthorizations =
+ [ Authority.CheckedKernelConstruction
+ (Authority.FoundationLeaf
+ Foundation.EmptyCharacteristic)
+ , Authority.CheckedKernelConstruction
+ (Authority.GuardedFoundationRules
+ (Authority.guardedRuleSet
+ (Foundation.SetLfpBound :| [])))
+ , Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation
+ (fixtureIntrinsic fixture))
+ , Authority.CheckedKernelConstruction
+ (Authority.CheckedSetConstructionExtensionality
+ (fixtureIntrinsic fixture)
+ (hashCacheFields
+ "test-named-construction" ["checked"]))
+ , Authority.CheckedSourceProof requests
+ , Authority.TrustedCompilation
+ (Authority.DatatypeCompilation
+ (Authority.datatypeCompilationDescriptor
+ (fixtureIntrinsic fixture)
+ (NonEmpty.singleton
+ (fixtureIntrinsic fixture))
+ [reference]))
+ , Authority.SourceAxiomAuthorization
+ , Authority.OmittedAuthorization
+ ]
+ sourceCertificate <- expectRight
+ (Authority.validationCertificate
+ sourceTarget
+ Authority.SourceAxiomAuthorization)
+ proofCertificate <- expectRight
+ (Authority.validationCertificate
+ bothTarget
+ (Authority.CheckedSourceProof requests))
+ assertEqual
+ "escape bits have canonical order"
+ [Authority.SourceAxiom, Authority.Omitted]
+ (Authority.escapeKindsToList bothKinds)
+ traverse_
+ (\direct ->
+ assertEqual
+ ("direct authorization round trip: " <> show direct)
+ (Right direct)
+ (decodeCache
+ Authority.getDirectAuthorizationCache
+ (encodeCache
+ (Authority.putDirectAuthorizationCache
+ direct))))
+ directAuthorizations
+ assertEqual
+ "certificate cache retains repeated ordered requests"
+ (Right proofCertificate)
+ (decodeCache
+ Authority.getValidationCertificateCache
+ (encodeCache
+ (Authority.putValidationCertificateCache
+ proofCertificate)))
+ assertBool
+ "request mode participates in exact request identity"
+ (directRequest /= indirectRequest)
+ assertEqual
+ "prepared-request cache identity vector"
+ "1d72b851cb8b1704617becbf9f2cf492aed1c674d9e6ca759e244f169b15f278"
+ (hex
+ (encodeCache
+ (Authority.putPreparedRequestIdCache
+ directRequest)))
+ assertEqual
+ "source certificate round trip"
+ (Right sourceCertificate)
+ (decodeCache
+ Authority.getValidationCertificateCache
+ (encodeCache
+ (Authority.putValidationCertificateCache
+ sourceCertificate)))
+ assertBool
+ "source axiom requires its exact singleton safety"
+ (isLeft
+ (Authority.validationCertificate
+ bothTarget
+ Authority.SourceAxiomAuthorization))
+ assertBool
+ "omitted authorization requires its distinct bit"
+ (isLeft
+ (Authority.validationCertificate
+ sourceTarget
+ Authority.OmittedAuthorization))
+ assertBool
+ "cache rejects an empty escape-backed value"
+ (isLeft
+ (decodeCache
+ Authority.getAuthoritySafetyCache
+ (encodeCache do
+ putCacheTag 0x01
+ putCacheTag 0x00)))
+ let taintedCandidate =
+ Authority.addCandidateEscape
+ Authority.SourceAxiom
+ Authority.initialCandidateSafety
+ assertEqual
+ "candidate completion freezes accumulated safety"
+ sourceTarget
+ (Authority.candidateFactAuthority
+ reference
+ taintedCandidate)
+
+propagatesCandidateSafety :: Assertion
+propagatesCandidateSafety = do
+ fixture <- makeIdentityFixture
+ let reference = fixtureTheoremRef fixture
+ imported =
+ Authority.factAuthority
+ reference
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind
+ Authority.SourceAxiom))
+ afterImported <- expectRight
+ (Authority.accumulateFactSafety
+ reference
+ imported
+ Authority.initialCandidateSafety)
+ -- A local claim shares the enclosing candidate value; citing that claim
+ -- does not create a second support representation.
+ afterLocalClaim <- expectRight
+ (Authority.accumulateFactSafety
+ reference
+ (Authority.factAuthority
+ reference
+ Authority.cleanAuthoritySafety)
+ afterImported)
+ let finalSafety =
+ Authority.addCandidateEscape
+ Authority.Omitted
+ afterLocalClaim
+ assertEqual
+ "local claim retains prior safety and direct omission"
+ [Authority.SourceAxiom, Authority.Omitted]
+ (Authority.escapeKindsToList
+ (Authority.authoritySafetyEscapeKinds
+ (Authority.candidateSafetyAuthority finalSafety)))
+
+data IdentityFixture = IdentityFixture
+ { fixtureTheory :: !Identity.TheoryId
+ , fixtureIntrinsic :: !Identity.ObjectId
+ , fixtureTransparent :: !Identity.ObjectId
+ , fixtureOpaqueSeed :: !Identity.OpaqueDeclarationSeed
+ , fixtureOpaque :: !Identity.ObjectId
+ , fixtureClosure :: !Identity.CheckedObjectClosure
+ , fixtureProposition :: !Identity.CheckedPropositionContent
+ , fixtureTheoremRef :: !Identity.TheoremRef
+ , fixtureTheorem :: !Identity.TheoremId
+ }
+
+makeIdentityFixture :: IO IdentityFixture
+makeIdentityFixture = do
+ foundation <- expectRight Foundation.checkedFoundation
+ pathRecord <- expectRight
+ (encodeCanonicalPathRecord
+ "felix-absolute-source-root-path-v1"
+ ["a"])
+ namespaceDigest <- expectRight
+ (hashCanonicalFields
+ "felix-source-namespace-v1"
+ [pathRecord])
+ relative <- expectRight (safeRelativePath "b.tex")
+ let theory =
+ Identity.theoryId foundation
+ intrinsic =
+ Identity.intrinsicObjectId
+ theory
+ Core.Empty
+ Core.TySet
+ transparent =
+ Identity.transparentObjectId
+ theory
+ Core.TySet
+ (Core.CIntrinsic Core.Empty)
+ owner =
+ moduleNameFromParts
+ (sourceNamespaceIdFromDigest
+ namespaceDigest)
+ relative
+ seed =
+ Identity.opaqueDeclarationSeed
+ owner
+ (localDeclarationOrdinal 2)
+ SignatureDeclaration
+ (generatedObjectSlot 0)
+ opaque =
+ Identity.opaqueObjectId
+ theory
+ seed
+ Core.TySet
+ closure <- expectRight
+ (Identity.validateObjectClosure
+ theory
+ [ Identity.assertedObject
+ intrinsic
+ (Identity.IntrinsicObjectContent
+ theory
+ Core.Empty
+ Core.TySet)
+ ])
+ proposition <- expectRight
+ (Identity.validatePropositionContent
+ closure
+ (Core.CEq
+ Core.TySet
+ (Core.CGlobal intrinsic)
+ (Core.CGlobal intrinsic)))
+ let reference =
+ Identity.theoremRef
+ theory
+ (Identity.checkedPropositionId proposition)
+ pure
+ IdentityFixture
+ { fixtureTheory = theory
+ , fixtureIntrinsic = intrinsic
+ , fixtureTransparent = transparent
+ , fixtureOpaqueSeed = seed
+ , fixtureOpaque = opaque
+ , fixtureClosure = closure
+ , fixtureProposition = proposition
+ , fixtureTheoremRef = reference
+ , fixtureTheorem =
+ Identity.theoremId reference
+ }
+
+hex :: ByteString.ByteString -> Text
+hex =
+ Text.pack
+ . concatMap byteHex
+ . ByteString.unpack
+ where
+ byteHex byte =
+ let digits = "0123456789abcdef"
+ high = fromIntegral (byte `div` 16)
+ low = fromIntegral (byte `mod` 16)
+ in [digits `at` high, digits `at` low]
+
+ at characters index =
+ fromMaybe
+ (impossible "hex digit index")
+ (nth index characters)
+
+expectRight :: Show error => Either error value -> IO value
+expectRight = \case
+ Left err ->
+ assertFailure (show err) >> fail "unreachable"
+ Right value ->
+ pure value
+
+withTemporaryDirectory :: String -> (FilePath -> IO a) -> IO a
+withTemporaryDirectory template =
+ bracket create Directory.removePathForcibly
+ where
+ create = do
+ systemTemp <- Directory.getTemporaryDirectory
+ (path, handle) <- openTempFile systemTemp template
+ hClose handle
+ Directory.removeFile path
+ Directory.createDirectory path
+ pure path
diff --git a/source/Felix/Test/Unit/Kernel.hs b/source/Felix/Test/Unit/Kernel.hs
new file mode 100644
index 0000000..7762a7e
--- /dev/null
+++ b/source/Felix/Test/Unit/Kernel.hs
@@ -0,0 +1,858 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE PatternSynonyms #-}
+
+module Felix.Test.Unit.Kernel (unitTests) where
+
+import Base hiding (Empty)
+import Felix.Checking.Core
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Checking.Kernel.Derivation
+import Felix.Checking.Kernel.Semantics qualified as Semantics
+import Felix.Checking.Kernel.SetLfp qualified as SetLfp
+import Felix.Checking.Typed.Inductive qualified as Inductive
+import Felix.Report.Location (pattern Nowhere)
+import Felix.Syntax.Internal qualified as Internal
+
+import Data.Set qualified as Set
+import Data.Vector qualified as Vector
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+data TestGlobal = TestGlobal
+ deriving (Show, Eq, Ord)
+
+testGlobalType :: TestGlobal -> CoreType
+testGlobalType _global = TySet
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Kernel replay"
+ [ testCase
+ "replays equality reflexivity through kernel semantics"
+ replaysEqualityReflexivity
+ , testCase
+ "replays logical scopes and elimination"
+ replaysLogicalScopes
+ , testCase
+ "replays quantifier and equality structure"
+ replaysQuantifierAndEqualityStructure
+ , testCase
+ "records foundation and import leaves"
+ recordsAuthorityLeaves
+ , testCase
+ "checks and replays the exact set fixed-point rules"
+ checksSetLfpRules
+ , testCase
+ "replays direct inductive facts"
+ replaysDirectInductiveFacts
+ , testCase
+ "rejects altered set fixed-point applications"
+ rejectsAlteredSetLfpApplications
+ , testCase
+ "rejects invalid scoped replay"
+ rejectsInvalidScopedReplay
+ , testCase
+ "rejects a caller-supplied target mismatch"
+ rejectsTargetMismatch
+ ]
+
+replaysEqualityReflexivity :: Assertion
+replaysEqualityReflexivity = do
+ foundation <-
+ expectRight Foundation.checkedFoundation
+ operand <-
+ expectRight
+ (checkCanonicalCore
+ absurd
+ (CIntrinsic Empty))
+ direct <-
+ expectRight
+ (Semantics.equalityReflexivity
+ absurd
+ (embedClosedCore [] operand))
+ directClosed <-
+ maybe
+ (assertFailure
+ "closed reflexivity result remained scoped")
+ pure
+ (closeScopedCore direct)
+ replayed <-
+ expectRight
+ (replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ absurd
+ Vector.empty
+ directClosed
+ (equalityReflexivityDerivation operand))
+ assertEqual
+ "replay agrees with direct semantics"
+ directClosed
+ (replayedKernelTarget replayed)
+ assertEqual
+ "one replayed inference"
+ 1
+ (replayedKernelNodeCount replayed)
+
+replaysLogicalScopes :: Assertion
+replaysLogicalScopes = do
+ foundation <-
+ expectRight Foundation.checkedFoundation
+ proposition <-
+ checkedClosed
+ (CEq TySet
+ (CIntrinsic Empty)
+ (CIntrinsic Empty))
+ implication <-
+ checkedScoped []
+ (CImp
+ (frozenCoreTerm proposition)
+ (frozenCoreTerm proposition))
+ let propositionScoped =
+ embedClosedCore [] proposition
+ identity =
+ implicationIntroductionDerivation
+ propositionScoped
+ (localHypothesisDerivation
+ (hypothesisIx 0))
+ elimination =
+ implicationIntroductionDerivation
+ propositionScoped
+ (implicationIntroductionDerivation
+ implication
+ (implicationEliminationDerivation
+ (localHypothesisDerivation
+ (hypothesisIx 0))
+ (localHypothesisDerivation
+ (hypothesisIx 1))))
+ fromFalsum =
+ implicationIntroductionDerivation
+ falsum
+ (falsumEliminationDerivation
+ (localHypothesisDerivation
+ (hypothesisIx 0))
+ propositionScoped)
+ falsum =
+ unsafeScoped [] CFalsum
+ assertReplayTarget
+ foundation
+ (CImp
+ (frozenCoreTerm proposition)
+ (frozenCoreTerm proposition))
+ identity
+ assertReplayTarget
+ foundation
+ (CImp
+ (frozenCoreTerm proposition)
+ (CImp
+ (scopedCoreTerm implication)
+ (frozenCoreTerm proposition)))
+ elimination
+ assertReplayTarget
+ foundation
+ (CImp
+ CFalsum
+ (frozenCoreTerm proposition))
+ fromFalsum
+
+replaysQuantifierAndEqualityStructure :: Assertion
+replaysQuantifierAndEqualityStructure = do
+ foundation <-
+ expectRight Foundation.checkedFoundation
+ boundSet <-
+ checkedScoped [TySet] (CBound 0)
+ emptySet <-
+ checkedScoped [] (CIntrinsic Empty)
+ unionFunction <-
+ checkedScoped [] (CIntrinsic FamilyUnion)
+ proposition <-
+ checkedClosed
+ (CEq TySet
+ (CIntrinsic Empty)
+ (CIntrinsic Empty))
+ convertedTarget <-
+ checkedScoped []
+ (CEq TySet
+ (CApp
+ (CLam TySet (CBound 0))
+ (CIntrinsic Empty))
+ (CIntrinsic Empty))
+ oneReduction <-
+ expectRight (conversionPlan 1)
+ let boundReflexivity =
+ scopedEqualityReflexivityDerivation boundSet
+ universalReflexivity =
+ forallIntroductionDerivation
+ TySet
+ boundReflexivity
+ specializedReflexivity =
+ forallEliminationDerivation
+ universalReflexivity
+ emptySet
+ applicationCongruence =
+ equalityCongruenceApplicationDerivation
+ (scopedEqualityReflexivityDerivation
+ unionFunction)
+ (scopedEqualityReflexivityDerivation
+ emptySet)
+ lambdaCongruence =
+ equalityCongruenceLambdaDerivation
+ TySet
+ boundReflexivity
+ equalityMp =
+ implicationIntroductionDerivation
+ (embedClosedCore [] proposition)
+ (equalityModusPonensDerivation
+ (scopedEqualityReflexivityDerivation
+ (embedClosedCore []
+ proposition))
+ (localHypothesisDerivation
+ (hypothesisIx 0)))
+ conversion =
+ convertJudgmentDerivation
+ (scopedEqualityReflexivityDerivation
+ emptySet)
+ convertedTarget
+ oneReduction
+ assertReplayTarget
+ foundation
+ (CForall TySet
+ (CEq TySet
+ (CBound 0)
+ (CBound 0)))
+ universalReflexivity
+ assertReplayTarget
+ foundation
+ (CEq TySet
+ (CIntrinsic Empty)
+ (CIntrinsic Empty))
+ specializedReflexivity
+ assertReplayTarget
+ foundation
+ (CEq TySet
+ (CApp
+ (CIntrinsic FamilyUnion)
+ (CIntrinsic Empty))
+ (CApp
+ (CIntrinsic FamilyUnion)
+ (CIntrinsic Empty)))
+ applicationCongruence
+ assertReplayTarget
+ foundation
+ (CEq
+ (TySet `TyArrow` TySet)
+ (CLam TySet (CBound 0))
+ (CLam TySet (CBound 0)))
+ lambdaCongruence
+ assertReplayTarget
+ foundation
+ (CImp
+ (frozenCoreTerm proposition)
+ (frozenCoreTerm proposition))
+ equalityMp
+ assertReplayTarget
+ foundation
+ (scopedCoreTerm convertedTarget)
+ conversion
+
+recordsAuthorityLeaves :: Assertion
+recordsAuthorityLeaves = do
+ foundation <-
+ expectRight Foundation.checkedFoundation
+ let foundationTag =
+ Foundation.EmptyCharacteristic
+ foundationTarget =
+ mapFrozenGlobals
+ absurd
+ (Foundation.foundationAxiomFrozen
+ foundation
+ foundationTag)
+ foundationReplay <-
+ expectRight
+ (replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ absurd
+ Vector.empty
+ foundationTarget
+ (foundationFactDerivation
+ foundationTag))
+ assertEqual
+ "exact foundation use"
+ (Set.singleton foundationTag)
+ (replayedKernelFoundationUses
+ foundationReplay)
+ importedStatement <-
+ checkedClosed
+ (CEq TySet
+ (CIntrinsic Empty)
+ (CIntrinsic Empty))
+ importedJudgment <-
+ expectRight
+ (derivationImportJudgment
+ importedStatement)
+ importedReplay <-
+ expectRight
+ (replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ absurd
+ (Vector.singleton importedJudgment)
+ importedStatement
+ (importedFactDerivation
+ (importIx 0)))
+ assertEqual
+ "exact import use"
+ (Set.singleton (importIx 0))
+ (replayedKernelImportUses importedReplay)
+
+checksSetLfpRules :: Assertion
+checksSetLfpRules = do
+ foundation <-
+ expectRight Foundation.checkedFoundation
+ ( domain
+ , operator
+ , predicate
+ , element
+ , fixedPoint
+ , closedPremise
+ , boundedPremise
+ , monotonePremise
+ , memberPremise
+ , closurePremise
+ ) <-
+ setLfpFixture
+ bound <-
+ expectRight
+ (SetLfp.setLfpBound
+ foundation
+ absurd
+ domain
+ operator)
+ least <-
+ expectRight
+ (SetLfp.setLfpLeast
+ foundation
+ absurd
+ domain
+ operator
+ domain
+ closedPremise
+ boundedPremise)
+ fixed <-
+ expectRight
+ (SetLfp.setLfpFixed
+ foundation
+ absurd
+ domain
+ operator
+ monotonePremise)
+ inducted <-
+ expectRight
+ (SetLfp.setLfpInduct
+ foundation
+ absurd
+ domain
+ operator
+ predicate
+ element
+ monotonePremise
+ memberPremise
+ closurePremise)
+ expectedSubset <-
+ expectRight
+ (SetLfp.subsetProposition
+ absurd
+ fixedPoint
+ domain)
+ expectedFixed <-
+ checkedScoped []
+ (CEq TySet
+ (scopedCoreTerm fixedPoint)
+ (CApp
+ (scopedCoreTerm operator)
+ (scopedCoreTerm fixedPoint)))
+ expectedPredicate <-
+ checkedScoped []
+ (CApp
+ (scopedCoreTerm predicate)
+ (scopedCoreTerm element))
+ assertEqual "bound conclusion" expectedSubset bound
+ assertEqual "least conclusion" expectedSubset least
+ assertEqual "fixed conclusion" expectedFixed fixed
+ assertEqual "induction conclusion" expectedPredicate inducted
+
+ target <-
+ maybe
+ (assertFailure
+ "closed fixed-point bound remained scoped")
+ pure
+ (closeScopedCore bound)
+ replayed <-
+ expectRight
+ (replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ absurd
+ Vector.empty
+ target
+ (setLfpBoundDerivation
+ domain
+ operator))
+ assertEqual
+ "exact guarded-rule use"
+ (Set.singleton Foundation.SetLfpBound)
+ (replayedKernelRuleUses replayed)
+
+replaysDirectInductiveFacts :: Assertion
+replaysDirectInductiveFacts = do
+ foundation <-
+ expectRight Foundation.checkedFoundation
+ traverse_
+ (replayInductive foundation)
+ [ Inductive.DirectInductive
+ []
+ (Internal.EmptySet Nowhere)
+ (Inductive.DirectInductiveClause
+ []
+ []
+ (Internal.EmptySet Nowhere)
+ :| [])
+ , let x = Internal.NamedVar "x"
+ in Inductive.DirectInductive
+ []
+ (Internal.EmptySet Nowhere)
+ ( Inductive.DirectInductiveClause
+ []
+ []
+ (Internal.EmptySet Nowhere)
+ :| [ Inductive.DirectInductiveClause
+ [x]
+ [Inductive.DirectRecursiveCondition
+ (Internal.TermVar x)
+ (Inductive.directRecursiveCarrierContext Nowhere)]
+ (Internal.TermVar x)
+ ]
+ )
+ ]
+ where
+ replayInductive foundation inductive = do
+ prepared <-
+ expectRight
+ (Inductive.prepareTypedInductive
+ testGlobalType
+ foundation
+ (const Nothing)
+ (Internal.Marker "direct_inductive")
+ inductive)
+ imports <-
+ traverse
+ (expectRight . derivationImportJudgment)
+ (Inductive.typedInductiveGuardTargets
+ prepared)
+ traverse_
+ (\fact -> do
+ replayed <-
+ expectRight
+ (replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ (const Nothing)
+ imports
+ (Inductive.typedInductiveFactTarget
+ fact)
+ (Inductive.typedInductiveFactDerivation
+ fact))
+ assertEqual
+ "replay target"
+ (Inductive.typedInductiveFactTarget
+ fact)
+ (replayedKernelTarget replayed))
+ (Inductive.typedInductiveFacts
+ prepared)
+
+rejectsAlteredSetLfpApplications :: Assertion
+rejectsAlteredSetLfpApplications = do
+ foundation <-
+ expectRight Foundation.checkedFoundation
+ ( domain
+ , operator
+ , predicate
+ , _element
+ , _fixedPoint
+ , _closedPremise
+ , boundedPremise
+ , _monotonePremise
+ , _memberPremise
+ , _closurePremise
+ ) <-
+ setLfpFixture
+ falsum <-
+ checkedScoped [] CFalsum
+ assertEqual
+ "altered leastness premise"
+ (Left
+ (SetLfp.SetLfpRulePremiseMismatch
+ Foundation.SetLfpLeast
+ 0))
+ (SetLfp.setLfpLeast
+ foundation
+ absurd
+ domain
+ operator
+ domain
+ falsum
+ boundedPremise)
+ assertEqual
+ "operator type mismatch"
+ (Left
+ (SetLfp.SetLfpRuleArgumentTypeMismatch
+ Foundation.SetLfpBound
+ 1
+ (TySet `TyArrow` TySet)
+ (TySet `TyArrow` TyProp)))
+ (SetLfp.setLfpBound
+ foundation
+ absurd
+ domain
+ predicate)
+ bound <-
+ expectRight
+ (SetLfp.setLfpBound
+ foundation
+ absurd
+ domain
+ operator)
+ wrongTarget <-
+ checkedClosed CFalsum
+ assertEqual
+ "altered replay target"
+ (Left KernelReplayTargetMismatch)
+ (replayedKernelTarget
+ <$> replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ absurd
+ Vector.empty
+ wrongTarget
+ (setLfpBoundDerivation
+ domain
+ operator))
+ assertEqual
+ "the direct bound remains well formed"
+ TyProp
+ (scopedCoreType bound)
+
+setLfpFixture
+ :: IO
+ ( ScopedCheckedCore Void
+ , ScopedCheckedCore Void
+ , ScopedCheckedCore Void
+ , ScopedCheckedCore Void
+ , ScopedCheckedCore Void
+ , ScopedCheckedCore Void
+ , ScopedCheckedCore Void
+ , ScopedCheckedCore Void
+ , ScopedCheckedCore Void
+ , ScopedCheckedCore Void
+ )
+setLfpFixture = do
+ domain <-
+ checkedScoped [] (CIntrinsic Empty)
+ operator <-
+ checkedScoped []
+ (CLam TySet (CBound 0))
+ predicate <-
+ checkedScoped []
+ (CLam TySet
+ (CEq TySet
+ (CBound 0)
+ (CBound 0)))
+ element <-
+ checkedScoped [] (CIntrinsic Empty)
+ fixedPoint <-
+ expectRight
+ (SetLfp.setLfpTerm
+ absurd
+ domain
+ operator)
+ operatorDomain <-
+ checkedScoped []
+ (CApp
+ (scopedCoreTerm operator)
+ (scopedCoreTerm domain))
+ closedPremise <-
+ expectRight
+ (SetLfp.subsetProposition
+ absurd
+ operatorDomain
+ domain)
+ boundedPremise <-
+ expectRight
+ (SetLfp.subsetProposition
+ absurd
+ domain
+ domain)
+ monotonePremise <-
+ expectRight
+ (SetLfp.boundedMonoProposition
+ absurd
+ domain
+ operator)
+ memberPremise <-
+ expectRight
+ (SetLfp.memberProposition
+ absurd
+ element
+ fixedPoint)
+ closurePremise <-
+ expectRight
+ (SetLfp.inductionClosureProposition
+ absurd
+ domain
+ operator
+ predicate)
+ pure
+ ( domain
+ , operator
+ , predicate
+ , element
+ , fixedPoint
+ , closedPremise
+ , boundedPremise
+ , monotonePremise
+ , memberPremise
+ , closurePremise
+ )
+
+rejectsInvalidScopedReplay :: Assertion
+rejectsInvalidScopedReplay = do
+ foundation <-
+ expectRight Foundation.checkedFoundation
+ proposition <-
+ checkedClosed
+ (CEq TySet
+ (CIntrinsic Empty)
+ (CIntrinsic Empty))
+ boundSet <-
+ checkedScoped [TySet] (CBound 0)
+ assertEqual
+ "missing local hypothesis"
+ (Left
+ (KernelReplayHypothesisOutOfBounds
+ (hypothesisIx 0)))
+ (replayedKernelTarget
+ <$> replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ absurd
+ Vector.empty
+ proposition
+ (localHypothesisDerivation
+ (hypothesisIx 0)))
+ assertEqual
+ "stored term from another lexical context"
+ (Left
+ (KernelReplayStoredContextMismatch
+ []
+ [TySet]))
+ (replayedKernelTarget
+ <$> replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ absurd
+ Vector.empty
+ proposition
+ (scopedEqualityReflexivityDerivation
+ boundSet))
+ expanded <-
+ checkedScoped []
+ (CEq TySet
+ (CApp
+ (CLam TySet (CBound 0))
+ (CIntrinsic Empty))
+ (CIntrinsic Empty))
+ noReductions <-
+ expectRight (conversionPlan 0)
+ assertEqual
+ "conversion budget"
+ (Left
+ (KernelReplaySemanticsError
+ Semantics.KernelConversionBudgetExhausted))
+ (replayedKernelTarget
+ <$> replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ absurd
+ Vector.empty
+ (unsafeClosed
+ (scopedCoreTerm expanded))
+ (convertJudgmentDerivation
+ (equalityReflexivityDerivation
+ (unsafeClosed
+ (CIntrinsic Empty)))
+ expanded
+ noReductions))
+ let checkedAsSet _global =
+ Just TySet
+ replayedAsProposition _global =
+ Just TyProp
+ globalTarget <-
+ expectRight
+ (checkCanonicalCore
+ checkedAsSet
+ (CEq TySet
+ (CGlobal TestGlobal)
+ (CGlobal TestGlobal)))
+ assertEqual
+ "stored global types are rechecked"
+ (Left
+ (KernelReplayStoredTermIllTyped
+ (EqualityOperandTypeMismatch
+ TySet
+ TyProp)))
+ (replayedKernelTarget
+ <$> replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ replayedAsProposition
+ Vector.empty
+ globalTarget
+ (equalityReflexivityDerivation
+ (unsafeGlobalOperand
+ checkedAsSet)))
+ oneNode <-
+ expectRight (kernelReplayLimits 1 10)
+ let propositionScoped =
+ embedClosedCore [] proposition
+ identityTarget =
+ unsafeClosed
+ (CImp
+ (frozenCoreTerm proposition)
+ (frozenCoreTerm proposition))
+ assertEqual
+ "replay node limit"
+ (Left
+ (KernelReplayNodeLimitExceeded 1))
+ (replayedKernelTarget
+ <$> replayKernelDerivation
+ foundation
+ oneNode
+ absurd
+ Vector.empty
+ identityTarget
+ (implicationIntroductionDerivation
+ propositionScoped
+ (localHypothesisDerivation
+ (hypothesisIx 0))))
+
+rejectsTargetMismatch :: Assertion
+rejectsTargetMismatch = do
+ foundation <-
+ expectRight Foundation.checkedFoundation
+ operand <-
+ expectRight
+ (checkCanonicalCore
+ absurd
+ (CIntrinsic Empty))
+ wrongTarget <-
+ expectRight
+ (checkCanonicalCore
+ absurd
+ (CImp CFalsum CFalsum))
+ assertEqual
+ "the expected target is comparison input, not evidence"
+ (Left KernelReplayTargetMismatch)
+ (replayedKernelTarget
+ <$> replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ absurd
+ Vector.empty
+ wrongTarget
+ (equalityReflexivityDerivation operand))
+
+assertReplayTarget
+ :: Foundation.CheckedFoundation
+ -> CanonicalTerm Void
+ -> KernelDerivation Void
+ -> Assertion
+assertReplayTarget foundation expectedTerm derivation = do
+ expected <-
+ checkedClosed expectedTerm
+ replayed <-
+ expectRight
+ (replayKernelDerivation
+ foundation
+ defaultKernelReplayLimits
+ absurd
+ Vector.empty
+ expected
+ derivation)
+ assertEqual
+ "replayed exact target"
+ expected
+ (replayedKernelTarget replayed)
+
+checkedClosed
+ :: CanonicalTerm Void
+ -> IO (FrozenCheckedCore Void)
+checkedClosed =
+ expectRight . checkCanonicalCore absurd
+
+checkedScoped
+ :: [CoreType]
+ -> CanonicalTerm Void
+ -> IO (ScopedCheckedCore Void)
+checkedScoped context =
+ expectRight
+ . checkScopedCanonicalCore absurd context
+
+unsafeScoped
+ :: [CoreType]
+ -> CanonicalTerm Void
+ -> ScopedCheckedCore Void
+unsafeScoped context term =
+ case checkScopedCanonicalCore absurd context term of
+ Left coreError ->
+ impossible
+ ("invalid static kernel fixture: "
+ <> show coreError)
+ Right checked ->
+ checked
+
+unsafeClosed
+ :: CanonicalTerm Void
+ -> FrozenCheckedCore Void
+unsafeClosed term =
+ case checkCanonicalCore absurd term of
+ Left coreError ->
+ impossible
+ ("invalid static closed kernel fixture: "
+ <> show coreError)
+ Right checked ->
+ checked
+
+unsafeGlobalOperand
+ :: (TestGlobal -> Maybe CoreType)
+ -> FrozenCheckedCore TestGlobal
+unsafeGlobalOperand globalType =
+ case checkCanonicalCore
+ globalType
+ (CGlobal TestGlobal) of
+ Left coreError ->
+ impossible
+ ("invalid static global kernel fixture: "
+ <> show coreError)
+ Right checked ->
+ checked
+
+expectRight
+ :: Show error
+ => Either error value
+ -> IO value
+expectRight =
+ either
+ (assertFailure . show)
+ pure
diff --git a/source/Felix/Test/Unit/Lexicon.hs b/source/Felix/Test/Unit/Lexicon.hs
new file mode 100644
index 0000000..4b7f9e7
--- /dev/null
+++ b/source/Felix/Test/Unit/Lexicon.hs
@@ -0,0 +1,333 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Test.Unit.Lexicon (unitTests) where
+
+import Base
+import Felix.Cache.Codec
+import Felix.Syntax.Abstract
+import Felix.Syntax.Interface
+import Felix.Syntax.Lexicon
+
+import Data.Set qualified as Set
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Lexicon"
+ [ testCase "retains the literal ten-row base mixfix grouping"
+ retainsBaseMixfixGrouping
+ , testCase "checks literal mixfix levels"
+ checksMixfixLevels
+ , testCase "coalesces equal canonical syntax entries"
+ coalescesCanonicalEntries
+ , testCase "rejects shared plural parser surfaces"
+ rejectsSharedPluralSurfaces
+ , testCase "round-trips complete canonical lexical entries"
+ roundTripsCanonicalEntries
+ , testCase "round-trips asserted module syntax interfaces"
+ roundTripsSyntaxInterfaces
+ ]
+
+retainsBaseMixfixGrouping :: Assertion
+retainsBaseMixfixGrouping =
+ do
+ assertEqual
+ "least-to-most-tight marker and associativity rows"
+ expectedBaseRows
+ (fmap (fmap entryShape) builtinMixfixLevels)
+ assertEqual
+ "canonical base manifest"
+ expectedBaseRows
+ (fmap (fmap canonicalEntryShape) baseSyntaxManifest)
+ assertEqual
+ "cache-epoch base syntax identity"
+ "97acdc8153821b20dd0f45cf9d44870705723bb60737f7bacbb9ac31a98987a5"
+ (cacheDigestHex
+ (baseSyntaxInterfaceIdDigest
+ baseSyntaxInterfaceId))
+ where
+ entryShape (MixfixItem _pattern marker associativity) =
+ (marker, associativity)
+
+ canonicalEntryShape = \case
+ CanonicalExpressionFunction
+ _pattern
+ marker
+ (Fixity associativity _level) ->
+ (marker, associativity)
+ entry ->
+ impossible
+ ("non-expression entry in base manifest: "
+ <> show entry)
+
+checksMixfixLevels :: Assertion
+checksMixfixLevels = do
+ assertEqual
+ "lowest level"
+ (Right 0)
+ (mixfixLevelValue <$> mixfixLevel 0)
+ assertEqual
+ "highest internal level"
+ (Right 9)
+ (mixfixLevelValue <$> mixfixLevel 9)
+ assertEqual
+ "out-of-range level"
+ (Left (MixfixLevelOutOfRange 10))
+ (mixfixLevel 10)
+
+coalescesCanonicalEntries :: Assertion
+coalescesCanonicalEntries = do
+ level <- expectRight (mixfixLevel 3)
+ let pat =
+ patternFromHoley
+ [ Nothing
+ , Just (Command "star")
+ , Nothing
+ ]
+ first =
+ CanonicalExpressionFunction
+ pat
+ "star"
+ (Fixity LeftAssoc level)
+ conflicting =
+ CanonicalExpressionFunction
+ pat
+ "other_star"
+ (Fixity LeftAssoc level)
+ delta <- expectRight
+ (canonicalSyntaxDelta [first, first])
+ assertEqual
+ "equal entries coalesce"
+ 1
+ (canonicalSyntaxDeltaSize delta)
+ assertEqual
+ "coalesced entry"
+ [first]
+ (canonicalSyntaxDeltaEntries delta)
+ assertEqual
+ "collision is independent of occurrence order"
+ (canonicalSyntaxDelta [first, conflicting])
+ (canonicalSyntaxDelta [conflicting, first])
+
+rejectsSharedPluralSurfaces :: Assertion
+rejectsSharedPluralSurfaces = do
+ let nounEntry =
+ CanonicalNoun
+ (wordPattern "member")
+ (wordPattern "objects")
+ "member"
+ otherNoun =
+ CanonicalNoun
+ (wordPattern "element")
+ (wordPattern "objects")
+ "element"
+ verbEntry =
+ CanonicalVerb
+ (wordPattern "belongs")
+ (wordPattern "objects")
+ "belongs"
+ assertPluralCollision nounEntry otherNoun
+ assertPluralCollision nounEntry verbEntry
+ case decodeCache
+ getCanonicalSyntaxDeltaCache
+ (encodeCache
+ (putCacheList
+ putCanonicalLexicalEntryCache
+ [nounEntry, otherNoun])) of
+ Left _ ->
+ pure ()
+ Right _ ->
+ assertFailure
+ "decoded a delta with a shared plural collision"
+ where
+ wordPattern word =
+ TokenCons (Word word) End
+
+ assertPluralCollision first second =
+ case canonicalSyntaxDelta [first, second] of
+ Left collision -> do
+ assertEqual
+ "shared plural pattern"
+ (wordPattern "objects")
+ (canonicalCollisionPattern collision)
+ assertEqual
+ "both complete entries"
+ (Set.fromList [first, second])
+ (Set.fromList
+ (toList
+ (canonicalCollisionEntries collision)))
+ Right _ ->
+ assertFailure
+ "accepted two entries with one parser-active plural"
+
+roundTripsCanonicalEntries :: Assertion
+roundTripsCanonicalEntries = do
+ level <- expectRight (mixfixLevel 4)
+ let unary =
+ patternFromHoley
+ [ Just (Word "red")
+ , Nothing
+ ]
+ singular =
+ patternFromHoley
+ [ Just (Word "member")
+ , Just (Word "of")
+ , Nothing
+ ]
+ plural =
+ patternFromHoley
+ [ Just (Word "members")
+ , Just (Word "of")
+ , Nothing
+ ]
+ binary =
+ patternFromHoley
+ [ Nothing
+ , Just (Command "star")
+ , Nothing
+ ]
+ entries =
+ [ CanonicalLeftAdjective unary "red"
+ , CanonicalRightAdjective unary "red_right"
+ , CanonicalFunctionPhrase singular plural "member_fun"
+ , CanonicalNoun singular plural "member"
+ , CanonicalStructureNoun singular plural "member_struct"
+ , CanonicalVerb singular plural "member_verb"
+ , CanonicalRelation (Command "rel") (ParameterArity 2) "rel"
+ , CanonicalExpressionFunction
+ binary
+ "star"
+ (Fixity LeftAssoc level)
+ , CanonicalPrefixPredicate "Pred" 3 "pred"
+ , CanonicalStructureOperation "operation"
+ ]
+ traverse_
+ (\entry ->
+ assertEqual
+ ("cache round trip for " <> show entry)
+ (Right entry)
+ (decodeCache
+ getCanonicalLexicalEntryCache
+ (encodeCache
+ (putCanonicalLexicalEntryCache entry))))
+ entries
+
+roundTripsSyntaxInterfaces :: Assertion
+roundTripsSyntaxInterfaces = do
+ level <- expectRight (mixfixLevel 7)
+ changedLevel <- expectRight (mixfixLevel 6)
+ let entry =
+ CanonicalExpressionFunction
+ (patternFromHoley
+ [ Nothing
+ , Just (Command "diamond")
+ , Nothing
+ ])
+ "diamond"
+ (Fixity NonAssoc level)
+ changedEntry =
+ CanonicalExpressionFunction
+ (patternFromHoley
+ [ Nothing
+ , Just (Command "diamond")
+ , Nothing
+ ])
+ "diamond"
+ (Fixity RightAssoc changedLevel)
+ delta <- expectRight (canonicalSyntaxDelta [entry])
+ changedDelta <- expectRight
+ (canonicalSyntaxDelta [changedEntry])
+ interface <- expectRight
+ (moduleSyntaxInterface [] delta)
+ changedInterface <- expectRight
+ (moduleSyntaxInterface [] changedDelta)
+ assertEqual
+ "current fixed base"
+ baseSyntaxInterfaceId
+ (moduleSyntaxBase interface)
+ assertEqual
+ "module syntax cache round trip"
+ (Right interface)
+ (decodeCache
+ getModuleSyntaxInterfaceCache
+ (encodeCache
+ (putModuleSyntaxInterfaceCache interface)))
+ assertEqual
+ "asserted ID is deterministic"
+ (Right (moduleSyntaxAssertedId interface))
+ (moduleSyntaxAssertedId
+ <$> moduleSyntaxInterface [] delta)
+ assertBool
+ "normalized fixity changes syntax identity"
+ (moduleSyntaxAssertedId interface
+ /= moduleSyntaxAssertedId changedInterface)
+ case moduleSyntaxInterface
+ [ moduleSyntaxAssertedId interface
+ , moduleSyntaxAssertedId interface
+ ]
+ delta of
+ Left (DuplicateDirectSyntaxInterface duplicate) ->
+ assertEqual
+ "duplicate direct interface"
+ (moduleSyntaxAssertedId interface)
+ duplicate
+ Left err ->
+ assertFailure
+ ("expected duplicate direct syntax input, got "
+ <> show err)
+ Right _ ->
+ assertFailure
+ "accepted a duplicate direct syntax input"
+
+expectRight :: Show err => Either err value -> IO value
+expectRight = \case
+ Left err ->
+ assertFailure (show err)
+ >> fail "unreachable"
+ Right value ->
+ pure value
+
+expectedBaseRows :: [[(Marker, Associativity)]]
+expectedBaseRows =
+ [ []
+ , [ ("add", LeftAssoc)
+ , ("union", LeftAssoc)
+ , ("minus", LeftAssoc)
+ , ("rminus", LeftAssoc)
+ , ("monus", LeftAssoc)
+ ]
+ , [("relcomp", LeftAssoc)]
+ , [("circ", LeftAssoc)]
+ , [ ("mul", LeftAssoc)
+ , ("inter", LeftAssoc)
+ , ("rmul", LeftAssoc)
+ ]
+ , [("setminus", LeftAssoc)]
+ , [("times", RightAssoc)]
+ , []
+ , [ ("rfrac", NonAssoc)
+ , ("exp", NonAssoc)
+ , ("unions", NonAssoc)
+ , ("cumul", NonAssoc)
+ , ("fst", NonAssoc)
+ , ("snd", NonAssoc)
+ , ("pow", NonAssoc)
+ , ("neg", NonAssoc)
+ , ("inv", NonAssoc)
+ , ("abs", NonAssoc)
+ , ("cons", NonAssoc)
+ , ("pair", NonAssoc)
+ , ("upair", NonAssoc)
+ ]
+ , [ ("emptyset", NonAssoc)
+ , ("naturals", NonAssoc)
+ , ("naturalsPlus", NonAssoc)
+ , ("integers", NonAssoc)
+ , ("rationals", NonAssoc)
+ , ("reals", NonAssoc)
+ , ("unit", NonAssoc)
+ , ("zero", NonAssoc)
+ ]
+ ]
diff --git a/source/Felix/Test/Unit/Materialization.hs b/source/Felix/Test/Unit/Materialization.hs
new file mode 100644
index 0000000..9c4f54f
--- /dev/null
+++ b/source/Felix/Test/Unit/Materialization.hs
@@ -0,0 +1,357 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Test.Unit.Materialization (unitTests) where
+
+import Base
+import Felix.Checking.Authority qualified as Authority
+import Felix.Checking.Core qualified as Core
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Checking.Identity qualified as Identity
+import Felix.Checking.Materialization qualified as Materialization
+import Felix.Checking.Semantic qualified as Semantic
+import Felix.Math.Codec
+import Felix.Module
+import Felix.Source
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Validation applicability"
+ [ testCase "keeps serializable candidate validation inert"
+ keepsCandidateValidationInert
+ , testCase "rejects inexact candidate validation"
+ rejectsInexactCandidateValidation
+ , testCase "rejects mismatched candidate fields"
+ rejectsMismatchedCandidateFields
+ , testCase "selects ordered declaration certificates"
+ selectsDeclarationCertificate
+ , testCase "keeps raw interface membership inert"
+ keepsImportedMembershipInert
+ ]
+
+keepsCandidateValidationInert :: Assertion
+keepsCandidateValidationInert = do
+ fixture <- makeFixture
+ let check =
+ Materialization.checkCandidateValidation
+ (fixtureTheory fixture)
+ (fixturePrefix fixture)
+ (fixtureProposition fixture)
+ (fixtureAuthority fixture)
+ (fixtureDirect fixture)
+ (fixtureProofValidation fixture)
+ assertEqual
+ "freely constructed records yield only inert applicability"
+ (Right ())
+ check
+ assertEqual
+ "inert applicability data is reusable"
+ (Right ())
+ check
+
+rejectsInexactCandidateValidation :: Assertion
+rejectsInexactCandidateValidation = do
+ fixture <- makeFixture
+ let wrongKey =
+ Semantic.proofValidationKey
+ (Identity.theoremId
+ (fixtureReference fixture))
+ (Semantic.proofSyntaxId "other-proof")
+ (fixturePrefix fixture)
+ wrongValidation =
+ Materialization.candidateProofValidation
+ (Semantic.proofValidationRecord
+ wrongKey
+ (fixtureCertificate fixture))
+ (Semantic.proofSyntaxId "proof")
+ case Materialization.checkCandidateValidation
+ (fixtureTheory fixture)
+ (fixturePrefix fixture)
+ (fixtureProposition fixture)
+ (fixtureAuthority fixture)
+ (fixtureDirect fixture)
+ wrongValidation of
+ Left Materialization.ProofValidationKeyMismatch{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected validation-key error: " <> show other)
+ Right _ ->
+ assertFailure "inexact proof validation key succeeded"
+
+rejectsMismatchedCandidateFields :: Assertion
+rejectsMismatchedCandidateFields = do
+ fixture <- makeFixture
+ let sourceAuthority =
+ Authority.factAuthority
+ (fixtureReference fixture)
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind
+ Authority.SourceAxiom))
+ sourceCertificate <- expectRight
+ (Authority.validationCertificate
+ sourceAuthority
+ Authority.SourceAxiomAuthorization)
+ let key =
+ Semantic.proofValidationKey
+ (Identity.theoremId (fixtureReference fixture))
+ (Semantic.proofSyntaxId "proof")
+ (fixturePrefix fixture)
+ sourceValidation =
+ Materialization.candidateProofValidation
+ (Semantic.proofValidationRecord
+ key
+ sourceCertificate)
+ (Semantic.proofSyntaxId "proof")
+ case Materialization.checkCandidateValidation
+ (fixtureTheory fixture)
+ (fixturePrefix fixture)
+ (fixtureProposition fixture)
+ (fixtureAuthority fixture)
+ (fixtureDirect fixture)
+ sourceValidation of
+ Left Materialization.CandidateCertificateTargetMismatch{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected target mismatch: " <> show other)
+ Right _ ->
+ assertFailure "mismatched safety was accepted"
+ differentDirect <- expectRight
+ (Authority.validationCertificate
+ (fixtureAuthority fixture)
+ (Authority.CheckedSourceProof
+ [Authority.preparedRequestId
+ Authority.PreparedRequestFof
+ Authority.PreparedRequestDirect
+ "different-request"]))
+ let directValidation =
+ Materialization.candidateProofValidation
+ (Semantic.proofValidationRecord
+ key
+ differentDirect)
+ (Semantic.proofSyntaxId "proof")
+ case Materialization.checkCandidateValidation
+ (fixtureTheory fixture)
+ (fixturePrefix fixture)
+ (fixtureProposition fixture)
+ (fixtureAuthority fixture)
+ (fixtureDirect fixture)
+ directValidation of
+ Left Materialization.CandidateDirectAuthorizationMismatch{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected direct-authorization mismatch: "
+ <> show other)
+ Right _ ->
+ assertFailure "mismatched request list was accepted"
+ closure <- expectRight
+ (Identity.validateObjectClosure
+ (fixtureTheory fixture)
+ [])
+ otherProposition <- expectRight
+ (Identity.validatePropositionContent
+ closure
+ (Core.CImp Core.CFalsum Core.CFalsum))
+ case Materialization.checkCandidateValidation
+ (fixtureTheory fixture)
+ (fixturePrefix fixture)
+ otherProposition
+ (fixtureAuthority fixture)
+ (fixtureDirect fixture)
+ (fixtureProofValidation fixture) of
+ Left Materialization.CandidatePropositionMismatch{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected proposition mismatch: " <> show other)
+ Right _ ->
+ assertFailure "mismatched proposition content was accepted"
+
+selectsDeclarationCertificate :: Assertion
+selectsDeclarationCertificate = do
+ fixture <- makeFixture
+ let sourceAuthority =
+ Authority.factAuthority
+ (fixtureReference fixture)
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind
+ Authority.SourceAxiom))
+ sourceCertificate <- expectRight
+ (Authority.validationCertificate
+ sourceAuthority
+ Authority.SourceAxiomAuthorization)
+ let syntax =
+ Semantic.declarationSyntaxId "declaration"
+ producedTheorems =
+ [ Identity.theoremId
+ (fixtureReference fixture)
+ , Identity.theoremId
+ (fixtureReference fixture)
+ ]
+ key =
+ Semantic.declarationValidationKey
+ syntax
+ (fixturePrefix fixture)
+ []
+ producedTheorems
+ validation =
+ Materialization.candidateDeclarationValidation
+ (Semantic.declarationValidationRecord
+ key
+ [sourceCertificate, fixtureCertificate fixture])
+ syntax []
+ producedTheorems
+ 1
+ assertEqual
+ "candidate ordinal selects the second certificate"
+ (Right ())
+ (Materialization.checkCandidateValidation
+ (fixtureTheory fixture)
+ (fixturePrefix fixture)
+ (fixtureProposition fixture)
+ (fixtureAuthority fixture)
+ (fixtureDirect fixture)
+ validation)
+
+keepsImportedMembershipInert :: Assertion
+keepsImportedMembershipInert = do
+ fixture <- makeFixture
+ occurrence <- expectRight
+ (Materialization.checkImportedMembership
+ (fixtureTheory fixture)
+ (fixtureInterface fixture)
+ (fixtureFingerprint fixture)
+ (fixtureProposition fixture)
+ (fixtureAuthority fixture))
+ assertEqual
+ "raw interface yields only its inert occurrence"
+ (fixtureAuthority fixture)
+ (Semantic.semanticFactAuthority occurrence)
+ let wrongAuthority =
+ Authority.factAuthority
+ (fixtureReference fixture)
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind
+ Authority.Omitted))
+ case Materialization.checkImportedMembership
+ (fixtureTheory fixture)
+ (fixtureInterface fixture)
+ (fixtureFingerprint fixture)
+ (fixtureProposition fixture)
+ wrongAuthority of
+ Left Materialization.ImportedAuthorityMismatch{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected imported-authority error: " <> show other)
+ Right _ ->
+ assertFailure "imported authority mismatch succeeded"
+
+
+data Fixture = Fixture
+ { fixtureTheory :: !Identity.TheoryId
+ , fixtureReference :: !Identity.TheoremRef
+ , fixtureProposition :: !Identity.CheckedPropositionContent
+ , fixtureAuthority :: !Authority.FactAuthority
+ , fixtureDirect :: !Authority.DirectAuthorization
+ , fixtureCertificate :: !Authority.ValidationCertificate
+ , fixtureFingerprint
+ :: !Semantic.SemanticFactOccurrenceFingerprint
+ , fixtureInterface :: !Semantic.SemanticInterface
+ , fixturePrefix :: !Semantic.PrefixContextId
+ , fixtureProofValidation
+ :: !Materialization.CandidateValidation
+ }
+
+makeFixture :: IO Fixture
+makeFixture = do
+ foundation <- expectRight Foundation.checkedFoundation
+ namespaceDigest <- expectRight
+ (hashCanonicalFields
+ "materialization-test-namespace"
+ ["root"])
+ relative <- expectRight (safeRelativePath "producer.tex")
+ let theory =
+ Identity.theoryId foundation
+ owner =
+ moduleNameFromParts
+ (sourceNamespaceIdFromDigest namespaceDigest)
+ relative
+ closure <- expectRight
+ (Identity.validateObjectClosure theory [])
+ proposition <- expectRight
+ (Identity.validatePropositionContent
+ closure Core.CFalsum)
+ let reference =
+ Identity.theoremRef
+ theory
+ (Identity.checkedPropositionId proposition)
+ authority =
+ Authority.factAuthority
+ reference
+ Authority.cleanAuthoritySafety
+ direct =
+ Authority.CheckedSourceProof []
+ slot =
+ Semantic.factSlot owner (localFactOrdinal 0)
+ fingerprint =
+ Semantic.semanticFactOccurrenceFingerprint
+ slot authority
+ occurrence =
+ Semantic.semanticFactOccurrence
+ slot
+ authority
+ Semantic.SearchEligible
+ declaration =
+ Semantic.declarationSlot
+ owner
+ (localDeclarationOrdinal 0)
+ certificate <- expectRight
+ (Authority.validationCertificate authority direct)
+ delta <- expectRight
+ (Semantic.declarationInterfaceDelta
+ declaration
+ [occurrence]
+ []
+ []
+ [Identity.checkedPropositionId proposition]
+ Semantic.emptySemanticEnvironmentDelta)
+ interface <- expectRight
+ (Semantic.semanticInterface owner [] [delta])
+ prefix <- expectRight
+ (Semantic.initialPrefixContextId theory owner [])
+ let syntax =
+ Semantic.proofSyntaxId "proof"
+ key =
+ Semantic.proofValidationKey
+ (Identity.theoremId reference)
+ syntax prefix
+ pure
+ Fixture
+ { fixtureTheory = theory
+ , fixtureReference = reference
+ , fixtureProposition = proposition
+ , fixtureAuthority = authority
+ , fixtureDirect = direct
+ , fixtureCertificate = certificate
+ , fixtureFingerprint = fingerprint
+ , fixtureInterface = interface
+ , fixturePrefix = prefix
+ , fixtureProofValidation =
+ Materialization.candidateProofValidation
+ (Semantic.proofValidationRecord
+ key certificate)
+ syntax
+ }
+
+expectRight :: Show error => Either error value -> IO value
+expectRight = \case
+ Left err ->
+ assertFailure (show err) >> fail "unreachable"
+ Right value ->
+ pure value
diff --git a/source/Felix/Test/Unit/Meaning.hs b/source/Felix/Test/Unit/Meaning.hs
new file mode 100644
index 0000000..3093a84
--- /dev/null
+++ b/source/Felix/Test/Unit/Meaning.hs
@@ -0,0 +1,1119 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.Meaning (unitTests) where
+
+import Base
+import Felix.Meaning
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Internal qualified as Sem
+import Felix.Syntax.LexicalPhrase
+ ( unsafeReadPhrase
+ , unsafeReadPhraseSgPl
+ )
+
+import Bound (instantiate)
+import Control.Monad.Except (runExceptT)
+import Control.Monad.State (evalState, gets)
+import Data.Map qualified as Map
+import Data.Set qualified as Set
+import Test.Tasty
+import Test.Tasty.HUnit
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Meaning"
+ [ testCase "relation applications reject missing and extra parameters" do
+ for_ [(0, Raw.ParameterArity 0), (2, Raw.ParameterArity 2)]
+ \(actualCount, actualArity) ->
+ case meaning [relationClaim actualCount] of
+ Left
+ (GlossRelationApplicationError
+ (Sem.RelationParameterArityMismatch
+ actualLocation
+ actualSymbol
+ expectedArity
+ reportedActualArity)) -> do
+ assertEqual
+ "relation location"
+ relationLocation
+ actualLocation
+ assertEqual
+ "relation symbol"
+ relationSymbol
+ actualSymbol
+ assertEqual
+ "expected parameter arity"
+ (Raw.ParameterArity 1)
+ expectedArity
+ assertEqual
+ "actual parameter arity"
+ actualArity
+ reportedActualArity
+ Left err ->
+ assertFailure
+ ("expected a relation arity error, got "
+ <> show err)
+ Right _ ->
+ assertFailure
+ "expected relation arity validation to fail"
+ , testCase
+ "dependent replacement domains report their occurrences"
+ dependentReplacementDomains
+ , testCase
+ "replacement domains remain outside own and future binders"
+ independentReplacementDomains
+ , testCase
+ "functional definitions reject quantified terms with source context"
+ quantifiedFunctionalDefinition
+ , testCase
+ "unsupported source constructs return located errors"
+ unsupportedSourceConstructs
+ , testCase
+ "proof-local function definitions reject mismatched heads"
+ proofLocalFunctionDefinitionMismatches
+ , testCase
+ "abbreviations reject duplicate parameters with source context"
+ duplicateAbbreviationParameters
+ , testCase
+ "abbreviations reject named free body variables"
+ freeAbbreviationBodyVariable
+ , testCase
+ "abbreviation parameters retain positional slots"
+ orderedAbbreviationParameters
+ , testCase
+ "quantified noun binders resolve their whole scope"
+ quantifiedNounBinderScope
+ , testCase
+ "quantified noun binders obey lexical scope"
+ quantifiedNounLexicalScope
+ , testCase
+ "resolved binder adaptation is injective and ignores trivia"
+ resolvedBinderAdapter
+ ]
+
+dependentReplacementDomains :: Assertion
+dependentReplacementDomains =
+ for_ cases \(label, replacement, expectedLocation) ->
+ assertEqual
+ label
+ (Left
+ (DependentReplacementDomainNotSupported
+ expectedLocation))
+ (glossTestExpr replacement)
+ where
+ cases =
+ [ ( "two-domain replacement"
+ , replacementExpr
+ (Raw.ExprVar y)
+ ( (x, rawInteger 1)
+ :| [(y, rawVarAt twoXLocation "x")]
+ )
+ , twoXLocation
+ )
+ , ( "three-domain replacement reports its first dependent occurrence"
+ , replacementExpr
+ (Raw.ExprVar z)
+ ( (x, rawInteger 1)
+ :| [ (y, rawInteger 2)
+ , (z, Raw.ExprOp
+ Nowhere
+ (testFunctionSymbol "dependent_domain" 2)
+ [ rawVarAt threeYLocation "y"
+ , rawVarAt threeXLocation "x"
+ ])
+ ]
+ )
+ , threeYLocation
+ )
+ ]
+ x = Raw.NamedVar "x"
+ y = Raw.NamedVar "y"
+ z = Raw.NamedVar "z"
+ twoXLocation = mkLocation replacementFile 3 17
+ threeYLocation = mkLocation replacementFile 4 29
+ threeXLocation = mkLocation replacementFile 4 32
+
+independentReplacementDomains :: Assertion
+independentReplacementDomains =
+ case glossTestExpr replacement of
+ Right
+ (Sem.ReplaceFun
+ ( (actualX, Sem.TermVar actualOwnX)
+ :| [ (actualY, Sem.TermVar actualFutureZ)
+ , ( actualZ
+ , Sem.TermSymbol
+ _thirdDomainLocation
+ (Sem.SymbolInteger 3)
+ []
+ )
+ ]
+ )
+ valueScope
+ conditionScope) -> do
+ assertEqual
+ "replacement binder order"
+ [x, y, z]
+ [actualX, actualY, actualZ]
+ assertEqual
+ "own-domain occurrence remains free"
+ ownXLocation
+ (locate actualOwnX)
+ assertEqual
+ "future-binder occurrence remains free"
+ futureZLocation
+ (locate actualFutureZ)
+ assertEqual
+ "replacement value lowering"
+ expectedValue
+ (instantiate instantiateBinder valueScope)
+ assertEqual
+ "default replacement condition"
+ Sem.Top
+ (instantiate instantiateBinder conditionScope)
+ Right expr ->
+ assertFailure
+ ("expected an independent functional replacement, got "
+ <> show expr)
+ Left err ->
+ assertFailure
+ ("expected independent replacement domains to succeed, got "
+ <> show err)
+ where
+ replacement =
+ replacementExpr
+ ( Raw.ExprOp
+ replacementValueLocation
+ replacementValueSymbol
+ [ rawVarAt replacementValueLocation "x"
+ , rawVarAt replacementValueLocation "y"
+ , rawVarAt replacementValueLocation "z"
+ ]
+ )
+ ( (x, rawVarAt ownXLocation "x")
+ :| [ (y, rawVarAt futureZLocation "z")
+ , (z, rawInteger 3)
+ ]
+ )
+ x = Raw.NamedVar "x"
+ y = Raw.NamedVar "y"
+ z = Raw.NamedVar "z"
+ ownXLocation = mkLocation replacementFile 6 7
+ futureZLocation = mkLocation replacementFile 6 16
+ replacementValueLocation = mkLocation replacementFile 6 29
+ replacementValueSymbol = testFunctionSymbol "replacement_value" 3
+ expectedValue =
+ Sem.TermSymbol
+ replacementValueLocation
+ (Sem.SymbolMixfix replacementValueSymbol)
+ [ closedInteger 11
+ , closedInteger 22
+ , closedInteger 33
+ ]
+ instantiateBinder binder
+ | binder == x = closedInteger 11
+ | binder == y = closedInteger 22
+ | binder == z = closedInteger 33
+ | otherwise = closedInteger (-1)
+
+replacementExpr
+ :: Raw.Expr
+ -> NonEmpty (Raw.VarSymbol, Raw.Expr)
+ -> Raw.Expr
+replacementExpr value bounds =
+ Raw.ExprReplace replacementLocation value bounds Nothing
+
+rawVarAt :: Location -> Text -> Raw.Expr
+rawVarAt location name =
+ Raw.ExprVar (Raw.NamedVarAt location name)
+
+rawInteger :: Int -> Raw.Expr
+rawInteger =
+ Raw.ExprInteger Nowhere
+
+glossTestExpr :: Raw.Expr -> Either GlossError Sem.Expr
+glossTestExpr expr =
+ evalState
+ (runExceptT (glossExpr expr))
+ initialGlossState
+
+glossTestStmt :: Raw.Stmt -> Either GlossError Sem.Formula
+glossTestStmt statement =
+ evalState
+ (runExceptT (glossStmt statement))
+ initialGlossState
+
+unsupportedSourceConstructs :: Assertion
+unsupportedSourceConstructs = do
+ assertEqual
+ "definite-description term"
+ (Left (IotaTermNotSupported iotaLocation))
+ (runGlossUnit (glossH0Term [] iotaTerm))
+ assertEqual
+ "definite-function assumption"
+ (Left
+ (DefiniteFunctionAssumptionNotSupported
+ definiteFunctionLocation))
+ (runGlossUnit (glossAsm definiteFunctionAssumption))
+ where
+ iotaTerm =
+ Raw.TermIota
+ iotaLocation
+ (Raw.NamedVarAt iotaLocation "x")
+ (Raw.StmtFormula
+ (Raw.PropositionalConstant iotaLocation Raw.IsTop))
+ definiteFunctionAssumption =
+ Raw.AsmLetThe
+ (Raw.NamedVarAt definiteFunctionLocation "f")
+ (Raw.Fun
+ definiteFunctionLocation
+ (Raw.mkLexicalItemSgPl
+ (unsafeReadPhraseSgPl "function[/s]")
+ "function")
+ [])
+
+runGlossUnit :: Gloss a -> Either GlossError ()
+runGlossUnit action =
+ void
+ (evalState
+ (runExceptT action)
+ initialGlossState)
+
+quantifiedNounBinderScope :: Assertion
+quantifiedNounBinderScope = do
+ case glossTestStmt (namedSetEquality "x") of
+ Right formula@(Sem.Quantified Sem.Universally scope) -> do
+ assertEqual
+ "the written binder does not remain free"
+ Set.empty
+ (Sem.freeVars formula)
+ assertEqual
+ "the continuation uses the quantified witness"
+ (Sem.Top
+ `Sem.Implies`
+ Sem.Equals
+ equalityLocation
+ witness
+ witness)
+ (instantiate (const witness) scope)
+ Right formula ->
+ assertFailure
+ ("expected a universal named binder, got " <> show formula)
+ Left err ->
+ assertFailure
+ ("expected the named binder to gloss, got " <> show err)
+
+ case glossTestStmt constrainedNamedBinder of
+ Right formula@(Sem.Quantified Sem.Universally scope) -> do
+ assertEqual
+ "only the ambient noun argument remains free"
+ (Set.singleton ambientVariable)
+ (Sem.freeVars formula)
+ assertEqual
+ "noun, modifier, such-that, and continuation share the binder"
+ expectedConstrainedBody
+ (instantiate (const witness) scope)
+ Right formula ->
+ assertFailure
+ ("expected a constrained universal binder, got "
+ <> show formula)
+ Left err ->
+ assertFailure
+ ("expected the constrained binder to gloss, got "
+ <> show err)
+ where
+ witness = closedInteger 17
+ ambientVariable =
+ Raw.NamedVarAt ambientLocation "T"
+ nounConstraint =
+ Sem.FormulaNoun
+ nounLocation
+ witness
+ subsetNounPattern
+ [Sem.TermVar ambientVariable]
+ modifierConstraint =
+ Sem.FormulaAdj
+ modifierLocation
+ witness
+ modifierPattern
+ [witness]
+ suchThatConstraint =
+ Sem.Equals suchThatLocation witness witness
+ continuation =
+ Sem.Equals equalityLocation witness witness
+ expectedConstrainedBody =
+ Sem.makeConjunction
+ [ suchThatConstraint
+ , Sem.makeConjunction
+ [nounConstraint, modifierConstraint]
+ ]
+ `Sem.Implies` continuation
+
+quantifiedNounLexicalScope :: Assertion
+quantifiedNounLexicalScope = do
+ assertEqual
+ "alpha-renaming a referenced binder"
+ (glossTestStmt (namedSetEquality "x"))
+ (glossTestStmt (namedSetEquality "renamed"))
+ assertEqual
+ "a vacuous written name is semantic trivia"
+ (glossTestStmt (vacuousSetStatement Nothing))
+ (glossTestStmt
+ (vacuousSetStatement
+ (Just
+ (Raw.NamedVarAt binderLocation "unused"))))
+
+ assertEqual
+ "overlapping sibling names are rejected"
+ (Left
+ (DuplicateQuantifiedNounBinder
+ firstSiblingLocation
+ secondSiblingLocation
+ "same"))
+ (glossTestStmt duplicateSiblingStatement)
+
+ case glossTestStmt nestedShadowingStatement of
+ Right (Sem.Quantified Sem.Universally outerScope) ->
+ case instantiate (const outerWitness) outerScope of
+ Sem.Top
+ `Sem.Implies`
+ Sem.Quantified Sem.Existentially innerScope ->
+ assertEqual
+ "the nearest binder owns the nested occurrences"
+ expectedInnerBody
+ (instantiate
+ (const innerWitness)
+ innerScope)
+ body ->
+ assertFailure
+ ("expected a nested existential binder, got "
+ <> show body)
+ Right formula ->
+ assertFailure
+ ("expected an outer universal binder, got " <> show formula)
+ Left err ->
+ assertFailure
+ ("expected nested shadowing to gloss, got " <> show err)
+ where
+ outerWitness = closedInteger 23
+ innerWitness = closedInteger 29
+ expectedInnerBody =
+ Sem.makeConjunction
+ [ Sem.Equals
+ nestedSuchThatLocation
+ innerWitness
+ innerWitness
+ , Sem.Top
+ ]
+ `Sem.And`
+ Sem.Equals
+ nestedEqualityLocation
+ outerWitness
+ innerWitness
+
+resolvedBinderAdapter :: Assertion
+resolvedBinderAdapter = do
+ case ( binderAdapterObservation
+ ("first", firstAdapterLocation)
+ ("second", secondAdapterLocation)
+ , binderAdapterObservation
+ ("alpha", alternateFirstLocation)
+ ("beta", alternateSecondLocation)
+ ) of
+ ( Right (firstId :| [secondId], firstTokens, firstResult)
+ , Right (alternateIds, alternateTokens, alternateResult)
+ ) -> do
+ assertBool
+ "pre-adapter local identities are distinct"
+ (firstId /= secondId)
+ case
+ ( Map.lookup firstId firstTokens
+ , Map.lookup secondId firstTokens
+ ) of
+ (Just firstToken, Just secondToken) -> do
+ assertBool
+ "legacy tokens are injective"
+ (firstToken /= secondToken)
+ assertBool
+ "legacy tokens avoid ambient variables"
+ ( firstToken /= adapterAmbientVariable
+ && secondToken
+ /= adapterAmbientVariable
+ )
+ tokens ->
+ assertFailure
+ ("expected two adapter assignments, got "
+ <> show tokens)
+ assertEqual
+ "trivia does not affect local identities"
+ (firstId :| [secondId])
+ alternateIds
+ assertEqual
+ "trivia does not affect adapter assignments"
+ firstTokens
+ alternateTokens
+ assertEqual
+ "trivia does not affect the alpha-normal result"
+ firstResult
+ alternateResult
+ assertEqual
+ "ambient references pass through unchanged"
+ (Set.singleton adapterAmbientVariable)
+ (Sem.freeVars firstResult)
+ (firstResult, secondResult) ->
+ assertFailure
+ ("expected successful adapter observations, got "
+ <> show (firstResult, secondResult))
+
+ assertEqual
+ "an unadapted local reference is a located typed error"
+ (Left
+ (GlossResolvedBinderAdapterError
+ firstAdapterLocation
+ (UnknownResolvedLocal (LocalId 0))))
+ ( evalState
+ (runExceptT
+ do
+ binder <-
+ freshH0Binder
+ firstAdapterLocation
+ (Just
+ (Raw.NamedVarAt
+ firstAdapterLocation
+ "unadapted"))
+ lowerH0Expr
+ (Sem.TermVar
+ (LocalRef (h0BinderId binder))))
+ initialGlossState
+ )
+
+binderAdapterObservation
+ :: (Text, Location)
+ -> (Text, Location)
+ -> Either
+ GlossError
+ ( NonEmpty LocalId
+ , Map LocalId Sem.VarSymbol
+ , Sem.Expr
+ )
+binderAdapterObservation
+ (firstName, firstLocation)
+ (secondName, secondLocation) =
+ evalState
+ (runExceptT do
+ firstBinder <-
+ freshH0Binder
+ firstLocation
+ (Just
+ (Raw.NamedVarAt firstLocation firstName))
+ secondBinder <-
+ freshH0Binder
+ secondLocation
+ (Just
+ (Raw.NamedVarAt secondLocation secondName))
+ let firstId = h0BinderId firstBinder
+ secondId = h0BinderId secondBinder
+ resolvedBody =
+ Sem.TermSymbol
+ Nowhere
+ (Sem.SymbolMixfix adapterBodySymbol)
+ [ Sem.TermVar (LocalRef firstId)
+ , Sem.TermVar (LocalRef secondId)
+ , Sem.TermVar
+ (AmbientRef adapterAmbientVariable)
+ ]
+ quantifiedTerms =
+ [ H0QuantifiedTerm
+ Raw.Universally
+ firstBinder
+ []
+ , H0QuantifiedTerm
+ Raw.Existentially
+ secondBinder
+ []
+ ]
+ adapted <-
+ applyH0Quantifiers quantifiedTerms resolvedBody
+ >>= lowerH0Expr
+ assignments <- gets legacyLocalTokens
+ pure
+ ( firstId :| [secondId]
+ , assignments
+ , adapted
+ ))
+ initialGlossState
+
+namedSetEquality :: Text -> Raw.Stmt
+namedSetEquality name =
+ Raw.StmtVerbPhrase
+ ( quantifiedSetTerm
+ Raw.Universally
+ binderLocation
+ (Just
+ (Raw.NamedVarAt binderLocation name))
+ []
+ Nothing
+ :| []
+ )
+ (equalityVerbPhrase
+ equalityLocation
+ (rawTermVar equalityLocation name))
+
+constrainedNamedBinder :: Raw.Stmt
+constrainedNamedBinder =
+ Raw.StmtVerbPhrase
+ ( Raw.TermQuantified
+ Raw.Universally
+ binderLocation
+ ( Raw.NounPhrase
+ [ Raw.AdjL
+ modifierLocation
+ modifierPattern
+ [rawTermVar modifierLocation "x"]
+ ]
+ ( Raw.Noun
+ nounLocation
+ subsetNounPattern
+ [rawTermVar ambientLocation "T"]
+ )
+ (Just
+ (Raw.NamedVarAt binderLocation "x"))
+ []
+ (Just
+ (equalityStatement
+ suchThatLocation
+ "x"
+ "x"))
+ )
+ :| []
+ )
+ (equalityVerbPhrase
+ equalityLocation
+ (rawTermVar equalityLocation "x"))
+
+vacuousSetStatement :: Maybe Raw.VarSymbol -> Raw.Stmt
+vacuousSetStatement mayName =
+ Raw.StmtVerbPhrase
+ ( quantifiedSetTerm
+ Raw.Universally
+ binderLocation
+ mayName
+ []
+ Nothing
+ :| []
+ )
+ ( Raw.VPAdj
+ ( Raw.Adj
+ reflexiveLocation
+ reflexivePattern
+ []
+ :| []
+ )
+ )
+
+duplicateSiblingStatement :: Raw.Stmt
+duplicateSiblingStatement =
+ Raw.StmtVerbPhrase
+ ( quantifiedSetTerm
+ Raw.Universally
+ firstSiblingLocation
+ (Just
+ (Raw.NamedVarAt firstSiblingLocation "same"))
+ []
+ Nothing
+ :| [ quantifiedSetTerm
+ Raw.Existentially
+ secondSiblingLocation
+ (Just
+ (Raw.NamedVarAt secondSiblingLocation "same"))
+ []
+ Nothing
+ ]
+ )
+ ( Raw.VPAdj
+ ( Raw.Adj
+ reflexiveLocation
+ reflexivePattern
+ []
+ :| []
+ )
+ )
+
+nestedShadowingStatement :: Raw.Stmt
+nestedShadowingStatement =
+ Raw.StmtVerbPhrase
+ ( quantifiedSetTerm
+ Raw.Universally
+ outerBinderLocation
+ (Just
+ (Raw.NamedVarAt outerBinderLocation "shadow"))
+ []
+ Nothing
+ :| []
+ )
+ ( equalityVerbPhrase
+ nestedEqualityLocation
+ ( Raw.TermQuantified
+ Raw.Existentially
+ innerBinderLocation
+ ( Raw.NounPhrase
+ []
+ (setNoun innerBinderLocation)
+ (Just
+ (Raw.NamedVarAt
+ innerBinderLocation
+ "shadow"))
+ []
+ (Just
+ (equalityStatement
+ nestedSuchThatLocation
+ "shadow"
+ "shadow"))
+ )
+ )
+ )
+
+quantifiedSetTerm
+ :: Raw.Quantifier
+ -> Location
+ -> Maybe Raw.VarSymbol
+ -> [Raw.AdjL]
+ -> Maybe Raw.Stmt
+ -> Raw.Term
+quantifiedSetTerm quantifier location mayName leftAdjectives maySuchThat =
+ Raw.TermQuantified
+ quantifier
+ location
+ ( Raw.NounPhrase
+ leftAdjectives
+ (setNoun location)
+ mayName
+ []
+ maySuchThat
+ )
+
+setNoun :: Location -> Raw.Noun
+setNoun location =
+ Raw.Noun location setNounPattern []
+
+rawTermVar :: Location -> Text -> Raw.Term
+rawTermVar location name =
+ Raw.TermExpr (rawVarAt location name)
+
+equalityStatement :: Location -> Text -> Text -> Raw.Stmt
+equalityStatement location leftName rightName =
+ Raw.StmtVerbPhrase
+ (rawTermVar location leftName :| [])
+ (equalityVerbPhrase
+ location
+ (rawTermVar location rightName))
+
+equalityVerbPhrase :: Location -> Raw.Term -> Raw.VerbPhrase
+equalityVerbPhrase location argument =
+ Raw.VPAdj
+ ( Raw.Adj
+ location
+ equalityPattern
+ [argument]
+ :| []
+ )
+
+setNounPattern :: Raw.LexicalItemSgPl
+setNounPattern =
+ Raw.mkLexicalItemSgPl
+ (unsafeReadPhraseSgPl "set[/s]")
+ "set"
+
+subsetNounPattern :: Raw.LexicalItemSgPl
+subsetNounPattern =
+ Raw.mkLexicalItemSgPl
+ (unsafeReadPhraseSgPl "subset[/s] of ?")
+ "test_subset"
+
+modifierPattern :: Raw.LexicalItem
+modifierPattern =
+ Raw.mkLexicalItem
+ (unsafeReadPhrase "related to ?")
+ "test_modifier"
+
+equalityPattern :: Raw.LexicalItem
+equalityPattern =
+ Raw.mkLexicalItem
+ (unsafeReadPhrase "equal to ?")
+ "eq"
+
+reflexivePattern :: Raw.LexicalItem
+reflexivePattern =
+ Raw.mkLexicalItem
+ (unsafeReadPhrase "reflexive")
+ "test_reflexive"
+
+adapterBodySymbol :: Raw.FunctionSymbol
+adapterBodySymbol =
+ testFunctionSymbol "adapter_body" 3
+
+adapterAmbientVariable :: Sem.VarSymbol
+adapterAmbientVariable =
+ Sem.FreshVar 0
+
+binderLocation, equalityLocation, nounLocation, ambientLocation :: Location
+binderLocation = mkLocation (FileId 48) 2 7
+equalityLocation = mkLocation (FileId 48) 2 24
+nounLocation = mkLocation (FileId 48) 3 7
+ambientLocation = mkLocation (FileId 48) 3 20
+
+modifierLocation, suchThatLocation, reflexiveLocation :: Location
+modifierLocation = mkLocation (FileId 48) 3 27
+suchThatLocation = mkLocation (FileId 48) 3 42
+reflexiveLocation = mkLocation (FileId 48) 4 17
+
+firstSiblingLocation, secondSiblingLocation :: Location
+firstSiblingLocation = mkLocation (FileId 48) 5 7
+secondSiblingLocation = mkLocation (FileId 48) 5 24
+
+outerBinderLocation, innerBinderLocation :: Location
+outerBinderLocation = mkLocation (FileId 48) 6 7
+innerBinderLocation = mkLocation (FileId 48) 6 31
+
+nestedSuchThatLocation, nestedEqualityLocation :: Location
+nestedSuchThatLocation = mkLocation (FileId 48) 6 45
+nestedEqualityLocation = mkLocation (FileId 48) 6 20
+
+firstAdapterLocation, secondAdapterLocation :: Location
+firstAdapterLocation = mkLocation (FileId 48) 7 7
+secondAdapterLocation = mkLocation (FileId 48) 7 19
+
+alternateFirstLocation, alternateSecondLocation :: Location
+alternateFirstLocation = mkLocation (FileId 48) 8 7
+alternateSecondLocation = mkLocation (FileId 48) 8 19
+
+replacementFile :: FileId
+replacementFile = FileId 47
+
+replacementLocation :: Location
+replacementLocation = mkLocation replacementFile 2 1
+
+iotaLocation :: Location
+iotaLocation = mkLocation (FileId 49) 3 5
+
+definiteFunctionLocation :: Location
+definiteFunctionLocation = mkLocation (FileId 49) 4 9
+
+proofLocalFunctionDefinitionMismatches :: Assertion
+proofLocalFunctionDefinitionMismatches =
+ for_ mismatchCases \(label, proof, expectedError) ->
+ assertEqual
+ label
+ (Left expectedError)
+ (meaning
+ [ Raw.BlockProof
+ proofLocation
+ proof
+ proofLocation
+ ])
+ where
+ mismatchCases =
+ [ ( "argument and domain binder"
+ , Raw.DefineFunction
+ proofLocation
+ "f"
+ "x"
+ (Raw.ExprVar "x")
+ "y"
+ (Raw.ExprVar "domain")
+ (Raw.Omitted proofLocation)
+ , GlossProofFunctionArgumentMismatch
+ proofLocation
+ "x"
+ "y"
+ )
+ , ( "declared and defined function name"
+ , Raw.DefineFunctionLocal
+ proofLocation
+ "f"
+ "domain"
+ (Raw.ExprVar "range")
+ "g"
+ "x"
+ ( ( Raw.ExprVar "x"
+ , Raw.PropositionalConstant
+ proofLocation
+ Raw.IsTop
+ )
+ :| []
+ )
+ (Raw.Omitted proofLocation)
+ , GlossProofFunctionNameMismatch
+ proofLocation
+ "f"
+ "g"
+ )
+ ]
+ proofLocation = mkLocation (FileId 46) 5 9
+
+quantifiedFunctionalDefinition :: Assertion
+quantifiedFunctionalDefinition =
+ case meaning [definitionBlock] of
+ Left
+ (GlossDefnError
+ actualLocation
+ DefnErrorQuantifiedRhsTerm
+ actualMarker) -> do
+ assertEqual
+ "quantified term location"
+ termLocation
+ actualLocation
+ assertEqual
+ "definition marker"
+ definitionMarker
+ actualMarker
+ Left err ->
+ assertFailure
+ ("expected a quantified definition term error, got "
+ <> show err)
+ Right _ ->
+ assertFailure
+ "expected quantified definition term validation to fail"
+ where
+ definitionBlock =
+ Raw.BlockDefn
+ blockLocation
+ Nothing
+ definitionMarker
+ ( Raw.DefnFun
+ []
+ ( Raw.Fun
+ blockLocation
+ ( Raw.mkLexicalItemSgPl
+ (unsafeReadPhraseSgPl "value[/s] of ?")
+ "quantified_function"
+ )
+ ["argument"]
+ )
+ Nothing
+ ( Raw.TermQuantified
+ Raw.Existentially
+ termLocation
+ ( Raw.NounPhrase
+ []
+ ( Raw.Noun
+ termLocation
+ ( Raw.mkLexicalItemSgPl
+ (unsafeReadPhraseSgPl "set[/s]")
+ "set"
+ )
+ []
+ )
+ Nothing
+ []
+ Nothing
+ )
+ )
+ )
+ definitionMarker = "quantified_definition"
+ blockLocation = mkLocation (FileId 44) 8 1
+ termLocation = mkLocation (FileId 44) 8 29
+
+duplicateAbbreviationParameters :: Assertion
+duplicateAbbreviationParameters = do
+ let marker = "duplicate_abbreviation"
+ duplicate = Raw.NamedVar "duplicate"
+ expectAbbreviationError
+ marker
+ [duplicate, duplicate]
+ (Raw.ExprVar duplicate)
+ \case
+ DuplicateAbbreviationParameters actualVariables ->
+ assertEqual
+ "duplicate parameter names"
+ (duplicate :| [])
+ actualVariables
+ err ->
+ assertFailure
+ ("expected duplicate abbreviation parameters, got "
+ <> show err)
+
+freeAbbreviationBodyVariable :: Assertion
+freeAbbreviationBodyVariable = do
+ let marker = "free_abbreviation_body"
+ freeVariable = Raw.NamedVar "free"
+ expectAbbreviationError
+ marker
+ ["parameter"]
+ (Raw.ExprVar freeVariable)
+ \case
+ FreeAbbreviationBodyVariables actualVariables ->
+ assertEqual
+ "free body variable names"
+ (freeVariable :| [])
+ actualVariables
+ err ->
+ assertFailure
+ ("expected free abbreviation variables, got "
+ <> show err)
+
+orderedAbbreviationParameters :: Assertion
+orderedAbbreviationParameters = do
+ let first = Raw.NamedVar "first"
+ second = Raw.NamedVar "second"
+ third = Raw.NamedVar "third"
+ firstArgument = closedInteger 11
+ secondArgument = closedInteger 22
+ thirdArgument = closedInteger 33
+ arguments =
+ [ firstArgument
+ , secondArgument
+ , thirdArgument
+ ]
+ expectedBody =
+ Sem.TermSymbol
+ abbreviationLocation
+ (Sem.SymbolMixfix abbreviationBodySymbol)
+ [thirdArgument, firstArgument, secondArgument]
+ unexpectedArgument = closedInteger (-1)
+ case meaning
+ [ abbreviationBlock
+ abbreviationLocation
+ "ordered_abbreviation"
+ [first, second, third]
+ ( Raw.ExprOp
+ abbreviationLocation
+ abbreviationBodySymbol
+ [ Raw.ExprVar third
+ , Raw.ExprVar first
+ , Raw.ExprVar second
+ ]
+ )
+ ] of
+ Right
+ [Sem.BlockAbbr
+ _actualLocation
+ _actualMarker
+ (Sem.Abbreviation _actualSymbol scope)] ->
+ assertEqual
+ "instantiated abbreviation body"
+ expectedBody
+ ( instantiate
+ (\parameterIndex ->
+ nth parameterIndex arguments
+ ?? unexpectedArgument)
+ scope
+ )
+ Right blocks ->
+ assertFailure
+ ("expected one glossed abbreviation, got "
+ <> show blocks)
+ Left err ->
+ assertFailure
+ ("expected a valid abbreviation, got "
+ <> show err)
+
+expectAbbreviationError
+ :: Raw.Marker
+ -> [Raw.VarSymbol]
+ -> Raw.Expr
+ -> (AbbreviationParameterError -> Assertion)
+ -> Assertion
+expectAbbreviationError marker parameters body checkError =
+ case meaning
+ [ abbreviationBlock
+ abbreviationLocation
+ marker
+ parameters
+ body
+ ] of
+ Left
+ (GlossAbbreviationError
+ actualLocation
+ actualMarker
+ abbreviationError) -> do
+ assertEqual
+ "abbreviation location"
+ abbreviationLocation
+ actualLocation
+ assertEqual
+ "abbreviation marker"
+ marker
+ actualMarker
+ checkError abbreviationError
+ Left err ->
+ assertFailure
+ ("expected an abbreviation parameter error, got "
+ <> show err)
+ Right _ ->
+ assertFailure
+ "expected abbreviation parameter validation to fail"
+
+abbreviationBlock
+ :: Location
+ -> Raw.Marker
+ -> [Raw.VarSymbol]
+ -> Raw.Expr
+ -> Raw.Block
+abbreviationBlock location marker parameters body =
+ Raw.BlockAbbr
+ location
+ Nothing
+ marker
+ ( Raw.AbbreviationEq
+ ( Raw.SymbolPattern
+ (testFunctionSymbol
+ "abbreviation_head"
+ (length parameters))
+ parameters
+ )
+ body
+ )
+
+abbreviationBodySymbol :: Raw.FunctionSymbol
+abbreviationBodySymbol =
+ testFunctionSymbol "abbreviation_body" 3
+
+testFunctionSymbol :: Text -> Int -> Raw.FunctionSymbol
+testFunctionSymbol name arity =
+ Raw.mkMixfixItem
+ (Just (Raw.Command name) : replicate arity Nothing)
+ (Raw.Marker name)
+ Raw.NonAssoc
+
+closedInteger :: Int -> Sem.ExprOf a
+closedInteger value =
+ Sem.TermSymbol
+ Nowhere
+ (Sem.SymbolInteger value)
+ []
+
+abbreviationLocation :: Location
+abbreviationLocation =
+ mkLocation (FileId 43) 8 12
+
+relationClaim :: Int -> Raw.Block
+relationClaim actualParameterCount =
+ Raw.BlockClaim
+ Raw.Proposition
+ relationLocation
+ Nothing
+ "relation_arity"
+ (Raw.Claim []
+ (Raw.StmtFormula
+ (Raw.FormulaChain
+ (Raw.ChainBase
+ (Raw.ExprVar "x" :| [])
+ Raw.Positive
+ (Raw.Relation
+ relationLocation
+ relationSymbol
+ (replicate
+ actualParameterCount
+ (Raw.ExprVar "p")))
+ (Raw.ExprVar "y" :| [])))))
+
+relationSymbol :: Raw.RelationSymbol
+relationSymbol =
+ Raw.RelationSymbol
+ (Raw.Command "parametric")
+ (Raw.ParameterArity 1)
+ "parametric"
+
+relationLocation :: Location
+relationLocation = mkLocation (FileId 42) 7 11
diff --git a/source/Felix/Test/Unit/Module.hs b/source/Felix/Test/Unit/Module.hs
new file mode 100644
index 0000000..44f0ccb
--- /dev/null
+++ b/source/Felix/Test/Unit/Module.hs
@@ -0,0 +1,9759 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Test.Unit.Module (unitTests) where
+
+import Base
+import Felix.Checking.Authority qualified as Authority
+import Felix.Checking.Backend.Problem qualified as Backend
+import Felix.Checking.Core qualified as Core
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Exact qualified as Exact
+import Felix.Checking.Exact.Datatype qualified as ExactDatatype
+import Felix.Checking.Exact.Inductive qualified as ExactInductive
+import Felix.Checking.Exact.Proof qualified as ExactProof
+import Felix.Checking.FinalPrelude qualified as FinalPrelude
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Checking.Identity qualified as Identity
+import Felix.Checking.Module qualified as Module
+import Felix.Checking.Semantic qualified as Semantic
+import Felix.Checking.Typed.Inductive qualified as TypedInductive
+import Felix.CommandLine qualified as CommandLine
+import Felix.Math.Codec
+import Felix.Module
+import Felix.Parse qualified as Parse
+import Felix.Prelude qualified as Prelude
+import Felix.Provers qualified as Provers
+import Felix.Report.Location
+import Felix.Source
+import Felix.Source.Content qualified as Content
+import Felix.Store qualified as Store
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Interface qualified as Syntax
+import Felix.Syntax.Internal qualified as Internal
+import Felix.Syntax.Lexicon qualified as Lexicon
+import Felix.Syntax.Pragma qualified as Pragma
+import Felix.Verification qualified as Verification
+import Felix.Workspace qualified as Workspace
+import Paths_felix qualified as Paths
+
+import Control.Concurrent (threadDelay)
+import Control.Concurrent.STM
+ ( atomically
+ , check
+ , newEmptyTMVarIO
+ , newTQueueIO
+ , newTVarIO
+ , putTMVar
+ , readTQueue
+ , readTVar
+ , takeTMVar
+ , tryReadTMVar
+ , tryReadTQueue
+ , writeTQueue
+ , writeTVar
+ )
+import Control.Exception (bracket)
+import Control.Exception qualified as Exception
+import Control.Monad (foldM, when)
+import Data.ByteString qualified as ByteString
+import Data.Text qualified as StrictText
+import Data.Text.Encoding qualified as Text
+import Data.IORef
+ ( IORef
+ , atomicModifyIORef'
+ , modifyIORef'
+ , newIORef
+ , readIORef
+ )
+import Data.List (sort)
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+import System.Directory
+ ( createDirectoryIfMissing
+ , doesFileExist
+ , getCurrentDirectory
+ , getPermissions
+ , setOwnerExecutable
+ , setPermissions
+ )
+import System.FilePath.Posix qualified as Posix
+import System.IO.Temp qualified as Temp
+import System.Timeout qualified as Timeout
+import Test.Tasty
+import Test.Tasty.HUnit
+import UnliftIO.Async (withAsync, wait)
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Typed module inputs"
+ [ testCase "constructs the empty bootstrap ordinarily"
+ constructsEmptyBootstrap
+ , testCase "identifies comment-only reserved input"
+ identifiesCommentOnlyInput
+ , testCase "loads and parses the packaged final prelude"
+ parsesPackagedFinalPrelude
+ , testCase "renders packaged final-prelude failures"
+ rendersPackagedPreludeFailures
+ , testCase "confines exact foundation-leaf completion"
+ confinesFoundationLeafCompletion
+ , testCase "builds the confined final prelude"
+ buildsConfinedFinalPrelude
+ , testCase "publishes the final prelude as an ordinary sealed root"
+ publishesFinalPreludeRoot
+ , testCase "retains exact omitted-proof locations"
+ retainsExactOmittedProofLocation
+ , testCase "coalesces syntax without collapsing semantic imports"
+ coalescesSharedDirectSyntax
+ , testCase "makes selected source errors terminal"
+ rejectsUnsupportedTypedSource
+ , testCase "reuses one verification session for successive checks"
+ reusesVerificationSession
+ , testCase "compiles exact declarations across an import"
+ compilesExactDeclarationGraph
+ , testCase "compiles and imports exact structures"
+ compilesExactStructures
+ , testCase "compiles and caches contextual abbreviations"
+ compilesContextualAbbreviations
+ , testCase "rejects an unknown exact structure parent atomically"
+ rejectsUnknownExactStructureParent
+ , testCase "compiles exact relation expressions"
+ compilesExactRelationExpressions
+ , testCase "resolves source-owned set application"
+ resolvesSourceOwnedApplication
+ , testCase "scopes quantified terms in proposition contexts"
+ confinesExactQuantifiedTerms
+ , testCase "closes the exact definition declaration boundary"
+ closesExactDefinitionDeclarationBoundary
+ , testCase "compiles exact ordinary proofs"
+ compilesExactOrdinaryProofs
+ , testCase "restores exact binder and witness proof forms"
+ restoresExactBinderAndWitnessProofForms
+ , testCase "restores exact local reasoning and calculations"
+ restoresExactLocalReasoningAndCalculations
+ , testCase "selects calculation link failures by source order"
+ selectsCalculationLinkFailureBySourceOrder
+ , testCase "compiles and reuses proof-local set definitions"
+ compilesAndReusesProofLocalSetDefinitions
+ , testCase "compiles and reuses proof-local function graphs"
+ compilesAndReusesProofLocalFunctionGraphs
+ , testCase "restores exact cases and classical contradiction"
+ confinesTerminalExactContradiction
+ , testCase "compiles exact separation comprehensions"
+ compilesExactSeparationComprehensions
+ , testCase "compiles exact replacement comprehensions"
+ compilesExactReplacementComprehensions
+ , testCase "compiles and reuses relational replacement"
+ compilesAndReusesRelationalReplacement
+ , testCase "compiles and reuses exact finite sets"
+ compilesAndReusesExactFiniteSets
+ , testCase "prepares exact deterministic datatypes"
+ preparesExactDatatypes
+ , testCase "rejects nested exact datatype recursion"
+ rejectsNestedExactDatatypeRecursion
+ , testCase "compiles and reuses exact datatypes"
+ compilesAndReusesExactDatatypes
+ , testCase "prepares exact direct inductives"
+ preparesExactDirectInductives
+ , testCase "prepares nested exact inductive recursion"
+ preparesNestedExactInductiveRecursion
+ , testCase "compiles transparent nested inductive wrappers"
+ compilesTransparentNestedInductiveWrappers
+ , testCase "normalizes nested exact inductive contexts"
+ normalizesNestedExactInductiveContexts
+ , testCase "compiles and reuses exact inductives"
+ compilesAndReusesExactInductives
+ , testCase "authorizes recursive exact inductives"
+ authorizesRecursiveExactInductives
+ , testCase "reuses exact separation validation"
+ reusesExactSeparationValidation
+ , testCase "compiles exact source axioms"
+ compilesExactSourceAxioms
+ , testCase "does not treat marker-only nouns as the fixed set noun"
+ doesNotTreatMarkerOnlyNounAsSet
+ , testCase "rejects proof-local generalization"
+ rejectsProofLocalGeneralization
+ , testCase "restores checked set induction"
+ restoresCheckedSetInduction
+ , testCase "compiles exact omitted proofs"
+ compilesExactOmittedProofs
+ , testCase "propagates and reuses exact escape authority"
+ reusesExactEscapeAuthority
+ , testCase "checks continuations after omitted subclaims"
+ rejectsAfterExactOmittedSubclaim
+ , testCase "reuses exact proof validation across module misses"
+ reusesExactProofValidationAcrossModuleMisses
+ , testCase "rejects declarations of fixed semantics"
+ rejectsFixedSemanticDeclaration
+ , testCase "rejects inductive carriers with fixed semantics"
+ rejectsFixedSemanticInductive
+ , testCase "keeps exact semantics independent of fixity"
+ keepsExactSemanticsIndependentOfFixity
+ , testCase "loads a cached exact producer for a fresh importer"
+ loadsCachedExactProducerForFreshImporter
+ , testCase "reports admitted source escapes on fresh, warm, and failure paths"
+ reportsAdmittedSourceEscapes
+ , testCase "selects concurrent module failures by source order"
+ selectsConcurrentModuleFailureDeterministically
+ , testCase "batches independent structure obligations atomically"
+ batchesStructureObligationsAtomically
+ , testCase "speculates dependent proof obligations without admitting ahead"
+ speculatesDependentProofObligationsWithoutAdmittingAhead
+ , testCase "starts diamond consumers after sealed acknowledgements"
+ schedulesDiamondAfterSealedImports
+ , testCase "classifies typed Vampire failures conservatively"
+ classifiesTypedVampireFailures
+ , testCase "retains the exact prefix before a later failure"
+ retainsExactPrefixBeforeFailure
+ , testCase "routes every production root through exact checking"
+ routesProductionVerification
+ , testCase "installs nonempty implicit prelude evidence"
+ installsNonemptyImplicitPreludeEvidence
+ ]
+
+constructsEmptyBootstrap :: Assertion
+constructsEmptyBootstrap = do
+ foundation <- expectRight Foundation.checkedFoundation
+ result <-
+ Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ session <- expectRight result
+ let input = Module.bootstrapPreludeInput session
+ parsed = Module.identifiedModuleParsed input
+ sealed = Module.bootstrapPreludeModule session
+ syntax = Module.sealedTypedModuleSyntax sealed
+ semantic = Module.sealedTypedModuleSemantic sealed
+ assertEqual "reserved owner"
+ preludeModuleName
+ (Module.identifiedModuleOwner input)
+ case Module.identifiedModuleBinding input of
+ Module.ReservedModuleBinding fileId label -> do
+ assertEqual "diagnostic label"
+ Prelude.preludeDiagnosticLabel
+ label
+ assertEqual "registered display label"
+ (Just Prelude.preludeDiagnosticLabel)
+ (lookupFilePath fileId)
+ assertEqual "registered identity label"
+ (Just Prelude.preludeDiagnosticLabel)
+ (lookupFileIdentityPath fileId)
+ Module.PhysicalModuleBinding source ->
+ assertFailure
+ ("bootstrap acquired a physical source: " <> show source)
+ assertEqual "empty parsed blocks"
+ []
+ (Parse.identifiedParsedModuleBlocks parsed)
+ assertEqual "exact empty source identity"
+ (Content.sourceContentIdBytes ByteString.empty)
+ (Parse.identifiedParsedModuleSourceContentId parsed)
+ assertEqual "no syntax imports"
+ []
+ (Syntax.moduleSyntaxDirectInputs syntax)
+ assertEqual "empty local syntax"
+ []
+ (Syntax.canonicalSyntaxDeltaEntries
+ (Syntax.moduleSyntaxLocalDelta syntax))
+ assertEqual "semantic owner"
+ preludeModuleName
+ (Semantic.semanticInterfaceOwner semantic)
+ assertEqual "no semantic imports"
+ []
+ (Semantic.semanticInterfaceDirectInputs semantic)
+ assertEqual "no semantic declarations"
+ []
+ (Semantic.semanticInterfaceDeclarations semantic)
+ expectedPrefix <-
+ expectRight
+ (Semantic.initialPrefixContextId
+ (Identity.theoryId foundation)
+ preludeModuleName
+ [])
+ assertEqual "empty sealed prefix"
+ expectedPrefix
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix sealed))
+
+identifiesCommentOnlyInput :: Assertion
+identifiesCommentOnlyInput = do
+ emptySource <-
+ expectRight
+ =<< Prelude.parseReservedPreludeSource
+ Prelude.emptyBootstrapSourceInput
+ source <-
+ expectRight
+ (Prelude.reservedPreludeSourceInput
+ (Text.encodeUtf8 "% an in-memory comment\n"))
+ first <-
+ expectRight
+ =<< Prelude.parseReservedPreludeSource source
+ second <-
+ expectRight
+ =<< Prelude.parseReservedPreludeSource source
+ let emptyParsed = Prelude.reservedParsedPreludeModule emptySource
+ firstParsed = Prelude.reservedParsedPreludeModule first
+ secondParsed = Prelude.reservedParsedPreludeModule second
+ assertEqual "reserved live source binding"
+ Parse.FreshReservedSource
+ (Parse.freshModuleInputBinding
+ (Prelude.reservedParsedPreludeInput first))
+ assertEqual "comment-only source has no blocks"
+ []
+ (Parse.identifiedParsedModuleBlocks firstParsed)
+ assertBool "content changes parsed identity"
+ (Parse.identifiedParsedModuleId emptyParsed
+ /= Parse.identifiedParsedModuleId firstParsed)
+ assertEqual "same input has stable parsed identity"
+ (Parse.identifiedParsedModuleId firstParsed)
+ (Parse.identifiedParsedModuleId secondParsed)
+ assertEqual "comments do not change syntax"
+ (Syntax.moduleSyntaxAssertedId
+ (Parse.identifiedParsedModuleSyntaxInterface emptyParsed))
+ (Syntax.moduleSyntaxAssertedId
+ (Parse.identifiedParsedModuleSyntaxInterface firstParsed))
+
+parsesPackagedFinalPrelude :: Assertion
+parsesPackagedFinalPrelude = do
+ path <- Paths.getDataFileName "data/felix-prelude.tex"
+ expectedBytes <- ByteString.readFile path
+ source <- expectRight =<< Prelude.loadReservedPreludeSourceInput
+ assertEqual "exact packaged bytes"
+ expectedBytes
+ (Prelude.reservedPreludeSourceBytes source)
+ assertEqual "reserved owner"
+ preludeModuleName
+ (Prelude.reservedPreludeSourceOwner source)
+ assertEqual "diagnostic label"
+ Prelude.preludeDiagnosticLabel
+ (Prelude.reservedPreludeSourceLabel source)
+ first <- expectRight =<< Prelude.parseReservedPreludeSource source
+ second <- expectRight =<< Prelude.parseReservedPreludeSource source
+ let firstInput = Prelude.reservedParsedPreludeInput first
+ firstParsed = Prelude.reservedParsedPreludeModule first
+ secondParsed = Prelude.reservedParsedPreludeModule second
+ assertEqual "no textual imports"
+ []
+ (Parse.freshModuleInputImports firstInput)
+ assertBool "declaration-bearing source"
+ (not (null (Parse.identifiedParsedModuleBlocks firstParsed)))
+ assertEqual "deterministic syntax interface"
+ (Syntax.moduleSyntaxAssertedId
+ (Parse.identifiedParsedModuleSyntaxInterface firstParsed))
+ (Syntax.moduleSyntaxAssertedId
+ (Parse.identifiedParsedModuleSyntaxInterface secondParsed))
+
+rendersPackagedPreludeFailures :: Assertion
+rendersPackagedPreludeFailures = do
+ assertEqual "load failure"
+ "/missing/felix-prelude.tex: unable to read packaged final prelude: not found"
+ (Prelude.renderPreludeLoadError
+ (Prelude.PreludeSourceReadFailed
+ "/missing/felix-prelude.tex"
+ "not found"))
+ assertEqual "located syntax failure"
+ "<felix-prelude>: syntax pragma location is out of range at 7:3"
+ (Prelude.renderPreludeParseError parseFailure)
+ assertEqual "authority-free API presentation"
+ ("packaged final prelude parsing failed: "
+ <> "<felix-prelude>: syntax pragma location is out of range at 7:3")
+ (Workspace.renderAuthorityFreeParseError
+ (Workspace.AuthorityFreePreludeParseFailed parseFailure))
+ where
+ parseFailure =
+ Prelude.PreludeSyntaxPragmaFailed
+ (Pragma.SyntaxPragmaLocationOutOfRange
+ Prelude.preludeDiagnosticLabel
+ 7
+ 3)
+
+confinesFoundationLeafCompletion :: Assertion
+confinesFoundationLeafCompletion = do
+ foundation <- expectRight Foundation.checkedFoundation
+ packaged <- expectRight =<< Prelude.loadReservedPreludeSourceInput
+ parsed <- expectRight =<< Prelude.parseReservedPreludeSource packaged
+ matching <- sole "matching foundation claim"
+ (take 1
+ (Parse.identifiedParsedModuleBlocks
+ (Prelude.reservedParsedPreludeModule parsed)))
+ mismatchInput <-
+ expectRight
+ (Prelude.reservedPreludeSourceInput
+ (Text.encodeUtf8
+ "\\begin{proposition}\\label{not_foundation}\n $\\emptyset = \\emptyset$.\n\\end{proposition}\n"))
+ mismatchParsed <-
+ expectRight =<< Prelude.parseReservedPreludeSource mismatchInput
+ mismatch <- sole "mismatching claim"
+ (Parse.identifiedParsedModuleBlocks
+ (Prelude.reservedParsedPreludeModule mismatchParsed))
+ outcome <-
+ Declaration.runModuleDriver
+ foundation
+ preludeModuleName
+ []
+ unusedResolver
+ Declaration.FreshValidation do
+ explicit <-
+ admitFoundationClaim
+ foundation
+ matching
+ (Just (Raw.Omitted (locate matching)))
+ nonmatching <-
+ admitFoundationClaim
+ foundation
+ mismatch
+ Nothing
+ committed <-
+ admitFoundationClaim
+ foundation
+ matching
+ Nothing
+ pure (explicit, nonmatching, committed)
+ case outcome of
+ Right (Declaration.DriverSucceeded
+ (explicit, nonmatching, committed)
+ _semantic _prefix _closure) -> do
+ case explicit of
+ Left ExactProof.ExactProofFoundationLeafRequiresImplicitAuto{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("explicit foundation result: " <> show other)
+ Right{} ->
+ assertFailure "explicit foundation proof was accepted"
+ batch <- expectRight committed
+ case nonmatching of
+ Left ExactProof.ExactProofFoundationLeafTargetMismatch{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("mismatching foundation result: " <> show other)
+ Right{} ->
+ assertFailure "mismatching foundation claim was accepted"
+ assertEqual "foundation tag"
+ Foundation.UnivOfContains
+ (case Declaration.committedBatchProofValidations batch of
+ [record] ->
+ case Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate
+ record) of
+ Authority.CheckedKernelConstruction
+ (Authority.FoundationLeaf tag) ->
+ tag
+ authorization ->
+ error
+ ("unexpected foundation authorization: "
+ <> show authorization)
+ records ->
+ error
+ ("unexpected foundation validation count: "
+ <> show (length records)))
+ Right Declaration.DriverFailed{} ->
+ assertFailure "foundation driver failed"
+ Right (Declaration.DriverSealFailed failure _prefix) ->
+ assertFailure ("foundation driver did not seal: " <> show failure)
+ Left failure ->
+ assertFailure ("foundation driver did not open: " <> show failure)
+ where
+ admitFoundationClaim foundation block proof =
+ Declaration.runProspectiveLoweringDriver
+ (ExactProof.prepareFinalPreludeFoundationClaim
+ foundation block proof) >>= \case
+ Left failure -> pure (Left failure)
+ Right prepared -> do
+ lowered <-
+ Declaration.runProspectiveLoweringDriver
+ (ExactProof.lowerPreparedFinalPreludeFoundationClaim
+ prepared)
+ checked <-
+ either Declaration.failDeclarationDriver pure lowered
+ batch <-
+ Declaration.admitCheckedDeclaration
+ checked
+ ExactProof.authorizeCheckedFinalPreludeFoundationClaim
+ pure (Right batch)
+
+buildsConfinedFinalPrelude :: Assertion
+buildsConfinedFinalPrelude = do
+ foundation <- expectRight Foundation.checkedFoundation
+ FinalPrelude.buildFinalPreludeCandidate
+ foundation finalPreludeResolver >>= \case
+ FinalPrelude.FinalPreludeBuilt candidate -> do
+ assertEqual "confined semantic owner"
+ preludeModuleName
+ (Semantic.semanticInterfaceOwner
+ (FinalPrelude.finalPreludeSemantic candidate))
+ assertEqual "confined semantic imports"
+ []
+ (Semantic.semanticInterfaceDirectInputs
+ (FinalPrelude.finalPreludeSemantic candidate))
+ let baseDeltas =
+ [ delta
+ | delta <- Semantic.semanticInterfaceDeclarations
+ (FinalPrelude.finalPreludeSemantic candidate)
+ , not
+ (null
+ (Semantic.semanticEnvironmentStructures
+ (Semantic.declarationDeltaEnvironment delta)))
+ ]
+ case baseDeltas of
+ [delta] -> do
+ assertEqual "base structure has no facts"
+ []
+ (Semantic.declarationDeltaFacts delta)
+ assertEqual "base structure has no propositions"
+ []
+ (Semantic.declarationDeltaPropositions delta)
+ case Semantic.semanticEnvironmentStructures
+ (Semantic.declarationDeltaEnvironment delta) of
+ [descriptor] -> do
+ assertEqual "base structure is metadata-only"
+ Nothing
+ (Semantic.semanticStructureDescriptorPredicate
+ descriptor)
+ case Semantic.semanticStructureDescriptorOperations
+ descriptor of
+ [operation] ->
+ case Identity.lookupCheckedObjectContent
+ (Semantic.semanticStructureOperationObject
+ operation)
+ (FinalPrelude.finalPreludeObjects candidate) of
+ Just Identity.OpaqueObjectContent{} -> pure ()
+ content ->
+ assertFailure
+ ("expected opaque carrier, got "
+ <> show content)
+ operations ->
+ assertFailure
+ ("expected one base operation, got "
+ <> show operations)
+ descriptors ->
+ assertFailure
+ ("expected one base descriptor, got "
+ <> show descriptors)
+ deltas ->
+ assertFailure
+ ("expected one base structure delta, got "
+ <> show (length deltas))
+ let role roleName =
+ maybe
+ (assertFailure
+ ("missing final-prelude role "
+ <> show roleName))
+ pure
+ (FinalPrelude.finalPreludePublicRole
+ candidate roleName)
+ omega <- role FinalPrelude.PreludeOmegaObject
+ naturals <- role FinalPrelude.PreludeNaturalsAlias
+ assertEqual "naturals expands to Omega"
+ omega naturals
+ traverse_
+ (void . role)
+ (Set.toList FinalPrelude.expectedFinalPreludePublicRoles)
+ let foundationTags = Set.fromList
+ [ tag
+ | batch <-
+ Declaration.pendingModulePrefixBatches
+ (FinalPrelude.finalPreludePrefix candidate)
+ , record <-
+ Declaration.committedBatchProofValidations batch
+ , Authority.CheckedKernelConstruction
+ (Authority.FoundationLeaf tag) <-
+ [ Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate
+ record)
+ ]
+ ]
+ assertBool "protected foundation presentation"
+ ( Set.fromList
+ [ Foundation.SetExtensionality
+ , Foundation.EmptyCharacteristic
+ , Foundation.PairSetCharacteristic
+ , Foundation.FamilyUnionCharacteristic
+ ]
+ `Set.isSubsetOf` foundationTags
+ )
+ assertFinalPreludeFoundationAlias
+ candidate
+ "pairset_iff"
+ Foundation.PairSetCharacteristic
+ assertFinalPreludeFoundationAlias
+ candidate
+ "pow_iff"
+ Foundation.PowerSetCharacteristic
+ assertRejectsAdditionalOmegaFact candidate
+ FinalPrelude.FinalPreludeBuildFailed failure prefix ->
+ assertFailure
+ ("final prelude failed after "
+ <> show
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ <> " declarations: "
+ <> show failure)
+ FinalPrelude.FinalPreludeBuildOpenFailed failure ->
+ assertFailure ("final prelude did not open: " <> show failure)
+ FinalPrelude.FinalPreludeSourceLoadFailed failure ->
+ assertFailure ("final prelude did not load: " <> show failure)
+ FinalPrelude.FinalPreludeSourceParseFailed failure ->
+ assertFailure ("final prelude did not parse: " <> show failure)
+
+assertRejectsAdditionalOmegaFact
+ :: FinalPrelude.FinalPreludeCandidate
+ -> Assertion
+assertRejectsAdditionalOmegaFact candidate = do
+ omegaId <-
+ case FinalPrelude.finalPreludePublicRole
+ candidate FinalPrelude.PreludeOmegaObject of
+ Just (FinalPrelude.FinalPreludeObjectRole identity) ->
+ pure identity
+ role ->
+ assertFailure ("unexpected Omega role " <> show role)
+ >> fail "unreachable"
+ batch <- batchByAlias
+ (FinalPrelude.finalPreludePrefix candidate)
+ "prelude_omega"
+ let delta = Declaration.committedBatchDelta batch
+ facts = Semantic.declarationDeltaFacts delta
+ aliases = Semantic.declarationDeltaAliases delta
+ propositions = Declaration.committedBatchPropositions batch
+ certificates <-
+ maybe
+ (assertFailure "Omega declaration validation is absent"
+ >> fail "unreachable")
+ (pure . Semantic.declarationValidationRecordCertificates)
+ (Declaration.committedBatchDeclarationValidation batch)
+ (omegaBody, extensional, descriptor, extraFact, extraProposition,
+ extraCertificate) <-
+ case (facts, propositions, certificates) of
+ ( [_equationFact, extensionalFact]
+ , [equationProposition, extensionalProposition]
+ , [ _equationCertificate
+ , extensionalCertificate
+ ]
+ ) -> do
+ body <- case Core.frozenCoreTerm
+ (Identity.checkedPropositionTerm equationProposition) of
+ Core.CEq Core.TySet
+ (Core.CGlobal identity) candidateBody
+ | identity == omegaId -> pure candidateBody
+ target ->
+ assertFailure
+ ("unexpected Omega equation " <> show target)
+ >> fail "unreachable"
+ constructionDescriptor <-
+ case Authority.validationDirectAuthorization
+ extensionalCertificate of
+ Authority.CheckedKernelConstruction
+ (Authority.CheckedSetConstructionExtensionality
+ identity candidateDescriptor)
+ | identity == omegaId -> pure candidateDescriptor
+ authorization ->
+ assertFailure
+ ("unexpected Omega extensional authority "
+ <> show authorization)
+ >> fail "unreachable"
+ pure
+ ( body
+ , Identity.checkedPropositionTerm extensionalProposition
+ , constructionDescriptor
+ , extensionalFact
+ , extensionalProposition
+ , extensionalCertificate
+ )
+ (candidateFacts, candidatePropositions, candidateCertificates) ->
+ assertFailure
+ ("unexpected Omega inventory shape "
+ <> show
+ ( length candidateFacts
+ , length candidatePropositions
+ , length candidateCertificates
+ ))
+ >> fail "unreachable"
+ case FinalPrelude.validateOmegaFactInventory
+ omegaId omegaBody extensional descriptor
+ (facts <> [extraFact])
+ aliases
+ (propositions <> [extraProposition])
+ (certificates <> [extraCertificate]) of
+ Left (FinalPrelude.FinalPreludeFactContentMismatch
+ "prelude_omega") ->
+ pure ()
+ result ->
+ assertFailure
+ ("additional Omega construction fact was accepted: "
+ <> show result)
+
+publishesFinalPreludeRoot :: Assertion
+publishesFinalPreludeRoot = do
+ foundation <- expectRight Foundation.checkedFoundation
+ Temp.withSystemTempDirectory "felix-final-prelude-root" \directory -> do
+ let path = directory Posix.</> "store.sqlite"
+ theory = Identity.theoryId foundation
+ open = do
+ (_startup, store) <-
+ Store.openStore path theory >>= expectRight
+ pure store
+ bracket open Store.closeStore \store -> do
+ freshMemo <- Store.newStoreMemo store
+ session <-
+ expectRight
+ =<< Module.acquireFinalPreludeSession
+ freshMemo store foundation finalPreludeResolver
+ let input = Module.finalPreludeInput session
+ sealed = Module.finalPreludeModule session
+ syntax = Module.sealedTypedModuleSyntax sealed
+ semantic = Module.sealedTypedModuleSemantic sealed
+ assertEqual "empty store constructs the final-prelude root"
+ Module.ModuleRootMiss
+ (Module.finalPreludeAcquisition session)
+ assertEqual "final prelude owner"
+ preludeModuleName
+ (Module.identifiedModuleOwner input)
+ assertEqual "final prelude has no semantic parents"
+ []
+ (Semantic.semanticInterfaceDirectInputs semantic)
+ warmMemo <- Store.newStoreMemo store
+ warmSession <- expectRight
+ =<< Module.acquireFinalPreludeSession
+ warmMemo store foundation unusedResolver
+ let cached = Module.finalPreludeModule warmSession
+ assertEqual "persisted final-prelude root is a cache hit"
+ Module.ModuleRootHit
+ (Module.finalPreludeAcquisition warmSession)
+ assertEqual "generic root syntax"
+ syntax
+ (Module.sealedTypedModuleSyntax cached)
+ assertEqual "generic root semantics"
+ semantic
+ (Module.sealedTypedModuleSemantic cached)
+ assertEqual "cached base structure descriptor"
+ (semanticStructureDescriptors semantic)
+ (semanticStructureDescriptors
+ (Module.sealedTypedModuleSemantic cached))
+ assertEqual "generic root final prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix sealed))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix cached))
+ visits <- Store.storeMemoVisits warmMemo
+ assertEqual "cached prelude validates one artifact root"
+ 1
+ (Store.storeArtifactsValidated visits)
+
+semanticStructureDescriptors
+ :: Semantic.SemanticInterface
+ -> [Semantic.SemanticStructureDescriptor]
+semanticStructureDescriptors semantic =
+ [ descriptor
+ | delta <- Semantic.semanticInterfaceDeclarations semantic
+ , descriptor <- Semantic.semanticEnvironmentStructures
+ (Semantic.declarationDeltaEnvironment delta)
+ ]
+
+assertTransparentObjectAlias
+ :: Module.SealedTypedModule
+ -> Text
+ -> Assertion
+assertTransparentObjectAlias sealed name = do
+ target <- localObjectAliasTarget sealed name
+ assertEqual
+ ("transparent object for " <> StrictText.unpack name)
+ Identity.TransparentObject
+ (Identity.objectIdFamily target)
+
+localObjectKeyTarget
+ :: Module.SealedTypedModule
+ -> Semantic.SemanticGlobalKey
+ -> IO Identity.ObjectId
+localObjectKeyTarget sealed key = do
+ binding <- sole
+ ("semantic binding for " <> show key)
+ [ candidate
+ | delta <- localSemanticDeltas sealed
+ , candidate <- Semantic.semanticEnvironmentBindings
+ (Semantic.declarationDeltaEnvironment delta)
+ , Semantic.semanticGlobalBindingKey candidate == key
+ ]
+ pure
+ (Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget binding))
+
+localObjectAliasTarget
+ :: Module.SealedTypedModule
+ -> Text
+ -> IO Identity.ObjectId
+localObjectAliasTarget sealed name = do
+ delta <- localDeltaByAlias sealed name
+ binding <- sole
+ ("semantic binding for " <> StrictText.unpack name)
+ (Semantic.semanticEnvironmentBindings
+ (Semantic.declarationDeltaEnvironment delta))
+ pure
+ (Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget binding))
+
+checkedPropositionTermByAlias
+ :: Module.SealedTypedModule
+ -> Text
+ -> IO (Core.FrozenCheckedCore Identity.ObjectId)
+checkedPropositionTermByAlias sealed name = do
+ batch <- batchByAlias
+ (Module.sealedTypedModulePrefix sealed)
+ name
+ alias <- sole
+ ("semantic alias for " <> StrictText.unpack name)
+ [ candidate
+ | candidate <- Semantic.declarationDeltaAliases
+ (Declaration.committedBatchDelta batch)
+ , Semantic.semanticAliasName candidate
+ == Semantic.semanticName name
+ ]
+ occurrence <- sole
+ ("semantic fact for " <> StrictText.unpack name)
+ [ candidate
+ | candidate <- Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch)
+ , Semantic.semanticFactFingerprint candidate
+ == Semantic.semanticAliasTarget alias
+ ]
+ proposition <- sole
+ ("checked proposition for " <> StrictText.unpack name)
+ [ candidate
+ | candidate <- Declaration.committedBatchPropositions batch
+ , Identity.checkedPropositionId candidate
+ == Semantic.semanticFactProposition occurrence
+ ]
+ pure (Identity.checkedPropositionTerm proposition)
+
+batchByAlias
+ :: Declaration.PendingModulePrefix
+ -> Text
+ -> IO Declaration.CommittedDeclarationBatch
+batchByAlias prefix name =
+ sole
+ ("declaration batch for " <> StrictText.unpack name)
+ [ batch
+ | batch <- Declaration.pendingModulePrefixBatches prefix
+ , alias <- Semantic.declarationDeltaAliases
+ (Declaration.committedBatchDelta batch)
+ , Semantic.semanticAliasName alias
+ == Semantic.semanticName name
+ ]
+
+assertFinalPreludeFoundationAlias
+ :: FinalPrelude.FinalPreludeCandidate
+ -> Text
+ -> Foundation.FoundationAxiomTag
+ -> Assertion
+assertFinalPreludeFoundationAlias candidate name tag = do
+ batch <- batchByAlias
+ (FinalPrelude.finalPreludePrefix candidate)
+ name
+ fact <- sole
+ ("foundation fact " <> StrictText.unpack name)
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch))
+ assertEqual
+ ("foundation safety for " <> StrictText.unpack name)
+ Authority.cleanAuthoritySafety
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority fact))
+ validation <- sole
+ ("foundation validation for " <> StrictText.unpack name)
+ (Declaration.committedBatchProofValidations batch)
+ assertEqual
+ ("exact foundation authority for " <> StrictText.unpack name)
+ (Authority.CheckedKernelConstruction
+ (Authority.FoundationLeaf tag))
+ (Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate validation))
+
+localDeltaByAlias
+ :: Module.SealedTypedModule
+ -> Text
+ -> IO Semantic.DeclarationInterfaceDelta
+localDeltaByAlias sealed name =
+ sole
+ ("declaration delta for " <> StrictText.unpack name)
+ [ delta
+ | delta <- localSemanticDeltas sealed
+ , any
+ ((== Semantic.semanticName name)
+ . Semantic.semanticAliasName)
+ (Semantic.declarationDeltaAliases delta)
+ ]
+
+localSemanticDeltas
+ :: Module.SealedTypedModule
+ -> [Semantic.DeclarationInterfaceDelta]
+localSemanticDeltas =
+ Semantic.semanticInterfaceDeclarations
+ . Module.sealedTypedModuleSemantic
+
+retainsExactOmittedProofLocation :: Assertion
+retainsExactOmittedProofLocation = do
+ foundation <- expectRight Foundation.checkedFoundation
+ source <-
+ expectRight
+ (Prelude.reservedPreludeSourceInput
+ (Text.encodeUtf8
+ (StrictText.unlines
+ [ "\\begin{proposition}\\label{omitted_location}"
+ , " For all $x$ we have $x = x$."
+ , "\\end{proposition}"
+ , "\\begin{proof}"
+ , " Omitted."
+ , "\\end{proof}"
+ ])))
+ parsed <- expectRight =<< Prelude.parseReservedPreludeSource source
+ let blocks =
+ Parse.identifiedParsedModuleBlocks
+ (Prelude.reservedParsedPreludeModule parsed)
+ claim <- sole "omitted claim"
+ [ block
+ | block@Raw.BlockClaim{} <- blocks
+ ]
+ proof <- sole "omitted proof"
+ [ sourceProof
+ | Raw.BlockProof _location sourceProof _end <- blocks
+ ]
+ outcome <-
+ Declaration.runModuleDriver
+ foundation
+ preludeModuleName
+ []
+ unusedResolver
+ Declaration.FreshValidation do
+ Declaration.runProspectiveLoweringDriver
+ (ExactProof.prepareExactProof claim (Just proof))
+ >>= either Declaration.failModuleDriver pure
+ case outcome of
+ Right (Declaration.DriverSucceeded
+ prepared _semantic prefix _closure) -> do
+ location <-
+ maybe
+ (assertFailure "prepared omitted proof lost its location")
+ pure
+ (ExactProof.preparedExactProofFirstOmission prepared)
+ assertEqual "omitted source line" 5 (locLine location)
+ assertBool "preparation publishes no declaration"
+ (null (Declaration.pendingModulePrefixBatches prefix))
+ Right (Declaration.DriverFailed failure _prefix) ->
+ assertFailure ("omitted preparation failed: " <> show failure)
+ Right (Declaration.DriverSealFailed failure _prefix) ->
+ assertFailure ("omitted preparation did not seal: " <> show failure)
+ Left failure ->
+ assertFailure ("omitted preparation did not open: " <> show failure)
+
+coalescesSharedDirectSyntax :: Assertion
+coalescesSharedDirectSyntax = do
+ foundation <- expectRight Foundation.checkedFoundation
+ session <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ root <- getCurrentDirectory
+ mounts <-
+ expectRight
+ =<< prepareSourceMounts
+ [ (sourceMountId "project", root)
+ , (sourceMountId "library", root Posix.</> "library")
+ , (sourceMountId "debug", root Posix.</> "debug")
+ ]
+ let bootstrapSyntax =
+ Module.sealedTypedModuleSyntax
+ (Module.bootstrapPreludeModule session)
+ syntaxInputs _source = [bootstrapSyntax]
+ request <-
+ expectRight
+ (searchedRoot "test/phase3/typed-shared-root.tex")
+ workspace <-
+ expectRight
+ =<< Parse.parseSourceWorkspaceWithSyntaxInputs
+ mounts
+ request
+ syntaxInputs
+ case Parse.parsedWorkspaceModules workspace of
+ [firstParsed, secondParsed, rootParsed] -> do
+ first <- seal foundation session firstParsed []
+ second <- seal foundation session secondParsed []
+ assertEqual "distinct modules share one syntax interface"
+ (Syntax.moduleSyntaxAssertedId
+ (Module.sealedTypedModuleSyntax first))
+ (Syntax.moduleSyntaxAssertedId
+ (Module.sealedTypedModuleSyntax second))
+ assertBool "semantic module owners remain distinct"
+ (Module.sealedTypedModuleOwner first
+ /= Module.sealedTypedModuleOwner second)
+ assertBool "semantic interfaces remain distinct"
+ (Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic first)
+ /= Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic second))
+ let rootSyntax = Parse.parsedModuleSyntaxInterface rootParsed
+ assertEqual "root coalesces the shared direct syntax"
+ [ Syntax.moduleSyntaxAssertedId bootstrapSyntax
+ , Syntax.moduleSyntaxAssertedId
+ (Module.sealedTypedModuleSyntax first)
+ ]
+ (Syntax.moduleSyntaxDirectInputs rootSyntax)
+ sealedRoot <-
+ seal foundation session rootParsed [first, second]
+ assertEqual "root retains both semantic imports"
+ [ Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic
+ (Module.bootstrapPreludeModule session))
+ , Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic first)
+ , Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic second)
+ ]
+ (Semantic.semanticInterfaceDirectInputs
+ (Module.sealedTypedModuleSemantic sealedRoot))
+ modules ->
+ assertFailure
+ ("unexpected shared-syntax module count: "
+ <> show (length modules))
+ where
+ seal foundation session parsed direct = do
+ input <-
+ expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness session)
+ unusedResolver
+ Declaration.FreshValidation
+ parsed
+ direct)
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleSucceeded sealed ->
+ pure sealed
+ Module.TypedModuleOpenFailed{} ->
+ assertFailure "empty typed module did not open"
+ >> fail "unreachable"
+ Module.TypedModuleFailed{} ->
+ assertFailure "empty typed module did not seal"
+ >> fail "unreachable"
+
+rejectsUnsupportedTypedSource :: Assertion
+rejectsUnsupportedTypedSource = do
+ result <-
+ (checkFileFresh
+ (Provers.vampire
+ "vampire"
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ "test/phase3/typed-unsupported.tex")
+ case result of
+ Right
+ ( Verification.VerificationCheckingFailure _report
+ (failure@(Verification.VerificationTypedModuleError
+ source
+ (Module.TypedActionFailed
+ (Module.TypedExactCompileFailed
+ (Exact.ExactGuardedOpaqueSignature location)))
+ prefix))
+ , _slowReport
+ ) -> do
+ assertEqual "failed source"
+ "test/phase3/typed-unsupported.tex"
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source))
+ assertEqual "unsupported source location line"
+ 2
+ (locLine location)
+ assertEqual "failure retains the initial module prefix"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ let diagnostic =
+ Verification.renderVerificationDriverError failure
+ assertBool "diagnostic retains resolved source"
+ ("project:test/phase3/typed-unsupported.tex"
+ `StrictText.isInfixOf` diagnostic)
+ assertBool "diagnostic retains best location"
+ ("typed-unsupported.tex 2:14"
+ `StrictText.isInfixOf` diagnostic)
+ assertBool "diagnostic explains the typed failure"
+ ("opaque signature cannot have a header assumption"
+ `StrictText.isInfixOf` diagnostic)
+ Left err ->
+ assertFailure ("unexpected verification driver error: " <> show err)
+ Right{} ->
+ assertFailure "unsupported typed source was admitted"
+
+reusesVerificationSession :: Assertion
+reusesVerificationSession =
+ withAcceptedFixtureVampire "felix-session-reuse" \prover -> do
+ plan <- Store.planStore Store.FreshTemporaryStore >>= expectRight
+ graph <- Workspace.prepareDefaultSourceGraph source >>= expectRight
+ Store.withStoreLease plan \lease -> do
+ opened <- Verification.withVerificationSession lease \session -> do
+ let request = Verification.CheckRequest
+ { Verification.checkSourceGraph = graph
+ , Verification.checkStoreValidationMode =
+ Verification.FreshStoreValidation
+ , Verification.checkEffectiveJobs = testSequentialJobs
+ , Verification.checkVampire = prover
+ , Verification.checkRequestObserver =
+ ignoredVerificationRequests
+ }
+ first <-
+ Verification.checkWorkspace session request >>= expectRight
+ second <-
+ Verification.checkWorkspace session request >>= expectRight
+ traverse_
+ assertUnsupported
+ [ Verification.checkVerificationResult first
+ , Verification.checkVerificationResult second
+ ]
+ void (expectRight opened)
+ where
+ source = "test/phase3/typed-unsupported.tex"
+
+ assertUnsupported = \case
+ Verification.VerificationCheckingFailure
+ _report
+ Verification.VerificationTypedModuleError{} ->
+ pure ()
+ other ->
+ assertFailure
+ ("successive session check had unexpected result: "
+ <> show other)
+
+compilesExactDeclarationGraph :: Assertion
+compilesExactDeclarationGraph = do
+ (_foundation, _bootstrap, workspace, sealedModules) <-
+ compileExactFixture "test/phase5/exact-importer.tex"
+ assertEqual "dependency-closed module count" 2 (length sealedModules)
+ assertEqual "imported-before-importer source order"
+ [ "test/phase5/exact-producer.tex"
+ , "test/phase5/exact-importer.tex"
+ ]
+ [ safeRelativePathFilePath
+ (resolvedSourceRelativePath
+ (Parse.parsedModuleResolved parsed))
+ | parsed <- toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace)
+ ]
+ case sealedModules of
+ [producer, importer] -> do
+ let producerPrefix = Module.sealedTypedModulePrefix producer
+ importerPrefix = Module.sealedTypedModulePrefix importer
+ producerBatches =
+ Declaration.pendingModulePrefixBatches producerPrefix
+ importerBatches =
+ Declaration.pendingModulePrefixBatches importerPrefix
+ assertEqual "producer declaration batches" 3
+ (length producerBatches)
+ assertEqual "importer declaration batches" 1
+ (length importerBatches)
+ assertEqual "producer declaration order"
+ [0, 1, 2]
+ [ localDeclarationOrdinalValue
+ (Semantic.declarationSlotOrdinal
+ (Declaration.committedBatchSlot batch))
+ | batch <- producerBatches
+ ]
+
+ let producerDeltas =
+ Semantic.semanticInterfaceDeclarations
+ (Module.sealedTypedModuleSemantic producer)
+ importerDeltas =
+ Semantic.semanticInterfaceDeclarations
+ (Module.sealedTypedModuleSemantic importer)
+ assertEqual "one exact binding per producer declaration"
+ [1, 1, 1]
+ (bindingCount <$> producerDeltas)
+ assertEqual "one exact importer binding"
+ [1]
+ (bindingCount <$> importerDeltas)
+ assertEqual "producer object families"
+ ["opaque", "transparent"]
+ [ objectFamilyName
+ (Identity.assertedObjectContent object)
+ | batch <- producerBatches
+ , object <- Declaration.committedBatchObjects batch
+ ]
+
+ definitionDelta <- sole "producer definition delta"
+ (drop 2 producerDeltas)
+ definitionBinding <- sole "producer definition binding"
+ (bindings definitionDelta)
+ definitionFact <- sole "producer definition fact"
+ (Semantic.declarationDeltaFacts definitionDelta)
+ definitionAlias <- sole "producer definition alias"
+ (Semantic.declarationDeltaAliases definitionDelta)
+ assertEqual "definition alias"
+ (Semantic.semanticName "phase5_definition")
+ (Semantic.semanticAliasName definitionAlias)
+ assertEqual "definition is proof-search eligible"
+ Semantic.SearchEligible
+ (Semantic.semanticFactSearchEligibility definitionFact)
+ assertEqual "definition authority is clean"
+ Authority.cleanAuthoritySafety
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority definitionFact))
+ definitionBatch <- sole "producer definition batch"
+ (drop 2 producerBatches)
+ validation <-
+ maybe
+ (assertFailure "definition declaration validation is absent"
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation
+ definitionBatch)
+ certificate <- sole "definition validation certificate"
+ (Semantic.declarationValidationRecordCertificates validation)
+ assertEqual "direct defining-equation authority"
+ (Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation
+ (Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget
+ definitionBinding))))
+ (Authority.validationDirectAuthorization
+ certificate)
+
+ aliasDelta <- sole "producer abbreviation delta"
+ (take 1 (drop 1 producerDeltas))
+ aliasBinding <- sole "producer abbreviation binding"
+ (bindings aliasDelta)
+ seedDelta <- sole "producer signature delta"
+ (take 1 producerDeltas)
+ seedBinding <- sole "producer signature binding"
+ (bindings seedDelta)
+ let seedTarget =
+ Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget seedBinding)
+ aliasTarget =
+ Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget aliasBinding)
+ definitionTarget =
+ Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget
+ definitionBinding)
+ assertEqual "abbreviation expands transparently"
+ (Semantic.TransparentExpansion
+ aliasTarget)
+ (Semantic.semanticGlobalBindingTarget aliasBinding)
+ assertEqual "definition remains a named global"
+ (Semantic.GlobalReference definitionTarget)
+ (Semantic.semanticGlobalBindingTarget definitionBinding)
+ assertEqual "definition content coalesces with its expansion"
+ aliasTarget
+ definitionTarget
+ assertEqual "coalesced definition adds no object"
+ []
+ (Declaration.committedBatchObjects definitionBatch)
+ aliasBatch <- sole "producer abbreviation batch"
+ (take 1 (drop 1 producerBatches))
+ aliasObject <- sole "producer abbreviation object"
+ (Declaration.committedBatchObjects aliasBatch)
+ case Identity.assertedObjectContent aliasObject of
+ Identity.TransparentObjectContent _theory _coreType body ->
+ assertEqual
+ "expanded body retains only the opaque seed"
+ (Set.singleton seedTarget)
+ (Core.canonicalTermGlobals body)
+ content ->
+ assertFailure
+ ("abbreviation object is not transparent: "
+ <> show content)
+ importerBatch <- sole "importer declaration batch" importerBatches
+ importerDelta <- sole "importer semantic delta" importerDeltas
+ importerBinding <- sole "importer binding"
+ (bindings importerDelta)
+ assertEqual "equal transparent content reuses the producer object"
+ (Semantic.semanticGlobalBindingTarget definitionBinding)
+ (Semantic.semanticGlobalBindingTarget importerBinding)
+ assertEqual "reused transparent content adds no object"
+ []
+ (Declaration.committedBatchObjects importerBatch)
+ modules ->
+ assertFailure
+ ("unexpected exact module count: " <> show (length modules))
+ where
+ bindingCount = length . bindings
+
+ bindings =
+ Semantic.semanticEnvironmentBindings
+ . Semantic.declarationDeltaEnvironment
+
+ objectFamilyName :: Identity.ObjectContent -> String
+ objectFamilyName = \case
+ Identity.OpaqueObjectContent{} -> "opaque"
+ Identity.TransparentObjectContent{} -> "transparent"
+ Identity.IntrinsicObjectContent{} -> "intrinsic"
+
+compilesExactStructures :: Assertion
+compilesExactStructures = do
+ foundation <- expectRight Foundation.checkedFoundation
+ repository <- getCurrentDirectory
+ Temp.withSystemTempDirectory "felix-exact-structures" \directory -> do
+ let path = directory Posix.</> "store.sqlite"
+ executable = directory Posix.</> "vampire"
+ writeAcceptedFixtureVampire executable
+ runs <- newIORef (0 :: Int)
+ let resolver = countingAcceptedResolver executable runs
+ (_startup, store) <-
+ Store.openStore path (Identity.theoryId foundation)
+ >>= expectRight
+ bracket (pure store) Store.closeStore \opened -> do
+ prelude <-
+ expectRight
+ =<< acquireFinalPreludeSession
+ opened foundation resolver
+ carrierOperation <- sole "base carrier operation"
+ [ operation
+ | descriptor <- semanticStructureDescriptors
+ (Module.sealedTypedModuleSemantic
+ (Module.finalPreludeModule prelude))
+ , operation <-
+ Semantic.semanticStructureDescriptorOperations descriptor
+ ]
+ mounts <- exactFixtureMounts repository
+ workspace <- parseFinalExactWorkspace
+ prelude mounts "test/phase5/exact-structure-child.tex"
+ sealed <- compileFinalParsedWorkspaceWithResolver
+ foundation prelude resolver workspace
+ freshRuns <- readIORef runs
+ warm <- installAndLoadStructures
+ opened foundation prelude workspace sealed
+ warmRuns <- readIORef runs
+ assertEqual "warm structures preserve descriptors"
+ (structureDescriptors <$> sealed)
+ (structureDescriptors <$> warm)
+ assertEqual "warm structures make no prover calls"
+ freshRuns warmRuns
+ case sealed of
+ [parent, child] -> do
+ let parentBatches =
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix parent)
+ parentDeltas =
+ Semantic.semanticInterfaceDeclarations
+ (Module.sealedTypedModuleSemantic parent)
+ childBatches =
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix child)
+ childDeltas =
+ Semantic.semanticInterfaceDeclarations
+ (Module.sealedTypedModuleSemantic child)
+ parentBatch <- sole "parent structure batch"
+ (take 1 parentBatches)
+ parentDelta <- sole "parent structure delta"
+ (take 1 parentDeltas)
+ parentDescriptor <- sole "parent structure descriptor"
+ (Semantic.semanticEnvironmentStructures
+ (Semantic.declarationDeltaEnvironment parentDelta))
+ parentOperation <- sole "parent structure operation"
+ (Semantic.semanticStructureDescriptorOperations
+ parentDescriptor)
+ parentPredicate <-
+ maybe
+ (assertFailure "parent structure has no predicate"
+ >> fail "unreachable")
+ pure
+ (Semantic.semanticStructureDescriptorPredicate
+ parentDescriptor)
+ assertEqual "structure object family order"
+ ["opaque", "transparent"]
+ [ objectFamilyName
+ (Identity.assertedObjectContent object)
+ | object <- Declaration.committedBatchObjects parentBatch
+ ]
+ assertEqual "structure fact aliases"
+ [ Semantic.semanticName "pointed_set"
+ , Semantic.semanticName "pointed_refl"
+ ]
+ (Semantic.semanticAliasName
+ <$> Semantic.declarationDeltaAliases parentDelta)
+ definitionFact <- sole "structure definition fact"
+ (take 1 (Semantic.declarationDeltaFacts parentDelta))
+ definitionTarget <-
+ targetForOccurrence parentBatch definitionFact
+ assertEqual "pointwise structure definition"
+ (Core.CForall Core.TySet
+ (Core.CEq Core.TyProp
+ (Core.CApp
+ (Core.CGlobal parentPredicate)
+ (Core.CBound 0))
+ (Core.CEq Core.TySet
+ (Core.CBound 0)
+ (Core.CBound 0))))
+ definitionTarget
+ validations <-
+ maybe
+ (assertFailure "structure validation is absent"
+ >> fail "unreachable")
+ (pure
+ . Semantic.declarationValidationRecordCertificates)
+ (Declaration.committedBatchDeclarationValidation
+ parentBatch)
+ case validations of
+ definitionValidation : projectionValidation : [] -> do
+ assertEqual "structure definition authority"
+ (Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation
+ parentPredicate))
+ (Authority.validationDirectAuthorization
+ definitionValidation)
+ projectionFact <- sole
+ "structure projection fact"
+ (drop 1
+ (Semantic.declarationDeltaFacts
+ parentDelta))
+ assertEqual
+ "projection has independent authority"
+ (Semantic.semanticFactAuthority projectionFact)
+ (Authority.validationTarget
+ projectionValidation)
+ assertEqual "projection authority is clean"
+ Authority.cleanAuthoritySafety
+ (Authority.factAuthoritySafety
+ (Authority.validationTarget
+ projectionValidation))
+ records ->
+ assertFailure
+ ("expected two structure validations, got "
+ <> show records)
+ assertBool "all parent structure facts are clean"
+ (all
+ ((== Authority.cleanAuthoritySafety)
+ . Authority.factAuthoritySafety
+ . Semantic.semanticFactAuthority)
+ (Semantic.declarationDeltaFacts parentDelta))
+
+ let claimGlobals marker = do
+ batch <- batchWithAlias marker parentBatches
+ occurrence <- sole (marker <> " occurrence")
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch))
+ Core.canonicalTermGlobals
+ <$> targetForOccurrence batch occurrence
+ carrierGlobals <- claimGlobals "pointed_carrier"
+ operationGlobals <- claimGlobals "pointed_operation"
+ assertBool "membership uses inherited carrier"
+ (Semantic.semanticStructureOperationObject carrierOperation
+ `Set.member` carrierGlobals)
+ assertBool "implicit and explicit operation share one object"
+ (Semantic.semanticStructureOperationObject parentOperation
+ `Set.member` operationGlobals)
+
+ let assertEquivalentClaim surface explicit = do
+ surfaceTerm <-
+ checkedPropositionTermByAlias parent surface
+ explicitTerm <-
+ checkedPropositionTermByAlias parent explicit
+ assertEqual
+ (StrictText.unpack surface
+ <> " uses the inherited carrier")
+ explicitTerm
+ surfaceTerm
+ assertEquivalentClaim
+ "pointed_self_member"
+ "pointed_self_member_explicit"
+ assertEquivalentClaim
+ "pointed_self_not_member"
+ "pointed_self_not_member_explicit"
+ assertEquivalentClaim
+ "pointed_self_element"
+ "pointed_self_element_explicit"
+ assertEquivalentClaim
+ "pointed_header_member"
+ "pointed_header_member_explicit"
+
+ childBatch <- sole "child structure batch" childBatches
+ childDelta <- sole "child structure delta" childDeltas
+ childDescriptor <- sole "child structure descriptor"
+ (Semantic.semanticEnvironmentStructures
+ (Semantic.declarationDeltaEnvironment childDelta))
+ assertEqual "child allocates no replacement operation"
+ []
+ (Semantic.semanticStructureDescriptorOperations
+ childDescriptor)
+ assertEqual "child owns only its transparent predicate"
+ ["transparent"]
+ [ objectFamilyName
+ (Identity.assertedObjectContent object)
+ | object <- Declaration.committedBatchObjects childBatch
+ ]
+ modules ->
+ assertFailure
+ ("expected parent and child structures, got "
+ <> show (length modules))
+ where
+ installAndLoadStructures store foundation prelude workspace sealed = do
+ memo <- Store.newStoreMemo store
+ case
+ ( toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace)
+ , sealed
+ ) of
+ ([parentParsed, childParsed], [parent, child]) -> do
+ cachedParent <- persistAndLoad memo [] parentParsed parent
+ cachedChild <- persistAndLoad
+ memo [cachedParent] childParsed child
+ pure [cachedParent, cachedChild]
+ (parsed, modules) ->
+ assertFailure
+ ("expected two structure installations, got "
+ <> show (length parsed)
+ <> " parsed and "
+ <> show (length modules)
+ <> " checked modules")
+ >> fail "unreachable"
+ where
+ preludeModule = Module.finalPreludeModule prelude
+
+ persistAndLoad memo parents parsed sealedModule = do
+ let input = Module.identifiedPhysicalModule parsed
+ syntax = Module.sealedTypedModuleSyntax sealedModule
+ semantic = Module.sealedTypedModuleSemantic sealedModule
+ key <- expectRight
+ (Semantic.moduleArtifactKey
+ (Module.identifiedModuleOwner input)
+ (Parse.identifiedParsedModuleId
+ (Module.identifiedModuleParsed input))
+ (Semantic.semanticInterfaceDirectInputs semantic)
+ (Identity.theoryId foundation))
+ let artifact = Semantic.moduleArtifactResult
+ key
+ (Syntax.moduleSyntaxAssertedId syntax)
+ (Semantic.semanticInterfaceAssertedId semantic)
+ acknowledged <- expectRight
+ =<< Store.writeSealedModule
+ store
+ (Module.sealedTypedModulePrefix sealedModule)
+ [syntax]
+ [semantic]
+ artifact
+ assertEqual "cached structure artifact acknowledgement"
+ artifact acknowledged
+ loaded <- expectRight
+ =<< Store.loadCachedModuleInstallation
+ memo
+ store
+ key
+ (Syntax.moduleSyntaxAssertedId
+ (Parse.parsedModuleSyntaxInterface parsed))
+ installation <- maybe
+ (assertFailure "cached structure installation is absent"
+ >> fail "unreachable")
+ pure
+ loaded
+ expectRight
+ (Module.cachedSealedTypedModule
+ foundation
+ (preludeModule : parents)
+ installation)
+
+ structureDescriptors =
+ semanticStructureDescriptors
+ . Module.sealedTypedModuleSemantic
+
+ objectFamilyName :: Identity.ObjectContent -> String
+ objectFamilyName = \case
+ Identity.OpaqueObjectContent{} -> "opaque"
+ Identity.TransparentObjectContent{} -> "transparent"
+ Identity.IntrinsicObjectContent{} -> "intrinsic"
+
+ targetForOccurrence batch occurrence =
+ maybe
+ (assertFailure "structure proposition is absent"
+ >> fail "unreachable")
+ (pure . Core.frozenCoreTerm . Identity.checkedPropositionTerm)
+ (find
+ ((== Semantic.semanticFactProposition occurrence)
+ . Identity.checkedPropositionId)
+ (Declaration.committedBatchPropositions batch))
+
+ batchWithAlias marker batches =
+ maybe
+ (assertFailure ("missing batch alias " <> marker)
+ >> fail "unreachable")
+ pure
+ (find
+ (elem (Semantic.semanticName (StrictText.pack marker))
+ . fmap Semantic.semanticAliasName
+ . Semantic.declarationDeltaAliases
+ . Declaration.committedBatchDelta)
+ batches)
+
+compilesContextualAbbreviations :: Assertion
+compilesContextualAbbreviations = do
+ foundation <- expectRight Foundation.checkedFoundation
+ repository <- getCurrentDirectory
+ Temp.withSystemTempDirectory "felix-contextual-abbreviation" \directory -> do
+ let storePath = directory Posix.</> "store.sqlite"
+ executable = directory Posix.</> "vampire"
+ relative = "test/phase5/exact-contextual-abbreviation.tex"
+ writeAcceptedFixtureVampire executable
+ runs <- newIORef (0 :: Int)
+ let resolver = countingAcceptedResolver executable runs
+ (_startup, store) <-
+ Store.openStore storePath (Identity.theoryId foundation)
+ >>= expectRight
+ bracket (pure store) Store.closeStore \opened -> do
+ prelude <-
+ expectRight
+ =<< acquireFinalPreludeSession
+ opened foundation resolver
+ mounts <- exactFixtureMounts repository
+ workspace <- parseFinalExactWorkspace prelude mounts relative
+ sealed <- sole "contextual abbreviation module"
+ =<< compileFinalParsedWorkspaceWithResolver
+ foundation prelude resolver workspace
+ let deltas =
+ Semantic.semanticInterfaceDeclarations
+ (Module.sealedTypedModuleSemantic sealed)
+ contextualTargets =
+ [ (identity, requirements)
+ | delta <- deltas
+ , binding <- Semantic.semanticEnvironmentBindings
+ (Semantic.declarationDeltaEnvironment delta)
+ , Semantic.ContextualTransparentExpansion
+ identity requirements <-
+ [Semantic.semanticGlobalBindingTarget binding]
+ ]
+ assertEqual "contextual target count" 2
+ (length contextualTargets)
+ requirements <-
+ sole "canonical contextual requirement set"
+ (nubOrd (snd <$> contextualTargets))
+ assertEqual "one structure operation requirement" 1
+ (Map.size requirements)
+ let batches =
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed)
+ traverse_
+ (assertReflexiveFact batches)
+ [ "phase5_context_dot_explicit"
+ , "phase5_context_inherited"
+ , "phase5_context_nested"
+ , "phase5_context_explicit_unique"
+ ]
+
+ parsed <- pure (Parse.parsedWorkspaceRootModule workspace)
+ let syntax = Module.sealedTypedModuleSyntax sealed
+ semantic = Module.sealedTypedModuleSemantic sealed
+ key <- expectRight
+ (Semantic.moduleArtifactKey
+ (moduleName (Parse.parsedModuleAddress parsed))
+ (Parse.parsedModuleId parsed)
+ (Semantic.semanticInterfaceDirectInputs semantic)
+ (Identity.theoryId foundation))
+ let artifact =
+ Semantic.moduleArtifactResult
+ key
+ (Syntax.moduleSyntaxAssertedId syntax)
+ (Semantic.semanticInterfaceAssertedId semantic)
+ void
+ (expectRight
+ =<< Store.writeSealedModule
+ opened
+ (Module.sealedTypedModulePrefix sealed)
+ [syntax]
+ [semantic]
+ artifact)
+ memo <- Store.newStoreMemo opened
+ loaded <- expectRight
+ =<< Store.loadCachedModuleInstallation
+ memo opened key
+ (Syntax.moduleSyntaxAssertedId
+ (Parse.parsedModuleSyntaxInterface parsed))
+ installation <- maybe
+ (assertFailure "contextual cached installation is absent"
+ >> fail "unreachable")
+ pure
+ loaded
+ cached <- expectRight
+ (Module.cachedSealedTypedModule
+ foundation
+ [Module.finalPreludeModule prelude]
+ installation)
+ assertEqual "cached contextual semantic target"
+ semantic
+ (Module.sealedTypedModuleSemantic cached)
+
+ runsBeforeConsumer <- readIORef runs
+ consumerWorkspace <-
+ parseFinalExactWorkspace prelude mounts
+ "test/phase5/exact-contextual-abbreviation-consumer.tex"
+ let consumerParsed =
+ Parse.parsedWorkspaceRootModule consumerWorkspace
+ consumerInput <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.finalPreludeReadiness prelude)
+ resolver
+ Declaration.FreshValidation
+ consumerParsed
+ [cached])
+ consumer <- Module.runTypedModule consumerInput >>= \case
+ Module.TypedModuleSucceeded sealedConsumer ->
+ pure sealedConsumer
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("contextual consumer did not open: " <> show failure)
+ >> fail "unreachable"
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("contextual consumer did not seal: " <> show failure)
+ >> fail "unreachable"
+ let consumerTargets =
+ [ Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget binding)
+ | delta <- localSemanticDeltas consumer
+ , binding <- Semantic.semanticEnvironmentBindings
+ (Semantic.declarationDeltaEnvironment delta)
+ ]
+ assertEqual "two contextual consumer declarations" 2
+ (length consumerTargets)
+ void
+ (sole
+ "quantified contextual binder matches its explicit parameter"
+ (nubOrd consumerTargets))
+ runsAfterConsumer <- readIORef runs
+ assertEqual "contextual abbreviations require no prover call"
+ runsBeforeConsumer runsAfterConsumer
+
+ verifyFailure foundation resolver prelude mounts sealed
+ "test/phase5/exact-contextual-abbreviation-missing.tex"
+ (\case
+ Exact.ExactContextualExpansionNotAvailable location _key ->
+ assertEqual "missing context line" 5 (locLine location)
+ failure ->
+ assertFailure
+ ("unexpected missing-context failure: "
+ <> show failure))
+ verifyFailure foundation resolver prelude mounts sealed
+ "test/phase5/exact-contextual-abbreviation-ambiguous.tex"
+ (\case
+ Exact.ExactStructureOperationAmbiguous
+ location _symbol objects -> do
+ assertEqual "ambiguous operation line" 16
+ (locLine location)
+ assertEqual "two distinct operation objects" 2
+ (length objects)
+ failure ->
+ assertFailure
+ ("unexpected operation ambiguity failure: "
+ <> show failure))
+ where
+ assertReflexiveFact batches marker = do
+ batch <- maybe
+ (assertFailure ("missing contextual fact " <> marker)
+ >> fail "unreachable")
+ pure
+ (find
+ (elem (Semantic.semanticName (StrictText.pack marker))
+ . fmap Semantic.semanticAliasName
+ . Semantic.declarationDeltaAliases
+ . Declaration.committedBatchDelta)
+ batches)
+ proposition <- sole (marker <> " proposition")
+ (Declaration.committedBatchPropositions batch)
+ let body = stripClaimEnvelope
+ (Core.frozenCoreTerm
+ (Identity.checkedPropositionTerm proposition))
+ case body of
+ Core.CEq _ left right ->
+ assertEqual (marker <> " canonical sides") left right
+ _ ->
+ assertFailure
+ (marker <> " did not elaborate to reflexive equality: "
+ <> show body)
+
+ stripClaimEnvelope = \case
+ Core.CForall _ body -> stripClaimEnvelope body
+ Core.CImp _ body -> stripClaimEnvelope body
+ term -> term
+
+ verifyFailure foundation resolver prelude mounts imported relative checkFailure = do
+ workspace <- parseFinalExactWorkspace prelude mounts relative
+ let parsed = Parse.parsedWorkspaceRootModule workspace
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.finalPreludeReadiness prelude)
+ resolver
+ Declaration.FreshValidation
+ parsed
+ [imported])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactCompileFailed failure))
+ _prefix ->
+ checkFailure failure
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactProofFailed
+ (ExactProof.ExactProofElaborationFailed failure)))
+ _prefix ->
+ checkFailure failure
+ Module.TypedModuleSucceeded{} ->
+ assertFailure (relative <> " was unexpectedly accepted")
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ (relative <> " did not open: " <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ (relative <> " failed unexpectedly: " <> show failure)
+
+rejectsUnknownExactStructureParent :: Assertion
+rejectsUnknownExactStructureParent =
+ Temp.withSystemTempDirectory "felix-exact-structure-parent" \root -> do
+ let relative = "entry.tex"
+ path = root Posix.</> relative
+ source =
+ "\\begin{struct}\\label{known_structure}\n"
+ <> " A known structure $X$ is a onesorted structure.\n"
+ <> "\\end{struct}\n\n"
+ <> "\\begin{struct}\\label{invalid_structure}\n"
+ <> " An invalid structure $X$ is a future structure.\n"
+ <> "\\end{struct}\n\n"
+ <> "\\begin{struct}\\label{future_structure}\n"
+ <> " A future structure $X$ is a onesorted structure.\n"
+ <> "\\end{struct}\n"
+ ByteString.writeFile path
+ (Text.encodeUtf8 (StrictText.pack source))
+ foundation <- expectRight Foundation.checkedFoundation
+ Temp.withSystemTempDirectory "felix-exact-structure-store" \directory -> do
+ let storePath = directory Posix.</> "store.sqlite"
+ executable = directory Posix.</> "vampire"
+ writeAcceptedFixtureVampire executable
+ runs <- newIORef (0 :: Int)
+ let resolver = countingAcceptedResolver executable runs
+ (_startup, store) <-
+ Store.openStore storePath (Identity.theoryId foundation)
+ >>= expectRight
+ bracket (pure store) Store.closeStore \opened -> do
+ prelude <-
+ expectRight
+ =<< acquireFinalPreludeSession
+ opened foundation resolver
+ mounts <- exactFixtureMounts root
+ workspace <- parseFinalExactWorkspace prelude mounts relative
+ let parsed = Parse.parsedWorkspaceRootModule workspace
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.finalPreludeReadiness prelude)
+ resolver
+ Declaration.FreshValidation
+ parsed
+ [])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactCompileFailed
+ (Exact.ExactStructureNotVisible
+ location _phrase)))
+ prefix -> do
+ assertEqual "unknown parent line" 5 (locLine location)
+ assertEqual "only the valid structure was published"
+ 1
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Module.TypedModuleSucceeded{} ->
+ assertFailure "unknown structure parent was accepted"
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("invalid structure module did not open: "
+ <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("unexpected invalid structure failure: "
+ <> show failure)
+
+compilesExactRelationExpressions :: Assertion
+compilesExactRelationExpressions = do
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ repository <- getCurrentDirectory
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace
+ bootstrap mounts "test/phase5/exact-relation-expression.tex"
+ observed <- newIORef []
+ withAcceptedFixtureVampire "felix-exact-relation-expression" \prover -> do
+ let resolver = Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ claim = Backend.typedProblemClaim problem
+ locals = Backend.typedProblemLocalPremises problem
+ modifyIORef' observed
+ (<> [ [ Backend.supportedPropositionTerm
+ (Backend.typedLocalPremiseProposition premise)
+ == Backend.supportedPropositionTerm claim
+ | premise <- Vector.toList locals
+ ]
+ ])
+ (Provers.runPreparedTypedProver prover prepared)
+ void
+ (compileParsedWorkspaceWithResolver
+ foundation bootstrap resolver workspace)
+ assertEqual
+ "relation expression is ordered-pair membership"
+ [[True]]
+ =<< readIORef observed
+
+ missingPair <-
+ withAcceptedFixtureVampire "felix-exact-relation-expression-missing-pair" \prover ->
+ (checkFileFresh
+ prover
+ "test/phase5/exact-relation-expression-missing-pair.tex")
+ case missingPair of
+ Right
+ ( Verification.VerificationCheckingFailure _report
+ (Verification.VerificationTypedModuleError
+ _source
+ (Module.TypedActionFailed
+ (Module.TypedExactProofFailed
+ (ExactProof.ExactProofElaborationFailed
+ (Exact.ExactGlobalNotVisible location key))))
+ prefix)
+ , _slowReport
+ ) -> do
+ assertEqual "missing ordered-pair provider line"
+ 2
+ (locLine location)
+ assertEqual "missing ordered-pair semantic key"
+ (Semantic.SemanticExpressionFunction
+ (Raw.mixfixPattern Raw.PairSymbol))
+ key
+ assertEqual "missing provider publishes no declaration"
+ 0
+ (length (Declaration.pendingModulePrefixBatches prefix))
+ Left err ->
+ assertFailure
+ ("unexpected relation-expression failure: " <> show err)
+ Right{} ->
+ assertFailure "relation expression without ordered pairing was admitted"
+
+resolvesSourceOwnedApplication :: Assertion
+resolvesSourceOwnedApplication = do
+ foundation <- expectRight Foundation.checkedFoundation
+ repository <- getCurrentDirectory
+ withAcceptedFixtureVampire "felix-exact-application" \prover ->
+ Temp.withSystemTempDirectory "felix-exact-application" \directory -> do
+ let storePath = directory Posix.</> "store.sqlite"
+ resolver = Declaration.vampireResolver
+ (Provers.runPreparedTypedProver prover)
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ prelude <-
+ expectRight
+ =<< acquireFinalPreludeSession
+ store foundation resolver
+ mounts <- exactFixtureMounts repository
+ workspace <- parseFinalExactWorkspace
+ prelude mounts "test/phase5/exact-application.tex"
+ sealed <- compileFinalParsedWorkspaceWithResolver
+ foundation prelude resolver workspace
+ root <- case reverse sealed of
+ rootModule : _ -> pure rootModule
+ [] ->
+ assertFailure "application fixture root is absent"
+ >> fail "unreachable"
+ assertTransparentObjectAlias root "phase5_apply"
+ let applyKey = Semantic.SemanticExpressionFunction
+ (Raw.mixfixPattern Raw.ApplySymbol)
+ applyObject <- localObjectKeyTarget root applyKey
+ surface <- checkedPropositionTermByAlias
+ root "phase5_application_surface"
+ explicit <- checkedPropositionTermByAlias
+ root "phase5_application_explicit"
+ assertEqual
+ "surface and explicit application lower identically"
+ explicit
+ surface
+ assertBool
+ "surface application resolves through the declared object"
+ (applyObject `Set.member` Core.frozenCoreGlobals surface)
+
+ missing <-
+ withAcceptedFixtureVampire "felix-exact-application-missing"
+ \prover ->
+ (checkFileFresh
+ prover
+ "test/phase5/exact-application-missing.tex")
+ case missing of
+ Right
+ ( Verification.VerificationCheckingFailure _report
+ (Verification.VerificationTypedModuleError
+ _source
+ (Module.TypedActionFailed
+ (Module.TypedExactProofFailed
+ (ExactProof.ExactProofElaborationFailed
+ (Exact.ExactGlobalNotVisible location key))))
+ prefix)
+ , _slowReport
+ ) -> do
+ assertEqual "unresolved application line" 2 (locLine location)
+ assertEqual "unresolved application key"
+ (Semantic.SemanticExpressionFunction
+ (Raw.mixfixPattern Raw.ApplySymbol))
+ key
+ assertEqual "unresolved application publishes no declaration"
+ 0
+ (length (Declaration.pendingModulePrefixBatches prefix))
+ Left failure ->
+ assertFailure
+ ("unexpected unresolved application failure: " <> show failure)
+ Right{} ->
+ assertFailure "application without its source binding was admitted"
+
+confinesExactQuantifiedTerms :: Assertion
+confinesExactQuantifiedTerms = do
+ foundation <- expectRight Foundation.checkedFoundation
+ repository <- getCurrentDirectory
+ withAcceptedFixtureVampire "felix-exact-quantified-subject" \prover ->
+ Temp.withSystemTempDirectory "felix-quantified-subject" \directory -> do
+ let storePath = directory Posix.</> "store.sqlite"
+ resolver = Declaration.vampireResolver
+ (Provers.runPreparedTypedProver prover)
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ prelude <-
+ expectRight
+ =<< acquireFinalPreludeSession
+ store foundation resolver
+ mounts <- exactFixtureMounts repository
+ workspace <- parseFinalExactWorkspace
+ prelude mounts
+ "test/phase5/exact-quantified-subject.tex"
+ sealed <- compileFinalParsedWorkspaceWithResolver
+ foundation prelude resolver workspace
+ root <- case reverse sealed of
+ rootModule : _ -> pure rootModule
+ [] ->
+ assertFailure "quantified-subject root is absent"
+ >> fail "unreachable"
+ quantified <- checkedPropositionTermByAlias root
+ "phase5_quantified_subject"
+ explicit <- checkedPropositionTermByAlias root
+ "phase5_explicit_quantifier"
+ assertEqual
+ "quantified noun subject retains its domain constraint"
+ explicit
+ quantified
+
+ propositionWorkspace <- parseFinalExactWorkspace
+ prelude mounts
+ "test/phase5/exact-quantified-proposition-terms.tex"
+ observations <- newIORef []
+ let observingResolver =
+ Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem
+ prepared
+ request =
+ Provers.preparedTypedProverRequest
+ prepared
+ modifyIORef' observations
+ (<> [ ( Provers.preparedVerificationRequestId
+ request
+ , Backend.supportedPropositionTerm
+ (Backend.typedProblemClaim problem)
+ , Backend.typedProblemRoute problem
+ , Backend.typedProblemAuxiliaryTag
+ <$> Vector.toList
+ (Backend.typedProblemAuxiliaries
+ problem)
+ )
+ ])
+ (Provers.runPreparedTypedProver
+ prover prepared)
+ freshModules <-
+ compileFinalParsedWorkspaceWithResolver
+ foundation prelude observingResolver
+ propositionWorkspace
+ freshRoot <- case reverse freshModules of
+ rootModule : _ -> pure rootModule
+ [] ->
+ assertFailure
+ "quantified proposition-term root is absent"
+ >> fail "unreachable"
+ let member left right =
+ Core.CApp
+ (Core.CApp
+ (Core.CIntrinsic Core.Member) left)
+ right
+ memberAtX =
+ member (Core.CBound 0) (Core.CBound 1)
+ expectedFunctionTarget =
+ Core.CForall Core.TySet
+ (Core.CEq Core.TySet
+ (Core.CBound 0)
+ (Core.CBound 0))
+ expectedVerbRequestTarget =
+ Core.CForall Core.TySet
+ (Core.CImp memberAtX memberAtX)
+ expectedVerbProposition =
+ Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CImp memberAtX memberAtX))
+ expectedTargets =
+ [ expectedFunctionTarget
+ , expectedVerbRequestTarget
+ ]
+ ordinaryImplicitAuxiliaries =
+ [ Foundation.EmptyCharacteristic
+ , Foundation.PairSetCharacteristic
+ , Foundation.FamilyUnionCharacteristic
+ , Foundation.PowerSetCharacteristic
+ ]
+ freshObservations <- readIORef observations
+ assertEqual
+ "nested function and verb terms have exact FOF targets"
+ [ ( target
+ , Backend.RouteFof
+ , ordinaryImplicitAuxiliaries
+ )
+ | target <- expectedTargets
+ ]
+ [ (target, route, auxiliaries)
+ | (_request, target, route, auxiliaries) <-
+ freshObservations
+ ]
+ functionTarget <- checkedPropositionTermByAlias freshRoot
+ "phase5_quantified_function_argument"
+ verbTarget <- checkedPropositionTermByAlias freshRoot
+ "phase5_quantified_verb_argument"
+ assertEqual "nested function proposition core"
+ expectedFunctionTarget
+ (Core.frozenCoreTerm functionTarget)
+ assertEqual "nested verb proposition core"
+ expectedVerbProposition
+ (Core.frozenCoreTerm verbTarget)
+ let proofRecords moduleValue =
+ concatMap
+ Declaration.committedBatchProofValidations
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix moduleValue))
+ proofAuthorizations moduleValue =
+ Authority.validationDirectAuthorization
+ . Semantic.proofValidationRecordCertificate
+ <$> proofRecords moduleValue
+ case proofAuthorizations freshRoot of
+ [ Authority.CheckedSourceProof [_functionRequest]
+ , Authority.CheckedSourceProof [_verbRequest]
+ ] -> pure ()
+ authorizations ->
+ assertFailure
+ ("unexpected quantified-term authority: "
+ <> show authorizations)
+ assertBool
+ "quantified terms add no escape-backed authority"
+ (all
+ ((== Authority.cleanAuthoritySafety)
+ . Authority.factAuthoritySafety
+ . Semantic.semanticFactAuthority)
+ (concatMap
+ (Semantic.declarationDeltaFacts
+ . Declaration.committedBatchDelta)
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix freshRoot))))
+
+ traverse_
+ (expectRightIO
+ . Store.writePendingModulePrefix store
+ . Module.sealedTypedModulePrefix)
+ freshModules
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmModules <-
+ compileParsedWorkspaceWithReadiness
+ foundation
+ (Module.finalPreludeReadiness prelude)
+ unusedResolver
+ validation
+ propositionWorkspace
+ warmRoot <- case reverse warmModules of
+ rootModule : _ -> pure rootModule
+ [] ->
+ assertFailure
+ "warm quantified proposition-term root is absent"
+ >> fail "unreachable"
+ assertEqual "fresh and warm quantified semantic interface"
+ (Module.sealedTypedModuleSemantic freshRoot)
+ (Module.sealedTypedModuleSemantic warmRoot)
+ assertEqual "fresh and warm quantified request authority"
+ (proofAuthorizations freshRoot)
+ (proofAuthorizations warmRoot)
+ assertEqual "fresh and warm quantified prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix freshRoot))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warmRoot))
+
+ negative <-
+ withAcceptedFixtureVampire "felix-exact-quantified-term-valued"
+ \prover ->
+ (checkFileFresh
+ prover
+ "test/phase5/exact-quantified-term-valued.tex")
+ case negative of
+ Right
+ ( Verification.VerificationCheckingFailure _report
+ (Verification.VerificationTypedModuleError
+ _source
+ (Module.TypedActionFailed
+ (Module.TypedExactCompileFailed
+ (Exact.ExactQuantifiedTermRequiresPropositionContext
+ location)))
+ prefix)
+ , _slowReport
+ ) -> do
+ assertEqual "term-valued quantified term line"
+ 2 (locLine location)
+ assertBool "failed term-valued abbreviation publishes no prefix"
+ (null (Declaration.pendingModulePrefixBatches prefix))
+ Left failure ->
+ assertFailure
+ ("unexpected term-valued quantified-term failure: "
+ <> show failure)
+ Right{} ->
+ assertFailure "term-valued quantified exact term was admitted"
+
+closesExactDefinitionDeclarationBoundary :: Assertion
+closesExactDefinitionDeclarationBoundary = do
+ repository <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ repositoryMounts <- exactFixtureMounts repository
+ Temp.withSystemTempDirectory "felix-definition-boundary" \directory -> do
+ mounts <- exactFixtureMounts directory
+ annotatedText <-
+ readFile
+ (repository Posix.</>
+ "test/phase5/exact-definition-boundary.tex")
+ let relative = "entry.tex"
+ sourcePath = directory Posix.</> relative
+ unannotatedText =
+ StrictText.unpack
+ (StrictText.replace
+ "A set "
+ ""
+ (StrictText.pack annotatedText))
+ writeFile sourcePath annotatedText
+ annotatedWorkspace <-
+ parseExactWorkspace bootstrap mounts relative
+ annotated <- sole "annotated definition module"
+ =<< compileParsedWorkspace
+ foundation bootstrap annotatedWorkspace
+ assertEqual "annotated definition declaration count"
+ 4
+ (length
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix annotated)))
+ assertBool "annotated definitions prepare no Vampire validations"
+ (null (proofValidationRecords annotated))
+ let annotatedBatches =
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix annotated)
+ symbolicBatch <- sole "symbolic primary declaration"
+ (take 1 (drop 2 annotatedBatches))
+ wrapperBatch <- sole "functional wrapper declaration"
+ (take 1 (drop 3 annotatedBatches))
+ symbolicObject <- bindingObject "symbolic primary" symbolicBatch
+ wrapperObject <- bindingObject "functional wrapper" wrapperBatch
+ wrapperContent <- sole "functional wrapper transparent object"
+ [ Identity.assertedObjectContent object
+ | batch <-
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix annotated)
+ , object <- Declaration.committedBatchObjects batch
+ , Identity.assertedObjectId object == wrapperObject
+ ]
+ case wrapperContent of
+ Identity.TransparentObjectContent _theory _type body ->
+ assertEqual
+ "functional wrapper applies the primary symbolic object"
+ (Set.singleton symbolicObject)
+ (Core.canonicalTermGlobals body)
+ content ->
+ assertFailure
+ ("functional wrapper is not transparent: " <> show content)
+
+ writeFile sourcePath unannotatedText
+ unannotatedWorkspace <-
+ parseExactWorkspace bootstrap mounts relative
+ unannotated <- sole "unannotated definition module"
+ =<< compileParsedWorkspace
+ foundation bootstrap unannotatedWorkspace
+ assertEqual
+ "canonical set annotations do not change the semantic interface"
+ (Module.sealedTypedModuleSemantic unannotated)
+ (Module.sealedTypedModuleSemantic annotated)
+ assertEqual
+ "canonical set annotations do not change declaration identity"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix unannotated))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix annotated))
+ assertEqual
+ "canonical set annotations do not change direct authority"
+ (directDeclarationAuthorizations unannotated)
+ (directDeclarationAuthorizations annotated)
+
+ let storePath = directory Posix.</> "store.sqlite"
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix annotated))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warm <- sole "warm annotated definition module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation bootstrap unusedResolver validation
+ annotatedWorkspace
+ assertEqual "warm annotated semantic interface"
+ (Module.sealedTypedModuleSemantic annotated)
+ (Module.sealedTypedModuleSemantic warm)
+ assertEqual "warm annotated declaration identity"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix annotated))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warm))
+ assertEqual "warm annotated direct authority"
+ (directDeclarationAuthorizations annotated)
+ (directDeclarationAuthorizations warm)
+ assertBool "warm annotated definitions run no prover"
+ (null (proofValidationRecords warm))
+
+ annotationFailure <- exactFailure foundation bootstrap repositoryMounts
+ "test/phase5/exact-definition-annotation-failure.tex"
+ case annotationFailure of
+ ( Exact.ExactNonCanonicalSetDefinitionAnnotation location
+ , prefix
+ ) -> do
+ assertEqual "nontrivial annotation line" 2 (locLine location)
+ assertBool "nontrivial annotation publishes no prefix"
+ (null (Declaration.pendingModulePrefixBatches prefix))
+ assertBool "annotation diagnostic gives the explicit migration"
+ ("total condition in the definiens"
+ `StrictText.isInfixOf`
+ Exact.renderExactCompileError
+ (fst annotationFailure))
+ (failure, _prefix) ->
+ assertFailure
+ ("unexpected annotation failure: " <> show failure)
+
+ aliasFailure <- exactFailure foundation bootstrap repositoryMounts
+ "test/phase5/exact-definition-alias-failure.tex"
+ case aliasFailure of
+ (Exact.ExactDefinitionCombinedSymbolicAlias location, prefix) -> do
+ assertEqual "combined symbolic alias line" 2 (locLine location)
+ assertBool "combined symbolic alias publishes no prefix"
+ (null (Declaration.pendingModulePrefixBatches prefix))
+ assertBool "combined alias diagnostic gives the wrapper migration"
+ ("define the symbolic operator first"
+ `StrictText.isInfixOf`
+ Exact.renderExactCompileError (fst aliasFailure))
+ (failure, _prefix) ->
+ assertFailure
+ ("unexpected combined-alias failure: " <> show failure)
+
+ guardFailure <- exactFailure foundation bootstrap repositoryMounts
+ "test/phase5/exact-definition-guard-failure.tex"
+ case guardFailure of
+ (Exact.ExactGuardedTransparentDefinition location, prefix) -> do
+ assertEqual "guarded definition line" 2 (locLine location)
+ assertBool "guarded definition publishes no prefix"
+ (null (Declaration.pendingModulePrefixBatches prefix))
+ assertBool "guard diagnostic gives the total-definition migration"
+ ("where a corresponding opaque signature form exists"
+ `StrictText.isInfixOf`
+ Exact.renderExactCompileError (fst guardFailure))
+ (failure, _prefix) ->
+ assertFailure
+ ("unexpected guarded-definition failure: " <> show failure)
+
+ assertRussellSetAnnotation bootstrap repository
+ where
+ proofValidationRecords moduleValue =
+ concatMap
+ Declaration.committedBatchProofValidations
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix moduleValue))
+
+ directDeclarationAuthorizations moduleValue =
+ [ Authority.validationDirectAuthorization certificate
+ | batch <-
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix moduleValue)
+ , validation <-
+ maybeToList
+ (Declaration.committedBatchDeclarationValidation batch)
+ , certificate <-
+ Semantic.declarationValidationRecordCertificates validation
+ ]
+
+ bindingObject label batch = do
+ binding <- sole (label <> " semantic binding")
+ (Semantic.semanticEnvironmentBindings
+ (Semantic.declarationDeltaEnvironment
+ (Declaration.committedBatchDelta batch)))
+ pure
+ (Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget binding))
+
+ exactFailure foundation bootstrap mounts relative = do
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ parsed <- sole "failed exact definition module"
+ (toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ unusedResolver
+ Declaration.FreshValidation
+ parsed
+ [])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactCompileFailed failure))
+ prefix ->
+ pure (failure, prefix)
+ result ->
+ assertFailure
+ (case result of
+ Module.TypedModuleSucceeded{} ->
+ "expected exact definition failure, but the module succeeded"
+ Module.TypedModuleOpenFailed{} ->
+ "expected exact definition failure, but the module did not open"
+ Module.TypedModuleFailed{} ->
+ "expected an exact compile failure, but checking failed differently")
+ >> fail "unreachable"
+
+ assertRussellSetAnnotation bootstrap repository = do
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace
+ bootstrap mounts "test/examples/russell.tex"
+ parsed <- sole "Russell parity module"
+ (toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ case Parse.identifiedParsedModuleBlocks
+ (Module.identifiedModuleParsed
+ (Module.identifiedPhysicalModule parsed)) of
+ Raw.BlockDefn _location _title _marker
+ (Raw.Defn []
+ (Raw.DefnAdj
+ (Just (Raw.NounPhrase
+ [] (Raw.Noun _ noun []) Nothing [] Nothing))
+ _subject _adjective)
+ _statement) : _ ->
+ assertBool "Russell uses the canonical built-in set noun"
+ (Lexicon.isBuiltinSetNoun noun)
+ _ ->
+ assertFailure
+ "Russell source does not retain its annotated adjective head"
+
+compilesExactOrdinaryProofs :: Assertion
+compilesExactOrdinaryProofs =
+ Temp.withSystemTempDirectory "felix-exact-proofs" \root -> do
+ repository <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace
+ bootstrap mounts "test/phase5/exact-proofs.tex"
+ let executable = root Posix.</> "vampire"
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for exact-proof'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ observations <- newIORef []
+ let resolver = Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ claim =
+ Backend.typedProblemClaim problem
+ locals =
+ Backend.typedProblemLocalPremises problem
+ modifyIORef' observations
+ (<> [ ( Vector.length
+ (Backend.typedProblemGlobalPremises problem)
+ , Vector.length
+ locals
+ , [ Vector.length
+ (Backend.supportedPropositionSupport
+ (Backend.typedLocalPremiseProposition premise))
+ | premise <- Vector.toList locals
+ ]
+ , [ Backend.supportedPropositionTerm
+ (Backend.typedLocalPremiseProposition premise)
+ == Backend.supportedPropositionTerm claim
+ | premise <- Vector.toList locals
+ ]
+ )
+ ])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ sealed <-
+ compileParsedWorkspaceWithResolver
+ foundation bootstrap resolver workspace
+ rootModule <- sole "exact proof root" (drop 1 sealed)
+ let batches =
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix rootModule)
+ assertEqual "one definition and five theorem declarations"
+ 6
+ (length batches)
+ let proofBatches = drop 1 batches
+ assertEqual "only closed theorem facts are published"
+ [1, 1, 1, 1, 1]
+ [ length
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch))
+ | batch <- proofBatches
+ ]
+ assertEqual "proof request aggregation follows source structure"
+ [1, 2, 2, 1, 1]
+ [ case Declaration.committedBatchProofValidations batch of
+ [record] ->
+ case Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate
+ record) of
+ Authority.CheckedSourceProof requests ->
+ length requests
+ authorization ->
+ error
+ ("unexpected exact proof authority: "
+ <> show authorization)
+ records ->
+ error
+ ("unexpected exact proof validation count: "
+ <> show (length records))
+ | batch <- proofBatches
+ ]
+ headerBatch <- sole "header-envelope proof batch"
+ (take 1 (drop 3 proofBatches))
+ headerProposition <- sole "header-envelope checked proposition"
+ (Declaration.committedBatchPropositions headerBatch)
+ assertEqual "header-envelope closed target"
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (member
+ (Core.CBound 0)
+ (Core.CBound 1))
+ (member
+ (Core.CBound 0)
+ (Core.CBound 1)))))
+ (Core.frozenCoreTerm
+ (Identity.checkedPropositionTerm headerProposition))
+ headerFact <- sole "header-envelope published fact"
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta headerBatch))
+ assertEqual "header-envelope proof remains clean"
+ Authority.cleanAuthoritySafety
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority headerFact))
+ observed <- readIORef observations
+ case observed of
+ (implicitGlobals, 0, [], [])
+ : [ (0, 1, [1], _structuralMatches)
+ , (1, 2, [1, 1], _subclaimMatches)
+ , (0, 0, [], [])
+ , (0, 1, [1], _followingMatches)
+ , (0, 1, [2], [True])
+ , (generalizedGlobals, 0, [], [])
+ ] -> do
+ assertBool "implicit Auto selects visible FOF facts"
+ (implicitGlobals > 0)
+ assertBool "generalized Auto selects visible FOF facts"
+ (generalizedGlobals > 0)
+ _ ->
+ assertFailure
+ ("unexpected exact proof premise policies: "
+ <> show observed)
+ where
+ member element set =
+ Core.CApp
+ (Core.CApp
+ (Core.CIntrinsic Core.Member)
+ element)
+ set
+
+restoresExactBinderAndWitnessProofForms :: Assertion
+restoresExactBinderAndWitnessProofForms =
+ Temp.withSystemTempDirectory "felix-exact-proof-parity" \root -> do
+ repository <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace
+ bootstrap mounts "test/phase5/exact-proof-parity.tex"
+ parsed <- sole "parsed proof-parity module"
+ (toList (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ let blocks =
+ Parse.identifiedParsedModuleBlocks
+ (Parse.parsedModuleIdentified parsed)
+ claims = [claim | claim@Raw.BlockClaim{} <- blocks]
+ proofs =
+ [ proof
+ | Raw.BlockProof _location proof _end <- blocks
+ ]
+ omittedClaim <-
+ case reverse claims of
+ claim : _ -> pure claim
+ [] -> assertFailure "missing omitted witness claim"
+ >> fail "unreachable"
+ omittedProof <-
+ case reverse proofs of
+ proof : _ -> pure proof
+ [] -> assertFailure "missing omitted witness proof"
+ >> fail "unreachable"
+ Declaration.runModuleDriver
+ foundation
+ preludeModuleName
+ []
+ unusedResolver
+ Declaration.FreshValidation do
+ Declaration.runProspectiveLoweringDriver
+ (ExactProof.prepareExactProof
+ omittedClaim (Just omittedProof))
+ >>= either Declaration.failModuleDriver pure
+ >>= \case
+ Right (Declaration.DriverSucceeded
+ prepared _semantic _prefix _closure) ->
+ case ExactProof.preparedExactProofFirstOmission prepared of
+ Just location ->
+ assertEqual "nested Take retains first omission"
+ 106 (locLine location)
+ Nothing ->
+ assertFailure "nested Take lost its omission"
+ Right Declaration.DriverFailed{} ->
+ assertFailure "omitted witness preparation failed"
+ Right Declaration.DriverSealFailed{} ->
+ assertFailure "omitted witness preparation did not seal"
+ Left failure ->
+ assertFailure
+ ("omitted witness preparation did not open: "
+ <> show failure)
+ let executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for exact-proof-parity'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ observations <- newIORef []
+ fresh <-
+ sole "proof-parity module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ (observingAcceptedResolver executable observations)
+ Declaration.FreshValidation
+ workspace
+ observed <- readIORef observations
+ assertEqual "restored proof request count" 17 (length observed)
+ assertEqual
+ "restored proof declarations preserve discharge order"
+ [1, 1, 1, 1, 1, 1, 3, 2, 2, 3, 1]
+ (proofRequestCounts fresh)
+ case observed of
+ first : second : third : fourth : _rest -> do
+ assertGuardRequest "single bounded fix" 2 first
+ assertGuardRequest "multiple bounded fix" 3 second
+ assertGuardRequest "negative bounded fix" 2 third
+ assertGuardRequest "fix such that" 2 fourth
+ _ -> assertFailure "missing bounded-fix requests"
+ case drop 4 observed of
+ leftFirst : rightFirst : _ -> do
+ assertSequentialAssumptions "left conjunct first" leftFirst
+ assertSequentialAssumptions "right conjunct first" rightFirst
+ _ -> assertFailure "missing conjunction-assumption requests"
+ assertTakeSequence "bounded TakeVar" (drop 6 observed)
+ assertTakeSequence "existential Have" (drop 13 observed)
+ case drop 9 observed of
+ namedDischarge : _namedFinal : anonymousDischarge : _ -> do
+ assertExactDischarge "named noun" namedDischarge
+ assertEqual "named noun opens two witness binders"
+ 2
+ (leadingExistentials
+ (observedClaimTerm namedDischarge))
+ assertExactDischarge "anonymous noun" anonymousDischarge
+ assertEqual "anonymous noun opens one unnameable binder"
+ 1
+ (leadingExistentials
+ (observedClaimTerm anonymousDischarge))
+ _ -> assertFailure "missing noun-witness requests"
+ lastBatch <-
+ case reverse
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix fresh)) of
+ batch : _ -> pure batch
+ [] -> assertFailure "missing restored-proof batches"
+ >> fail "unreachable"
+ lastFact <- sole "omitted witness fact"
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta lastBatch))
+ assertEqual "omitted continuation remains escape-backed"
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind Authority.Omitted))
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority lastFact))
+ assertBool "proof-local witnesses publish no objects"
+ (all
+ (null . Declaration.committedBatchObjects)
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix fresh)))
+
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmRuns <- newIORef (0 :: Int)
+ warm <-
+ sole "warm proof-parity module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ (countingAcceptedResolver executable warmRuns)
+ validation
+ workspace
+ assertEqual "warm restored proofs skip Vampire"
+ 0 =<< readIORef warmRuns
+ assertEqual
+ "fresh and warm proof validation keys and authority"
+ (proofValidationRecords fresh)
+ (proofValidationRecords warm)
+ assertEqual
+ "fresh and warm checked proposition identities"
+ (map Identity.checkedPropositionId
+ (concatMap Declaration.committedBatchPropositions
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix fresh))))
+ (map Identity.checkedPropositionId
+ (concatMap Declaration.committedBatchPropositions
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix warm))))
+
+ assertProofParityFailure
+ foundation bootstrap mounts
+ "test/phase5/exact-proof-parity-invalid-fix.tex"
+ (\case
+ ExactProof.ExactProofGoalStatementMismatch location ->
+ locLine location == 6
+ _ -> False)
+ assertProofParityFailure
+ foundation bootstrap mounts
+ "test/phase5/exact-proof-parity-invalid-fix-shape.tex"
+ (\case
+ ExactProof.ExactProofExpectedUniversalGoal location ->
+ locLine location == 6
+ _ -> False)
+ assertProofParityFailure
+ foundation bootstrap mounts
+ "test/phase5/exact-proof-parity-invalid-assume.tex"
+ (\case
+ ExactProof.ExactProofGoalStatementMismatch location ->
+ locLine location == 6
+ _ -> False)
+ where
+ observingAcceptedResolver executable observations =
+ Declaration.vampireResolver \prepared -> do
+ let problem = Provers.preparedTypedProverLogicalProblem prepared
+ claim = Backend.typedProblemClaim problem
+ locals = Backend.typedProblemLocalPremises problem
+ observation =
+ ProofParityObservation
+ (snd <$> Vector.toList
+ (Backend.supportedPropositionSupport claim))
+ (Backend.supportedPropositionTerm claim)
+ [ ( Backend.localPremiseOrdinalValue
+ (Backend.typedLocalPremiseOrdinal premise)
+ , snd <$> Vector.toList
+ (Backend.supportedPropositionSupport
+ (Backend.typedLocalPremiseProposition
+ premise))
+ , Backend.supportedPropositionTerm
+ (Backend.typedLocalPremiseProposition premise)
+ )
+ | premise <- Vector.toList locals
+ ]
+ modifyIORef' observations (<> [observation])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+
+ assertGuardRequest label supportCount observation = do
+ assertEqual (label <> " support")
+ supportCount
+ (length (observedClaimSupport observation))
+ case observedLocals observation of
+ [(_ordinal, _support, local)] ->
+ assertEqual (label <> " exact guard")
+ (observedClaimTerm observation)
+ local
+ locals ->
+ assertFailure
+ (label <> ": expected one guard, found "
+ <> show (length locals))
+
+ assertTakeSequence label observations =
+ case observations of
+ discharge : continuation : _ -> do
+ assertExactDischarge label discharge
+ assertEqual (label <> " continuation premise ordinals")
+ [0, 1]
+ [ ordinal
+ | (ordinal, _support, _term) <-
+ observedLocals continuation
+ ]
+ assertEqual (label <> " continuation witness support")
+ 2
+ (length (observedClaimSupport continuation))
+ _ -> assertFailure (label <> ": missing request sequence")
+
+ assertSequentialAssumptions label observation = do
+ assertEqual (label <> " premise ordinals")
+ [0, 1]
+ [ ordinal
+ | (ordinal, _support, _term) <- observedLocals observation
+ ]
+ case observedLocals observation of
+ (_ordinal, _support, first) : _ ->
+ assertEqual (label <> " retained source order")
+ (observedClaimTerm observation)
+ first
+ [] -> assertFailure (label <> ": no scoped assumptions")
+
+ assertExactDischarge label discharge =
+ case observedLocals discharge of
+ [(_ordinal, _support, local)] ->
+ assertEqual (label <> " exact existential discharge")
+ (observedClaimTerm discharge)
+ local
+ locals ->
+ assertFailure
+ (label <> ": unexpected discharge premises "
+ <> show (length locals))
+
+ proofRequestCounts sealed =
+ [ case Declaration.committedBatchProofValidations batch of
+ [record] ->
+ case Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate record) of
+ Authority.CheckedSourceProof requests -> length requests
+ Authority.OmittedAuthorization -> 1
+ authorization ->
+ error ("unexpected restored-proof authority: "
+ <> show authorization)
+ records ->
+ error ("unexpected restored-proof validation count: "
+ <> show (length records))
+ | batch <- Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed)
+ ]
+
+ proofValidationRecords sealed =
+ concatMap Declaration.committedBatchProofValidations
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed))
+
+ leadingExistentials
+ :: Core.CanonicalTerm Identity.ObjectId
+ -> Int
+ leadingExistentials = \case
+ Core.CImp
+ (Core.CForall Core.TySet
+ (Core.CImp body Core.CFalsum))
+ Core.CFalsum ->
+ 1 + leadingExistentials body
+ _ -> 0
+
+data ProofParityObservation = ProofParityObservation
+ { observedClaimSupport :: ![Core.CoreType]
+ , observedClaimTerm :: !(Core.CanonicalTerm Identity.ObjectId)
+ , observedLocals ::
+ ![(Natural, [Core.CoreType], Core.CanonicalTerm Identity.ObjectId)]
+ }
+
+restoresExactLocalReasoningAndCalculations :: Assertion
+restoresExactLocalReasoningAndCalculations =
+ Temp.withSystemTempDirectory "felix-exact-local-reasoning" \root -> do
+ repository <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace
+ bootstrap mounts "test/phase5/exact-proof-local-reasoning.tex"
+ let executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ writeAcceptedFixtureVampire executable
+ observations <- newIORef []
+ fresh <-
+ sole "exact local-reasoning module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ (observingResolver executable observations)
+ Declaration.FreshValidation
+ workspace
+ observed <- readIORef observations
+ assertEqual "local-reasoning request count" 16 (length observed)
+ assertEqual "proof forms retain source request order"
+ [2, 3, 3, 2, 2, 3, 1]
+ (requestCounts fresh)
+ case observed of
+ sufficesImplication : sufficesReduction
+ : equalityFirst : equalitySecond : equalityContinuation
+ : biconditionalFirst : biconditionalSecond
+ : biconditionalContinuation
+ : quantifiedLink : quantifiedContinuation
+ : sinceStructuralClaim : sinceStructuralContinuation
+ : sinceDischarge : sinceClaim : sinceContinuation
+ : _omittedSufficesImplication
+ : [] -> do
+ case localReasoningTarget sufficesImplication of
+ Core.CImp antecedent conclusion -> do
+ assertEqual
+ "Suffices implication starts from the reduction"
+ (localReasoningTarget sufficesReduction)
+ antecedent
+ assertBool
+ "Suffices keeps its distinct current goal as conclusion"
+ (conclusion /= antecedent)
+ implication ->
+ assertFailure
+ ("expected Suffices implication, found "
+ <> show implication)
+ assertEqual "first equality link uses its destination citation"
+ 1 (localReasoningGlobalCount equalityFirst)
+ assertEqual "second equality link uses local-only justification"
+ 0 (localReasoningGlobalCount equalitySecond)
+ assertDerivedContinuation
+ "equality calculation"
+ [0, 1]
+ (localReasoningTarget equalityContinuation)
+ equalityContinuation
+ assertPairwiseDistinct
+ "equality links and endpoint"
+ [ localReasoningTarget equalityFirst
+ , localReasoningTarget equalitySecond
+ , localReasoningTarget equalityContinuation
+ ]
+ assertEqual "first biconditional link remains proposition equality"
+ Core.TyProp
+ (equalityOperandType
+ (localReasoningTarget biconditionalFirst))
+ assertDerivedContinuation
+ "biconditional calculation"
+ [0]
+ (localReasoningTarget biconditionalContinuation)
+ biconditionalContinuation
+ assertPairwiseDistinct
+ "biconditional links and endpoint"
+ [ localReasoningTarget biconditionalFirst
+ , localReasoningTarget biconditionalSecond
+ , localReasoningTarget biconditionalContinuation
+ ]
+ assertEqual "quantified calculation closes both binders"
+ 2
+ (leadingForalls
+ (localReasoningTarget quantifiedLink))
+ assertQuantifiedCalculationGuard
+ (localReasoningTarget quantifiedLink)
+ assertDerivedContinuation
+ "quantified calculation"
+ [0]
+ (localReasoningTarget quantifiedLink)
+ quantifiedContinuation
+ assertEqual
+ "quantified source goal and derived local retain the same guard shape"
+ (quantifiedCalculationShape
+ (localReasoningTarget quantifiedContinuation))
+ (quantifiedCalculationShape
+ (localReasoningTarget quantifiedLink))
+ assertQuantifiedCalculationGuard
+ (localReasoningTarget quantifiedContinuation)
+ assertEqual "structural Since submits no premise discharge"
+ [0]
+ (localReasoningLocalOrdinals sinceStructuralClaim)
+ assertEqual "structural Since does not duplicate its premise"
+ [0, 1]
+ (localReasoningLocalOrdinals
+ sinceStructuralContinuation)
+ assertEqual "ATP-backed Since starts from existing locals only"
+ [0]
+ (localReasoningLocalOrdinals sinceDischarge)
+ assertEqual "Since claim sees the admitted discourse premise"
+ [0, 1]
+ (localReasoningLocalOrdinals sinceClaim)
+ assertEqual "Since continuation sees premise then claim"
+ [0, 1, 2]
+ (localReasoningLocalOrdinals sinceContinuation)
+ assertEqual "local-only Since requests select no globals"
+ [0, 0, 0]
+ (localReasoningGlobalCount
+ <$> [sinceDischarge, sinceClaim, sinceContinuation])
+ assertEqual "biconditional second link keeps local-only policy"
+ 0 (localReasoningGlobalCount biconditionalSecond)
+ _ ->
+ assertFailure
+ ("unexpected local-reasoning observations: "
+ <> show observed)
+ omittedBatch <- sole "omitted Suffices batch"
+ (take 1
+ (reverse
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix fresh))))
+ omittedFact <- sole "omitted Suffices fact"
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta omittedBatch))
+ assertEqual "Suffices continuation omission reaches final authority"
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind Authority.Omitted))
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority omittedFact))
+
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmRuns <- newIORef (0 :: Int)
+ warm <-
+ sole "warm local-reasoning module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ (countingAcceptedResolver executable warmRuns)
+ validation
+ workspace
+ assertEqual "warm local-reasoning validation skips Vampire"
+ 0 =<< readIORef warmRuns
+ assertEqual "fresh and warm local-reasoning validations"
+ (validationRecords fresh)
+ (validationRecords warm)
+
+ assertRejectedPrefix
+ "Suffices implication failure" foundation bootstrap workspace
+ executable 0 0 1
+ assertRejectedPrefix
+ "Suffices reduction failure" foundation bootstrap workspace
+ executable 1 0 2
+ assertRejectedPrefix
+ "middle calculation link failure" foundation bootstrap workspace
+ executable 3 1 4
+ where
+ observingResolver executable observations =
+ Declaration.vampireResolver \prepared -> do
+ let problem = Provers.preparedTypedProverLogicalProblem prepared
+ claim = Backend.typedProblemClaim problem
+ locals = Backend.typedProblemLocalPremises problem
+ observation =
+ LocalReasoningObservation
+ { localReasoningTarget =
+ Backend.supportedPropositionTerm claim
+ , localReasoningGlobalCount = Vector.length
+ (Backend.typedProblemGlobalPremises problem)
+ , localReasoningLocalOrdinals =
+ [ Backend.localPremiseOrdinalValue
+ (Backend.typedLocalPremiseOrdinal premise)
+ | premise <- Vector.toList locals
+ ]
+ , localReasoningLocalTerms =
+ [ Backend.supportedPropositionTerm
+ (Backend.typedLocalPremiseProposition premise)
+ | premise <- Vector.toList locals
+ ]
+ , localReasoningAuxiliaries =
+ Backend.typedProblemAuxiliaryTag
+ <$> Vector.toList
+ (Backend.typedProblemAuxiliaries problem)
+ }
+ modifyIORef' observations (<> [observation])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+
+ requestCounts sealed =
+ [ case Declaration.committedBatchProofValidations batch of
+ [record] ->
+ case Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate record) of
+ Authority.CheckedSourceProof requests -> length requests
+ Authority.OmittedAuthorization -> 1
+ direct -> error
+ ("unexpected local-reasoning authority: " <> show direct)
+ records -> error
+ ("unexpected local-reasoning validation count: "
+ <> show (length records))
+ | batch <- Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed)
+ ]
+
+ validationRecords =
+ concatMap Declaration.committedBatchProofValidations
+ . Declaration.pendingModulePrefixBatches
+ . Module.sealedTypedModulePrefix
+
+ assertDerivedContinuation
+ label expectedOrdinals expectedEndpoint continuation = do
+ assertEqual (label <> " local source ordinals")
+ expectedOrdinals
+ (localReasoningLocalOrdinals continuation)
+ case reverse (localReasoningLocalTerms continuation) of
+ derived : _ ->
+ assertEqual (label <> " derived endpoint")
+ expectedEndpoint derived
+ [] ->
+ assertFailure
+ (label <> ": continuation has no derived endpoint")
+
+ assertPairwiseDistinct label terms =
+ assertEqual (label <> ": " <> show terms)
+ (length terms)
+ (Set.size (Set.fromList terms))
+
+ assertQuantifiedCalculationGuard proposition =
+ case dropForalls 2 proposition of
+ Core.CImp constraint endpoint -> do
+ assertEqual "quantified guard retains both membership bounds"
+ 2 (countIntrinsic Core.Member constraint)
+ assertEqual "quantified guard retains its such-that equality"
+ 1 (countSetEqualities constraint)
+ case endpoint of
+ Core.CEq Core.TySet (Core.CBound left) (Core.CBound right) ->
+ assertBool "quantified endpoint keeps asymmetric binders"
+ (left /= right)
+ _ ->
+ assertFailure
+ ("unexpected quantified endpoint: " <> show endpoint)
+ target ->
+ assertFailure
+ ("expected quantified guarded implication, found "
+ <> show target)
+
+ quantifiedCalculationShape proposition =
+ case dropForalls 2 proposition of
+ Core.CImp constraint endpoint ->
+ Just
+ ( countIntrinsic Core.Member constraint
+ , countSetEqualities constraint
+ , endpoint
+ )
+ _ -> Nothing
+
+ dropForalls
+ :: Int
+ -> Core.CanonicalTerm Identity.ObjectId
+ -> Core.CanonicalTerm Identity.ObjectId
+ dropForalls 0 term = term
+ dropForalls remaining (Core.CForall _binder body) =
+ dropForalls (remaining - 1) body
+ dropForalls _remaining term = term
+
+ countIntrinsic
+ :: Core.CoreIntrinsicTag
+ -> Core.CanonicalTerm Identity.ObjectId
+ -> Int
+ countIntrinsic intrinsic = \case
+ Core.CBound{} -> 0
+ Core.CGlobal{} -> 0
+ Core.CIntrinsic found -> fromEnum (found == intrinsic)
+ Core.COpaqueInteger{} -> 0
+ Core.CApp function argument ->
+ countIntrinsic intrinsic function
+ + countIntrinsic intrinsic argument
+ Core.CLam _binder body -> countIntrinsic intrinsic body
+ Core.CFalsum -> 0
+ Core.CImp premise conclusion ->
+ countIntrinsic intrinsic premise
+ + countIntrinsic intrinsic conclusion
+ Core.CEq _operand left right ->
+ countIntrinsic intrinsic left
+ + countIntrinsic intrinsic right
+ Core.CForall _binder body -> countIntrinsic intrinsic body
+
+ countSetEqualities
+ :: Core.CanonicalTerm Identity.ObjectId
+ -> Int
+ countSetEqualities = \case
+ Core.CBound{} -> 0
+ Core.CGlobal{} -> 0
+ Core.CIntrinsic{} -> 0
+ Core.COpaqueInteger{} -> 0
+ Core.CApp function argument ->
+ countSetEqualities function + countSetEqualities argument
+ Core.CLam _binder body -> countSetEqualities body
+ Core.CFalsum -> 0
+ Core.CImp premise conclusion ->
+ countSetEqualities premise + countSetEqualities conclusion
+ Core.CEq operand left right ->
+ fromEnum (operand == Core.TySet)
+ + countSetEqualities left
+ + countSetEqualities right
+ Core.CForall _binder body -> countSetEqualities body
+
+ equalityOperandType = \case
+ Core.CEq operandType _left _right -> operandType
+ term -> error ("expected checked equality, found " <> show term)
+
+ leadingForalls
+ :: Core.CanonicalTerm Identity.ObjectId
+ -> Int
+ leadingForalls = \case
+ Core.CForall _binder body -> 1 + leadingForalls body
+ _ -> 0
+
+ assertRejectedPrefix
+ label foundation bootstrap workspace executable rejectedIndex
+ expectedPrefix expectedRuns = do
+ runs <- newIORef (0 :: Int)
+ parsed <- sole (label <> " parsed module")
+ (toList (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ let resolver =
+ Declaration.vampireResolver \prepared -> do
+ index <- atomicModifyIORef' runs \current ->
+ (current + 1, current)
+ if index == rejectedIndex
+ then pure
+ (Right
+ (Provers.CounterSatisfiable
+ "focused deterministic rejection"))
+ else
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ resolver
+ Declaration.FreshValidation
+ parsed
+ [])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleFailed _failure prefix ->
+ assertEqual
+ (label <> " publishes only the prior prefix")
+ expectedPrefix
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Module.TypedModuleSucceeded{} ->
+ assertFailure (label <> " unexpectedly succeeded")
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ (label <> " did not open: " <> show failure)
+ assertEqual (label <> " selects the first rejected request")
+ expectedRuns =<< readIORef runs
+
+data LocalReasoningObservation = LocalReasoningObservation
+ { localReasoningTarget :: !(Core.CanonicalTerm Identity.ObjectId)
+ , localReasoningGlobalCount :: !Int
+ , localReasoningLocalOrdinals :: ![Natural]
+ , localReasoningLocalTerms ::
+ ![Core.CanonicalTerm Identity.ObjectId]
+ , localReasoningAuxiliaries :: ![Foundation.FoundationAxiomTag]
+ }
+ deriving (Show)
+
+selectsCalculationLinkFailureBySourceOrder :: Assertion
+selectsCalculationLinkFailureBySourceOrder = do
+ foundation <- expectRight Foundation.checkedFoundation
+ Temp.withSystemTempDirectory "felix-calculation-link-order" \root -> do
+ let executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ source = "test/phase7/calculation-link-order.tex"
+ laterCompleted = root Posix.</> "later-completed"
+ firstRun = root Posix.</> "first-run"
+ secondRun = root Posix.</> "second-run"
+ prover =
+ Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit
+ writeAcceptedFixtureVampire executable
+ (_startup, store) <-
+ Store.openStore storePath (Identity.theoryId foundation)
+ >>= expectRight
+ bracket (pure store) Store.closeStore \openStore -> do
+ let ignored =
+ Verification.verificationRequestObserver
+ (\_position _request -> pure ())
+ void
+ (
+ (checkFileWithStore
+ openStore
+ Verification.WarmStoreValidation
+ ignored
+ prover
+ "test/phase3/typed-unsupported.tex")
+ >>= expectRight)
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "if mkdir \"" <> firstRun <> "\" 2>/dev/null; then"
+ , " printf '%s\\n' '% SZS status Theorem for calculation-link-order'"
+ , "elif mkdir \"" <> secondRun <> "\" 2>/dev/null; then"
+ , " : > \"" <> laterCompleted <> "\""
+ , " printf '%s\\n' '% SZS status Theorem for calculation-link-order'"
+ , "else"
+ , " printf '%s\\n' '% SZS status CounterSatisfiable for calculation-link-order'"
+ , "fi"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable (setOwnerExecutable True permissions)
+ jobs <-
+ Provers.selectEffectiveJobs
+ (Provers.effectiveJobs 2)
+ (fail "explicit jobs unexpectedly detected processors")
+ positions <- newIORef []
+ middleStarted <- newEmptyTMVarIO
+ laterStarted <- newEmptyTMVarIO
+ releaseMiddle <- newEmptyTMVarIO
+ let observer =
+ Verification.verificationRequestObserver \position _request -> do
+ let ordinal =
+ Provers.workPositionLocalRequestOrdinal position
+ modifyIORef' positions (position :)
+ case ordinal of
+ 1 -> pure ()
+ 2 -> do
+ atomically (putTMVar middleStarted ())
+ atomically (takeTMVar releaseMiddle)
+ 3 -> atomically (putTMVar laterStarted ())
+ _ ->
+ assertFailure
+ ("unexpected calculation request ordinal: "
+ <> show ordinal)
+ withAsync
+ (
+ (checkFileWithStoreAndJobs
+ openStore
+ Verification.WarmStoreValidation
+ jobs
+ observer
+ prover
+ source)
+ >>= expectRight)
+ \verification -> do
+ void
+ (awaitTmvar "middle calculation link" middleStarted)
+ void
+ (awaitTmvar "later calculation continuation" laterStarted)
+ waitForFileSignal
+ "later calculation continuation" laterCompleted
+ atomically (putTMVar releaseMiddle ())
+ (result, _slowReport) <- wait verification
+ case result of
+ Verification.VerificationFailure report failed -> do
+ assertEqual "middle link failure location"
+ (source, 10)
+ ( locFile
+ (Verification.failedVerificationLocation failed)
+ , locLine
+ (Verification.failedVerificationLocation failed)
+ )
+ assertEqual "failed calculation admits no source fact"
+ [] (Verification.verificationDirectEscapes report)
+ other ->
+ assertFailure
+ ("calculation link order did not reject: "
+ <> show other)
+ observedPositions <-
+ fmap
+ (\position ->
+ ( Provers.workPositionModuleOrdinal position
+ , Provers.workPositionLocalRequestOrdinal
+ position
+ ))
+ <$> readIORef positions
+ assertEqual "all calculation requests executed"
+ [(1, 1), (1, 2), (1, 3)]
+ (sort observedPositions)
+
+ writeAcceptedFixtureVampire executable
+ retryPositions <- newIORef []
+ let retryObserver =
+ Verification.verificationRequestObserver \position _request ->
+ modifyIORef' retryPositions (position :)
+ (retry, _retrySlowReport) <-
+ (checkFileWithStoreAndJobs
+ openStore
+ Verification.WarmStoreValidation
+ jobs
+ retryObserver
+ prover
+ source)
+ >>= expectRight
+ case retry of
+ Verification.VerificationCompleted{} -> pure ()
+ other ->
+ assertFailure
+ ("calculation rollback retry failed: " <> show other)
+ retryObserved <- readIORef retryPositions
+ assertEqual "retry executes the complete calculation proof"
+ 3 (length retryObserved)
+ where
+ awaitTmvar label variable = do
+ result <- Timeout.timeout 10000000
+ (atomically (takeTMVar variable))
+ maybe
+ (assertFailure (label <> " was not observed")
+ >> fail "unreachable")
+ pure
+ result
+
+assertProofParityFailure
+ :: Foundation.CheckedFoundation
+ -> Module.BootstrapPreludeFixture
+ -> SourceMounts
+ -> FilePath
+ -> (ExactProof.ExactProofError -> Bool)
+ -> Assertion
+assertProofParityFailure foundation bootstrap mounts relative matches = do
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ parsed <- sole "invalid proof-parity module"
+ (toList (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ unusedResolver
+ Declaration.FreshValidation
+ parsed
+ [])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactProofFailed failure))
+ prefix -> do
+ assertBool ("unexpected proof failure: " <> show failure)
+ (matches failure)
+ assertBool "failing proof publishes no declaration"
+ (null (Declaration.pendingModulePrefixBatches prefix))
+ Module.TypedModuleSucceeded{} ->
+ assertFailure "invalid proof-parity module succeeded"
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("invalid proof-parity module did not open: " <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("unexpected proof-parity module failure: " <> show failure)
+
+compilesExactSeparationComprehensions :: Assertion
+compilesExactSeparationComprehensions =
+ Temp.withSystemTempDirectory "felix-exact-separation" \root -> do
+ let relative = "test/phase5/exact-separation.tex"
+ executable = root Posix.</> "vampire"
+ failedSource = root Posix.</> relative
+ repository <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for exact-separation'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ observations <- newIORef []
+ let resolver = Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ request =
+ Provers.preparedTypedProverRequest prepared
+ globals =
+ Backend.typedProblemGlobalPremises problem
+ modifyIORef' observations
+ (<> [ ( Backend.typedProblemRoute problem
+ , Backend.typedBackendFactReference <$> globals
+ , all
+ (\fact ->
+ case Backend.typedBackendFactCapability fact of
+ Backend.FofProjectable{} -> True
+ Backend.RequiresTh0{} -> False)
+ globals
+ , Backend.localPremiseOrdinalValue
+ . Backend.typedLocalPremiseOrdinal
+ <$> Vector.toList
+ (Backend.typedProblemLocalPremises problem)
+ , Backend.typedProblemAuxiliaryTag
+ <$> Vector.toList
+ (Backend.typedProblemAuxiliaries problem)
+ , Provers.preparedVerificationRequestId request
+ , Provers.preparedVerificationByteCount request
+ )
+ ])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ modules <- compileParsedWorkspaceWithResolver
+ foundation bootstrap resolver workspace
+ sealed <- sole "exact separation module" modules
+ assertExactSeparationModule "fresh" sealed
+ definition <- batchByAlias
+ (Module.sealedTypedModulePrefix sealed)
+ "phase5_separation_definition"
+ let definitionFacts =
+ Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta definition)
+ extensional <- sole "searchable separation view"
+ [ Semantic.semanticFactFingerprint occurrence
+ | occurrence <- definitionFacts
+ , Semantic.semanticFactSearchEligibility occurrence
+ == Semantic.SearchEligible
+ ]
+ equation <- sole "explicit separation equation"
+ [ Semantic.semanticFactFingerprint occurrence
+ | occurrence <- definitionFacts
+ , Semantic.semanticFactSearchEligibility occurrence
+ == Semantic.SearchIneligible
+ ]
+ readIORef observations >>= \case
+ [ ( Backend.RouteFof
+ , selectedGlobals
+ , True
+ , [0]
+ , []
+ , _requestId
+ , requestBytes
+ ) ] -> do
+ assertBool "searchable separation view is selected"
+ (extensional `elem` selectedGlobals)
+ assertBool "exact separation equation is not selected"
+ (equation `notElem` selectedGlobals)
+ assertBool "separation exact request has bytes"
+ (requestBytes > 0)
+ observed ->
+ assertFailure
+ ("unexpected implicit separation problem: "
+ <> show observed)
+
+ createDirectoryIfMissing True (Posix.takeDirectory failedSource)
+ original <- ByteString.readFile relative
+ let invalid =
+ Text.encodeUtf8
+ (StrictText.replace
+ "x \\in A \\mid x = x"
+ "x \\in x \\mid x = x"
+ (Text.decodeUtf8 original))
+ ByteString.writeFile failedSource invalid
+ failedMounts <- exactFixtureMounts root
+ failedWorkspace <-
+ parseExactWorkspace bootstrap failedMounts relative
+ let parsed = Parse.parsedWorkspaceRootModule failedWorkspace
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ unusedResolver
+ Declaration.FreshValidation
+ parsed
+ [])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactCompileFailed
+ (Exact.ExactFreeVariable location
+ (Raw.NamedVar "x"))))
+ prefix -> do
+ assertEqual "invalid separation bound line"
+ 2 (locLine location)
+ assertEqual
+ "invalid separation publishes none of its declaration"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Module.TypedModuleSucceeded{} ->
+ assertFailure "invalid separation was accepted"
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("invalid separation module did not open: "
+ <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("unexpected invalid separation failure: "
+ <> show failure)
+
+compilesAndReusesProofLocalSetDefinitions :: Assertion
+compilesAndReusesProofLocalSetDefinitions =
+ Temp.withSystemTempDirectory "felix-exact-local-definition" \root -> do
+ let relative = "test/phase5/exact-local-definition.tex"
+ failedRelative =
+ "test/phase5/exact-local-definition-failure.tex"
+ sourcePath = root Posix.</> relative
+ failedSourcePath = root Posix.</> failedRelative
+ executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ createDirectoryIfMissing True (Posix.takeDirectory sourcePath)
+ ByteString.readFile relative >>= ByteString.writeFile sourcePath
+ createDirectoryIfMissing True (Posix.takeDirectory failedSourcePath)
+ ByteString.readFile failedRelative
+ >>= ByteString.writeFile failedSourcePath
+ writeAcceptedFixtureVampire executable
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts root
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ freshRuns <- newIORef (0 :: Int)
+ observations <- newIORef []
+ let freshResolver = Declaration.vampireResolver \prepared -> do
+ modifyIORef' freshRuns (+ 1)
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ premises =
+ Backend.typedProblemLocalPremises problem
+ definition =
+ Vector.find
+ ((== Backend.localPremiseOrdinal 0)
+ . Backend.typedLocalPremiseOrdinal)
+ premises
+ modifyIORef' observations
+ (<> [ ( Backend.typedProblemRoute problem
+ , Backend.localPremiseOrdinalValue
+ . Backend.typedLocalPremiseOrdinal
+ <$> Vector.toList premises
+ , fmap localDefinitionShape definition
+ )
+ ])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ freshModules <-
+ compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ freshResolver
+ Declaration.FreshValidation
+ workspace
+ assertEqual "fresh local-definition discharge count"
+ 2 =<< readIORef freshRuns
+ assertEqual "implicit and local-only definition views"
+ [ (Backend.RouteFof, [0, 2], Just expectedLocalDefinitionShape)
+ , (Backend.RouteTh0, [0, 1, 3], Just expectedLocalDefinitionShape)
+ ]
+ =<< readIORef observations
+ fresh <- sole "fresh local-definition module" freshModules
+ localDefinitionBatch <- sole
+ "proof-local definition publishes one declaration"
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix fresh))
+ assertEqual "proof-local definition publishes no object"
+ []
+ (Declaration.committedBatchObjects localDefinitionBatch)
+ assertEqual "proof-local definition publishes only its theorem"
+ 1
+ (length
+ (Declaration.committedBatchPropositions
+ localDefinitionBatch))
+ localDefinitionFact <- sole
+ "proof-local definition theorem"
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta
+ localDefinitionBatch))
+ assertEqual "proof-local definition remains clean"
+ Authority.cleanAuthoritySafety
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority localDefinitionFact))
+
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmRuns <- newIORef (0 :: Int)
+ warmModules <-
+ compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ (countingAcceptedResolver executable warmRuns)
+ validation
+ workspace
+ assertEqual "warm local-definition proof skips Vampire"
+ 0 =<< readIORef warmRuns
+ warm <- sole "warm local-definition module" warmModules
+ assertEqual "warm local-definition semantic interface"
+ (Module.sealedTypedModuleSemantic fresh)
+ (Module.sealedTypedModuleSemantic warm)
+ assertEqual "warm local-definition prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix fresh))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warm))
+
+ failedWorkspace <-
+ parseExactWorkspace bootstrap mounts failedRelative
+ let failedParsed =
+ Parse.parsedWorkspaceRootModule failedWorkspace
+ failedInput <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ unusedResolver
+ Declaration.FreshValidation
+ failedParsed
+ [])
+ Module.runTypedModule failedInput >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactProofFailed
+ (ExactProof.ExactProofElaborationFailed
+ (Exact.ExactFreeVariable location
+ (Raw.NamedVar "B")))))
+ prefix -> do
+ assertEqual "self-reference rejection line"
+ 6 (locLine location)
+ assertBool "failed local definition publishes no theorem"
+ (null
+ (Declaration.pendingModulePrefixBatches prefix))
+ Module.TypedModuleSucceeded{} ->
+ assertFailure "self-referential local definition was accepted"
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("local-definition failure fixture did not open: "
+ <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("unexpected local-definition failure: "
+ <> show failure)
+ where
+ localDefinitionShape premise =
+ let proposition =
+ Backend.typedLocalPremiseProposition premise
+ in ( fmap
+ snd
+ (Vector.toList
+ (Backend.supportedPropositionSupport proposition))
+ , Backend.supportedPropositionTerm proposition
+ )
+
+ expectedLocalDefinitionShape =
+ ( [Core.TySet, Core.TySet]
+ , Core.CForall Core.TySet
+ (Core.CEq Core.TyProp
+ (member (Core.CBound 0) (Core.CBound 1))
+ (andP
+ (member (Core.CBound 0) (Core.CBound 2))
+ (Core.CEq Core.TySet
+ (Core.CBound 0)
+ (Core.CBound 0))))
+ )
+
+ member element set =
+ Core.CApp
+ (Core.CApp
+ (Core.CIntrinsic Core.Member)
+ element)
+ set
+
+ andP left right =
+ Core.CImp
+ (Core.CImp left (Core.CImp right Core.CFalsum))
+ Core.CFalsum
+
+compilesAndReusesProofLocalFunctionGraphs :: Assertion
+compilesAndReusesProofLocalFunctionGraphs =
+ Temp.withSystemTempDirectory "felix-exact-local-function" \root -> do
+ let relative = "test/phase5/exact-local-function.tex"
+ failedRelative =
+ "test/phase5/exact-local-function-failure.tex"
+ executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ writeAcceptedFixtureVampire executable
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ mounts <- exactFixtureMounts =<< getCurrentDirectory
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ observations <- newIORef []
+ let resolver = Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ premises =
+ [ ( Backend.typedProblemRoute problem
+ , fmap snd
+ (Vector.toList
+ (Backend.supportedPropositionSupport
+ proposition))
+ , Backend.supportedPropositionTerm proposition
+ )
+ | premise <-
+ Vector.toList
+ (Backend.typedProblemLocalPremises problem)
+ , let proposition =
+ Backend.typedLocalPremiseProposition premise
+ ]
+ modifyIORef' observations (<> premises)
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ freshModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap resolver
+ Declaration.FreshValidation workspace
+ allObserved <- readIORef observations
+ let observed =
+ [ (route, proposition)
+ | (route, support, proposition) <- allObserved
+ , support == [Core.TySet, Core.TySet]
+ , isJust (localFunctionPair proposition)
+ ]
+ assertBool
+ ("the local graph characteristic reaches a discharge: "
+ <> show allObserved)
+ (not (null observed))
+ for_ observed \(route, proposition) -> do
+ assertEqual "local function characteristic stays on FOF"
+ Backend.RouteFof route
+ assertExactLocalFunctionCharacteristic proposition
+ freshRoot <- sole "fresh local-function root"
+ (take 1 (reverse freshModules))
+ rootBatch <- sole "local function publishes only its theorem"
+ (drop 1
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix freshRoot)))
+ assertEqual "local function publishes no object"
+ [] (Declaration.committedBatchObjects rootBatch)
+ assertEqual "local function publishes only its theorem"
+ 1
+ (length
+ (Declaration.committedBatchPropositions rootBatch))
+ assertEqual "local function publishes no semantic binding"
+ []
+ (Semantic.semanticEnvironmentBindings
+ (Semantic.declarationDeltaEnvironment
+ (Declaration.committedBatchDelta rootBatch)))
+ rootFact <- sole "local-function theorem"
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta rootBatch))
+ assertEqual "local-function theorem remains clean"
+ Authority.cleanAuthoritySafety
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority rootFact))
+
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ traverse_
+ (expectRightIO
+ . Store.writePendingModulePrefix store
+ . Module.sealedTypedModulePrefix)
+ freshModules
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmRuns <- newIORef (0 :: Int)
+ warmModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (countingAcceptedResolver executable warmRuns)
+ validation workspace
+ assertEqual "warm local-function graph skips Vampire"
+ 0 =<< readIORef warmRuns
+ warmRoot <- sole "warm local-function root"
+ (take 1 (reverse warmModules))
+ assertEqual "warm local-function semantic interface"
+ (Module.sealedTypedModuleSemantic freshRoot)
+ (Module.sealedTypedModuleSemantic warmRoot)
+ assertEqual "warm local-function prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix freshRoot))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warmRoot))
+
+ failedWorkspace <-
+ parseExactWorkspace bootstrap mounts failedRelative
+ failedInput <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ unusedResolver
+ Declaration.FreshValidation
+ (Parse.parsedWorkspaceRootModule failedWorkspace)
+ [])
+ Module.runTypedModule failedInput >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactProofFailed
+ (ExactProof.ExactProofElaborationFailed
+ (Exact.ExactFreeVariable location
+ (Raw.NamedVar "f")))))
+ prefix -> do
+ assertEqual "self-reference rejection line"
+ 6 (locLine location)
+ assertBool "failed local function publishes no theorem"
+ (null
+ (Declaration.pendingModulePrefixBatches prefix))
+ Module.TypedModuleSucceeded{} ->
+ assertFailure "self-referential local function was accepted"
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("local-function failure fixture did not open: "
+ <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("unexpected local-function failure: "
+ <> show failure)
+ where
+ assertExactLocalFunctionCharacteristic proposition = do
+ pair <-
+ maybe
+ (assertFailure "local function characteristic has wrong shape")
+ pure
+ (localFunctionPair proposition)
+ assertEqual "local function uses the exact replacement characteristic"
+ (expectedLocalFunctionCharacteristic pair)
+ proposition
+
+ localFunctionPair proposition =
+ case Set.toList (Core.canonicalTermGlobals proposition) of
+ [pair]
+ | proposition == expectedLocalFunctionCharacteristic pair ->
+ Just pair
+ _ ->
+ Nothing
+
+ expectedLocalFunctionCharacteristic pair =
+ Core.CForall Core.TySet
+ (Core.CEq Core.TyProp
+ (member (Core.CBound 0) (Core.CBound 1))
+ (existsP
+ (andP
+ (member (Core.CBound 0) (Core.CBound 3))
+ (Core.CEq Core.TySet
+ (Core.CBound 1)
+ (Core.CApp
+ (Core.CApp
+ (Core.CGlobal pair)
+ (Core.CBound 0))
+ (Core.CBound 0))))))
+
+ member element set =
+ Core.CApp
+ (Core.CApp (Core.CIntrinsic Core.Member) element)
+ set
+
+ andP left right =
+ notP (Core.CImp left (notP right))
+
+ existsP proposition =
+ notP (Core.CForall Core.TySet (notP proposition))
+
+ notP proposition =
+ Core.CImp proposition Core.CFalsum
+
+confinesTerminalExactContradiction :: Assertion
+confinesTerminalExactContradiction =
+ Temp.withSystemTempDirectory "felix-exact-contradiction" \directory -> do
+ let acceptedExecutable = directory Posix.</> "accepted-vampire"
+ contradictoryExecutable = directory Posix.</> "contradictory-vampire"
+ storePath = directory Posix.</> "store.sqlite"
+ writeAcceptedFixtureVampire acceptedExecutable
+ writeFile contradictoryExecutable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status ContradictoryAxioms for exact-contradiction'"
+ ])
+ permissions <- getPermissions contradictoryExecutable
+ setPermissions contradictoryExecutable
+ (setOwnerExecutable True permissions)
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ mounts <- exactFixtureMounts =<< getCurrentDirectory
+ workspace <- parseExactWorkspace bootstrap mounts
+ "test/phase5/exact-cases-contradiction.tex"
+ assertEmptyCaseAstRejected foundation bootstrap workspace
+ observations <- newIORef []
+ fresh <-
+ sole "cases and contradiction module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ (observingResolver
+ acceptedExecutable
+ contradictoryExecutable
+ observations)
+ Declaration.FreshValidation
+ workspace
+ observed <- readIORef observations
+ assertEqual "cases and contradiction request count"
+ 8 (length observed)
+ case observed of
+ branchOne : branchTwo : branchThree : exhaustive
+ : byContradiction : arbitraryContradiction
+ : omittedLaterBranch : omittedExhaustive : [] -> do
+ assertEqual "case branches have isolated local ordinals"
+ [[0], [1], [2]]
+ (localReasoningLocalOrdinals
+ <$> [branchOne, branchTwo, branchThree])
+ assertEqual "exhaustiveness sees pre-case locals only"
+ [] (localReasoningLocalOrdinals exhaustive)
+ case
+ ( localReasoningLocalTerms branchOne
+ , localReasoningLocalTerms branchTwo
+ , localReasoningLocalTerms branchThree
+ ) of
+ ([caseOne], [caseTwo], [caseThree]) ->
+ assertEqual
+ "case exhaustiveness is left-associated in source order"
+ (orP (orP caseOne caseTwo) caseThree)
+ (localReasoningTarget exhaustive)
+ branchTerms ->
+ assertFailure
+ ("unexpected branch-local premises: "
+ <> show branchTerms)
+ assertEqual "proof by contradiction targets falsum"
+ Core.CFalsum
+ (localReasoningTarget byContradiction)
+ assertBool
+ "double-negation elimination is not an ATP auxiliary"
+ (Foundation.DoubleNegationElim
+ `notElem` localReasoningAuxiliaries byContradiction)
+ case localReasoningLocalTerms byContradiction of
+ [Core.CImp negatedGoal Core.CFalsum] ->
+ assertEqual
+ "proof by contradiction assumes the exact negated goal"
+ (localReasoningTarget branchOne)
+ negatedGoal
+ locals ->
+ assertFailure
+ ("unexpected contradiction locals: "
+ <> show locals)
+ assertEqual "arbitrary terminal contradiction targets falsum"
+ Core.CFalsum
+ (localReasoningTarget arbitraryContradiction)
+ assertEqual "omitted case does not leak into its sibling"
+ [1]
+ (localReasoningLocalOrdinals omittedLaterBranch)
+ assertEqual "omitted exhaustiveness sees no branch local"
+ [] (localReasoningLocalOrdinals omittedExhaustive)
+ _ ->
+ assertFailure
+ ("unexpected cases/contradiction observations: "
+ <> show observed)
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix fresh) of
+ [caseBatch, byContradictionBatch, terminalBatch, omittedBatch] -> do
+ traverse_
+ (assertBatchSafety Authority.cleanAuthoritySafety)
+ [caseBatch, byContradictionBatch, terminalBatch]
+ assertBatchSafety
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind Authority.Omitted))
+ omittedBatch
+ batches ->
+ assertFailure
+ ("unexpected cases/contradiction declaration count: "
+ <> show (length batches))
+
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmRuns <- newIORef (0 :: Int)
+ warm <-
+ sole "warm cases and contradiction module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ (countingAcceptedResolver
+ acceptedExecutable warmRuns)
+ validation
+ workspace
+ assertEqual "warm structural proofs skip Vampire"
+ 0 =<< readIORef warmRuns
+ assertEqual "fresh and warm structural proof validations"
+ (proofValidations fresh)
+ (proofValidations warm)
+
+ failureWorkspace <- parseExactWorkspace bootstrap mounts
+ "test/phase5/exact-case-failure.tex"
+ assertCaseFailure
+ "middle case branch"
+ foundation bootstrap failureWorkspace acceptedExecutable 1 2
+ assertCaseFailure
+ "case exhaustiveness"
+ foundation bootstrap failureWorkspace acceptedExecutable 3 4
+
+ directWorkspace <- parseExactWorkspace bootstrap mounts
+ "test/phase5/exact-direct-contradictory.tex"
+ directParsed <- sole "direct contradictory parsed module"
+ (toList
+ (Parse.parsedWorkspaceImportedBeforeImporter directWorkspace))
+ directInput <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ (Declaration.vampireResolver
+ (runWith contradictoryExecutable))
+ Declaration.FreshValidation
+ directParsed
+ [])
+ Module.runTypedModule directInput >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedDeclarationFailed
+ (Declaration.ProofObligationFailedAt
+ _location
+ Declaration.VampireObligationRejected{}))
+ prefix ->
+ assertBool
+ "direct contradictory input publishes no theorem"
+ (null (Declaration.pendingModulePrefixBatches prefix))
+ _result ->
+ assertFailure "direct contradictory input was accepted"
+ where
+ observingResolver acceptedExecutable contradictoryExecutable observations =
+ Declaration.vampireResolver \prepared -> do
+ let problem = Provers.preparedTypedProverLogicalProblem prepared
+ claim = Backend.typedProblemClaim problem
+ locals = Backend.typedProblemLocalPremises problem
+ target = Backend.supportedPropositionTerm claim
+ modifyIORef' observations
+ (<> [ LocalReasoningObservation
+ { localReasoningTarget = target
+ , localReasoningGlobalCount =
+ Vector.length
+ (Backend.typedProblemGlobalPremises problem)
+ , localReasoningLocalOrdinals =
+ [ Backend.localPremiseOrdinalValue
+ (Backend.typedLocalPremiseOrdinal premise)
+ | premise <- Vector.toList locals
+ ]
+ , localReasoningLocalTerms =
+ Backend.supportedPropositionTerm
+ . Backend.typedLocalPremiseProposition
+ <$> Vector.toList locals
+ , localReasoningAuxiliaries =
+ Backend.typedProblemAuxiliaryTag
+ <$> Vector.toList
+ (Backend.typedProblemAuxiliaries problem)
+ }
+ ])
+ runWith
+ (if target == Core.CFalsum
+ then contradictoryExecutable
+ else acceptedExecutable)
+ prepared
+
+ runWith executable prepared =
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+
+ assertEmptyCaseAstRejected foundation bootstrap workspace = do
+ parsed <- sole "cases parsed module"
+ (toList (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ let blocks =
+ Parse.identifiedParsedModuleBlocks
+ (Parse.parsedModuleIdentified parsed)
+ claim <- sole "cases source claim"
+ [ candidate
+ | candidate@Raw.BlockClaim{} <- take 1 blocks
+ ]
+ location <-
+ case
+ [ found
+ | Raw.BlockProof _ (Raw.ByCase found _cases) _ <- blocks
+ ] of
+ found : _ -> pure found
+ [] ->
+ assertFailure "cases source proof is absent"
+ >> fail "unreachable"
+ let preludeModule = Module.bootstrapPreludeModule bootstrap
+ outcome <- Declaration.runModuleDriver
+ foundation
+ (moduleName (Parse.parsedModuleAddress parsed))
+ [ Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic preludeModule)
+ ]
+ unusedResolver
+ Declaration.FreshValidation do
+ Declaration.importSealedModuleDriver
+ (Module.sealedTypedModuleEvidence preludeModule)
+ Declaration.runProspectiveLoweringDriver
+ (ExactProof.prepareExactProof
+ claim
+ (Just (Raw.ByCase location [])))
+ case outcome of
+ Right (Declaration.DriverSucceeded
+ (Left (ExactProof.ExactProofEmptyCaseSplit found))
+ _semantic prefix _closure) -> do
+ assertEqual "empty case AST failure location"
+ location found
+ assertBool "empty case AST publishes no declaration"
+ (null (Declaration.pendingModulePrefixBatches prefix))
+ _ ->
+ assertFailure "empty programmatic case split was not rejected"
+
+ assertBatchSafety expected batch = do
+ fact <- sole "structural proof fact"
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch))
+ assertEqual "structural proof authority safety"
+ expected
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority fact))
+
+ proofValidations =
+ concatMap Declaration.committedBatchProofValidations
+ . Declaration.pendingModulePrefixBatches
+ . Module.sealedTypedModulePrefix
+
+ assertCaseFailure
+ label foundation bootstrap workspace executable rejectedIndex
+ expectedRuns = do
+ runs <- newIORef (0 :: Int)
+ parsed <- sole (label <> " parsed module")
+ (toList (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ let resolver =
+ Declaration.vampireResolver \prepared -> do
+ index <- atomicModifyIORef' runs \current ->
+ (current + 1, current)
+ if index == rejectedIndex
+ then pure
+ (Right
+ (Provers.CounterSatisfiable
+ "focused case rejection"))
+ else runWith executable prepared
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ resolver
+ Declaration.FreshValidation
+ parsed
+ [])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleFailed _failure prefix ->
+ assertBool
+ (label <> " publishes no declaration")
+ (null (Declaration.pendingModulePrefixBatches prefix))
+ _result ->
+ assertFailure (label <> " unexpectedly succeeded")
+ assertEqual
+ (label <> " selects failures in source order")
+ expectedRuns =<< readIORef runs
+
+ orP left right = Core.CImp (Core.CImp left Core.CFalsum) right
+
+compilesExactReplacementComprehensions :: Assertion
+compilesExactReplacementComprehensions =
+ Temp.withSystemTempDirectory "felix-exact-replacement" \root -> do
+ let relative = "test/phase5/exact-replacement.tex"
+ executable = root Posix.</> "vampire"
+ repository <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for exact-replacement'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ observations <- newIORef []
+ let resolver = Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ modifyIORef' observations
+ (<> [ ( Backend.typedProblemRoute problem
+ , Backend.typedProblemAuxiliaryTag
+ <$> Vector.toList
+ (Backend.typedProblemAuxiliaries problem)
+ )
+ ])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ modules <- compileParsedWorkspaceWithResolver
+ foundation bootstrap resolver workspace
+ sealed <- sole "exact replacement module" modules
+ assertExactReplacementModule sealed
+ assertEqual
+ "replacement proof uses its checked characteristic on TH0"
+ [( Backend.RouteTh0
+ , [Foundation.ReplacementCharacteristic]
+ )]
+ =<< readIORef observations
+
+assertExactReplacementModule
+ :: Module.SealedTypedModule
+ -> Assertion
+assertExactReplacementModule sealed =
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed) of
+ [definitionBatch, theoremBatch] -> do
+ definitionObject <- sole
+ "replacement definition object"
+ (Declaration.committedBatchObjects definitionBatch)
+ case Identity.assertedObjectContent definitionObject of
+ Identity.TransparentObjectContent
+ _theory coreType body -> do
+ assertEqual "replacement definition type"
+ (Core.TyArrow Core.TySet Core.TySet)
+ coreType
+ assertEqual "replacement definition body"
+ expectedBody
+ body
+ assertEqual "replacement definition foundation helpers"
+ (Set.fromList
+ [ Foundation.FamilyUnionCharacteristic
+ , Foundation.SeparationCharacteristic
+ , Foundation.ReplacementCharacteristic
+ ])
+ (Foundation.foundationAxiomDependencies body)
+ content ->
+ assertFailure
+ ("unexpected replacement object " <> show content)
+ let definitionDelta =
+ Declaration.committedBatchDelta definitionBatch
+ definitionFacts =
+ Semantic.declarationDeltaFacts definitionDelta
+ assertEqual "replacement definition fact count"
+ 2 (length definitionFacts)
+ assertEqual "replacement equation/search view eligibility"
+ [Semantic.SearchIneligible, Semantic.SearchEligible]
+ (Semantic.semanticFactSearchEligibility <$> definitionFacts)
+ assertEqual "replacement generated view is unaliased"
+ 1
+ (length (Semantic.declarationDeltaAliases definitionDelta))
+ assertEqual "replacement definition proposition count"
+ 2
+ (length
+ (Declaration.committedBatchPropositions definitionBatch))
+ assertEqual "replacement definition proof validations"
+ []
+ (Declaration.committedBatchProofValidations definitionBatch)
+ definitionValidation <-
+ maybe
+ (assertFailure "replacement validation is absent"
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation
+ definitionBatch)
+ case Authority.validationDirectAuthorization
+ <$> Semantic.declarationValidationRecordCertificates
+ definitionValidation of
+ [ Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation target)
+ , Authority.CheckedKernelConstruction
+ (Authority.CheckedSetConstructionExtensionality
+ generatedTarget _descriptor)
+ ] ->
+ assertEqual "replacement construction authority object"
+ target generatedTarget
+ authorizations ->
+ assertFailure
+ ("unexpected replacement definition authorities "
+ <> show authorizations)
+
+ assertEqual "replacement theorem adds no object"
+ []
+ (Declaration.committedBatchObjects theoremBatch)
+ assertEqual "replacement theorem fact count"
+ 1
+ (length
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta theoremBatch)))
+ assertEqual "replacement theorem proposition count"
+ 1
+ (length
+ (Declaration.committedBatchPropositions theoremBatch))
+ theoremValidation <- sole
+ "replacement theorem validation"
+ (Declaration.committedBatchProofValidations theoremBatch)
+ case Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate
+ theoremValidation) of
+ Authority.CheckedSourceProof [_request] ->
+ pure ()
+ authorization ->
+ assertFailure
+ ("unexpected replacement theorem authority "
+ <> show authorization)
+ batches ->
+ assertFailure
+ ("expected replacement definition and theorem, found "
+ <> show (length batches))
+ where
+ app1 intrinsic argument =
+ Core.CApp (Core.CIntrinsic intrinsic) argument
+ app2 intrinsic first second =
+ Core.CApp (app1 intrinsic first) second
+ expectedBody =
+ Core.CLam Core.TySet $
+ app1 Core.FamilyUnion $
+ app2 Core.Repl (Core.CBound 0) $
+ Core.CLam Core.TySet $
+ app2 Core.Repl
+ (app2 Core.Sep
+ (Core.CBound 0)
+ (Core.CLam Core.TySet $
+ Core.CEq Core.TySet
+ (Core.CBound 1)
+ (Core.CBound 0)))
+ (Core.CLam Core.TySet
+ (Core.CBound 0))
+
+compilesAndReusesRelationalReplacement :: Assertion
+compilesAndReusesRelationalReplacement =
+ Temp.withSystemTempDirectory "felix-exact-relational-replacement" \root -> do
+ let relative = "test/phase5/exact-relational-replacement.tex"
+ failureRelative =
+ "test/phase5/exact-relational-replacement-failure.tex"
+ localFailureRelative =
+ "test/phase5/exact-relational-replacement-local-failure.tex"
+ executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ writeAcceptedFixtureVampire executable
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ mounts <- exactFixtureMounts =<< getCurrentDirectory
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ observed <- newIORef []
+ runs <- newIORef (0 :: Int)
+ let resolver = Declaration.vampireResolver \prepared -> do
+ modifyIORef' runs (+ 1)
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ modifyIORef' observed
+ (<> [ ( Backend.typedProblemRoute problem
+ , Backend.localPremiseOrdinalValue
+ . Backend.typedLocalPremiseOrdinal
+ <$> Vector.toList
+ (Backend.typedProblemLocalPremises problem)
+ , Backend.typedProblemAuxiliaryTag
+ <$> Vector.toList
+ (Backend.typedProblemAuxiliaries problem)
+ )
+ ])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ freshModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap resolver
+ Declaration.FreshValidation workspace
+ fresh <- sole "fresh relational replacement module" freshModules
+ assertRelationalReplacementModule fresh
+ problems <- readIORef observed
+ assertEqual "relational replacement request count"
+ 3 (length problems)
+ firstProblem <- sole "module functionality request" (take 1 problems)
+ assertEqual "module functionality uses FOF"
+ Backend.RouteFof
+ (case firstProblem of (route, _, _) -> route)
+ assertEqual "module functionality has no local premises"
+ []
+ (case firstProblem of (_, ordinals, _) -> ordinals)
+ assertEqual
+ "relational equivalence creates no ATP obligation or auxiliary"
+ [ (Backend.RouteFof, [], [])
+ , (Backend.RouteFof, [], [])
+ , (Backend.RouteFof, [0], [])
+ ]
+ problems
+
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmRuns <- newIORef (0 :: Int)
+ warmModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (countingAcceptedResolver executable warmRuns)
+ validation workspace
+ assertEqual "warm relational replacement skips Vampire"
+ 0 =<< readIORef warmRuns
+ warm <- sole "warm relational replacement module" warmModules
+ assertEqual "warm relational replacement interface"
+ (Module.sealedTypedModuleSemantic fresh)
+ (Module.sealedTypedModuleSemantic warm)
+ assertEqual "warm relational replacement prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix fresh))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warm))
+
+ let rejectingResolver =
+ Declaration.vampireResolver \_prepared ->
+ pure
+ (Right
+ (Provers.CounterSatisfiable
+ "relational functionality rejected"))
+ runRejected relativePath = do
+ failedWorkspace <-
+ parseExactWorkspace bootstrap mounts relativePath
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ rejectingResolver
+ Declaration.FreshValidation
+ (Parse.parsedWorkspaceRootModule failedWorkspace)
+ [])
+ Module.runTypedModule input
+ runRejected failureRelative >>= \case
+ Module.TypedModuleFailed _failure prefix -> do
+ batches <- pure
+ (Declaration.pendingModulePrefixBatches prefix)
+ assertEqual "failed relational definition keeps its prefix"
+ 1 (length batches)
+ prefixBatch <- sole "relational prefix declaration" batches
+ assertEqual "failed relational definition publishes no object"
+ 1 (length
+ (Declaration.committedBatchObjects prefixBatch))
+ _result ->
+ assertFailure
+ "nonfunctional relational definition did not fail"
+ runRejected localFailureRelative >>= \case
+ Module.TypedModuleFailed _failure prefix ->
+ assertBool
+ "failed local functionality publishes no theorem"
+ (null
+ (Declaration.pendingModulePrefixBatches prefix))
+ _result ->
+ assertFailure
+ "nonfunctional local definition did not fail"
+
+assertRelationalReplacementModule
+ :: Module.SealedTypedModule
+ -> Assertion
+assertRelationalReplacementModule sealed =
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed) of
+ [_axiomBatch, definitionBatch, proofBatch] -> do
+ _object <- sole "relational replacement object"
+ (Declaration.committedBatchObjects definitionBatch)
+ let definitionFacts =
+ Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta definitionBatch)
+ assertEqual "relational replacement fact eligibility"
+ [ Semantic.SearchIneligible
+ , Semantic.SearchIneligible
+ , Semantic.SearchEligible
+ ]
+ (Semantic.semanticFactSearchEligibility <$> definitionFacts)
+ let sourceSafety =
+ Authority.authoritySafety
+ (Authority.singletonEscapeKind Authority.SourceAxiom)
+ assertEqual "relational extensionality inherits functionality safety"
+ [ Authority.cleanAuthoritySafety
+ , sourceSafety
+ , sourceSafety
+ ]
+ ( Authority.factAuthoritySafety
+ . Semantic.semanticFactAuthority
+ <$> definitionFacts
+ )
+ assertEqual "relational replacement has only its equation alias"
+ 1
+ (length
+ (Semantic.declarationDeltaAliases
+ (Declaration.committedBatchDelta definitionBatch)))
+ validation <-
+ maybe
+ (assertFailure "relational replacement validation absent"
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation
+ definitionBatch)
+ case Authority.validationDirectAuthorization
+ <$> Semantic.declarationValidationRecordCertificates
+ validation of
+ [ Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation equationObject)
+ , Authority.CheckedSourceProof [_functionalityRequest]
+ , Authority.CheckedKernelConstruction
+ (Authority.CheckedSetConstructionExtensionality
+ extensionalObject _descriptor)
+ ] ->
+ assertEqual "relational facts target one object"
+ equationObject extensionalObject
+ authorizations ->
+ assertFailure
+ ("unexpected relational authorities "
+ <> show authorizations)
+ assertEqual "module construction generates no proof row"
+ []
+ (Declaration.committedBatchProofValidations definitionBatch)
+
+ proofValidation <- sole "proof-local relational validation"
+ (Declaration.committedBatchProofValidations proofBatch)
+ case Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate
+ proofValidation) of
+ Authority.CheckedSourceProof requests ->
+ assertEqual
+ "local functionality precedes its continuation"
+ 2 (length requests)
+ authorization ->
+ assertFailure
+ ("unexpected proof-local relational authority "
+ <> show authorization)
+ proofFact <- sole "proof-local relational theorem"
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta proofBatch))
+ assertEqual "local extensional premise retains discharge safety"
+ sourceSafety
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority proofFact))
+ batches ->
+ assertFailure
+ ("expected relational axiom, definition, and proof, found "
+ <> show (length batches))
+
+compilesAndReusesExactFiniteSets :: Assertion
+compilesAndReusesExactFiniteSets =
+ Temp.withSystemTempDirectory "felix-exact-finite-set" \root -> do
+ let relative = "test/phase5/exact-finite-set.tex"
+ sourcePath = root Posix.</> relative
+ executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ createDirectoryIfMissing True (Posix.takeDirectory sourcePath)
+ ByteString.readFile relative >>= ByteString.writeFile sourcePath
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for exact-finite-set'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts root
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ observations <- newIORef []
+ let resolver = Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ modifyIORef' observations
+ (<> [ ( Backend.typedProblemRoute problem
+ , Backend.typedProblemAuxiliaryTag
+ <$> Vector.toList
+ (Backend.typedProblemAuxiliaries problem)
+ )
+ ])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ freshModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap resolver
+ Declaration.FreshValidation
+ workspace
+ fresh <- sole "fresh finite-set module" freshModules
+ assertExactFiniteSetModule "fresh" fresh
+ assertEqual
+ "finite-set proof uses exactly its FOF characteristics"
+ [( Backend.RouteFof
+ , [ Foundation.EmptyCharacteristic
+ , Foundation.PairSetCharacteristic
+ , Foundation.FamilyUnionCharacteristic
+ ]
+ )]
+ =<< readIORef observations
+
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmRuns <- newIORef (0 :: Int)
+ warmModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (countingAcceptedResolver executable warmRuns)
+ validation
+ workspace
+ assertEqual "warm finite-set proof skips Vampire"
+ 0
+ =<< readIORef warmRuns
+ warm <- sole "warm finite-set module" warmModules
+ assertExactFiniteSetModule "warm" warm
+ assertEqual "warm finite-set semantic interface"
+ (Module.sealedTypedModuleSemantic fresh)
+ (Module.sealedTypedModuleSemantic warm)
+ assertEqual "warm finite-set final prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix fresh))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warm))
+
+preparesExactDirectInductives :: Assertion
+preparesExactDirectInductives = do
+ foundation <- expectRight Foundation.checkedFoundation
+ prepared <-
+ expectRight
+ =<< prepareExactInductiveFixture
+ "test/phase5/exact-inductive.tex"
+ assertEqual "exact inductive carrier type"
+ (Core.TyArrow Core.TySet Core.TySet)
+ (ExactInductive.preparedExactInductiveCarrierType prepared)
+ assertEqual "exact inductive carrier body"
+ expectedCarrier
+ (Core.frozenCoreTerm
+ (ExactInductive.preparedExactInductiveCarrierBody prepared))
+ assertEqual "foundation guard needs no imported fact"
+ []
+ (Vector.toList
+ (ExactInductive.preparedExactInductiveGuardTargets prepared))
+ let facts =
+ toList
+ (ExactInductive.preparedExactInductiveFacts prepared)
+ assertEqual "generated fact order"
+ [ Raw.Marker "phase5_fin_intro_1"
+ , Raw.Marker "phase5_fin_dom_subset"
+ , Raw.Marker "phase5_fin_cases"
+ , Raw.Marker "phase5_fin_induct"
+ ]
+ (TypedInductive.typedInductiveFactMarker <$> facts)
+ assertEqual "generated guarded-rule descriptors"
+ [ Set.singleton Foundation.SetLfpFixed
+ , Set.singleton Foundation.SetLfpBound
+ , Set.singleton Foundation.SetLfpFixed
+ , Set.singleton Foundation.SetLfpInduct
+ ]
+ ( Set.fromList
+ . toList
+ . TypedInductive.typedInductiveFactRules
+ <$> facts
+ )
+ assertBool "generated targets are closed propositions"
+ (all
+ (\fact ->
+ let target = TypedInductive.typedInductiveFactTarget fact
+ in Core.frozenCoreType target == Core.TyProp
+ && Set.null (Core.frozenCoreGlobals target))
+ facts)
+
+ let singleton =
+ Internal.finiteSet
+ Nowhere
+ (Internal.EmptySet Nowhere :| [])
+ noGlobalType :: Void -> Core.CoreType
+ noGlobalType = absurd
+ noGlobal
+ :: Internal.Symbol
+ -> Maybe (TypedInductive.SourceGlobal Void)
+ noGlobal = const Nothing
+ finite <-
+ expectRight
+ (TypedInductive.prepareTypedInductive
+ noGlobalType
+ foundation
+ noGlobal
+ (Internal.Marker "finite_internal")
+ (TypedInductive.DirectInductive
+ []
+ singleton
+ (TypedInductive.DirectInductiveClause
+ []
+ []
+ (Internal.EmptySet Nowhere)
+ :| [])))
+ finiteGuard <-
+ sole
+ "finite-set inductive guard"
+ (Vector.toList
+ (TypedInductive.typedInductiveGuardTargets finite))
+ assertEqual
+ "typed inductive path uses intrinsic finite-set adjunction"
+ (member
+ (Core.CIntrinsic Core.Empty)
+ (Core.canonicalSetInsert
+ (Core.CIntrinsic Core.Empty)
+ (Core.CIntrinsic Core.Empty)))
+ (Core.frozenCoreTerm finiteGuard)
+ where
+ apply1 intrinsic argument =
+ Core.CApp (Core.CIntrinsic intrinsic) argument
+ member element set =
+ Core.CApp
+ (Core.CApp (Core.CIntrinsic Core.Member) element)
+ set
+ expectedCarrier =
+ Core.CLam Core.TySet
+ (Core.CApp
+ (Core.CApp
+ (Core.CIntrinsic Core.ISetLfp)
+ (apply1 Core.UnivOf (Core.CBound 0)))
+ (Core.CLam Core.TySet
+ (Core.CApp
+ (Core.CApp
+ (Core.CIntrinsic Core.Sep)
+ (apply1 Core.UnivOf (Core.CBound 1)))
+ (Core.CLam Core.TySet
+ (Core.CEq Core.TySet
+ (Core.CBound 0)
+ (Core.CBound 2))))))
+
+preparesExactDatatypes :: Assertion
+preparesExactDatatypes = do
+ (foundation, owner, prepared) <-
+ expectRight
+ =<< prepareExactDatatypeFixture
+ "test/phase5/exact-datatype.tex"
+ let objects =
+ toList
+ (ExactDatatype.preparedExactDatatypeObjects prepared)
+ objectIds = fst <$> objects
+ objectTypes = snd <$> objects
+ expectedTypes =
+ [ Core.TySet
+ , Core.TySet
+ , Core.TyArrow Core.TySet Core.TySet
+ , Core.TyArrow Core.TySet
+ (Core.TyArrow Core.TySet Core.TySet)
+ ]
+ theory = Identity.theoryId foundation
+ expectedIds =
+ [ Identity.opaqueObjectId theory
+ (Identity.opaqueDeclarationSeed
+ owner
+ (localDeclarationOrdinal 0)
+ DatatypeDeclaration
+ (generatedObjectSlot index))
+ coreType
+ | (index, coreType) <- zip [0 ..] expectedTypes
+ ]
+ assertEqual "datatype opaque object types"
+ expectedTypes objectTypes
+ assertEqual "datatype opaque object slots"
+ expectedIds objectIds
+ assertBool "datatype objects are opaque"
+ (all ((== Identity.OpaqueObject) . Identity.objectIdFamily) objectIds)
+ (carrierId, zeroId, atomId, joinId, constructorIds) <-
+ case expectedIds of
+ [carrier, zero, atom, join] ->
+ pure
+ ( carrier
+ , zero
+ , atom
+ , join
+ , zero :| [atom, join]
+ )
+ _ ->
+ assertFailure "datatype object inventory is incomplete"
+ >> fail "unreachable"
+ let facts =
+ toList
+ (ExactDatatype.preparedExactDatatypeFacts prepared)
+ markers =
+ ExactDatatype.preparedExactDatatypeFactMarker <$> facts
+ assertEqual "datatype generated fact order"
+ [ Internal.Marker "phase5_data_phasefivezero_intro"
+ , Internal.Marker "phase5_data_phasefiveatom_intro"
+ , Internal.Marker "phase5_data_phasefivejoin_intro"
+ , Internal.Marker
+ "phase5_data_phasefivezero_phasefiveatom_distinct"
+ , Internal.Marker
+ "phase5_data_phasefivezero_phasefivejoin_distinct"
+ , Internal.Marker
+ "phase5_data_phasefiveatom_phasefivejoin_distinct"
+ , Internal.Marker "phase5_data_phasefiveatom_injective"
+ , Internal.Marker "phase5_data_phasefivejoin_injective"
+ , Internal.Marker "phase5_data_cases"
+ , Internal.Marker "phase5_data_induct"
+ ]
+ markers
+ assertBool "datatype generated targets are checked propositions"
+ (all
+ (\fact ->
+ Core.frozenCoreType
+ (ExactDatatype.preparedExactDatatypeFactTarget fact)
+ == Core.TyProp)
+ facts)
+ atomIntroduction <-
+ sole "domain-bearing datatype introduction"
+ [ fact
+ | fact <- facts
+ , ExactDatatype.preparedExactDatatypeFactMarker fact
+ == Internal.Marker "phase5_data_phasefiveatom_intro"
+ ]
+ assertEqual "domain-bearing datatype introduction target"
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (member
+ (Core.CBound 0)
+ singletonEmpty)
+ (member
+ (Core.CApp
+ (Core.CGlobal atomId)
+ (Core.CBound 0))
+ (Core.CGlobal carrierId))))
+ (Core.frozenCoreTerm
+ (ExactDatatype.preparedExactDatatypeFactTarget
+ atomIntroduction))
+ induction <-
+ sole "datatype induction law"
+ [ fact
+ | fact <- facts
+ , ExactDatatype.preparedExactDatatypeFactMarker fact
+ == Internal.Marker "phase5_data_induct"
+ ]
+ assertEqual "datatype induction target"
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (conjunctions
+ [ member
+ (Core.CGlobal zeroId)
+ (Core.CBound 0)
+ , Core.CForall Core.TySet
+ (Core.CImp
+ (member
+ (Core.CBound 0)
+ singletonEmpty)
+ (member
+ (Core.CApp
+ (Core.CGlobal atomId)
+ (Core.CBound 0))
+ (Core.CBound 1)))
+ , Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (conjunction
+ (member
+ (Core.CBound 1)
+ (Core.CBound 2))
+ (member
+ (Core.CBound 0)
+ (Core.CBound 2)))
+ (member
+ (Core.CApp
+ (Core.CApp
+ (Core.CGlobal joinId)
+ (Core.CBound 1))
+ (Core.CBound 0))
+ (Core.CBound 2))))
+ ])
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (member
+ (Core.CBound 0)
+ (Core.CGlobal carrierId))
+ (member
+ (Core.CBound 0)
+ (Core.CBound 1))))))
+ (Core.frozenCoreTerm
+ (ExactDatatype.preparedExactDatatypeFactTarget induction))
+ assertEqual "datatype descriptor membership"
+ (Authority.datatypeCompilationDescriptor
+ carrierId
+ constructorIds
+ (ExactDatatype.preparedExactDatatypeFactReference <$> facts))
+ (ExactDatatype.preparedExactDatatypeDescriptor prepared)
+ where
+ singletonEmpty =
+ Core.canonicalSetInsert
+ (Core.CIntrinsic Core.Empty)
+ (Core.CIntrinsic Core.Empty)
+
+ member element set =
+ Core.CApp
+ (Core.CApp
+ (Core.CIntrinsic Core.Member)
+ element)
+ set
+
+ conjunction left right =
+ Core.CImp
+ (Core.CImp left (Core.CImp right Core.CFalsum))
+ Core.CFalsum
+
+ conjunctions = \case
+ [] -> Core.CImp Core.CFalsum Core.CFalsum
+ first : remaining -> foldl' conjunction first remaining
+
+rejectsNestedExactDatatypeRecursion :: Assertion
+rejectsNestedExactDatatypeRecursion = do
+ result <-
+ prepareExactDatatypeFixture
+ "test/phase5/exact-datatype-nested.tex"
+ case result of
+ Left ExactDatatype.ExactDatatypeInvalid{} -> pure ()
+ Left failure ->
+ assertFailure
+ ("unexpected nested datatype failure: " <> show failure)
+ Right _prepared ->
+ assertFailure "nested exact datatype recursion was accepted"
+
+compilesAndReusesExactDatatypes :: Assertion
+compilesAndReusesExactDatatypes =
+ Temp.withSystemTempDirectory "felix-exact-datatype" \directory -> do
+ let relative = "test/phase5/exact-datatype.tex"
+ storePath = directory Posix.</> "store.sqlite"
+ (foundation, bootstrap, workspace, freshModules) <-
+ compileExactFixture relative
+ fresh <- sole "fresh exact datatype module" freshModules
+ assertExactDatatypeModule "fresh" fresh
+ parsed <-
+ sole "exact datatype parsed module"
+ (toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ let artifact sealed = do
+ key <- expectRight
+ (Semantic.moduleArtifactKey
+ (moduleName (Parse.parsedModuleAddress parsed))
+ (Parse.parsedModuleId parsed)
+ [ Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic
+ (Module.bootstrapPreludeModule bootstrap))
+ ]
+ (Identity.theoryId foundation))
+ pure
+ (Semantic.moduleArtifactResult
+ key
+ (Syntax.moduleSyntaxAssertedId
+ (Module.sealedTypedModuleSyntax sealed))
+ (Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic sealed)))
+ freshArtifact <- artifact fresh
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmModules <-
+ compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ unusedResolver
+ validation
+ workspace
+ warm <- sole "warm exact datatype module" warmModules
+ assertExactDatatypeModule "warm" warm
+ assertEqual "warm exact datatype semantic interface"
+ (Module.sealedTypedModuleSemantic fresh)
+ (Module.sealedTypedModuleSemantic warm)
+ assertEqual "warm exact datatype final prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix fresh))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warm))
+ assertEqual "warm exact datatype module artifact"
+ freshArtifact
+ =<< artifact warm
+
+ mounts <- exactFixtureMounts =<< getCurrentDirectory
+ nestedWorkspace <-
+ parseExactWorkspace bootstrap mounts
+ "test/phase5/exact-datatype-nested.tex"
+ nestedParsed <-
+ sole "nested exact datatype module"
+ (toList
+ (Parse.parsedWorkspaceImportedBeforeImporter
+ nestedWorkspace))
+ nestedInput <-
+ expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ unusedResolver
+ Declaration.FreshValidation
+ nestedParsed
+ [])
+ Module.runTypedModule nestedInput >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactDatatypeFailed
+ (ExactDatatype.ExactDatatypeInvalid
+ location _message)))
+ prefix -> do
+ assertEqual "nested datatype failure line"
+ 4
+ (locLine location)
+ assertBool "nested datatype publishes no prefix"
+ (null
+ (Declaration.pendingModulePrefixBatches prefix))
+ _result ->
+ assertFailure "unexpected nested datatype result"
+
+preparesNestedExactInductiveRecursion :: Assertion
+preparesNestedExactInductiveRecursion =
+ withAcceptedFixtureVampire "felix-nested-inductive" \vampire -> do
+ root <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts root
+ workspace <-
+ parseExactWorkspace bootstrap mounts
+ "test/phase5/exact-inductive-nested.tex"
+ parsed <- sole "nested exact inductive parsed module"
+ (toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ observed <- newIORef []
+ let resolver =
+ Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ modifyIORef' observed
+ (<> [ ( Backend.typedProblemRoute problem
+ , Backend.supportedPropositionTerm
+ (Backend.typedProblemClaim problem)
+ )
+ ])
+ (Provers.runPreparedTypedProver vampire prepared)
+ modules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap resolver
+ Declaration.FreshValidation workspace
+ sealed <- sole "nested exact inductive module" modules
+ observations <- readIORef observed
+ assertEqual "guard proof plus nested monotonicity request count"
+ 2 (length observations)
+ (route, target) <-
+ sole "nested monotonicity request"
+ [ observation
+ | observation@(_route, candidate) <- observations
+ , candidate == expectedPowerMonotonicity
+ ]
+ assertEqual "nested monotonicity target"
+ expectedPowerMonotonicity
+ target
+ assertEqual "nested monotonicity request is first-order"
+ Backend.RouteFof
+ route
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed) of
+ [_guardBatch, _unsafeBatch, inductiveBatch] -> do
+ let facts =
+ Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta inductiveBatch)
+ aliases =
+ Semantic.declarationDeltaAliases
+ (Declaration.committedBatchDelta inductiveBatch)
+ sourceSafety =
+ Authority.authoritySafety
+ (Authority.singletonEscapeKind Authority.SourceAxiom)
+ assertEqual "nested inductive fact eligibility"
+ ( Semantic.SearchEligible
+ : Semantic.SearchIneligible
+ : replicate 4 Semantic.SearchEligible
+ )
+ (Semantic.semanticFactSearchEligibility <$> facts)
+ monotonicityFact <- case facts of
+ _definition : fact : _laws -> pure fact
+ _ -> assertFailure "nested inductive fact inventory"
+ >> fail "unreachable"
+ assertEqual "nested monotonicity fact is unaliased"
+ False
+ (Semantic.semanticFactFingerprint monotonicityFact
+ `elem` (Semantic.semanticAliasTarget <$> aliases))
+ assertEqual "nested authority safety reaches generated laws"
+ [ Authority.cleanAuthoritySafety
+ , sourceSafety
+ , sourceSafety
+ , Authority.cleanAuthoritySafety
+ , sourceSafety
+ , sourceSafety
+ ]
+ ( Authority.factAuthoritySafety
+ . Semantic.semanticFactAuthority
+ <$> facts
+ )
+ validation <-
+ maybe
+ (assertFailure "nested inductive validation is absent"
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation
+ inductiveBatch)
+ assertEqual "nested inductive candidate authority shape"
+ [ "definition"
+ , "source-proof"
+ , "kernel"
+ , "kernel"
+ , "kernel"
+ , "kernel"
+ ]
+ (authorizationKind
+ . Authority.validationDirectAuthorization
+ <$> Semantic.declarationValidationRecordCertificates
+ validation)
+ requestId <- nestedRequestId inductiveBatch
+ Temp.withSystemTempDirectory
+ "felix-nested-inductive-cache" \temporary -> do
+ let storePath = temporary Posix.</> "store.sqlite"
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix sealed))
+ let warmValidation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation
+ store)
+ (expectRightIO
+ . Store.loadDeclarationValidation
+ store))
+ warmModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap unusedResolver
+ warmValidation workspace
+ warm <- sole
+ "warm nested exact inductive module"
+ warmModules
+ warmBatch <- case
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix warm) of
+ [_warmGuard, _warmUnsafe, batch] -> pure batch
+ batches ->
+ assertFailure
+ ("warm nested batch count: "
+ <> show (length batches))
+ >> fail "unreachable"
+ assertEqual "warm nested exact request"
+ requestId
+ =<< nestedRequestId warmBatch
+ assertEqual "warm nested semantic interface"
+ (Module.sealedTypedModuleSemantic sealed)
+ (Module.sealedTypedModuleSemantic warm)
+ assertEqual "warm nested admitted prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix sealed))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warm))
+ freshArtifact <-
+ moduleArtifact
+ foundation bootstrap parsed sealed
+ warmArtifact <-
+ moduleArtifact
+ foundation bootstrap parsed warm
+ assertEqual "warm nested module artifact"
+ freshArtifact warmArtifact
+ batches ->
+ assertFailure
+ ("expected guard and nested inductive batches, found "
+ <> show (length batches))
+
+ failureWorkspace <-
+ parseExactWorkspace bootstrap mounts
+ "test/phase5/exact-inductive-nested-failure.tex"
+ successfulRequests <- newIORef []
+ let successfulResolver =
+ Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ modifyIORef' successfulRequests
+ (<> [Backend.supportedPropositionTerm
+ (Backend.typedProblemClaim problem)])
+ (Provers.runPreparedTypedProver vampire prepared)
+ successfulModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap successfulResolver
+ Declaration.FreshValidation failureWorkspace
+ successful <- sole
+ "successful repeated/distinct nested inductive module"
+ successfulModules
+ assertEqual
+ "repeated and distinct contexts use two monotonicity requests"
+ [ expectedPowerMonotonicity
+ , expectedDoublePowerMonotonicity
+ ]
+ =<< readIORef successfulRequests
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix successful) of
+ [_guardOne, _guardTwo, batch] -> do
+ validation <- maybe
+ (assertFailure
+ "successful multi-context validation absent"
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation batch)
+ assertEqual
+ "deduplicated monotonicities precede all kernel laws"
+ ( ["definition", "source-proof", "source-proof"]
+ <> replicate 6 "kernel"
+ )
+ (authorizationKind
+ . Authority.validationDirectAuthorization
+ <$> Semantic.declarationValidationRecordCertificates
+ validation)
+ batches ->
+ assertFailure
+ ("successful multi-context batch count: "
+ <> show (length batches))
+ attempts <- newIORef (0 :: Int)
+ let rejectingResolver =
+ Declaration.vampireResolver \prepared -> do
+ index <- atomicModifyIORef' attempts \current ->
+ (current + 1, current)
+ if index == 0
+ then pure
+ (Right
+ (Provers.CounterSatisfiable
+ "first monotonicity rejected"))
+ else
+ (Provers.runPreparedTypedProver vampire prepared)
+ failureInput <-
+ expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ rejectingResolver
+ Declaration.FreshValidation
+ (Parse.parsedWorkspaceRootModule failureWorkspace)
+ [])
+ Module.runTypedModule failureInput >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedDeclarationFailed
+ (Declaration.ProofObligationFailedAt
+ location
+ Declaration.VampireObligationRejected{}))
+ prefix -> do
+ assertEqual "earliest monotonicity failure location"
+ 14 (locLine location)
+ assertEqual
+ "later monotonicity still resolves before first rejection"
+ 2 =<< readIORef attempts
+ assertEqual
+ "rejected monotonicity preserves only earlier declarations"
+ 2
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ _result ->
+ assertFailure
+ "nested monotonicity rejection unexpectedly succeeded"
+ where
+ authorizationKind = \case
+ Authority.CheckedKernelConstruction
+ Authority.CheckedDefinitionEquation{} -> "definition"
+ Authority.CheckedKernelConstruction{} -> "kernel"
+ Authority.CheckedSourceProof{} -> "source-proof"
+ authorization -> show authorization
+
+ nestedRequestId batch = do
+ validation <- maybe
+ (assertFailure "nested declaration validation absent"
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation batch)
+ certificate <- case
+ Semantic.declarationValidationRecordCertificates validation of
+ _definition : monotonicity : _laws -> pure monotonicity
+ certificates ->
+ assertFailure
+ ("nested declaration certificate count: "
+ <> show (length certificates))
+ >> fail "unreachable"
+ case Authority.validationDirectAuthorization certificate of
+ Authority.CheckedSourceProof [request] -> pure request
+ authorization ->
+ assertFailure
+ ("unexpected nested proof authorization "
+ <> show authorization)
+ >> fail "unreachable"
+
+ moduleArtifact foundation bootstrap parsed sealed = do
+ key <- expectRight
+ (Semantic.moduleArtifactKey
+ (moduleName (Parse.parsedModuleAddress parsed))
+ (Parse.parsedModuleId parsed)
+ [ Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic
+ (Module.bootstrapPreludeModule bootstrap))
+ ]
+ (Identity.theoryId foundation))
+ pure
+ (Semantic.moduleArtifactResult
+ key
+ (Syntax.moduleSyntaxAssertedId
+ (Module.sealedTypedModuleSyntax sealed))
+ (Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic sealed)))
+
+ expectedPowerMonotonicity =
+ Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (subset (Core.CBound 1) (Core.CBound 0))
+ (subset
+ (power (Core.CBound 1))
+ (power (Core.CBound 0)))))))
+
+ expectedDoublePowerMonotonicity =
+ Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (subset (Core.CBound 1) (Core.CBound 0))
+ (subset
+ (power (power (Core.CBound 1)))
+ (power (power (Core.CBound 0))))))))
+
+ power argument =
+ Core.CApp (Core.CIntrinsic Core.PowerSet) argument
+
+ subset left right =
+ Core.CForall Core.TySet
+ (Core.CImp
+ (member
+ (Core.CBound 0)
+ (Core.shiftCanonical 1 0 left))
+ (member
+ (Core.CBound 0)
+ (Core.shiftCanonical 1 0 right)))
+
+ member element set =
+ Core.CApp
+ (Core.CApp (Core.CIntrinsic Core.Member) element)
+ set
+
+compilesTransparentNestedInductiveWrappers :: Assertion
+compilesTransparentNestedInductiveWrappers =
+ withAcceptedFixtureVampire "felix-nested-wrapper" \vampire -> do
+ repository <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ mounts <- exactFixtureMounts repository
+ workspace <-
+ parseExactWorkspace bootstrap mounts
+ "test/phase5/exact-inductive-wrapper.tex"
+ observed <- newIORef []
+ let resolver =
+ Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ modifyIORef' observed
+ (<> [ ( Backend.typedProblemRoute problem
+ , Backend.supportedPropositionTerm
+ (Backend.typedProblemClaim problem)
+ )
+ ])
+ (Provers.runPreparedTypedProver vampire prepared)
+ modules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap resolver
+ Declaration.FreshValidation workspace
+ sealed <- sole "transparent-wrapper nested module" modules
+ observations <- readIORef observed
+ assertEqual "wrapper guard plus monotonicity request count"
+ 2 (length observations)
+ (route, target) <- case
+ [ observation
+ | observation@(_route, candidate) <- observations
+ , candidate == expectedPowerMonotonicity
+ ] of
+ [observation] -> pure observation
+ matches ->
+ assertFailure
+ ("normalized wrapper monotonicity matches: "
+ <> show matches
+ <> "; observed: " <> show observations)
+ >> fail "unreachable"
+ assertEqual "transparent-wrapper monotonicity is FOF"
+ Backend.RouteFof route
+ assertEqual "transparent-wrapper monotonicity target"
+ expectedPowerMonotonicity target
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed) of
+ [_wrapperDefinition, _guardProof, inductiveBatch] -> do
+ let facts =
+ Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta inductiveBatch)
+ assertEqual "transparent-wrapper inductive stays clean"
+ (replicate 6 Authority.cleanAuthoritySafety)
+ ( Authority.factAuthoritySafety
+ . Semantic.semanticFactAuthority
+ <$> facts
+ )
+ validation <- maybe
+ (assertFailure
+ "transparent-wrapper declaration validation absent"
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation
+ inductiveBatch)
+ assertEqual "transparent-wrapper staged authority"
+ [ "definition"
+ , "source-proof"
+ , "kernel"
+ , "kernel"
+ , "kernel"
+ , "kernel"
+ ]
+ (authorizationKind
+ . Authority.validationDirectAuthorization
+ <$> Semantic.declarationValidationRecordCertificates
+ validation)
+ batches ->
+ assertFailure
+ ("transparent-wrapper declaration count: "
+ <> show (length batches))
+ where
+ authorizationKind = \case
+ Authority.CheckedKernelConstruction
+ Authority.CheckedDefinitionEquation{} -> "definition"
+ Authority.CheckedKernelConstruction{} -> "kernel"
+ Authority.CheckedSourceProof{} -> "source-proof"
+ authorization -> show authorization
+
+ expectedPowerMonotonicity =
+ Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (subset (Core.CBound 1) (Core.CBound 0))
+ (subset
+ (power (Core.CBound 1))
+ (power (Core.CBound 0)))))))
+
+ power argument =
+ Core.CApp (Core.CIntrinsic Core.PowerSet) argument
+
+ subset left right =
+ Core.CForall Core.TySet
+ (Core.CImp
+ (member
+ (Core.CBound 0)
+ (Core.shiftCanonical 1 0 left))
+ (member
+ (Core.CBound 0)
+ (Core.shiftCanonical 1 0 right)))
+
+ member element set =
+ Core.CApp
+ (Core.CApp (Core.CIntrinsic Core.Member) element)
+ set
+
+normalizesNestedExactInductiveContexts :: Assertion
+normalizesNestedExactInductiveContexts = do
+ foundation <- expectRight Foundation.checkedFoundation
+ powerSymbol <- fixedFunctionSymbol "pow"
+ carrierSymbol <- fixedFunctionSymbol "cumul"
+ let a = Internal.NamedVar "A"
+ x = Internal.NamedVar "x"
+ y = Internal.NamedVar "y"
+ z = Internal.NamedVar "z"
+ carrier =
+ Internal.TermOp Nowhere carrierSymbol [Internal.TermVar a]
+ powerCarrier =
+ Internal.TermOp Nowhere powerSymbol [carrier]
+ doublePowerCarrier =
+ Internal.TermOp Nowhere powerSymbol [powerCarrier]
+ parameterizedCarrier =
+ Internal.TermOp Nowhere powerSymbol
+ [ Internal.TermOp Nowhere Lexicon.UpairSymbol
+ [carrier, Internal.TermVar x]
+ ]
+ powerContext <-
+ expectRight
+ (TypedInductive.prepareRecursiveCarrierContext
+ carrierSymbol [a] powerCarrier)
+ doublePowerContext <-
+ expectRight
+ (TypedInductive.prepareRecursiveCarrierContext
+ carrierSymbol [a] doublePowerCarrier)
+ parameterizedContext <-
+ expectRight
+ (TypedInductive.prepareRecursiveCarrierContext
+ carrierSymbol [a] parameterizedCarrier)
+ deduplicated <-
+ expectRight
+ (TypedInductive.prepareTypedInductive
+ (const Core.TySet)
+ foundation
+ (const Nothing)
+ (Internal.Marker "nested_dedup")
+ (TypedInductive.DirectInductive
+ [a]
+ (Internal.EmptySet Nowhere)
+ (TypedInductive.DirectInductiveClause
+ [x, y, z]
+ [ TypedInductive.DirectRecursiveCondition
+ (Internal.TermVar x) powerContext
+ , TypedInductive.DirectRecursiveCondition
+ (Internal.TermVar y) powerContext
+ , TypedInductive.DirectRecursiveCondition
+ (Internal.TermVar z) doublePowerContext
+ , TypedInductive.DirectRecursiveCondition
+ (Internal.TermVar z) parameterizedContext
+ ]
+ (Internal.TermVar a)
+ :| [])))
+ assertEqual "equal contexts deduplicate in first-occurrence order"
+ [ monotonicityTarget 4 power
+ , monotonicityTarget 4 (power . power)
+ , monotonicityTarget 4
+ (\hole -> power (pair hole (Core.CBound 4)))
+ ]
+ ( Core.frozenCoreTerm
+ . TypedInductive.typedInductiveMonotonicityTarget
+ <$> Vector.toList
+ (TypedInductive.typedInductiveMonotonicities deduplicated)
+ )
+
+ let wrapperSymbol =
+ Raw.mkMixfixItem
+ [ Just (Internal.Command "phasefivecheckedwrapper")
+ , Just Internal.InvisibleBraceL
+ , Nothing
+ , Just Internal.InvisibleBraceR
+ ]
+ (Internal.Marker "phasefivecheckedwrapper")
+ Raw.NonAssoc
+ wrapperCarrier =
+ Internal.TermOp Nowhere wrapperSymbol [carrier]
+ wrapperContext <-
+ expectRight
+ (TypedInductive.prepareRecursiveCarrierContext
+ carrierSymbol [a] wrapperCarrier)
+ wrapperBody <-
+ expectRight
+ (Core.checkCanonicalCore
+ (const Nothing)
+ (Core.CLam Core.TySet
+ (power (Core.CBound 0))))
+ let wrapperId =
+ Identity.transparentObjectId
+ (Identity.theoryId foundation)
+ (Core.TyArrow Core.TySet Core.TySet)
+ (Core.frozenCoreTerm wrapperBody)
+ wrapped <-
+ expectRight
+ (TypedInductive.prepareTypedInductive
+ (const (Core.TyArrow Core.TySet Core.TySet))
+ foundation
+ (\symbol ->
+ if symbol == Internal.SymbolMixfix wrapperSymbol
+ then Just
+ (TypedInductive.SourceGlobal
+ wrapperId (Just wrapperBody))
+ else Nothing)
+ (Internal.Marker "nested_wrapper")
+ (TypedInductive.DirectInductive
+ [a]
+ (Internal.EmptySet Nowhere)
+ (TypedInductive.DirectInductiveClause
+ [x]
+ [TypedInductive.DirectRecursiveCondition
+ (Internal.TermVar x) wrapperContext]
+ (Internal.TermVar a)
+ :| [])))
+ assertEqual
+ "transparent content, not a primitive-name whitelist, owns context semantics"
+ [monotonicityTarget 2 power]
+ ( Core.frozenCoreTerm
+ . TypedInductive.typedInductiveMonotonicityTarget
+ <$> Vector.toList
+ (TypedInductive.typedInductiveMonotonicities wrapped)
+ )
+ assertBool "transparent context target contains no wrapper global"
+ (all
+ (Set.null
+ . Core.frozenCoreGlobals
+ . TypedInductive.typedInductiveMonotonicityTarget)
+ (Vector.toList
+ (TypedInductive.typedInductiveMonotonicities wrapped)))
+
+ assertExactFailure
+ "test/phase5/exact-inductive-wrong-arguments.tex"
+ 4
+ (\case
+ ExactInductive.ExactInductiveRecursiveCarrierWrongArguments{} ->
+ True
+ _ -> False)
+ assertExactFailure
+ "test/phase5/exact-inductive-outside-membership.tex"
+ 4
+ (\case
+ ExactInductive.ExactInductiveRecursiveCarrierOutsideMembership{} ->
+ True
+ _ -> False)
+ assertExactFailure
+ "test/phase5/exact-inductive-recursive-element.tex"
+ 4
+ (\case
+ ExactInductive.ExactInductiveRecursiveTermMentionsCarrier{} ->
+ True
+ _ -> False)
+ assertExactFailure
+ "test/phase5/exact-inductive-recursive-domain.tex"
+ 2
+ (\case
+ ExactInductive.ExactInductiveDomainMentionsCarrier{} -> True
+ _ -> False)
+ assertExactFailure
+ "test/phase5/exact-inductive-recursive-result.tex"
+ 4
+ (\case
+ ExactInductive.ExactInductiveResultMentionsCarrier{} -> True
+ _ -> False)
+ assertExactFailure
+ "test/phase5/exact-inductive-unsupported-context.tex"
+ 4
+ (\case
+ ExactInductive.ExactInductiveUnsupportedRecursiveCarrierContext{} ->
+ True
+ _ -> False)
+ where
+ fixedFunctionSymbol marker =
+ sole ("fixed function " <> StrictText.unpack marker)
+ [ symbol
+ | symbol <- Lexicon.prefixOps
+ , Raw.mixfixMarker symbol == Internal.Marker marker
+ ]
+
+ assertExactFailure relative expectedLine expected =
+ prepareExactInductiveFixture relative >>= \case
+ Left failure
+ | expected failure ->
+ assertEqual
+ ("nested-context failure line for " <> relative)
+ expectedLine
+ (locLine
+ (ExactInductive.exactInductiveErrorLocation
+ failure))
+ | otherwise ->
+ assertFailure
+ ("unexpected nested-context failure for "
+ <> relative <> ": " <> show failure)
+ Right{} ->
+ assertFailure
+ ("unsupported nested context was accepted: " <> relative)
+
+ monotonicityTarget
+ :: Int
+ -> (Core.CanonicalTerm Identity.ObjectId
+ -> Core.CanonicalTerm Identity.ObjectId)
+ -> Core.CanonicalTerm Identity.ObjectId
+ monotonicityTarget sourceBinders context =
+ foldr
+ (const (Core.CForall Core.TySet))
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (subset (Core.CBound 1) (Core.CBound 0))
+ (subset
+ (context (Core.CBound 1))
+ (context (Core.CBound 0))))))
+ [1 .. sourceBinders]
+
+ power argument =
+ Core.CApp (Core.CIntrinsic Core.PowerSet) argument
+
+ pair left right =
+ Core.CApp
+ (Core.CApp (Core.CIntrinsic Core.PairSet) left)
+ right
+
+ subset left right =
+ Core.CForall Core.TySet
+ (Core.CImp
+ (member
+ (Core.CBound 0)
+ (Core.shiftCanonical 1 0 left))
+ (member
+ (Core.CBound 0)
+ (Core.shiftCanonical 1 0 right)))
+
+ member element set =
+ Core.CApp
+ (Core.CApp (Core.CIntrinsic Core.Member) element)
+ set
+
+compilesAndReusesExactInductives :: Assertion
+compilesAndReusesExactInductives =
+ Temp.withSystemTempDirectory "felix-exact-inductive" \directory -> do
+ let relative = "test/phase5/exact-inductive.tex"
+ storePath = directory Posix.</> "store.sqlite"
+ (foundation, bootstrap, workspace, freshModules) <-
+ compileExactFixture relative
+ fresh <- sole "fresh exact inductive module" freshModules
+ assertExactInductiveModule foundation "fresh" fresh
+ parsed <-
+ sole "exact inductive parsed module"
+ (toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ let artifact sealed = do
+ key <- expectRight
+ (Semantic.moduleArtifactKey
+ (moduleName (Parse.parsedModuleAddress parsed))
+ (Parse.parsedModuleId parsed)
+ [ Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic
+ (Module.bootstrapPreludeModule bootstrap))
+ ]
+ (Identity.theoryId foundation))
+ pure
+ (Semantic.moduleArtifactResult
+ key
+ (Syntax.moduleSyntaxAssertedId
+ (Module.sealedTypedModuleSyntax sealed))
+ (Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic sealed)))
+ freshArtifact <- artifact fresh
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmModules <-
+ compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ unusedResolver
+ validation
+ workspace
+ warm <- sole "warm exact inductive module" warmModules
+ assertExactInductiveModule foundation "warm" warm
+ assertEqual "warm exact inductive semantic interface"
+ (Module.sealedTypedModuleSemantic fresh)
+ (Module.sealedTypedModuleSemantic warm)
+ assertEqual "warm exact inductive final prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix fresh))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warm))
+ assertEqual "warm exact inductive module artifact"
+ freshArtifact
+ =<< artifact warm
+
+authorizesRecursiveExactInductives :: Assertion
+authorizesRecursiveExactInductives =
+ Temp.withSystemTempDirectory "felix-recursive-inductive" \directory -> do
+ let relative = "test/phase5/exact-inductive-recursive.tex"
+ storePath = directory Posix.</> "store.sqlite"
+ (foundation, bootstrap, workspace, freshModules) <-
+ compileExactFixture relative
+ fresh <- sole "fresh recursive inductive module" freshModules
+ assertRecursiveExactInductiveModule "fresh" fresh
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmModules <-
+ compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ unusedResolver
+ validation
+ workspace
+ warm <- sole "warm recursive inductive module" warmModules
+ assertRecursiveExactInductiveModule "warm" warm
+ assertEqual "warm recursive inductive semantic interface"
+ (Module.sealedTypedModuleSemantic fresh)
+ (Module.sealedTypedModuleSemantic warm)
+ assertEqual "warm recursive inductive final prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix fresh))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warm))
+
+assertRecursiveExactInductiveModule
+ :: String
+ -> Module.SealedTypedModule
+ -> Assertion
+assertRecursiveExactInductiveModule label sealed =
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed) of
+ [_axiomBatch, inductiveBatch] -> do
+ object <-
+ sole (label <> " recursive inductive carrier")
+ (Declaration.committedBatchObjects inductiveBatch)
+ let facts =
+ Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta inductiveBatch)
+ sourceSafety =
+ Authority.authoritySafety
+ (Authority.singletonEscapeKind
+ Authority.SourceAxiom)
+ assertEqual (label <> " recursive inductive safety")
+ (Authority.cleanAuthoritySafety : replicate 4 sourceSafety)
+ ( Authority.factAuthoritySafety
+ . Semantic.semanticFactAuthority
+ <$> facts
+ )
+ validation <-
+ maybe
+ (assertFailure
+ (label <> ": recursive validation is absent")
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation
+ inductiveBatch)
+ assertEqual (label <> " recursive inductive descriptors")
+ [ Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation
+ (Identity.assertedObjectId object))
+ , guardedRules
+ (Foundation.SetLfpBound :| [Foundation.SetLfpFixed])
+ , guardedRules (Foundation.SetLfpBound :| [])
+ , guardedRules (Foundation.SetLfpFixed :| [])
+ , guardedRules (Foundation.SetLfpInduct :| [])
+ ]
+ ( Authority.validationDirectAuthorization
+ <$> Semantic.declarationValidationRecordCertificates
+ validation
+ )
+ batches ->
+ assertFailure
+ (label <> ": expected axiom and inductive batches, found "
+ <> show (length batches))
+ where
+ guardedRules rules =
+ Authority.CheckedKernelConstruction
+ (Authority.GuardedFoundationRules
+ (Authority.guardedRuleSet rules))
+
+assertExactDatatypeModule
+ :: String
+ -> Module.SealedTypedModule
+ -> Assertion
+assertExactDatatypeModule label sealed = do
+ assertEqual (label <> " datatype semantic declaration count")
+ 1
+ (length
+ (Semantic.semanticInterfaceDeclarations
+ (Module.sealedTypedModuleSemantic sealed)))
+ batch <-
+ sole (label <> " datatype declaration batch")
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed))
+ let objects = Declaration.committedBatchObjects batch
+ objectIds = Identity.assertedObjectId <$> objects
+ delta = Declaration.committedBatchDelta batch
+ facts = Semantic.declarationDeltaFacts delta
+ aliases = Semantic.declarationDeltaAliases delta
+ bindings =
+ Semantic.semanticEnvironmentBindings
+ (Semantic.declarationDeltaEnvironment delta)
+ boundIds =
+ Semantic.semanticGlobalTargetObject
+ . Semantic.semanticGlobalBindingTarget
+ <$> bindings
+ assertEqual (label <> " datatype object count") 4 (length objects)
+ assertBool (label <> " datatype objects are opaque")
+ (all
+ ((== Identity.OpaqueObject)
+ . Identity.objectIdFamily
+ . Identity.assertedObjectId)
+ objects)
+ assertEqual (label <> " datatype global binding count")
+ 4
+ (length bindings)
+ assertBool (label <> " datatype globals are references")
+ (all
+ (\binding ->
+ case Semantic.semanticGlobalBindingTarget binding of
+ Semantic.GlobalReference{} -> True
+ Semantic.TransparentExpansion{} -> False
+ Semantic.ContextualTransparentExpansion{} -> False)
+ bindings)
+ assertEqual (label <> " datatype global targets")
+ (Set.fromList objectIds)
+ (Set.fromList boundIds)
+ assertEqual (label <> " datatype fact count") 10 (length facts)
+ assertEqual (label <> " datatype aliases")
+ (Semantic.semanticName <$>
+ [ "phase5_data_phasefivezero_intro"
+ , "phase5_data_phasefiveatom_intro"
+ , "phase5_data_phasefivejoin_intro"
+ , "phase5_data_phasefivezero_phasefiveatom_distinct"
+ , "phase5_data_phasefivezero_phasefivejoin_distinct"
+ , "phase5_data_phasefiveatom_phasefivejoin_distinct"
+ , "phase5_data_phasefiveatom_injective"
+ , "phase5_data_phasefivejoin_injective"
+ , "phase5_data_cases"
+ , "phase5_data_induct"
+ ])
+ (Semantic.semanticAliasName <$> aliases)
+ assertBool (label <> " datatype facts are clean")
+ (all
+ ((== Authority.cleanAuthoritySafety)
+ . Authority.factAuthoritySafety
+ . Semantic.semanticFactAuthority)
+ facts)
+ assertEqual (label <> " datatype proof validations")
+ []
+ (Declaration.committedBatchProofValidations batch)
+ validation <-
+ maybe
+ (assertFailure (label <> ": datatype validation is absent")
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation batch)
+ descriptor <-
+ case objectIds of
+ carrier : firstConstructor : remainingConstructors ->
+ pure
+ (Authority.datatypeCompilationDescriptor
+ carrier
+ (firstConstructor :| remainingConstructors)
+ ( Authority.factAuthorityTheorem
+ . Semantic.semanticFactAuthority
+ <$> facts
+ ))
+ _ ->
+ assertFailure (label <> ": datatype object family is absent")
+ >> fail "unreachable"
+ assertEqual (label <> " datatype validation descriptors")
+ (replicate 10
+ (Authority.TrustedCompilation
+ (Authority.DatatypeCompilation descriptor)))
+ ( Authority.validationDirectAuthorization
+ <$> Semantic.declarationValidationRecordCertificates validation
+ )
+
+assertExactInductiveModule
+ :: Foundation.CheckedFoundation
+ -> String
+ -> Module.SealedTypedModule
+ -> Assertion
+assertExactInductiveModule foundation label sealed =
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed) of
+ [batch] -> do
+ object <-
+ sole (label <> " inductive carrier")
+ (Declaration.committedBatchObjects batch)
+ case Identity.assertedObjectContent object of
+ Identity.TransparentObjectContent
+ _theory coreType body -> do
+ assertEqual (label <> " inductive carrier type")
+ (Core.TyArrow Core.TySet Core.TySet)
+ coreType
+ assertEqual (label <> " inductive carrier identity")
+ (Identity.transparentObjectId
+ (Identity.theoryId foundation)
+ coreType
+ body)
+ (Identity.assertedObjectId object)
+ content ->
+ assertFailure
+ (label <> ": unexpected inductive carrier "
+ <> show content)
+ let delta = Declaration.committedBatchDelta batch
+ facts = Semantic.declarationDeltaFacts delta
+ aliases = Semantic.declarationDeltaAliases delta
+ assertEqual (label <> " inductive fact count")
+ 5
+ (length facts)
+ assertEqual (label <> " inductive aliases")
+ (Semantic.semanticName <$>
+ [ "phase5_fin"
+ , "phase5_fin_intro_1"
+ , "phase5_fin_dom_subset"
+ , "phase5_fin_cases"
+ , "phase5_fin_induct"
+ ])
+ (Semantic.semanticAliasName <$> aliases)
+ assertBool (label <> " inductive facts are clean")
+ (all
+ ((== Authority.cleanAuthoritySafety)
+ . Authority.factAuthoritySafety
+ . Semantic.semanticFactAuthority)
+ facts)
+ assertEqual (label <> " inductive proof validations")
+ []
+ (Declaration.committedBatchProofValidations batch)
+ validation <-
+ maybe
+ (assertFailure
+ (label <> ": inductive validation is absent")
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation batch)
+ assertEqual (label <> " inductive validation descriptors")
+ [ Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation
+ (Identity.assertedObjectId object))
+ , guardedRules (Foundation.SetLfpFixed :| [])
+ , guardedRules (Foundation.SetLfpBound :| [])
+ , guardedRules (Foundation.SetLfpFixed :| [])
+ , guardedRules (Foundation.SetLfpInduct :| [])
+ ]
+ ( Authority.validationDirectAuthorization
+ <$> Semantic.declarationValidationRecordCertificates
+ validation
+ )
+ batches ->
+ assertFailure
+ (label <> ": expected one inductive batch, found "
+ <> show (length batches))
+ where
+ guardedRules rules =
+ Authority.CheckedKernelConstruction
+ (Authority.GuardedFoundationRules
+ (Authority.guardedRuleSet rules))
+
+assertExactFiniteSetModule
+ :: String
+ -> Module.SealedTypedModule
+ -> Assertion
+assertExactFiniteSetModule label sealed = do
+ assertEqual (label <> " finite-set semantic declarations")
+ 2
+ (length
+ (Semantic.semanticInterfaceDeclarations
+ (Module.sealedTypedModuleSemantic sealed)))
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed) of
+ [definitionBatch, theoremBatch] -> do
+ definitionObject <- sole
+ (label <> " finite-set definition object")
+ (Declaration.committedBatchObjects definitionBatch)
+ case Identity.assertedObjectContent definitionObject of
+ Identity.TransparentObjectContent
+ _theory coreType body -> do
+ assertEqual (label <> " finite-set definition type")
+ (Core.TyArrow Core.TySet
+ (Core.TyArrow Core.TySet Core.TySet))
+ coreType
+ assertEqual (label <> " finite-set definition body")
+ expectedBody
+ body
+ content ->
+ assertFailure
+ (label <> ": unexpected finite-set object "
+ <> show content)
+ assertEqual (label <> " finite-set definition fact count")
+ 1
+ (length
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta definitionBatch)))
+ assertEqual (label <> " finite-set definition proof validations")
+ []
+ (Declaration.committedBatchProofValidations definitionBatch)
+
+ assertEqual (label <> " finite-set theorem adds no object")
+ []
+ (Declaration.committedBatchObjects theoremBatch)
+ theoremFact <- sole
+ (label <> " finite-set theorem fact")
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta theoremBatch))
+ assertEqual (label <> " finite-set theorem safety")
+ Authority.cleanAuthoritySafety
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority theoremFact))
+ theoremValidation <- sole
+ (label <> " finite-set theorem validation")
+ (Declaration.committedBatchProofValidations theoremBatch)
+ case Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate
+ theoremValidation) of
+ Authority.CheckedSourceProof [_request] ->
+ pure ()
+ authorization ->
+ assertFailure
+ (label <> ": unexpected finite-set theorem authority "
+ <> show authorization)
+ batches ->
+ assertFailure
+ (label <> ": expected finite-set definition and theorem, found "
+ <> show (length batches))
+ where
+ expectedBody =
+ Core.CLam Core.TySet
+ (Core.CLam Core.TySet
+ (Core.canonicalSetInsert
+ (Core.CBound 1)
+ (Core.canonicalSetInsert
+ (Core.CBound 0)
+ (Core.CIntrinsic Core.Empty))))
+
+assertExactSeparationModule
+ :: String
+ -> Module.SealedTypedModule
+ -> Assertion
+assertExactSeparationModule label sealed = do
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed) of
+ [definitionBatch, theoremBatch] -> do
+ assertEqual (label <> " separation definition object count")
+ 1
+ (length
+ (Declaration.committedBatchObjects definitionBatch))
+ definitionObject <- sole
+ (label <> " separation definition object")
+ (Declaration.committedBatchObjects definitionBatch)
+ case Identity.assertedObjectContent definitionObject of
+ Identity.TransparentObjectContent
+ _theory coreType body -> do
+ assertEqual (label <> " separation definition type")
+ (Core.TyArrow Core.TySet Core.TySet)
+ coreType
+ assertEqual (label <> " separation definition body")
+ (Core.CLam Core.TySet
+ (Core.CApp
+ (Core.CApp
+ (Core.CIntrinsic Core.Sep)
+ (Core.CBound 0))
+ (Core.CLam Core.TySet
+ (Core.CEq Core.TySet
+ (Core.CBound 0)
+ (Core.CBound 0)))))
+ body
+ content ->
+ assertFailure
+ (label <> ": unexpected separation object "
+ <> show content)
+ let definitionDelta =
+ Declaration.committedBatchDelta definitionBatch
+ definitionFacts =
+ Semantic.declarationDeltaFacts definitionDelta
+ assertEqual (label <> " definition fact count")
+ 2 (length definitionFacts)
+ assertEqual (label <> " defining equation is explicit-only")
+ [Semantic.SearchIneligible, Semantic.SearchEligible]
+ (Semantic.semanticFactSearchEligibility <$> definitionFacts)
+ assertEqual (label <> " generated view is unaliased")
+ 1
+ (length (Semantic.declarationDeltaAliases definitionDelta))
+ assertEqual (label <> " definition proposition count")
+ 2
+ (length
+ (Declaration.committedBatchPropositions definitionBatch))
+ assertEqual (label <> " definition proof validations")
+ []
+ (Declaration.committedBatchProofValidations definitionBatch)
+ definitionValidation <-
+ maybe
+ (assertFailure
+ (label <> ": definition validation is absent")
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation
+ definitionBatch)
+ case Authority.validationDirectAuthorization
+ <$> Semantic.declarationValidationRecordCertificates
+ definitionValidation of
+ [ Authority.CheckedKernelConstruction
+ (Authority.CheckedDefinitionEquation target)
+ , Authority.CheckedKernelConstruction
+ (Authority.CheckedSetConstructionExtensionality
+ generatedTarget _descriptor)
+ ] ->
+ assertEqual
+ (label <> " construction authority object")
+ target generatedTarget
+ authorizations ->
+ assertFailure
+ (label <> ": unexpected definition authorities "
+ <> show authorizations)
+
+ assertEqual (label <> " theorem adds no object")
+ []
+ (Declaration.committedBatchObjects theoremBatch)
+ theoremFact <- sole
+ (label <> " separation theorem fact")
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta theoremBatch))
+ assertEqual (label <> " theorem safety")
+ Authority.cleanAuthoritySafety
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority theoremFact))
+ theoremValidation <- sole
+ (label <> " separation theorem validation")
+ (Declaration.committedBatchProofValidations theoremBatch)
+ case Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate
+ theoremValidation) of
+ Authority.CheckedSourceProof [_request] ->
+ pure ()
+ authorization ->
+ assertFailure
+ (label <> ": unexpected theorem authority "
+ <> show authorization)
+ batches ->
+ assertFailure
+ (label <> ": expected definition and theorem, found "
+ <> show (length batches))
+
+reusesExactSeparationValidation :: Assertion
+reusesExactSeparationValidation =
+ Temp.withSystemTempDirectory "felix-exact-separation-cache" \root -> do
+ let relative = "test/phase5/exact-separation.tex"
+ sourcePath = root Posix.</> relative
+ executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ createDirectoryIfMissing True (Posix.takeDirectory sourcePath)
+ ByteString.readFile relative >>= ByteString.writeFile sourcePath
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for exact-separation-cache'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts root
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ freshRequests <- newIORef []
+ let freshResolver = Declaration.vampireResolver \prepared -> do
+ modifyIORef' freshRequests
+ (<> [Provers.preparedTypedProverRequest prepared])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ freshModules <-
+ compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ freshResolver
+ Declaration.FreshValidation
+ workspace
+ assertEqual "fresh separation proof runs Vampire once"
+ 1
+ . length
+ =<< readIORef freshRequests
+ fresh <- sole "fresh exact separation module" freshModules
+ assertExactSeparationModule "fresh cached" fresh
+ freshDefinitionBatch <-
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix fresh) of
+ batch : _theorem : [] -> pure batch
+ batches ->
+ assertFailure
+ ("fresh separation declaration count: "
+ <> show (length batches))
+ >> fail "unreachable"
+ freshDefinitionValidation <-
+ maybe
+ (assertFailure "fresh separation definition validation absent"
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation
+ freshDefinitionBatch)
+ corruptedDefinitionValidation <-
+ case Semantic.declarationValidationRecordCertificates
+ freshDefinitionValidation of
+ [equation, extensional] -> do
+ corruptedExtensional <-
+ expectRight
+ (Authority.validationCertificate
+ (Authority.validationTarget extensional)
+ (Authority.validationDirectAuthorization
+ equation))
+ pure
+ (Semantic.declarationValidationRecord
+ (Semantic.declarationValidationRecordKey
+ freshDefinitionValidation)
+ [equation, corruptedExtensional])
+ certificates ->
+ assertFailure
+ ("fresh separation certificate count: "
+ <> show (length certificates))
+ >> fail "unreachable"
+ freshRequest <-
+ sole "fresh separation request"
+ =<< readIORef freshRequests
+ freshAcceptedRequest <-
+ acceptedRequestId "fresh separation" fresh
+ assertEqual "fresh authority binds the exact request bytes"
+ freshAcceptedRequest
+ (Provers.preparedVerificationRequestId freshRequest)
+ assertBool "fresh separation request bytes are retained by the caller"
+ (Provers.preparedVerificationByteCount freshRequest > 0)
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmRuns <- newIORef (0 :: Int)
+ warmModules <-
+ compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ (countingAcceptedResolver executable warmRuns)
+ validation
+ workspace
+ assertEqual "warm separation proof skips Vampire"
+ 0
+ =<< readIORef warmRuns
+ warm <- sole "warm exact separation module" warmModules
+ assertExactSeparationModule "warm cached" warm
+ warmAcceptedRequest <-
+ acceptedRequestId "warm separation" warm
+ assertEqual
+ "warm validation retains the fresh request-byte identity"
+ freshAcceptedRequest
+ warmAcceptedRequest
+ assertEqual "warm separation semantic interface"
+ (Module.sealedTypedModuleSemantic fresh)
+ (Module.sealedTypedModuleSemantic warm)
+ assertEqual "warm separation final prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix fresh))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warm))
+ let components sealed =
+ let batches =
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed)
+ in ( concatMap
+ Declaration.committedBatchObjects
+ batches
+ , concatMap
+ (fmap Identity.checkedPropositionId
+ . Declaration.committedBatchPropositions)
+ batches
+ , concatMap
+ Declaration.committedBatchProofValidations
+ batches
+ , Declaration.committedBatchDeclarationValidation
+ <$> batches
+ )
+ assertEqual "warm separation checked artifacts"
+ (components fresh)
+ (components warm)
+ corruptRuns <- newIORef (0 :: Int)
+ let corruptedValidation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (\key ->
+ if key
+ == Semantic.declarationValidationRecordKey
+ corruptedDefinitionValidation
+ then pure
+ (Just corruptedDefinitionValidation)
+ else expectRightIO
+ (Store.loadDeclarationValidation
+ store key)))
+ corrupted <- Exception.try
+ (compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ (countingAcceptedResolver executable corruptRuns)
+ corruptedValidation
+ workspace)
+ :: IO
+ (Either
+ Declaration.ValidationIntegrityError
+ [Module.SealedTypedModule])
+ case corrupted of
+ Left Declaration.CachedValidationIntegrityError{} ->
+ pure ()
+ Right _ ->
+ assertFailure
+ "mismatched generated authority replay succeeded"
+ assertEqual
+ "mismatched generated authority does not invoke Vampire"
+ 0
+ =<< readIORef corruptRuns
+ where
+ acceptedRequestId label sealed = do
+ theoremBatch <-
+ case Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed) of
+ [_definitionBatch, batch] -> pure batch
+ batches ->
+ assertFailure
+ (label <> ": unexpected declaration count "
+ <> show (length batches))
+ >> fail "unreachable"
+ validation <- sole
+ (label <> " proof validation")
+ (Declaration.committedBatchProofValidations theoremBatch)
+ case Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate validation) of
+ Authority.CheckedSourceProof [request] -> pure request
+ authorization ->
+ assertFailure
+ (label <> ": unexpected direct authorization "
+ <> show authorization)
+ >> fail "unreachable"
+
+compilesExactSourceAxioms :: Assertion
+compilesExactSourceAxioms =
+ Temp.withSystemTempDirectory "felix-exact-source-axiom" \root -> do
+ let storePath = root Posix.</> "store.sqlite"
+ (foundation, bootstrap, workspace, freshModules) <-
+ compileExactFixture
+ "test/phase5/exact-source-axiom-assumptions.tex"
+ fresh <- sole "fresh source-axiom module" freshModules
+ assertSourceAxiom "fresh" fresh
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix fresh))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap unusedResolver validation workspace
+ warm <- sole "warm source-axiom module" warmModules
+ assertSourceAxiom "warm" warm
+ assertEqual "warm source axiom preserves semantics"
+ (Module.sealedTypedModuleSemantic fresh)
+ (Module.sealedTypedModuleSemantic warm)
+ assertEqual "warm source axiom preserves prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix fresh))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix warm))
+ where
+ assertSourceAxiom label sealed = do
+ batch <- sole (label <> " source-axiom batch")
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed))
+ fact <- sole (label <> " source-axiom fact")
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch))
+ alias <- sole (label <> " source-axiom alias")
+ (Semantic.declarationDeltaAliases
+ (Declaration.committedBatchDelta batch))
+ assertEqual (label <> " source-axiom search eligibility")
+ Semantic.SearchEligible
+ (Semantic.semanticFactSearchEligibility fact)
+ assertEqual (label <> " source-axiom marker alias")
+ (Semantic.semanticName "phase5_exact_source_axiom_assumptions")
+ (Semantic.semanticAliasName alias)
+ proposition <- sole (label <> " source-axiom proposition")
+ (Declaration.committedBatchPropositions batch)
+ assertEqual (label <> " source-axiom closed target")
+ (Core.CForall Core.TySet
+ (Core.CForall Core.TySet
+ (Core.CImp
+ (member (Core.CBound 0) (Core.CBound 1))
+ (Core.CImp
+ (Core.CEq Core.TySet
+ (Core.CBound 0)
+ (Core.CBound 0))
+ (member
+ (Core.CBound 0)
+ (Core.CBound 1))))))
+ (Core.frozenCoreTerm
+ (Identity.checkedPropositionTerm proposition))
+ assertEqual (label <> " source-axiom safety")
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind Authority.SourceAxiom))
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority fact))
+ validation <-
+ maybe
+ (assertFailure (label <> " source-axiom validation is absent")
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation batch)
+ certificate <- sole (label <> " source-axiom certificate")
+ (Semantic.declarationValidationRecordCertificates validation)
+ assertEqual (label <> " source-axiom direct authority")
+ Authority.SourceAxiomAuthorization
+ (Authority.validationDirectAuthorization certificate)
+ assertEqual (label <> " source axiom has no proof validations")
+ []
+ (Declaration.committedBatchProofValidations batch)
+
+ member element set =
+ Core.CApp
+ (Core.CApp
+ (Core.CIntrinsic Core.Member)
+ element)
+ set
+
+rejectsProofLocalGeneralization :: Assertion
+rejectsProofLocalGeneralization = do
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ repository <- getCurrentDirectory
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace
+ bootstrap mounts
+ "test/phase5/exact-proof-local-free.tex"
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ unusedResolver
+ Declaration.FreshValidation
+ (Parse.parsedWorkspaceRootModule workspace)
+ [])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactProofFailed
+ (ExactProof.ExactProofElaborationFailed
+ (Exact.ExactFreeVariable location
+ (Raw.NamedVar "y")))))
+ prefix -> do
+ assertEqual "proof-local free variable line"
+ 6 (locLine location)
+ assertEqual "proof-local failure commits nothing"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Module.TypedModuleSucceeded{} ->
+ assertFailure "proof-local variable was generalized"
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("proof-local generalization module did not open: "
+ <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("unexpected proof-local generalization failure: "
+ <> show failure)
+
+doesNotTreatMarkerOnlyNounAsSet :: Assertion
+doesNotTreatMarkerOnlyNounAsSet = do
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ repository <- getCurrentDirectory
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace
+ bootstrap mounts
+ "test/phase5/exact-set-marker.tex"
+ Temp.withSystemTempDirectory "felix-exact-set-marker" \root -> do
+ let executable = root Posix.</> "vampire"
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for set-marker'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ observed <- newIORef []
+ let resolver = Declaration.vampireResolver \prepared -> do
+ let problem =
+ Provers.preparedTypedProverLogicalProblem prepared
+ claim = Backend.typedProblemClaim problem
+ locals = Backend.typedProblemLocalPremises problem
+ modifyIORef' observed
+ (<> [ [ Backend.supportedPropositionTerm
+ (Backend.typedLocalPremiseProposition premise)
+ == Backend.supportedPropositionTerm claim
+ | premise <- Vector.toList locals
+ ]
+ ])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ resolver
+ Declaration.FreshValidation
+ (Parse.parsedWorkspaceRootModule workspace)
+ [])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleSucceeded{} ->
+ assertEqual
+ "the source noun supplies the local proof premise"
+ [[True]]
+ =<< readIORef observed
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("marker-only set noun module did not open: "
+ <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("unexpected marker-only set noun failure: "
+ <> show failure)
+
+compilesExactOmittedProofs :: Assertion
+compilesExactOmittedProofs =
+ Temp.withSystemTempDirectory "felix-exact-omitted" \root -> do
+ repository <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace
+ bootstrap mounts "test/phase5/exact-omitted.tex"
+ let executable = root Posix.</> "vampire"
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for exact-omitted'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ calls <- newIORef (0 :: Int)
+ let resolver = Declaration.vampireResolver \prepared -> do
+ modifyIORef' calls (+ 1)
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ modules <-
+ compileParsedWorkspaceWithResolver
+ foundation bootstrap resolver workspace
+ sealed <- sole "exact omitted module" modules
+ assertEqual "only the non-omitted continuation invokes Vampire"
+ 1
+ =<< readIORef calls
+ let batches =
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix sealed)
+ assertEqual "top-level and nested omitted declarations"
+ 2 (length batches)
+ for_ (zip ["top-level", "nested"] batches) \(label, batch) -> do
+ fact <- sole (label <> " omitted fact")
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch))
+ assertEqual (label <> " omitted safety")
+ (Authority.authoritySafety
+ (Authority.singletonEscapeKind Authority.Omitted))
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority fact))
+ record <- sole (label <> " omitted validation")
+ (Declaration.committedBatchProofValidations batch)
+ assertEqual (label <> " omitted direct authority")
+ Authority.OmittedAuthorization
+ (Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate record))
+ assertEqual (label <> " publishes only its final theorem")
+ 1
+ (length
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch)))
+
+reusesExactEscapeAuthority :: Assertion
+reusesExactEscapeAuthority =
+ Temp.withSystemTempDirectory "felix-exact-escape-cache" \root -> do
+ let consumerRelative = "test/phase5/exact-escape-consumer.tex"
+ producerRelative = "test/phase5/exact-escape-producer.tex"
+ consumerPath = root Posix.</> consumerRelative
+ producerPath = root Posix.</> producerRelative
+ executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ createDirectoryIfMissing True (Posix.takeDirectory consumerPath)
+ consumerSource <- ByteString.readFile consumerRelative
+ producerSource <- ByteString.readFile producerRelative
+ ByteString.writeFile consumerPath consumerSource
+ ByteString.writeFile producerPath producerSource
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for exact-escape'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ mounts <- exactFixtureMounts root
+ freshWorkspace <-
+ parseExactWorkspace bootstrap mounts consumerRelative
+ freshRuns <- newIORef (0 :: Int)
+ freshModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (acceptedResolver executable freshRuns)
+ Declaration.FreshValidation
+ freshWorkspace
+ assertEqual "fresh escape graph Vampire requests"
+ 5
+ =<< readIORef freshRuns
+ assertEscapeGraph "fresh" freshModules
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ traverse_
+ (expectRightIO
+ . Store.writePendingModulePrefix store
+ . Module.sealedTypedModulePrefix)
+ freshModules
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ compileWarm workspace = do
+ runs <- newIORef (0 :: Int)
+ modules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (acceptedResolver executable runs)
+ validation
+ workspace
+ runCount <- readIORef runs
+ pure (modules, runCount)
+ (warmModules, warmRuns) <- compileWarm freshWorkspace
+ assertEqual "exact escape warm hit skips Vampire"
+ 0 warmRuns
+ assertEscapeGraph "warm" warmModules
+ assertEqual "warm escape graph preserves module semantics"
+ (moduleSemantics freshModules)
+ (moduleSemantics warmModules)
+ assertEqual "warm escape graph preserves module prefixes"
+ (modulePrefixes freshModules)
+ (modulePrefixes warmModules)
+
+ let formattingOnly =
+ Text.encodeUtf8
+ ("% shifted exact escape source\n"
+ <> Text.decodeUtf8 consumerSource)
+ ByteString.writeFile consumerPath formattingOnly
+ formattedWorkspace <-
+ parseExactWorkspace bootstrap mounts consumerRelative
+ assertBool "formatting changes the escape parsed identity"
+ (Parse.parsedModuleId
+ (Parse.parsedWorkspaceRootModule freshWorkspace)
+ /= Parse.parsedModuleId
+ (Parse.parsedWorkspaceRootModule
+ formattedWorkspace))
+ (formattedModules, formattedRuns) <-
+ compileWarm formattedWorkspace
+ assertEqual "formatting-only escape edit reuses validation"
+ 0 formattedRuns
+ assertEqual "formatting-only escape edit preserves semantics"
+ (moduleSemantics freshModules)
+ (moduleSemantics formattedModules)
+
+ let omittedGoalEdit =
+ Text.encodeUtf8
+ (StrictText.replace
+ " Show $x = x$."
+ " Show if $x = x$, then $x = x$."
+ (Text.decodeUtf8 consumerSource))
+ ByteString.writeFile consumerPath omittedGoalEdit
+ editedWorkspace <-
+ parseExactWorkspace bootstrap mounts consumerRelative
+ (editedModules, editedRuns) <-
+ compileWarm editedWorkspace
+ assertEqual "changed omitted goal misses its proof validation"
+ 1 editedRuns
+ assertEqual "changed omitted goal preserves public semantics"
+ (moduleSemantics freshModules)
+ (moduleSemantics editedModules)
+ where
+ acceptedResolver executable runs =
+ Declaration.vampireResolver \prepared -> do
+ modifyIORef' runs (+ 1)
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+
+ moduleSemantics = fmap Module.sealedTypedModuleSemantic
+ modulePrefixes =
+ fmap
+ (Declaration.pendingModulePrefixCurrent
+ . Module.sealedTypedModulePrefix)
+
+ assertEscapeGraph label = \case
+ [producer, consumer] -> do
+ let producerBatches =
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix producer)
+ consumerBatches =
+ Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix consumer)
+ case producerBatches of
+ [sourceAxiom, omitted] -> do
+ assertEscapeSafety
+ (label <> " source axiom")
+ [Authority.SourceAxiom]
+ sourceAxiom
+ assertDeclarationDirect
+ (label <> " source axiom")
+ Authority.SourceAxiomAuthorization
+ sourceAxiom
+ assertEscapeSafety
+ (label <> " omitted theorem")
+ [Authority.Omitted]
+ omitted
+ assertProofDirectOmitted
+ (label <> " omitted theorem")
+ omitted
+ batches ->
+ assertFailure
+ (label <> " producer batch count: "
+ <> show (length batches))
+ case consumerBatches of
+ [fromAxiom, fromOmitted, throughLocal, ownOmission] -> do
+ assertEscapeSafety
+ (label <> " source-axiom consumer")
+ [Authority.SourceAxiom]
+ fromAxiom
+ assertProofDirectChecked
+ (label <> " source-axiom consumer") 1 fromAxiom
+ assertEscapeSafety
+ (label <> " omitted consumer")
+ [Authority.Omitted]
+ fromOmitted
+ assertProofDirectChecked
+ (label <> " omitted consumer") 1 fromOmitted
+ assertEscapeSafety
+ (label <> " local source-axiom consumer")
+ [Authority.SourceAxiom]
+ throughLocal
+ assertProofDirectChecked
+ (label <> " local source-axiom consumer")
+ 2 throughLocal
+ assertEscapeSafety
+ (label <> " own omission")
+ [Authority.SourceAxiom, Authority.Omitted]
+ ownOmission
+ assertProofDirectOmitted
+ (label <> " own omission") ownOmission
+ batches ->
+ assertFailure
+ (label <> " consumer batch count: "
+ <> show (length batches))
+ modules ->
+ assertFailure
+ (label <> " escape module count: "
+ <> show (length modules))
+
+ assertEscapeSafety label expected batch = do
+ fact <- sole (label <> " fact")
+ (Semantic.declarationDeltaFacts
+ (Declaration.committedBatchDelta batch))
+ assertEqual (label <> " public escape kinds")
+ expected
+ (Authority.escapeKindsToList
+ (Authority.authoritySafetyEscapeKinds
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority fact))))
+
+ assertDeclarationDirect label expected batch = do
+ validation <-
+ maybe
+ (assertFailure (label <> " declaration validation is absent")
+ >> fail "unreachable")
+ pure
+ (Declaration.committedBatchDeclarationValidation batch)
+ certificate <- sole (label <> " declaration certificate")
+ (Semantic.declarationValidationRecordCertificates validation)
+ assertEqual (label <> " direct authorization")
+ expected
+ (Authority.validationDirectAuthorization certificate)
+
+ assertProofDirectChecked label expectedCount batch = do
+ authorization <- proofDirect label batch
+ case authorization of
+ Authority.CheckedSourceProof requests ->
+ assertEqual (label <> " accepted request count")
+ expectedCount (length requests)
+ direct ->
+ assertFailure
+ (label <> " has unexpected direct authority: "
+ <> show direct)
+
+ assertProofDirectOmitted label batch = do
+ authorization <- proofDirect label batch
+ assertEqual (label <> " direct authorization")
+ Authority.OmittedAuthorization authorization
+
+ proofDirect label batch = do
+ record <- sole (label <> " proof validation")
+ (Declaration.committedBatchProofValidations batch)
+ pure
+ (Authority.validationDirectAuthorization
+ (Semantic.proofValidationRecordCertificate record))
+
+rejectsAfterExactOmittedSubclaim :: Assertion
+rejectsAfterExactOmittedSubclaim =
+ Temp.withSystemTempDirectory "felix-exact-omitted-rollback" \root -> do
+ repository <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace
+ bootstrap mounts "test/phase5/exact-escape-consumer.tex"
+ parsedModules <-
+ pure
+ (toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ (producerParsed, consumerParsed) <-
+ case parsedModules of
+ [producer, consumer] -> pure (producer, consumer)
+ modules ->
+ assertFailure
+ ("unexpected rollback graph size: "
+ <> show (length modules))
+ >> fail "unreachable"
+ producerInput <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ unusedResolver
+ Declaration.FreshValidation
+ producerParsed
+ [])
+ producer <- Module.runTypedModule producerInput >>= \case
+ Module.TypedModuleSucceeded sealed -> pure sealed
+ _ ->
+ assertFailure "escape producer did not seal"
+ >> fail "unreachable"
+ let executable = root Posix.</> "vampire"
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for omitted-rollback'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ calls <- newIORef (0 :: Int)
+ let resolver = Declaration.vampireResolver \prepared -> do
+ runCount <- readIORef calls
+ modifyIORef' calls (+ 1)
+ if runCount < 4
+ then
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ else
+ pure
+ (Right
+ (Provers.CounterSatisfiable
+ "rejected continuation"))
+ consumerInput <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ resolver
+ Declaration.FreshValidation
+ consumerParsed
+ [producer])
+ Module.runTypedModule consumerInput >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedDeclarationFailed
+ (Declaration.ProofObligationFailedAt
+ location
+ Declaration.VampireObligationRejected{}))
+ prefix -> do
+ assertEqual "the continuation is the fifth request"
+ 5
+ =<< readIORef calls
+ assertEqual "rejected continuation location"
+ 37 (locLine location)
+ assertEqual "omitted declaration rolls back atomically"
+ 3
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Module.TypedModuleSucceeded{} ->
+ assertFailure "rejected omitted continuation was accepted"
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("omitted rollback module did not open: "
+ <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("unexpected omitted rollback failure: "
+ <> show failure)
+
+reusesExactProofValidationAcrossModuleMisses :: Assertion
+reusesExactProofValidationAcrossModuleMisses =
+ Temp.withSystemTempDirectory "felix-exact-proof-cache" \root -> do
+ let relative = "test/phase5/exact-proofs.tex"
+ producerRelative = "test/phase5/exact-producer.tex"
+ sourcePath = root Posix.</> relative
+ producerPath = root Posix.</> producerRelative
+ executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ createDirectoryIfMissing True (Posix.takeDirectory sourcePath)
+ original <- ByteString.readFile relative
+ producer <- ByteString.readFile producerRelative
+ ByteString.writeFile sourcePath original
+ ByteString.writeFile producerPath producer
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for exact-cache'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts root
+ freshWorkspace <- parseExactWorkspace bootstrap mounts relative
+ freshRuns <- newIORef (0 :: Int)
+ freshModules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (countingAcceptedResolver executable freshRuns)
+ Declaration.FreshValidation
+ freshWorkspace
+ assertEqual "fresh proof obligations run Vampire"
+ 7
+ =<< readIORef freshRuns
+ freshRoot <- sole "fresh exact proof root" (drop 1 freshModules)
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ traverse_
+ (expectRightIO
+ . Store.writePendingModulePrefix store
+ . Module.sealedTypedModulePrefix)
+ freshModules
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ compileWarm workspace = do
+ runs <- newIORef (0 :: Int)
+ modules <-
+ compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (countingAcceptedResolver executable runs)
+ validation
+ workspace
+ rootModule <- sole "warm exact proof root" (drop 1 modules)
+ runCount <- readIORef runs
+ pure (rootModule, runCount)
+ (unchangedRoot, unchangedRuns) <-
+ compileWarm freshWorkspace
+ assertEqual "exact warm hit skips Vampire"
+ 0 unchangedRuns
+ assertEqual "exact warm hit preserves public semantics"
+ (Module.sealedTypedModuleSemantic freshRoot)
+ (Module.sealedTypedModuleSemantic unchangedRoot)
+
+ let formattingOnly =
+ Text.encodeUtf8
+ (StrictText.replace
+ "\\begin{proposition}\\label{phase5_structural_proof}"
+ ("% shifted source location\n"
+ <> "\\begin{proposition}\\label{phase5_structural_proof}")
+ (Text.decodeUtf8 original))
+ ByteString.writeFile sourcePath formattingOnly
+ formattedWorkspace <-
+ parseExactWorkspace bootstrap mounts relative
+ assertBool "formatting changes parsed module identity"
+ (Parse.parsedModuleId
+ (Parse.parsedWorkspaceRootModule freshWorkspace)
+ /= Parse.parsedModuleId
+ (Parse.parsedWorkspaceRootModule formattedWorkspace))
+ (formattedRoot, formattedRuns) <-
+ compileWarm formattedWorkspace
+ assertEqual "formatting-only module miss reuses proof validation"
+ 0 formattedRuns
+ assertEqual "formatting-only miss preserves public semantics"
+ (Module.sealedTypedModuleSemantic freshRoot)
+ (Module.sealedTypedModuleSemantic formattedRoot)
+
+ let semanticEdit =
+ Text.encodeUtf8
+ (StrictText.replace
+ " We have $x = x$ by assumption."
+ (StrictText.intercalate "\n"
+ [ " Show $x = x$."
+ , " \\begin{subproof}"
+ , " Follows by assumption."
+ , " \\end{subproof}"
+ ])
+ (Text.decodeUtf8 original))
+ ByteString.writeFile sourcePath semanticEdit
+ editedWorkspace <-
+ parseExactWorkspace bootstrap mounts relative
+ (editedRoot, editedRuns) <-
+ compileWarm editedWorkspace
+ assertEqual "semantic proof edit reruns its obligations"
+ 2 editedRuns
+ assertEqual "request-equivalent proof preserves public semantics"
+ (Module.sealedTypedModuleSemantic freshRoot)
+ (Module.sealedTypedModuleSemantic editedRoot)
+rejectsFixedSemanticDeclaration :: Assertion
+rejectsFixedSemanticDeclaration =
+ Temp.withSystemTempDirectory "felix-fixed-semantic" \root -> do
+ let relative = "entry.tex"
+ path = root Posix.</> relative
+ source =
+ "\\begin{signature}\\label{source_unions}\n"
+ <> " $\\unions{X}$ is a set.\n"
+ <> "\\end{signature}\n"
+ ByteString.writeFile path
+ (Text.encodeUtf8 (StrictText.pack source))
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ mounts <- exactFixtureMounts root
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ let parsed = Parse.parsedWorkspaceRootModule workspace
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ unusedResolver
+ Declaration.FreshValidation
+ parsed
+ [])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactCompileFailed
+ (Exact.ExactFixedSemanticCollision
+ location key)))
+ prefix -> do
+ assertEqual "fixed collision line" 1 (locLine location)
+ assertEqual "fixed collision key"
+ (Semantic.SemanticExpressionFunction
+ (Raw.TokenCons (Raw.Command "unions")
+ (Raw.TokenCons Raw.InvisibleBraceL
+ (Raw.HoleCons
+ (Raw.TokenCons
+ Raw.InvisibleBraceR Raw.End)))))
+ key
+ assertEqual "fixed collision commits no prefix"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Module.TypedModuleSucceeded{} ->
+ assertFailure "fixed semantic declaration was accepted"
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("fixed semantic module did not open: "
+ <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("unexpected fixed semantic failure: "
+ <> show failure)
+
+rejectsFixedSemanticInductive :: Assertion
+rejectsFixedSemanticInductive =
+ Temp.withSystemTempDirectory "felix-fixed-inductive" \root -> do
+ let relative = "entry.tex"
+ path = root Posix.</> relative
+ source =
+ "\\begin{inductive}\\label{source_pow}\n"
+ <> " Define $\\pow{A}\\subseteq\\cumul{A}$ inductively as follows.\n"
+ <> " \\begin{enumerate}\n"
+ <> " \\item $A\\in\\pow{A}$.\n"
+ <> " \\end{enumerate}\n"
+ <> "\\end{inductive}\n"
+ ByteString.writeFile path
+ (Text.encodeUtf8 (StrictText.pack source))
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ mounts <- exactFixtureMounts root
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ let parsed = Parse.parsedWorkspaceRootModule workspace
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ unusedResolver
+ Declaration.FreshValidation
+ parsed
+ [])
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleFailed
+ (Module.TypedActionFailed
+ (Module.TypedExactInductiveFailed
+ (ExactInductive.ExactInductiveFixedSemanticCollision
+ location key)))
+ prefix -> do
+ assertEqual "fixed inductive collision line"
+ 1
+ (locLine location)
+ assertEqual "fixed inductive collision key"
+ (Semantic.SemanticExpressionFunction
+ (Raw.TokenCons (Raw.Command "pow")
+ (Raw.TokenCons Raw.InvisibleBraceL
+ (Raw.HoleCons
+ (Raw.TokenCons
+ Raw.InvisibleBraceR Raw.End)))))
+ key
+ assertEqual "fixed inductive collision commits no prefix"
+ 0
+ (length
+ (Declaration.pendingModulePrefixBatches prefix))
+ Module.TypedModuleSucceeded{} ->
+ assertFailure "fixed semantic inductive was accepted"
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("fixed semantic inductive did not open: "
+ <> show failure)
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("unexpected fixed inductive failure: "
+ <> show failure)
+
+keepsExactSemanticsIndependentOfFixity :: Assertion
+keepsExactSemanticsIndependentOfFixity =
+ Temp.withSystemTempDirectory "felix-exact-fixity" \root -> do
+ let relative = "test/phase5/exact-producer.tex"
+ path = root Posix.</> relative
+ createDirectoryIfMissing True (Posix.takeDirectory path)
+ original <- ByteString.readFile relative
+ let changed =
+ Text.encodeUtf8
+ (StrictText.replace
+ "infixl 2"
+ "infixr 6"
+ (Text.decodeUtf8 original))
+ ByteString.writeFile path original
+ first <- compileExactRootAt root relative
+ ByteString.writeFile path changed
+ second <- compileExactRootAt root relative
+ let firstParsed = Parse.parsedWorkspaceRootModule (fst first)
+ secondParsed = Parse.parsedWorkspaceRootModule (fst second)
+ firstSealed = snd first
+ secondSealed = snd second
+ assertBool "fixity changes syntax identity"
+ (Syntax.moduleSyntaxAssertedId
+ (Parse.parsedModuleSyntaxInterface firstParsed)
+ /= Syntax.moduleSyntaxAssertedId
+ (Parse.parsedModuleSyntaxInterface secondParsed))
+ assertBool "fixity changes parsed identity"
+ (Parse.parsedModuleId firstParsed
+ /= Parse.parsedModuleId secondParsed)
+ assertEqual "fixity preserves semantic interface"
+ (Module.sealedTypedModuleSemantic firstSealed)
+ (Module.sealedTypedModuleSemantic secondSealed)
+ assertEqual "fixity preserves semantic prefix"
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix firstSealed))
+ (Declaration.pendingModulePrefixCurrent
+ (Module.sealedTypedModulePrefix secondSealed))
+
+loadsCachedExactProducerForFreshImporter :: Assertion
+loadsCachedExactProducerForFreshImporter = do
+ foundation <- expectRight Foundation.checkedFoundation
+ Temp.withSystemTempDirectory "felix-exact-cache" \root -> do
+ let path = root Posix.</> "store.sqlite"
+ executable = root Posix.</> "vampire"
+ writeAcceptedFixtureVampire executable
+ (_startup, store) <-
+ Store.openStore path (Identity.theoryId foundation)
+ >>= expectRight
+ let observer = Verification.verificationRequestObserver \_ordinal _request ->
+ pure ()
+ prover =
+ Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit
+ verify mode source =
+ (checkResultWithStore
+ store mode observer prover source)
+ >>= expectRight
+ producer <-
+ verify Verification.FreshStoreValidation
+ "test/phase5/exact-producer.tex"
+ importer <-
+ verify Verification.WarmStoreValidation
+ "test/phase5/exact-importer.tex"
+ assertTypedSuccess "fresh producer" producer
+ assertTypedSuccess "warm producer/fresh importer" importer
+ memo <- Store.newStoreMemo store
+ prelude <-
+ expectRight
+ =<< Module.acquireFinalPreludeSession
+ memo store foundation unusedResolver
+ preludeVisits <- Store.storeMemoVisits memo
+ repository <- getCurrentDirectory
+ mounts <- exactFixtureMounts repository
+ workspace <- parseFinalExactWorkspace
+ prelude mounts "test/phase5/exact-importer.tex"
+ let parsedModules =
+ toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace)
+ preludeSemantic =
+ Module.sealedTypedModuleSemantic
+ (Module.finalPreludeModule prelude)
+ preludeId =
+ Semantic.semanticInterfaceAssertedId preludeSemantic
+ theory = Identity.theoryId foundation
+ loadInstallation parsed direct = do
+ key <- expectRight
+ (Semantic.moduleArtifactKey
+ (moduleName (Parse.parsedModuleAddress parsed))
+ (Parse.parsedModuleId parsed)
+ direct
+ theory)
+ loaded <- expectRight
+ =<< Store.loadCachedModuleInstallation
+ memo
+ store
+ key
+ (Syntax.moduleSyntaxAssertedId
+ (Parse.parsedModuleSyntaxInterface parsed))
+ maybe
+ (assertFailure "exact cached installation is absent"
+ >> fail "unreachable")
+ pure
+ loaded
+ environmentBindings installation =
+ [ binding
+ | delta <- Semantic.semanticInterfaceDeclarations
+ (Store.cachedInstallationSemantic installation)
+ , binding <- Semantic.semanticEnvironmentBindings
+ (Semantic.declarationDeltaEnvironment delta)
+ ]
+ case parsedModules of
+ [producerParsed, importerParsed] -> do
+ producerInstallation <-
+ loadInstallation producerParsed [preludeId]
+ producerVisits <- Store.storeMemoVisits memo
+ assertEqual "ordinary root adds one artifact validation"
+ (Store.storeArtifactsValidated preludeVisits + 1)
+ (Store.storeArtifactsValidated producerVisits)
+ assertEqual "ordinary root reuses prelude syntax validation"
+ (Store.storeSyntaxRowsValidated preludeVisits + 1)
+ (Store.storeSyntaxRowsValidated producerVisits)
+ assertEqual "ordinary root reuses prelude semantic validation"
+ (Store.storeSemanticRowsValidated preludeVisits + 1)
+ (Store.storeSemanticRowsValidated producerVisits)
+ let producerSemanticId =
+ Semantic.semanticInterfaceAssertedId
+ (Store.cachedInstallationSemantic
+ producerInstallation)
+ importerInstallation <-
+ loadInstallation
+ importerParsed [preludeId, producerSemanticId]
+ case ( environmentBindings producerInstallation
+ , environmentBindings importerInstallation
+ ) of
+ (seedBinding : aliasBinding : _definitionBinding : [],
+ [importerBinding]) -> do
+ let seedTarget =
+ Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget
+ seedBinding)
+ aliasTarget =
+ Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget
+ aliasBinding)
+ importerTarget =
+ Semantic.semanticGlobalTargetObject
+ (Semantic.semanticGlobalBindingTarget
+ importerBinding)
+ assertEqual "cached importer reuses expanded content"
+ aliasTarget importerTarget
+ assertEqual "cached importer adds no object"
+ []
+ (Store.cachedInstallationObjects
+ importerInstallation)
+ expandedObject <-
+ maybe
+ (assertFailure
+ "cached expanded object is absent"
+ >> fail "unreachable")
+ pure
+ (find
+ ((== aliasTarget)
+ . Identity.assertedObjectId)
+ (Store.cachedInstallationObjects
+ producerInstallation))
+ case Identity.assertedObjectContent expandedObject of
+ Identity.TransparentObjectContent
+ _identity _coreType body ->
+ assertEqual
+ "cached expansion retains the opaque seed"
+ (Set.singleton seedTarget)
+ (Core.canonicalTermGlobals body)
+ content ->
+ assertFailure
+ ("cached expansion is not transparent: "
+ <> show content)
+ (producerBindings, importerBindings) ->
+ assertFailure
+ ("unexpected cached exact bindings: "
+ <> show
+ ( length producerBindings
+ , length importerBindings
+ ))
+ modules ->
+ assertFailure
+ ("unexpected cached exact module count: "
+ <> show (length modules))
+ Store.closeStore store
+
+selectsConcurrentModuleFailureDeterministically :: Assertion
+selectsConcurrentModuleFailureDeterministically = do
+ foundation <- expectRight Foundation.checkedFoundation
+ Temp.withSystemTempDirectory "felix-concurrent-module-failure" \root -> do
+ let executable = root Posix.</> "vampire"
+ source = "test/phase7/concurrent-failure-root.tex"
+ prover =
+ Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit
+ ignored =
+ Verification.verificationRequestObserver
+ (\_position _request -> pure ())
+ select amount =
+ Provers.selectEffectiveJobs
+ (Provers.effectiveJobs amount)
+ (fail "explicit jobs unexpectedly detected processors")
+ reportEntry escape =
+ ( Verification.reportedEscapeKind escape
+ , locFile (Verification.reportedEscapeLocation escape)
+ , locLine (Verification.reportedEscapeLocation escape)
+ )
+ inspect label expectedPositions
+ (result, _slowReport, positions) = do
+ case result of
+ Verification.VerificationFailure report failed -> do
+ assertEqual (label <> " selected earlier failure")
+ "test/phase7/concurrent-earlier.tex"
+ (locFile (Verification.failedVerificationLocation failed))
+ assertEqual (label <> " admitted source prefix")
+ [ ( Verification.ReportedSourceAxiom
+ , "test/phase7/concurrent-earlier.tex"
+ , 1
+ )
+ ]
+ (reportEntry
+ <$> Verification.verificationDirectEscapes report)
+ other ->
+ assertFailure
+ (label <> " did not reject deterministically: "
+ <> show other)
+ assertEqual (label <> " executed only sibling obligations")
+ expectedPositions
+ (sort
+ [ ( Provers.workPositionModuleOrdinal position
+ , Provers.workPositionLocalRequestOrdinal position
+ )
+ | position <- positions
+ ])
+ runCase label jobsAmount = do
+ let storePath = root Posix.</> (label <> ".sqlite")
+ processLock = root Posix.</> (label <> ".process-lock")
+ processStarted =
+ root Posix.</> (label <> ".process-started")
+ writeAcceptedFixtureVampire executable
+ (_startup, store) <-
+ Store.openStore storePath (Identity.theoryId foundation)
+ >>= expectRight
+ bracket (pure store) Store.closeStore \openStore -> do
+ -- Seed only the final prelude. The unsupported ordinary
+ -- module cannot publish a root.
+ void
+ (
+ (checkFileWithStore
+ openStore
+ Verification.WarmStoreValidation
+ ignored
+ prover
+ "test/phase3/typed-unsupported.tex")
+ >>= expectRight)
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "while ! mkdir \"" <> processLock
+ <> "\" 2>/dev/null; do sleep 0.01; done"
+ , "trap 'rmdir \"" <> processLock
+ <> "\"' EXIT"
+ , ": > \"" <> processStarted <> "\""
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status CounterSatisfiable for concurrent-fixture'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ positionsRef <- newIORef []
+ let observer =
+ Verification.verificationRequestObserver
+ (\position _request -> do
+ atomicModifyIORef' positionsRef
+ (\positions ->
+ (position : positions, ()))
+ when
+ (jobsAmount > 1
+ && Provers.workPositionModuleOrdinal
+ position == 1)
+ (waitForFileSignal
+ "later module process"
+ processStarted))
+ jobs <- select jobsAmount
+ (result, slowReport) <-
+ (checkFileWithStoreAndJobs
+ openStore
+ Verification.WarmStoreValidation
+ jobs
+ observer
+ prover
+ source)
+ >>= expectRight
+ positions <- readIORef positionsRef
+ pure (result, slowReport, positions)
+ void $ runCase "parallel" 2
+ >>= inspect "parallel" [(1, 1), (2, 1)]
+ void $ runCase "sequential" 1
+ >>= inspect "sequential" [(1, 1)]
+
+waitForFileSignal :: String -> FilePath -> Assertion
+waitForFileSignal label path = do
+ guarded <- Timeout.timeout 10000000 loop
+ case guarded of
+ Just () ->
+ pure ()
+ Nothing ->
+ assertFailure (label <> " was not observed")
+ where
+ loop = do
+ exists <- doesFileExist path
+ if exists
+ then pure ()
+ else do
+ threadDelay 10000
+ loop
+
+batchesStructureObligationsAtomically :: Assertion
+batchesStructureObligationsAtomically = do
+ foundation <- expectRight Foundation.checkedFoundation
+ Temp.withSystemTempDirectory "felix-structure-obligation-batch" \root -> do
+ let storePath = root Posix.</> "store.sqlite"
+ executable = root Posix.</> "vampire"
+ unavailable = root Posix.</> "must-not-run-vampire"
+ source = "test/phase7/structure-obligation-batch.tex"
+ prover path =
+ Provers.vampire
+ path
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit
+ select amount =
+ Provers.selectEffectiveJobs
+ (Provers.effectiveJobs amount)
+ (fail "explicit jobs unexpectedly detected processors")
+ run openStore jobs observer vampireCommand =
+ (checkFileWithStoreAndJobs
+ openStore
+ Verification.WarmStoreValidation
+ jobs
+ observer
+ vampireCommand
+ source)
+ >>= expectRight
+ inspectFailure label positions (result, _slowReport) = do
+ case result of
+ Verification.VerificationFailure report failed -> do
+ assertEqual (label <> " selects first consequence")
+ (source, 12)
+ ( locFile (Verification.failedVerificationLocation failed)
+ , locLine (Verification.failedVerificationLocation failed)
+ )
+ assertEqual (label <> " retains preceding prefix")
+ [(Verification.ReportedSourceAxiom, source, 1)]
+ [ ( Verification.reportedEscapeKind escape
+ , locFile (Verification.reportedEscapeLocation escape)
+ , locLine (Verification.reportedEscapeLocation escape)
+ )
+ | escape <- Verification.verificationDirectEscapes report
+ ]
+ other ->
+ assertFailure
+ (label <> " did not reject its structure batch: "
+ <> show other)
+ assertEqual (label <> " assigns consecutive positions")
+ [(1, 1), (1, 2)]
+ (sort positions)
+ writeAcceptedFixtureVampire executable
+ (_startup, store) <-
+ Store.openStore storePath (Identity.theoryId foundation)
+ >>= expectRight
+ bracket (pure store) Store.closeStore \openStore -> do
+ let ignored =
+ Verification.verificationRequestObserver
+ (\_position _request -> pure ())
+ -- Seed only the confined prelude so this fixture observes exactly
+ -- the ordinary structure module's ready batch.
+ void
+ (
+ (checkFileWithStore
+ openStore
+ Verification.WarmStoreValidation
+ ignored
+ (prover executable)
+ "test/phase3/typed-unsupported.tex")
+ >>= expectRight)
+ parallelJobs <- select 2
+ parallelPositions <- newIORef []
+ firstStarted <- newEmptyTMVarIO
+ secondStarted <- newEmptyTMVarIO
+ releaseFirst <- newEmptyTMVarIO
+ let laterCompleted = root Posix.</> "later-completed"
+ writeLaterAcceptingVampire executable laterCompleted
+ let parallelObserver =
+ Verification.verificationRequestObserver \position _request -> do
+ let ordinal =
+ Provers.workPositionLocalRequestOrdinal position
+ atomicModifyIORef' parallelPositions
+ (\positions ->
+ ( ( Provers.workPositionModuleOrdinal position
+ , ordinal
+ ) : positions
+ , ()
+ ))
+ case ordinal of
+ 1 -> do
+ atomically (putTMVar firstStarted ())
+ atomically (takeTMVar releaseFirst)
+ 2 ->
+ atomically (putTMVar secondStarted ())
+ _ ->
+ assertFailure
+ ("unexpected structure request ordinal: "
+ <> show ordinal)
+ withAsync
+ (run openStore parallelJobs parallelObserver
+ (prover executable))
+ \verification -> do
+ void
+ (awaitSignal "first structure request"
+ (atomically (takeTMVar firstStarted)))
+ void
+ (awaitSignal "second structure request"
+ (atomically (takeTMVar secondStarted)))
+ -- Only the later member can reach the subprocess while
+ -- the first observer is gated. Its completed signal
+ -- therefore establishes reversed wall-clock completion.
+ waitForFileSignal
+ "later structure consequence"
+ laterCompleted
+ atomically (putTMVar releaseFirst ())
+ parallelResult <- wait verification
+ positions <- readIORef parallelPositions
+ inspectFailure "parallel" positions parallelResult
+
+ sequentialJobs <- select 1
+ sequentialPositions <- newIORef []
+ let sequentialCompleted = root Posix.</> "sequential-completed"
+ writeRejectingVampire executable sequentialCompleted
+ let sequentialObserver =
+ Verification.verificationRequestObserver \position _request ->
+ atomicModifyIORef' sequentialPositions
+ (\positions ->
+ ( ( Provers.workPositionModuleOrdinal position
+ , Provers.workPositionLocalRequestOrdinal
+ position
+ ) : positions
+ , ()
+ ))
+ sequentialResult <-
+ run openStore sequentialJobs sequentialObserver
+ (prover executable)
+ sequentialObserved <- readIORef sequentialPositions
+ inspectFailure "sequential"
+ sequentialObserved sequentialResult
+
+ -- A rejected sibling wrote neither validation nor a module root:
+ -- the complete batch executes again, while the earlier source
+ -- axiom remains the admitted prefix. A subsequent hit executes
+ -- no request at all.
+ writeAcceptedFixtureVampire executable
+ acceptedPositions <- newIORef []
+ let acceptedObserver =
+ Verification.verificationRequestObserver \position _request ->
+ modifyIORef' acceptedPositions
+ (position :)
+ (accepted, _acceptedSlowReport) <-
+ run openStore parallelJobs acceptedObserver
+ (prover executable)
+ case accepted of
+ Verification.VerificationCompleted report _presentation ->
+ assertEqual "successful retry retains only source axiom"
+ [Verification.ReportedSourceAxiom]
+ (Verification.reportedEscapeKind
+ <$> Verification.verificationDirectEscapes report)
+ other ->
+ assertFailure
+ ("successful structure retry failed: " <> show other)
+ acceptedObserved <- readIORef acceptedPositions
+ assertEqual "successful retry executes the complete batch"
+ 2
+ (length acceptedObserved)
+ let forbiddenObserver =
+ Verification.verificationRequestObserver \position _request ->
+ assertFailure
+ ("warm structure batch invoked Vampire at "
+ <> show position)
+ (warm, _warmSlowReport) <-
+ run openStore parallelJobs forbiddenObserver
+ (prover unavailable)
+ case warm of
+ Verification.VerificationCompleted{} -> pure ()
+ other ->
+ assertFailure
+ ("warm structure batch did not install: " <> show other)
+ where
+ awaitSignal label action = do
+ result <- Timeout.timeout 10000000 action
+ maybe
+ (assertFailure (label <> " was not observed")
+ >> fail "unreachable")
+ pure
+ result
+
+ writeRejectingVampire executable completed = do
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , ": > \"" <> completed <> "\""
+ , "printf '%s\\n' '% SZS status CounterSatisfiable for structure-batch-fixture'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+
+ writeLaterAcceptingVampire executable completed = do
+ let firstProcess = completed <> ".first-process"
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , ": > \"" <> completed <> "\""
+ , "if mkdir \"" <> firstProcess <> "\" 2>/dev/null; then"
+ , " printf '%s\\n' '% SZS status Theorem for structure-batch-fixture'"
+ , "else"
+ , " printf '%s\\n' '% SZS status CounterSatisfiable for structure-batch-fixture'"
+ , "fi"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+
+speculatesDependentProofObligationsWithoutAdmittingAhead :: Assertion
+speculatesDependentProofObligationsWithoutAdmittingAhead = do
+ foundation <- expectRight Foundation.checkedFoundation
+ Temp.withSystemTempDirectory "felix-dependent-proof-chain" \root -> do
+ let executable = root Posix.</> "vampire"
+ unavailable = root Posix.</> "must-not-run-vampire"
+ storePath = root Posix.</> "store.sqlite"
+ source = "test/phase7/dependent-proof-chain.tex"
+ prover path =
+ Provers.vampire
+ path
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit
+ ignored =
+ Verification.verificationRequestObserver
+ (\_position _request -> pure ())
+ writeAcceptedFixtureVampire executable
+ (_startup, store) <-
+ Store.openStore storePath (Identity.theoryId foundation)
+ >>= expectRight
+ bracket (pure store) Store.closeStore \openStore -> do
+ -- Seed only the final prelude so the observed work belongs to the
+ -- ordinary proof module.
+ void
+ (
+ (checkFileWithStore
+ openStore
+ Verification.WarmStoreValidation
+ ignored
+ (prover executable)
+ "test/phase3/typed-unsupported.tex")
+ >>= expectRight)
+ jobs <-
+ Provers.selectEffectiveJobs
+ (Provers.effectiveJobs 2)
+ (fail "explicit jobs unexpectedly detected processors")
+ firstStarted <- newEmptyTMVarIO
+ secondStarted <- newEmptyTMVarIO
+ releaseFirst <- newEmptyTMVarIO
+ positionsRef <- newIORef []
+ let observer =
+ Verification.verificationRequestObserver \position _request -> do
+ let ordinal =
+ Provers.workPositionLocalRequestOrdinal position
+ atomicModifyIORef' positionsRef
+ (\positions ->
+ ( ( Provers.workPositionModuleOrdinal position
+ , ordinal
+ ) : positions
+ , ()
+ ))
+ case ordinal of
+ 1 -> do
+ atomically (putTMVar firstStarted ())
+ atomically (takeTMVar releaseFirst)
+ 2 -> atomically (putTMVar secondStarted ())
+ _ ->
+ assertFailure
+ ("unexpected dependent proof request: "
+ <> show ordinal)
+ withAsync
+ (
+ (checkFileWithStoreAndJobs
+ openStore
+ Verification.WarmStoreValidation
+ jobs
+ observer
+ (prover executable)
+ source)
+ >>= expectRight)
+ \checking -> do
+ void
+ (awaitSignal "local subclaim request"
+ (atomically (takeTMVar firstStarted)))
+ -- The continuation is semantically dependent, but its
+ -- already checked request may execute prospectively. It
+ -- cannot be admitted until the local claim succeeds.
+ void
+ (awaitSignal "dependent continuation request"
+ (atomically (takeTMVar secondStarted)))
+ atomically (putTMVar releaseFirst ())
+ (result, _slowReport) <- wait checking
+ case result of
+ Verification.VerificationCompleted report _presentation ->
+ assertEqual "only the preceding axiom is reported"
+ [Verification.ReportedSourceAxiom]
+ (Verification.reportedEscapeKind
+ <$> Verification.verificationDirectEscapes report)
+ other ->
+ assertFailure
+ ("dependent proof module did not seal: "
+ <> show other)
+ positions <- readIORef positionsRef
+ assertEqual "dependent requests retain source positions"
+ [(1, 1), (1, 2)]
+ (sort positions)
+
+ let forbiddenObserver =
+ Verification.verificationRequestObserver \position _request ->
+ assertFailure
+ ("warm dependent proof invoked Vampire at "
+ <> show position)
+ (warm, _warmSlowReport) <-
+ (checkFileWithStoreAndJobs
+ openStore
+ Verification.WarmStoreValidation
+ jobs
+ forbiddenObserver
+ (prover unavailable)
+ source)
+ >>= expectRight
+ case warm of
+ Verification.VerificationCompleted{} -> pure ()
+ other ->
+ assertFailure
+ ("warm dependent proof did not install: " <> show other)
+ where
+ awaitSignal label action = do
+ result <- Timeout.timeout 10000000 action
+ maybe
+ (assertFailure (label <> " was not observed")
+ >> fail "unreachable")
+ pure
+ result
+
+schedulesDiamondAfterSealedImports :: Assertion
+schedulesDiamondAfterSealedImports = do
+ foundation <- expectRight Foundation.checkedFoundation
+ Temp.withSystemTempDirectory "felix-concurrent-diamond" \root -> do
+ let storePath = root Posix.</> "store.sqlite"
+ executable = root Posix.</> "vampire"
+ unavailable = root Posix.</> "must-not-run-vampire"
+ source = "test/phase7/diamond-root.tex"
+ prover path =
+ Provers.vampire
+ path
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit
+ ignored =
+ Verification.verificationRequestObserver
+ (\_position _request -> pure ())
+ writeAcceptedFixtureVampire executable
+ (_startup, store) <-
+ Store.openStore storePath (Identity.theoryId foundation)
+ >>= expectRight
+ bracket (pure store) Store.closeStore \openStore -> do
+ -- Acquire the final prelude before introducing scheduler gates.
+ void
+ (
+ (checkFileWithStore
+ openStore
+ Verification.WarmStoreValidation
+ ignored
+ (prover executable)
+ "test/phase3/typed-unsupported.tex")
+ >>= expectRight)
+ jobs <- Provers.selectEffectiveJobs
+ (Provers.effectiveJobs 2)
+ (fail "explicit jobs unexpectedly detected processors")
+ baseStarted <- newEmptyTMVarIO
+ branchStarted <- newTQueueIO
+ rootStarted <- newEmptyTMVarIO
+ releaseBase <- newTVarIO False
+ releaseBranches <- newTVarIO False
+ let awaitRelease released =
+ atomically (readTVar released >>= check)
+ observer =
+ Verification.verificationRequestObserver
+ (\position _request ->
+ case Provers.workPositionModuleOrdinal position of
+ 1 -> do
+ atomically (putTMVar baseStarted ())
+ awaitRelease releaseBase
+ ordinal@2 -> do
+ atomically
+ (writeTQueue branchStarted ordinal)
+ awaitRelease releaseBranches
+ ordinal@3 -> do
+ atomically
+ (writeTQueue branchStarted ordinal)
+ awaitRelease releaseBranches
+ 4 ->
+ atomically (putTMVar rootStarted ())
+ _ ->
+ pure ())
+ verify vampireCommand requestObserver =
+ (checkFileWithStoreAndJobs
+ openStore
+ Verification.WarmStoreValidation
+ jobs
+ requestObserver
+ vampireCommand
+ source)
+ >>= expectRight
+ await label action = do
+ result <- Timeout.timeout 10000000 action
+ maybe
+ (assertFailure (label <> " was not observed")
+ >> fail "unreachable")
+ pure
+ result
+ withAsync (verify (prover executable) observer) \verification -> do
+ void (await "base request" (atomically (takeTMVar baseStarted)))
+ threadDelay 50000
+ atomically (tryReadTQueue branchStarted) >>= \case
+ Nothing -> pure ()
+ Just ordinal ->
+ assertFailure
+ ("dependent module started before base seal: "
+ <> show ordinal)
+ atomically (writeTVar releaseBase True)
+ firstBranch <- await "first branch"
+ (atomically (readTQueue branchStarted))
+ secondBranch <- await "second branch"
+ (atomically (readTQueue branchStarted))
+ assertEqual "both diamond branches became ready together"
+ [2, 3]
+ (sort [firstBranch, secondBranch])
+ atomically (tryReadTMVar rootStarted) >>= \case
+ Nothing -> pure ()
+ Just () ->
+ assertFailure
+ "diamond root started before both branch seals"
+ atomically (writeTVar releaseBranches True)
+ void (await "diamond root" (atomically (takeTMVar rootStarted)))
+ (coldResult, _coldSlowReport) <- wait verification
+ case coldResult of
+ Verification.VerificationCompleted{} -> pure ()
+ other ->
+ assertFailure
+ ("cold diamond did not complete: " <> show other)
+ let forbiddenObserver =
+ Verification.verificationRequestObserver
+ (\position _request ->
+ assertFailure
+ ("warm diamond invoked Vampire at "
+ <> show position))
+ (warmResult, _warmSlowReport) <-
+ verify (prover unavailable) forbiddenObserver
+ case warmResult of
+ Verification.VerificationCompleted{} -> pure ()
+ other ->
+ assertFailure
+ ("warm diamond did not install: " <> show other)
+
+reportsAdmittedSourceEscapes :: Assertion
+reportsAdmittedSourceEscapes = do
+ foundation <- expectRight Foundation.checkedFoundation
+ Temp.withSystemTempDirectory "felix-admitted-source-report" \root -> do
+ let storePath = root Posix.</> "store.sqlite"
+ executable = root Posix.</> "vampire"
+ unavailable = root Posix.</> "must-not-run-vampire"
+ observer =
+ Verification.verificationRequestObserver \_ordinal _request -> pure ()
+ prover path =
+ Provers.vampire
+ path
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit
+ writeAcceptedFixtureVampire executable
+ (_startup, store) <-
+ Store.openStore storePath (Identity.theoryId foundation)
+ >>= expectRight
+ bracket (pure store) Store.closeStore \openStore -> do
+ let verify mode vampirePath source =
+ (checkFileWithStore
+ openStore
+ mode
+ observer
+ (prover vampirePath)
+ source)
+ >>= expectRight
+ reportEntries =
+ fmap
+ (\escape ->
+ ( Verification.reportedEscapeKind escape
+ , locFile (Verification.reportedEscapeLocation escape)
+ , locLine (Verification.reportedEscapeLocation escape)
+ ))
+ . Verification.verificationDirectEscapes
+ expectedConsumer =
+ [ ( Verification.ReportedSourceAxiom
+ , "test/phase5/exact-escape-producer.tex"
+ , 1
+ )
+ , ( Verification.ReportedOmitted
+ , "test/phase5/exact-escape-producer.tex"
+ , 9
+ )
+ , ( Verification.ReportedOmitted
+ , "test/phase5/exact-escape-consumer.tex"
+ , 35
+ )
+ ]
+ (freshResult, _freshSlowReport) <-
+ verify
+ Verification.FreshStoreValidation
+ executable
+ "test/phase5/exact-escape-consumer.tex"
+ freshReport <- case freshResult of
+ Verification.CompletedWithExplicitGaps report _presentation -> pure report
+ other ->
+ assertFailure
+ ("fresh escape report did not complete with gaps: "
+ <> show other)
+ >> fail "unreachable"
+ assertEqual "fresh direct escapes"
+ expectedConsumer
+ (reportEntries freshReport)
+ (warmResult, _warmSlowReport) <-
+ verify
+ Verification.WarmStoreValidation
+ unavailable
+ "test/phase5/exact-escape-consumer.tex"
+ warmReport <- case warmResult of
+ Verification.CompletedWithExplicitGaps report _presentation -> pure report
+ other ->
+ assertFailure
+ ("warm escape report did not complete with gaps: "
+ <> show other)
+ >> fail "unreachable"
+ assertEqual "warm report uses rebound current locations"
+ freshReport warmReport
+
+ void
+ (verify
+ Verification.FreshStoreValidation
+ executable
+ "test/phase5/exact-source-axiom.tex")
+ (failedResult, _failedSlowReport) <-
+ verify
+ Verification.WarmStoreValidation
+ unavailable
+ "test/phase6/admitted-prefix-failure.tex"
+ failedReport <- case failedResult of
+ Verification.VerificationCheckingFailure report _failure -> pure report
+ other ->
+ assertFailure
+ ("typed suffix failure was not report-bearing: "
+ <> show other)
+ >> fail "unreachable"
+ assertEqual "failure report retains only admitted source prefix"
+ (take 2 expectedConsumer
+ <> [ ( Verification.ReportedOmitted
+ , "test/phase6/admitted-prefix-failure.tex"
+ , 7
+ )
+ ])
+ (reportEntries failedReport)
+
+classifiesTypedVampireFailures :: Assertion
+classifiesTypedVampireFailures = do
+ foundation <- expectRight Foundation.checkedFoundation
+ Temp.withSystemTempDirectory "felix-typed-failure-classification" \root -> do
+ let storePath = root Posix.</> "store.sqlite"
+ executable = root Posix.</> "vampire"
+ observer =
+ Verification.verificationRequestObserver \_ordinal _request -> pure ()
+ prover =
+ Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit
+ writeAcceptedFixtureVampire executable
+ (_startup, store) <-
+ Store.openStore storePath (Identity.theoryId foundation)
+ >>= expectRight
+ bracket (pure store) Store.closeStore \openStore -> do
+ let verify source =
+ (checkFileWithStore
+ openStore
+ Verification.WarmStoreValidation
+ observer
+ prover
+ source)
+ >>= expectRight
+ writeProtocol lines = do
+ writeFile executable
+ (unlines (["#!/bin/sh", "cat >/dev/null"] <> lines))
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ expectTypedFailure classify = do
+ (result, slowReport) <-
+ verify "test/phase5/exact-runtime-failure.tex"
+ case result of
+ Verification.VerificationFailure report failed -> do
+ assertEqual "typed failure has no direct escapes"
+ []
+ (Verification.verificationDirectEscapes report)
+ assertEqual "typed failure retains source location"
+ "test/phase5/exact-runtime-failure.tex"
+ (locFile
+ (Verification.failedVerificationLocation failed))
+ classify
+ (Verification.failedVerificationReason failed)
+ (CommandLine.verificationCommandOutcome
+ result slowReport)
+ other ->
+ assertFailure
+ ("typed prover outcome was misclassified: "
+ <> show other)
+
+ -- Populate only the confined prelude. The selected ordinary
+ -- module then remains a miss for each classified live failure.
+ (_preludeResult, _preludeSlowReport) <-
+ verify "test/phase3/typed-unsupported.tex"
+
+ writeProtocol
+ [ "printf '%s\\n' '% SZS status CounterSatisfiable for typed-failure'"
+ , "exit 0"
+ ]
+ expectTypedFailure \reason outcome -> do
+ case reason of
+ Verification.CountermodelFailure{} -> pure ()
+ other -> assertFailure ("expected countermodel: " <> show other)
+ case outcome of
+ CommandLine.VerificationRejected{} -> pure ()
+ other -> assertFailure ("expected rejection: " <> show other)
+
+ writeProtocol
+ [ "printf '%s\\n' '% SZS status Timeout for typed-failure'"
+ , "exit 0"
+ ]
+ expectTypedFailure \reason outcome -> do
+ case reason of
+ Verification.IndeterminateFailure{} -> pure ()
+ other -> assertFailure ("expected indeterminate result: " <> show other)
+ case outcome of
+ CommandLine.VerificationRejected{} -> pure ()
+ other -> assertFailure ("expected prover failure: " <> show other)
+
+ writeProtocol
+ [ "printf '%s\\n' '% SZS status Theorem for typed-failure'"
+ , "exit 7"
+ ]
+ expectTypedFailure \reason outcome -> do
+ case reason of
+ Verification.ProtocolFailure{} -> pure ()
+ other -> assertFailure ("expected protocol failure: " <> show other)
+ case outcome of
+ CommandLine.VerificationRejected{} -> pure ()
+ other -> assertFailure ("expected prover failure: " <> show other)
+
+ writeFile executable "not executable"
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable False permissions)
+ expectTypedFailure \reason outcome -> do
+ case reason of
+ Verification.TransportFailure{} -> pure ()
+ other -> assertFailure ("expected transport failure: " <> show other)
+ case outcome of
+ CommandLine.VerificationRejected{} -> pure ()
+ other -> assertFailure ("expected prover failure: " <> show other)
+
+retainsExactPrefixBeforeFailure :: Assertion
+retainsExactPrefixBeforeFailure = do
+ result <-
+ withAcceptedFixtureVampire "felix-exact-failure" \prover ->
+ (checkFileFresh
+ prover
+ "test/phase5/exact-failure.tex")
+ case result of
+ Right
+ ( Verification.VerificationCheckingFailure _report
+ (Verification.VerificationTypedModuleError
+ source
+ (Module.TypedActionFailed
+ (Module.TypedExactCompileFailed
+ (Exact.ExactGuardedOpaqueSignature location)))
+ prefix)
+ , _slowReport
+ ) -> do
+ assertEqual "failed exact source"
+ "test/phase5/exact-failure.tex"
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source))
+ assertEqual "unsupported declaration line" 6 (locLine location)
+ assertEqual "earlier exact declaration remains committed"
+ 1
+ (length (Declaration.pendingModulePrefixBatches prefix))
+ Left err ->
+ assertFailure ("unexpected exact failure: " <> show err)
+ Right{} ->
+ assertFailure "unsupported declaration was admitted"
+
+ proofFailure <-
+ withAcceptedFixtureVampire "felix-exact-proof-failure" \prover ->
+ (checkFileFresh
+ prover
+ "test/phase5/exact-proof-failure.tex")
+ case proofFailure of
+ Right
+ ( Verification.VerificationCheckingFailure _report
+ (Verification.VerificationTypedModuleError
+ _source
+ (Module.TypedActionFailed
+ (Module.TypedExactProofFailed
+ (ExactProof.ExactProofGoalStatementMismatch
+ location)))
+ prefix)
+ , _slowReport
+ ) -> do
+ assertEqual "mismatched assumption line" 10 (locLine location)
+ assertEqual "failed proof publishes no theorem"
+ 1
+ (length (Declaration.pendingModulePrefixBatches prefix))
+ Left err ->
+ assertFailure
+ ("unexpected exact proof failure: " <> show err)
+ Right{} ->
+ assertFailure "mismatched exact proof was admitted"
+
+ unmatched <-
+ withAcceptedFixtureVampire "felix-unmatched-proof" \prover ->
+ (checkFileFresh
+ prover
+ "test/phase5/unmatched-proof.tex")
+ case unmatched of
+ Right
+ ( Verification.VerificationCheckingFailure _report
+ (Verification.VerificationTypedModuleError
+ _source
+ (Module.TypedActionFailed
+ (Module.TypedUnmatchedProof location))
+ prefix)
+ , _slowReport
+ ) -> do
+ assertEqual "unmatched proof line" 1 (locLine location)
+ assertEqual "unmatched proof publishes no declaration"
+ 0
+ (length (Declaration.pendingModulePrefixBatches prefix))
+ Left err ->
+ assertFailure
+ ("unexpected unmatched-proof failure: " <> show err)
+ Right{} ->
+ assertFailure "unmatched proof was admitted"
+
+ runtimeFailure <-
+ Temp.withSystemTempDirectory "felix-runtime-proof-failure" \root -> do
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ repository <- getCurrentDirectory
+ mounts <- exactFixtureMounts repository
+ workspace <- parseExactWorkspace
+ bootstrap mounts "test/phase5/exact-runtime-failure.tex"
+ let executable = root Posix.</> "vampire"
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for located-proof'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ runs <- newIORef (0 :: Int)
+ let resolver = Declaration.vampireResolver \prepared -> do
+ runNumber <- readIORef runs
+ modifyIORef' runs (+ 1)
+ if runNumber == 0
+ then
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+ else
+ pure
+ (Right
+ (Provers.CounterSatisfiable
+ "later exact obligation"))
+ parsed = Parse.parsedWorkspaceRootModule workspace
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ resolver
+ Declaration.FreshValidation
+ parsed
+ [])
+ Module.runTypedModule input
+ case runtimeFailure of
+ Module.TypedModuleFailed
+ failure@(Module.TypedDeclarationFailed
+ (Declaration.ProofObligationFailedAt
+ location
+ Declaration.VampireObligationRejected{}))
+ prefix -> do
+ assertEqual "later rejected obligation line"
+ 11
+ (locLine location)
+ assertEqual "typed failure retains obligation location"
+ (Just location)
+ (Module.typedModuleFailureLocation failure)
+ assertEqual "runtime proof failure publishes no theorem"
+ 1
+ (length (Declaration.pendingModulePrefixBatches prefix))
+ _result ->
+ assertFailure "unexpected runtime proof failure"
+
+restoresCheckedSetInduction :: Assertion
+restoresCheckedSetInduction =
+ Temp.withSystemTempDirectory "felix-checked-set-induction" \root -> do
+ let executable = root Posix.</> "vampire"
+ storePath = root Posix.</> "store.sqlite"
+ writeAcceptedFixtureVampire executable
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation unusedResolver
+ mounts <- exactFixtureMounts =<< getCurrentDirectory
+
+ initialWorkspace <- parseExactWorkspace bootstrap mounts
+ "test/phase5/exact-induction-initial.tex"
+ initialObservations <- newIORef []
+ initial <-
+ sole "initial set-induction module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (observingResolver executable initialObservations)
+ Declaration.FreshValidation
+ initialWorkspace
+ [initialRequest] <-
+ expectCount "initial set-induction request" 1
+ =<< readIORef initialObservations
+ assertEqual "initial induction retains header then hypothesis ordinals"
+ [0, 1]
+ (localReasoningLocalOrdinals initialRequest)
+ let initialTarget =
+ Core.CEq Core.TySet (Core.CBound 0) (Core.CBound 0)
+ initialAntecedent =
+ member (Core.CBound 1) (Core.CBound 0)
+ initialHypothesis =
+ Core.CForall Core.TySet
+ (Core.CImp
+ (member (Core.CBound 0) (Core.CBound 2))
+ (Core.CImp
+ (member (Core.CBound 0) (Core.CBound 1))
+ (Core.CEq Core.TySet
+ (Core.CBound 0)
+ (Core.CBound 0))))
+ assertEqual "initial induction child target"
+ initialTarget
+ (localReasoningTarget initialRequest)
+ assertEqual "initial induction uses the complete guarded property"
+ [initialAntecedent, initialHypothesis]
+ (localReasoningLocalTerms initialRequest)
+
+ nestedWorkspace <- parseExactWorkspace bootstrap mounts
+ "test/phase5/exact-induction-nested.tex"
+ nestedObservations <- newIORef []
+ nested <-
+ sole "nested set-induction module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (observingResolver executable nestedObservations)
+ Declaration.FreshValidation
+ nestedWorkspace
+ [nestedChild, nestedContinuation] <-
+ expectCount "nested set-induction requests" 2
+ =<< readIORef nestedObservations
+ let x = Core.CBound 0
+ a = Core.CBound 1
+ y = Core.CBound 0
+ xUnderY = Core.CBound 1
+ aUnderY = Core.CBound 2
+ guardAtX =
+ andP
+ (member x a)
+ (notP (Core.CEq Core.TySet x a))
+ guardAtY =
+ andP
+ (member y aUnderY)
+ (notP (Core.CEq Core.TySet y aUnderY))
+ nestedHypothesis =
+ Core.CForall Core.TySet
+ (Core.CImp
+ (member y xUnderY)
+ (Core.CImp
+ guardAtY
+ (Core.CEq Core.TySet y y)))
+ nestedTarget = Core.CEq Core.TySet x x
+ assertEqual
+ "omitted leading induction retains its source binder and guard"
+ ([0, 1], [nestedHypothesis, guardAtX], nestedTarget)
+ ( localReasoningLocalOrdinals nestedChild
+ , localReasoningLocalTerms nestedChild
+ , localReasoningTarget nestedChild
+ )
+ case localReasoningLocalTerms nestedContinuation of
+ [derived] -> do
+ assertEqual "subproof continuation uses one derived local"
+ [2] (localReasoningLocalOrdinals nestedContinuation)
+ assertEqual "subproof closes the exact binder-level result"
+ derived (localReasoningTarget nestedContinuation)
+ locals ->
+ assertFailure
+ ("unexpected induction continuation locals: "
+ <> show locals)
+
+ formulaWorkspace <- parseExactWorkspace bootstrap mounts
+ "test/phase5/exact-induction-formula-quantified.tex"
+ formulaObservations <- newIORef []
+ _formula <-
+ sole "formula-quantified set-induction module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (observingResolver executable formulaObservations)
+ Declaration.FreshValidation
+ formulaWorkspace
+ [formulaChild, formulaContinuation] <-
+ expectCount "formula-quantified set-induction requests" 2
+ =<< readIORef formulaObservations
+ assertEqual
+ "formula-quantified omitted induction retains its written binder"
+ (Core.CEq Core.TySet (Core.CBound 0) (Core.CBound 0))
+ (localReasoningTarget formulaChild)
+ assertEqual
+ "formula-quantified continuation retains hypothesis and derived local"
+ [0, 1]
+ (localReasoningLocalOrdinals formulaContinuation)
+
+ anchorWorkspace <- parseExactWorkspace bootstrap mounts
+ "test/examples/no-reflexive-set.tex"
+ anchorObservations <- newIORef []
+ _anchor <-
+ sole "omitted-focus set-induction module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (observingResolver executable anchorObservations)
+ Declaration.FreshValidation
+ anchorWorkspace
+ [anchorRequest] <-
+ expectCount "omitted-focus set-induction request" 1
+ =<< readIORef anchorObservations
+ anchorLocal <-
+ case localReasoningLocalTerms anchorRequest of
+ [term] -> pure term
+ terms ->
+ assertFailure
+ ("unexpected omitted-focus locals: " <> show terms)
+ >> fail "unreachable"
+ assertEqual "omitted focus retains its source binder in the child"
+ ([0], Core.CForall Core.TySet
+ (Core.CImp
+ (member (Core.CBound 0) (Core.CBound 1))
+ (notP (member (Core.CBound 0) (Core.CBound 0)))))
+ ( localReasoningLocalOrdinals anchorRequest
+ , anchorLocal
+ )
+
+ assertProofParityFailure
+ foundation bootstrap mounts
+ "test/phase5/exact-induction-ambiguous.tex"
+ (\case
+ ExactProof.ExactProofSetInductionFocusAmbiguous location ->
+ locLine location == 5
+ _failure -> False)
+ assertProofParityFailure
+ foundation bootstrap mounts
+ "test/phase5/exact-induction-fixed.tex"
+ (\case
+ ExactProof.ExactProofSetInductionActiveBinderIneligible
+ location (Raw.NamedVar "x") ->
+ locLine location == 7
+ _failure -> False)
+
+ failedInput <-
+ moduleInput
+ foundation bootstrap initialWorkspace
+ (Declaration.vampireResolver \_prepared ->
+ pure
+ (Right
+ (Provers.CounterSatisfiable
+ "focused induction child rejection")))
+ Declaration.FreshValidation
+ Module.runTypedModule failedInput >>= \case
+ Module.TypedModuleFailed _failure prefix ->
+ assertBool "failed induction child publishes no theorem"
+ (null (Declaration.pendingModulePrefixBatches prefix))
+ _result ->
+ assertFailure "rejected induction child unexpectedly succeeded"
+
+ bracket
+ (snd <$> (Store.openStore storePath
+ (Identity.theoryId foundation) >>= expectRight))
+ Store.closeStore
+ \store -> do
+ expectRightIO
+ (Store.writePendingModulePrefix store
+ (Module.sealedTypedModulePrefix nested))
+ let validation =
+ Declaration.WarmValidation
+ (Declaration.validationLookup
+ (expectRightIO
+ . Store.loadProofValidation store)
+ (expectRightIO
+ . Store.loadDeclarationValidation store))
+ warmRuns <- newIORef (0 :: Int)
+ warm <-
+ sole "warm nested set-induction module"
+ =<< compileParsedWorkspaceWithValidation
+ foundation bootstrap
+ (countingAcceptedResolver executable warmRuns)
+ validation nestedWorkspace
+ assertEqual "warm set induction skips Vampire"
+ 0 =<< readIORef warmRuns
+ assertEqual "fresh and warm induction proof validations"
+ (proofValidations nested)
+ (proofValidations warm)
+ assertBool "initial induction publishes one theorem"
+ (not
+ (null
+ (Declaration.pendingModulePrefixBatches
+ (Module.sealedTypedModulePrefix initial))))
+ where
+ observingResolver executable observations =
+ Declaration.vampireResolver \prepared -> do
+ let problem = Provers.preparedTypedProverLogicalProblem prepared
+ locals = Backend.typedProblemLocalPremises problem
+ modifyIORef' observations
+ (<> [ LocalReasoningObservation
+ { localReasoningTarget =
+ Backend.supportedPropositionTerm
+ (Backend.typedProblemClaim problem)
+ , localReasoningGlobalCount =
+ Vector.length
+ (Backend.typedProblemGlobalPremises problem)
+ , localReasoningLocalOrdinals =
+ Backend.localPremiseOrdinalValue
+ . Backend.typedLocalPremiseOrdinal
+ <$> Vector.toList locals
+ , localReasoningLocalTerms =
+ Backend.supportedPropositionTerm
+ . Backend.typedLocalPremiseProposition
+ <$> Vector.toList locals
+ , localReasoningAuxiliaries =
+ Backend.typedProblemAuxiliaryTag
+ <$> Vector.toList
+ (Backend.typedProblemAuxiliaries problem)
+ }
+ ])
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+
+ expectCount label expected values = do
+ assertEqual label expected (length values)
+ pure values
+
+ member element set =
+ Core.CApp
+ (Core.CApp (Core.CIntrinsic Core.Member) element)
+ set
+
+ notP proposition = Core.CImp proposition Core.CFalsum
+
+ andP left right = notP (Core.CImp left (notP right))
+
+ moduleInput foundation bootstrap workspace resolver validation = do
+ parsed <- sole "set-induction parsed module"
+ (toList (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ resolver validation parsed [])
+
+ proofValidations =
+ concatMap Declaration.committedBatchProofValidations
+ . Declaration.pendingModulePrefixBatches
+ . Module.sealedTypedModulePrefix
+
+routesProductionVerification :: Assertion
+routesProductionVerification =
+ Temp.withSystemTempDirectory "felix-production-route" \directory -> do
+ let executable = directory Posix.</> "vampire"
+ counter = directory Posix.</> "runs"
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' run >> " <> show counter
+ , "printf '%s\\n' '% SZS status Theorem for production-route'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+ producer <- verifyFixture executable "test/phase3/typed-producer.tex"
+ assertTypedSuccess "exact producer" producer
+ selectedRuns <- runCount counter
+ assertBool "ordinary roots construct the final prelude"
+ (selectedRuns > 0)
+ importer <- verifyFixture executable "test/phase3/typed-importer.tex"
+ assertTypedSuccess "ordinary importer" importer
+ assertBool "every root constructs the final prelude"
+ . (> selectedRuns)
+ =<< runCount counter
+ where
+ verifyFixture executable path =
+ fst
+ <$> (
+ (checkFileFresh
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ path)
+ >>= expectRight)
+
+ runCount path =
+ length . StrictText.lines . StrictText.pack <$> readFile path
+
+installsNonemptyImplicitPreludeEvidence :: Assertion
+installsNonemptyImplicitPreludeEvidence = do
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ closure <- expectRight
+ (Identity.validateObjectClosure
+ (Identity.theoryId foundation)
+ [])
+ proposition <- expectRight
+ (Identity.validatePropositionContent closure Core.CFalsum)
+ preludeDriver <- Declaration.runModuleDriver
+ foundation
+ preludeModuleName
+ []
+ unusedResolver
+ Declaration.FreshValidation
+ do
+ Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "nonempty-prelude") do
+ candidate <- Declaration.reserveCandidate
+ (Declaration.candidateSpec
+ proposition
+ Semantic.SearchEligible
+ [Semantic.semanticName "prelude-fact"])
+ Declaration.authorizeSourceAxiomCandidate candidate
+ preludeResult <- expectRight preludeDriver
+ (preludeSemantic, preludePrefix) <-
+ case preludeResult of
+ Declaration.DriverSucceeded _value semantic prefix _closure ->
+ pure (semantic, prefix)
+ _ ->
+ assertFailure "nonempty prelude fixture did not seal"
+ >> fail "unreachable"
+ preludeFingerprint <-
+ case concatMap
+ Semantic.declarationDeltaFacts
+ (Semantic.semanticInterfaceDeclarations preludeSemantic) of
+ [occurrence] ->
+ pure (Semantic.semanticFactFingerprint occurrence)
+ facts ->
+ assertFailure
+ ("unexpected prelude fact count: "
+ <> show (length facts))
+ >> fail "unreachable"
+ let preludeSyntax =
+ Module.sealedTypedModuleSyntax
+ (Module.bootstrapPreludeModule bootstrap)
+ nonemptyPrelude <-
+ Temp.withSystemTempDirectory "felix-nonempty-prelude" \directory -> do
+ let path = directory Posix.</> "store.sqlite"
+ theory = Identity.theoryId foundation
+ parsed =
+ Module.identifiedModuleParsed
+ (Module.bootstrapPreludeInput bootstrap)
+ (_startup, store) <-
+ Store.openStore path theory >>= expectRight
+ artifactKey <- expectRight
+ (Semantic.moduleArtifactKey
+ preludeModuleName
+ (Parse.identifiedParsedModuleId parsed)
+ []
+ theory)
+ let artifact =
+ Semantic.moduleArtifactResult
+ artifactKey
+ (Syntax.moduleSyntaxAssertedId preludeSyntax)
+ (Semantic.semanticInterfaceAssertedId
+ preludeSemantic)
+ _ <- expectRight
+ =<< Store.writeSealedModule
+ store
+ preludePrefix
+ [preludeSyntax]
+ [preludeSemantic]
+ artifact
+ memo <- Store.newStoreMemo store
+ loaded <- expectRight
+ =<< Store.loadCachedModuleInstallation
+ memo
+ store
+ artifactKey
+ (Syntax.moduleSyntaxAssertedId preludeSyntax)
+ installation <- maybe
+ (assertFailure "nonempty prelude was not installed"
+ >> fail "unreachable")
+ pure
+ loaded
+ sealed <- expectRight
+ (Module.cachedSealedTypedModule
+ foundation
+ []
+ installation)
+ Store.closeStore store
+ pure sealed
+
+ root <- getCurrentDirectory
+ mounts <-
+ expectRight
+ =<< prepareSourceMounts
+ [ (sourceMountId "project", root)
+ , (sourceMountId "library", root Posix.</> "library")
+ , (sourceMountId "debug", root Posix.</> "debug")
+ ]
+ request <- expectRight
+ (searchedRoot "test/phase3/typed-producer.tex")
+ workspace <-
+ expectRight
+ =<< Parse.parseSourceWorkspaceWithSyntaxInputs
+ mounts
+ request
+ (const [preludeSyntax])
+ let parsed = Parse.parsedWorkspaceRootModule workspace
+ input <- expectRight
+ (Module.typedModuleInput
+ foundation
+ (Module.fixtureFinalPreludeReadinessFromSealed nonemptyPrelude)
+ unusedResolver
+ Declaration.FreshValidation
+ parsed
+ [])
+ ordinary <- Module.runTypedModule input >>= \case
+ Module.TypedModuleSucceeded sealed -> pure sealed
+ _ ->
+ assertFailure "ordinary module rejected the nonempty prelude"
+ >> fail "unreachable"
+
+ consumerDigest <- expectRight
+ (hashCanonicalFields
+ "implicit-prelude-consumer"
+ ["consumer"])
+ consumerPath <- expectRight (safeRelativePath "consumer.tex")
+ let consumerOwner =
+ moduleNameFromParts
+ (sourceNamespaceIdFromDigest consumerDigest)
+ consumerPath
+ consumed <- Declaration.runModuleDriver
+ foundation
+ consumerOwner
+ [Semantic.semanticInterfaceAssertedId
+ (Module.sealedTypedModuleSemantic ordinary)]
+ unusedResolver
+ Declaration.FreshValidation
+ do
+ Declaration.importSealedModuleDriver
+ (Module.sealedTypedModuleEvidence ordinary)
+ Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "use-implicit-prelude") do
+ candidate <- Declaration.reserveCandidate
+ (Declaration.candidateSpec
+ proposition
+ Semantic.SearchIneligible
+ [])
+ Declaration.authorizeOmittedCandidate candidate do
+ void
+ (Declaration.useAuthorizedFact
+ preludeFingerprint)
+ Declaration.recordOmittedUse
+ consumedResult <- expectRight consumed
+ case consumedResult of
+ Declaration.DriverSucceeded{} -> pure ()
+ _ ->
+ assertFailure
+ "implicit prelude fact was not transitively visible"
+
+unusedResolver :: Declaration.VampireResolver
+unusedResolver =
+ Declaration.vampireResolver \_prepared ->
+ fail "empty bootstrap invoked Vampire"
+
+writeAcceptedFixtureVampire :: FilePath -> IO ()
+writeAcceptedFixtureVampire executable = do
+ writeFile executable
+ (unlines
+ [ "#!/bin/sh"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for typed-fixture'"
+ ])
+ permissions <- getPermissions executable
+ setPermissions executable
+ (setOwnerExecutable True permissions)
+
+withAcceptedFixtureVampire
+ :: String
+ -> (Provers.Vampire -> IO value)
+ -> IO value
+withAcceptedFixtureVampire label action =
+ Temp.withSystemTempDirectory label \directory -> do
+ let executable = directory Posix.</> "vampire"
+ writeAcceptedFixtureVampire executable
+ action
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+
+finalPreludeResolver :: Declaration.VampireResolver
+finalPreludeResolver =
+ Declaration.vampireResolver \prepared ->
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ "vampire"
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+
+countingAcceptedResolver
+ :: FilePath
+ -> IORef Int
+ -> Declaration.VampireResolver
+countingAcceptedResolver executable runs =
+ Declaration.vampireResolver \prepared -> do
+ modifyIORef' runs (+ 1)
+ (Provers.runPreparedTypedProver
+ (Provers.vampire
+ executable
+ Provers.defaultTimeLimit
+ Provers.defaultMemoryLimit)
+ prepared)
+
+prepareExactInductiveFixture
+ :: FilePath
+ -> IO
+ (Either
+ ExactInductive.ExactInductiveError
+ ExactInductive.PreparedExactInductive)
+prepareExactInductiveFixture relative = do
+ root <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts root
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ parsed <-
+ sole "exact inductive parsed module"
+ (toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ let identified = Module.identifiedPhysicalModule parsed
+ owner = Module.identifiedModuleOwner identified
+ parsedModule = Module.identifiedModuleParsed identified
+ (blockIndex, block) <-
+ sole "exact inductive block"
+ [ (index, candidate)
+ | (index, candidate@Raw.BlockInductive{}) <-
+ zip [0..]
+ (Parse.identifiedParsedModuleBlocks parsedModule)
+ ]
+ let entries =
+ [ Parse.parsedSyntaxOccurrenceEntry occurrence
+ | occurrence <-
+ Parse.identifiedParsedModuleSyntaxOccurrences parsedModule
+ , Parse.parsedSyntaxOccurrenceBlockIndex occurrence == blockIndex
+ ]
+ action
+ :: Declaration.ModuleDriver Void
+ (Either
+ ExactInductive.ExactInductiveError
+ ExactInductive.PreparedExactInductive)
+ action =
+ Declaration.runProspectiveLoweringDriver
+ (ExactInductive.prepareExactInductive
+ foundation
+ block
+ entries)
+ result <-
+ Declaration.runModuleDriver
+ foundation
+ owner
+ []
+ unusedResolver
+ Declaration.FreshValidation
+ action
+ driver <- expectRight result
+ case driver of
+ Declaration.DriverSucceeded prepared _semantic _prefix _closure ->
+ pure prepared
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("exact inductive preparation driver failed: "
+ <> show failure)
+ >> fail "unreachable"
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure
+ ("exact inductive preparation driver did not seal: "
+ <> show failure)
+ >> fail "unreachable"
+
+prepareExactDatatypeFixture
+ :: FilePath
+ -> IO
+ (Either
+ ExactDatatype.ExactDatatypeError
+ ( Foundation.CheckedFoundation
+ , ModuleName
+ , ExactDatatype.PreparedExactDatatype
+ ))
+prepareExactDatatypeFixture relative = do
+ root <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts root
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ parsed <-
+ sole "exact datatype parsed module"
+ (toList
+ (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ let identified = Module.identifiedPhysicalModule parsed
+ owner = Module.identifiedModuleOwner identified
+ parsedModule = Module.identifiedModuleParsed identified
+ block <-
+ sole "exact datatype block"
+ (Parse.identifiedParsedModuleBlocks parsedModule)
+ let occurrences =
+ [ ( Parse.parsedSyntaxOccurrenceLocation occurrence
+ , Parse.parsedSyntaxOccurrenceMarker occurrence
+ , Parse.parsedSyntaxOccurrenceEntry occurrence
+ )
+ | occurrence <-
+ Parse.identifiedParsedModuleSyntaxOccurrences parsedModule
+ , Parse.parsedSyntaxOccurrenceBlockIndex occurrence == 0
+ ]
+ action
+ :: Declaration.ModuleDriver Void
+ (Either
+ ExactDatatype.ExactDatatypeError
+ ExactDatatype.PreparedExactDatatype)
+ action =
+ Declaration.runProspectiveLoweringDriver
+ (ExactDatatype.prepareExactDatatype block occurrences)
+ result <-
+ Declaration.runModuleDriver
+ foundation
+ owner
+ []
+ unusedResolver
+ Declaration.FreshValidation
+ action
+ driver <- expectRight result
+ case driver of
+ Declaration.DriverSucceeded prepared _semantic _prefix _closure ->
+ pure
+ ((\datatype -> (foundation, owner, datatype))
+ <$> prepared)
+ Declaration.DriverFailed failure _prefix ->
+ assertFailure
+ ("exact datatype preparation driver failed: "
+ <> show failure)
+ >> fail "unreachable"
+ Declaration.DriverSealFailed failure _prefix ->
+ assertFailure
+ ("exact datatype preparation driver did not seal: "
+ <> show failure)
+ >> fail "unreachable"
+
+compileExactFixture
+ :: FilePath
+ -> IO
+ ( Foundation.CheckedFoundation
+ , Module.BootstrapPreludeFixture
+ , Parse.ParsedSourceWorkspace
+ , [Module.SealedTypedModule]
+ )
+compileExactFixture relative = do
+ root <- getCurrentDirectory
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts root
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ sealed <- compileParsedWorkspace foundation bootstrap workspace
+ pure (foundation, bootstrap, workspace, sealed)
+
+compileExactRootAt
+ :: FilePath
+ -> FilePath
+ -> IO (Parse.ParsedSourceWorkspace, Module.SealedTypedModule)
+compileExactRootAt projectRoot relative = do
+ foundation <- expectRight Foundation.checkedFoundation
+ bootstrap <-
+ expectRight
+ =<< Module.buildBootstrapPreludeFixture
+ foundation
+ unusedResolver
+ mounts <- exactFixtureMounts projectRoot
+ workspace <- parseExactWorkspace bootstrap mounts relative
+ sealed <- compileParsedWorkspace foundation bootstrap workspace
+ rootModule <- sole "exact root module" (reverse sealed)
+ pure (workspace, rootModule)
+
+exactFixtureMounts :: FilePath -> IO SourceMounts
+exactFixtureMounts projectRoot = do
+ repository <- getCurrentDirectory
+ expectRight
+ =<< prepareSourceMounts
+ [ (sourceMountId "project", projectRoot)
+ , (sourceMountId "library", repository Posix.</> "library")
+ , (sourceMountId "debug", repository Posix.</> "debug")
+ ]
+
+parseExactWorkspace
+ :: Module.BootstrapPreludeFixture
+ -> SourceMounts
+ -> FilePath
+ -> IO Parse.ParsedSourceWorkspace
+parseExactWorkspace bootstrap mounts relative =
+ parseExactWorkspaceWithPrelude
+ (Module.bootstrapPreludeModule bootstrap)
+ mounts
+ relative
+
+parseFinalExactWorkspace
+ :: Module.FinalPreludeSession
+ -> SourceMounts
+ -> FilePath
+ -> IO Parse.ParsedSourceWorkspace
+parseFinalExactWorkspace prelude mounts relative =
+ parseExactWorkspaceWithPrelude
+ (Module.finalPreludeModule prelude)
+ mounts
+ relative
+
+parseExactWorkspaceWithPrelude
+ :: Module.SealedTypedModule
+ -> SourceMounts
+ -> FilePath
+ -> IO Parse.ParsedSourceWorkspace
+parseExactWorkspaceWithPrelude prelude mounts relative = do
+ request <- expectRight (searchedRoot relative)
+ let preludeSyntax =
+ Module.sealedTypedModuleSyntax
+ prelude
+ expectRight
+ =<< Parse.parseSourceWorkspaceWithSyntaxInputs
+ mounts
+ request
+ (const [preludeSyntax])
+
+compileParsedWorkspace
+ :: Foundation.CheckedFoundation
+ -> Module.BootstrapPreludeFixture
+ -> Parse.ParsedSourceWorkspace
+ -> IO [Module.SealedTypedModule]
+compileParsedWorkspace foundation bootstrap workspace =
+ compileParsedWorkspaceWithResolver
+ foundation
+ bootstrap
+ unusedResolver
+ workspace
+
+compileParsedWorkspaceWithResolver
+ :: Foundation.CheckedFoundation
+ -> Module.BootstrapPreludeFixture
+ -> Declaration.VampireResolver
+ -> Parse.ParsedSourceWorkspace
+ -> IO [Module.SealedTypedModule]
+compileParsedWorkspaceWithResolver foundation bootstrap resolver workspace =
+ compileParsedWorkspaceWithValidation
+ foundation
+ bootstrap
+ resolver
+ Declaration.FreshValidation
+ workspace
+
+compileParsedWorkspaceWithValidation
+ :: Foundation.CheckedFoundation
+ -> Module.BootstrapPreludeFixture
+ -> Declaration.VampireResolver
+ -> Declaration.ValidationRun
+ -> Parse.ParsedSourceWorkspace
+ -> IO [Module.SealedTypedModule]
+compileParsedWorkspaceWithValidation
+ foundation bootstrap resolver validation workspace =
+ compileParsedWorkspaceWithReadiness
+ foundation
+ (Module.bootstrapPreludeReadiness bootstrap)
+ resolver
+ validation
+ workspace
+
+compileFinalParsedWorkspaceWithResolver
+ :: Foundation.CheckedFoundation
+ -> Module.FinalPreludeSession
+ -> Declaration.VampireResolver
+ -> Parse.ParsedSourceWorkspace
+ -> IO [Module.SealedTypedModule]
+compileFinalParsedWorkspaceWithResolver foundation prelude resolver workspace =
+ compileParsedWorkspaceWithReadiness
+ foundation
+ (Module.finalPreludeReadiness prelude)
+ resolver
+ Declaration.FreshValidation
+ workspace
+
+compileParsedWorkspaceWithReadiness
+ :: Foundation.CheckedFoundation
+ -> Module.FinalPreludeReadiness
+ -> Declaration.VampireResolver
+ -> Declaration.ValidationRun
+ -> Parse.ParsedSourceWorkspace
+ -> IO [Module.SealedTypedModule]
+compileParsedWorkspaceWithReadiness
+ foundation readiness resolver validation workspace =
+ snd
+ <$> foldM
+ compileOne
+ (Map.empty, [])
+ (toList (Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ where
+ compileOne (admitted, ordered) parsed = do
+ direct <-
+ traverse
+ (\address ->
+ maybe
+ (assertFailure
+ ("missing exact direct module: " <> show address)
+ >> fail "unreachable")
+ pure
+ (Map.lookup address admitted))
+ (nubOrd
+ (Parse.parsedImportedAddress
+ <$> Parse.parsedModuleImports parsed))
+ input <-
+ expectRight
+ (Module.typedModuleInput
+ foundation
+ readiness
+ resolver
+ validation
+ parsed
+ direct)
+ sealed <-
+ Module.runTypedModule input >>= \case
+ Module.TypedModuleSucceeded module' -> pure module'
+ Module.TypedModuleOpenFailed failure ->
+ assertFailure
+ ("exact module did not open: " <> show failure)
+ >> fail "unreachable"
+ Module.TypedModuleFailed failure _prefix ->
+ assertFailure
+ ("exact module did not seal: " <> show failure)
+ >> fail "unreachable"
+ pure
+ ( Map.insert (Parse.parsedModuleAddress parsed) sealed admitted
+ , ordered <> [sealed]
+ )
+
+checkFileFresh
+ :: Provers.Vampire
+ -> FilePath
+ -> IO
+ (Either
+ Verification.VerificationDriverError
+ (Verification.VerificationResult, Provers.SlowAtpReport))
+checkFileFresh prover source = do
+ plan <- Store.planStore Store.FreshTemporaryStore >>= expectRight
+ Store.withStoreLease plan \lease -> do
+ opened <- Verification.withVerificationSession lease
+ (\session ->
+ checkFileWithSession
+ session
+ Verification.FreshStoreValidation
+ testSequentialJobs
+ ignoredVerificationRequests
+ prover
+ source)
+ case opened of
+ Left failure ->
+ assertFailure
+ ("test verification session failed: " <> show failure)
+ >> fail "unreachable"
+ Right result -> pure result
+
+checkFileWithStore
+ :: Store.Store
+ -> Verification.StoreValidationMode
+ -> Verification.VerificationRequestObserver
+ -> Provers.Vampire
+ -> FilePath
+ -> IO
+ (Either
+ Verification.VerificationDriverError
+ (Verification.VerificationResult, Provers.SlowAtpReport))
+checkFileWithStore store mode =
+ checkFileWithStoreAndJobs store mode testSequentialJobs
+
+checkFileWithStoreAndJobs
+ :: Store.Store
+ -> Verification.StoreValidationMode
+ -> Provers.EffectiveJobs
+ -> Verification.VerificationRequestObserver
+ -> Provers.Vampire
+ -> FilePath
+ -> IO
+ (Either
+ Verification.VerificationDriverError
+ (Verification.VerificationResult, Provers.SlowAtpReport))
+checkFileWithStoreAndJobs store mode jobs observer prover source = do
+ opened <- Verification.withVerificationSessionUsingStore store
+ (\session ->
+ checkFileWithSession session mode jobs observer prover source)
+ case opened of
+ Left failure ->
+ assertFailure
+ ("test verification session failed: " <> show failure)
+ >> fail "unreachable"
+ Right result -> pure result
+
+checkResultWithStore
+ :: Store.Store
+ -> Verification.StoreValidationMode
+ -> Verification.VerificationRequestObserver
+ -> Provers.Vampire
+ -> FilePath
+ -> IO
+ (Either
+ Verification.VerificationDriverError
+ Verification.VerificationResult)
+checkResultWithStore store mode observer prover source =
+ fmap (fmap fst)
+ (checkFileWithStore store mode observer prover source)
+
+checkFileWithSession
+ :: Verification.VerificationSession
+ -> Verification.StoreValidationMode
+ -> Provers.EffectiveJobs
+ -> Verification.VerificationRequestObserver
+ -> Provers.Vampire
+ -> FilePath
+ -> IO
+ (Either
+ Verification.VerificationDriverError
+ (Verification.VerificationResult, Provers.SlowAtpReport))
+checkFileWithSession session mode jobs observer prover source =
+ Workspace.prepareDefaultSourceGraph source >>= \case
+ Left failure ->
+ pure (Left (Verification.VerificationWorkspaceError failure))
+ Right graph ->
+ fmap
+ (fmap
+ (\outcome ->
+ ( Verification.checkVerificationResult outcome
+ , Verification.checkSlowAtpReport outcome
+ )))
+ (Verification.checkWorkspace
+ session
+ Verification.CheckRequest
+ { Verification.checkSourceGraph = graph
+ , Verification.checkStoreValidationMode = mode
+ , Verification.checkEffectiveJobs = jobs
+ , Verification.checkVampire = prover
+ , Verification.checkRequestObserver = observer
+ })
+
+ignoredVerificationRequests :: Verification.VerificationRequestObserver
+ignoredVerificationRequests =
+ Verification.verificationRequestObserver
+ (\_position _request -> pure ())
+
+testSequentialJobs :: Provers.EffectiveJobs
+testSequentialJobs =
+ fromMaybe
+ (impossible "one is a positive worker count")
+ (Provers.effectiveJobs 1)
+
+assertTypedSuccess :: String -> Verification.VerificationResult -> Assertion
+assertTypedSuccess label = \case
+ Verification.VerificationCompleted _report _presentation ->
+ pure ()
+ Verification.CompletedWithExplicitGaps _report _presentation ->
+ assertFailure (label <> " completed with gaps")
+ Verification.VerificationFailure _report failure ->
+ assertFailure (label <> " failed: " <> show failure)
+ Verification.VerificationCheckingFailure _report failure ->
+ assertFailure (label <> " failed: " <> show failure)
+
+sole :: String -> [value] -> IO value
+sole label = \case
+ [value] -> pure value
+ values ->
+ assertFailure
+ (label <> ": expected one value, found " <> show (length values))
+ >> fail "unreachable"
+
+expectRight :: Show error => Either error value -> IO value
+expectRight = \case
+ Left err -> assertFailure (show err) >> fail "unreachable"
+ Right value -> pure value
+
+expectRightIO :: Show error => IO (Either error value) -> IO value
+expectRightIO action =
+ action >>= expectRight
+
+acquireFinalPreludeSession
+ :: Store.Store
+ -> Foundation.CheckedFoundation
+ -> Declaration.VampireResolver
+ -> IO
+ (Either
+ Module.FinalPreludeReadinessError
+ Module.FinalPreludeSession)
+acquireFinalPreludeSession store foundation resolver = do
+ memo <- Store.newStoreMemo store
+ Module.acquireFinalPreludeSession
+ memo store foundation resolver
diff --git a/source/Felix/Test/Unit/OutputPlan.hs b/source/Felix/Test/Unit/OutputPlan.hs
new file mode 100644
index 0000000..803b7da
--- /dev/null
+++ b/source/Felix/Test/Unit/OutputPlan.hs
@@ -0,0 +1,236 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Test.Unit.OutputPlan (unitTests) where
+
+import Base
+import Felix.OutputPlan qualified as Output
+import Felix.Source
+import Felix.Store qualified as Store
+
+import Control.Exception qualified as Exception
+import System.Directory qualified as Directory
+import System.Environment qualified as Environment
+import System.FilePath.Posix qualified as Posix
+import System.IO.Temp qualified as Temp
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Verification output preflight"
+ [ testCase "accepts absent and empty dump destinations"
+ acceptsAbsentAndEmptyDumpDestinations
+ , testCase "rejects a nonempty dump before store startup"
+ rejectsNonemptyDumpBeforeStoreStartup
+ , testCase "rejects persistent and fresh store collisions"
+ rejectsStoreCollisions
+ , testCase "reserves the store rollback journal"
+ reservesRollbackJournal
+ , testCase "rejects dump and HTML route collisions"
+ rejectsDumpHtmlCollisions
+ ]
+
+acceptsAbsentAndEmptyDumpDestinations :: Assertion
+acceptsAbsentAndEmptyDumpDestinations =
+ Temp.withSystemTempDirectory "felix-output-dump" \root -> do
+ let storeParent = root Posix.</> "store"
+ storeFile = storeParent Posix.</> "store.sqlite"
+ dump = root Posix.</> "dump"
+ Directory.createDirectory storeParent
+ plan <- expectRightIO
+ (Store.planStore
+ (Store.ExplicitStore storeFile))
+ Store.withStoreLease plan \lease -> do
+ absent <- Output.planVerificationOutputs
+ (Store.storeLeasePath lease)
+ (Just dump)
+ Nothing
+ plannedAbsent <- expectOutputRight absent
+ assertEqual "absent dump path"
+ (Just dump)
+ (Output.dumpOutputPath
+ <$> Output.verificationDumpOutput plannedAbsent)
+
+ Directory.createDirectory dump
+ emptyResult <- Output.planVerificationOutputs
+ (Store.storeLeasePath lease)
+ (Just dump)
+ Nothing
+ void (expectOutputRight emptyResult)
+
+rejectsNonemptyDumpBeforeStoreStartup :: Assertion
+rejectsNonemptyDumpBeforeStoreStartup =
+ Temp.withSystemTempDirectory "felix-output-before-store" \root -> do
+ let cacheRoot = root Posix.</> "cache"
+ dump = root Posix.</> "dump"
+ Directory.createDirectory cacheRoot
+ Directory.createDirectory dump
+ writeFile (dump Posix.</> "old.p") "stale"
+ withEnvironment "XDG_CACHE_HOME" cacheRoot do
+ plan <- expectRightIO
+ (Store.planStore Store.DefaultStore)
+ Store.withStoreLease plan \lease -> do
+ result <- Output.planVerificationOutputs
+ (Store.storeLeasePath lease)
+ (Just dump)
+ Nothing
+ case result of
+ Left Output.DumpDestinationNotEmpty{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected nonempty result: " <> show other)
+ Right _ ->
+ assertFailure "nonempty dump was accepted"
+ assertBool "preflight did not create the default store"
+ . not
+ =<< Directory.doesPathExist
+ (cacheRoot Posix.</> "felix")
+
+rejectsStoreCollisions :: Assertion
+rejectsStoreCollisions =
+ Temp.withSystemTempDirectory "felix-output-store-collision" \root -> do
+ let dump = root Posix.</> "dump"
+ persistentStore = dump Posix.</> "store.sqlite"
+ Directory.createDirectory dump
+ persistentPlan <- expectRightIO
+ (Store.planStore
+ (Store.ExplicitStore persistentStore))
+ Store.withStoreLease persistentPlan \lease -> do
+ result <- Output.planVerificationOutputs
+ (Store.storeLeasePath lease)
+ (Just dump)
+ Nothing
+ expectCollision result
+
+ freshPlan <- expectRightIO
+ (Store.planStore Store.FreshTemporaryStore)
+ Store.withStoreLease freshPlan \lease -> do
+ let freshParent =
+ Posix.takeDirectory
+ (Store.storePathFilePath
+ (Store.storeLeasePath lease))
+ result <- Output.planVerificationOutputs
+ (Store.storeLeasePath lease)
+ (Just freshParent)
+ Nothing
+ expectCollision result
+
+ relative <- expectRight (safeRelativePath "page.html")
+ let htmlRoot = root Posix.</> "html"
+ htmlStore = htmlRoot Posix.</> "page.html"
+ Directory.createDirectory htmlRoot
+ htmlPlan <- expectRightIO
+ (Store.planStore
+ (Store.ExplicitStore htmlStore))
+ Store.withStoreLease htmlPlan \lease -> do
+ result <- Output.planVerificationOutputs
+ (Store.storeLeasePath lease)
+ Nothing
+ (Just (htmlRoot, [relative]))
+ expectCollision result
+
+rejectsDumpHtmlCollisions :: Assertion
+rejectsDumpHtmlCollisions =
+ Temp.withSystemTempDirectory "felix-output-cross-collision" \root -> do
+ let storeParent = root Posix.</> "store"
+ storeFile = storeParent Posix.</> "store.sqlite"
+ htmlRoot = root Posix.</> "html"
+ Directory.createDirectory storeParent
+ Directory.createDirectory htmlRoot
+ relative <- expectRight (safeRelativePath "nested/page.html")
+ plan <- expectRightIO
+ (Store.planStore
+ (Store.ExplicitStore storeFile))
+ Store.withStoreLease plan \lease -> do
+ result <- Output.planVerificationOutputs
+ (Store.storeLeasePath lease)
+ (Just htmlRoot)
+ (Just (htmlRoot, [relative]))
+ expectCollision result
+
+reservesRollbackJournal :: Assertion
+reservesRollbackJournal =
+ Temp.withSystemTempDirectory "felix-output-journal" \root -> do
+ let storeParent = root Posix.</> "store"
+ storeFile = storeParent Posix.</> "store.sqlite"
+ journal = storeFile <> "-journal"
+ Directory.createDirectory storeParent
+ plan <- expectRightIO
+ (Store.planStore
+ (Store.ExplicitStore storeFile))
+ Store.withStoreLease plan \lease -> do
+ result <- Output.planVerificationOutputs
+ (Store.storeLeasePath lease)
+ (Just journal)
+ Nothing
+ case result of
+ Left
+ (Output.CollidingOutputNamespaces
+ (Output.OutputNamespaceCollision
+ Output.StoreJournalOutputNamespace
+ reserved
+ Output.DumpOutputNamespace
+ requested :| [])) -> do
+ assertEqual "reserved journal" journal reserved
+ assertEqual "requested dump" journal requested
+ Left other ->
+ assertFailure
+ ("unexpected journal collision: " <> show other)
+ Right _ ->
+ assertFailure "store rollback journal was not reserved"
+
+expectCollision
+ :: Either Output.OutputPlanError Output.VerificationOutputPlan
+ -> Assertion
+expectCollision = \case
+ Left Output.CollidingOutputNamespaces{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected output-plan result: " <> show other)
+ Right _ ->
+ assertFailure "colliding output namespaces were accepted"
+
+expectOutputRight
+ :: Either Output.OutputPlanError Output.VerificationOutputPlan
+ -> IO Output.VerificationOutputPlan
+expectOutputRight = \case
+ Left failure ->
+ assertFailure (show failure) >> fail "unreachable"
+ Right plan ->
+ pure plan
+
+expectRight :: Show failure => Either failure value -> IO value
+expectRight = \case
+ Left failure ->
+ assertFailure (show failure) >> fail "unreachable"
+ Right value ->
+ pure value
+
+expectRightIO
+ :: Show failure
+ => IO (Either failure value)
+ -> IO value
+expectRightIO action =
+ expectRight =<< action
+
+withEnvironment
+ :: String
+ -> String
+ -> IO value
+ -> IO value
+withEnvironment name value action =
+ Exception.bracket
+ (Environment.lookupEnv name)
+ restore
+ \_previous -> do
+ Environment.setEnv name value
+ action
+ where
+ restore = \case
+ Nothing ->
+ Environment.unsetEnv name
+ Just previous ->
+ Environment.setEnv name previous
diff --git a/source/Felix/Test/Unit/Provers.hs b/source/Felix/Test/Unit/Provers.hs
new file mode 100644
index 0000000..f1e3cc1
--- /dev/null
+++ b/source/Felix/Test/Unit/Provers.hs
@@ -0,0 +1,1159 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.Provers (unitTests) where
+
+import Base hiding (Empty)
+import Felix.Checking.Backend.Problem
+import Felix.Checking.Core
+import Felix.Provers
+
+import Control.Concurrent
+ ( newEmptyMVar
+ , putMVar
+ , takeMVar
+ , threadDelay
+ )
+import Control.Exception (bracket)
+import Control.Exception qualified as Exception
+import Control.Monad (when)
+import Data.IORef
+ ( atomicModifyIORef'
+ , newIORef
+ , readIORef
+ , writeIORef
+ )
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+import Data.Text.IO qualified as Text
+import Data.Vector qualified as Vector
+import Felix.Report.Location (Location(..))
+import System.Directory qualified as Directory
+import System.Exit (ExitCode(..))
+import System.FilePath.Posix ((</>))
+import System.Posix.Signals
+ ( nullSignal
+ , sigTERM
+ , signalProcess
+ )
+import System.Posix.Types (ProcessID)
+import System.Timeout qualified as Timeout
+import Test.Tasty
+import Test.Tasty.HUnit
+import Text.Read (readMaybe)
+import Text.Megaparsec (parseMaybe)
+import UnliftIO.Async (cancel, mapConcurrently, withAsync)
+import UnliftIO.Async qualified as Async
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Provers"
+ [ vampireStatusParserTests
+ , vampireClassifierTests
+ , jobsSelectionTests
+ , slowAtpReportTests
+ , vampireExecutorTests
+ , vampireProcessTests
+ ]
+
+jobsSelectionTests :: TestTree
+jobsSelectionTests =
+ testGroup "effective jobs"
+ [ testCase "uses a positive override exactly" do
+ detectorCalled <- newIORef False
+ selected <- selectEffectiveJobs
+ (effectiveJobs 3)
+ (writeIORef detectorCalled True >> pure 99)
+ selected `shouldBe` positiveJobs 3
+ readIORef detectorCalled >>= (`shouldBe` False)
+ , testCase "rounds automatic jobs to one third of detected processors" do
+ for_
+ [(8, 3), (16, 5), (24, 8), (32, 11)]
+ \(detected, expected) -> do
+ selected <- selectEffectiveJobs Nothing (pure detected)
+ selected `shouldBe` positiveJobs expected
+ , testCase "falls back to one after bad detection" do
+ nonPositive <- selectEffectiveJobs Nothing (pure 0)
+ nonPositive `shouldBe` positiveJobs 1
+ failed <- selectEffectiveJobs Nothing
+ (Exception.throwIO (userError "processor detection failed"))
+ failed `shouldBe` positiveJobs 1
+ ]
+
+slowAtpReportTests :: TestTree
+slowAtpReportTests =
+ testGroup "slow ATP report"
+ [ testCase "applies the threshold and retains the twelve slowest" do
+ prepared <- preparedTypedTask 0
+ let requestId =
+ preparedVerificationRequestId
+ (preparedTypedProverRequest prepared)
+ task nanoseconds position =
+ SlowAtpTask
+ { slowAtpDuration =
+ atpDurationFromNanoseconds nanoseconds
+ , slowAtpOutcome = SlowAtpAccepted
+ , slowAtpPosition = workPosition 1 position
+ , slowAtpLocation = testLocation
+ , slowAtpRequestId = requestId
+ }
+ report = slowAtpReportFromTasks
+ ( task 4999999999 0
+ : [ task (5000000000 + fromIntegral position) position
+ | position <- [0..13]
+ ]
+ )
+ slowAtpQualifyingTaskCount report `shouldBe` 14
+ length (slowAtpTasks report) `shouldBe` 12
+ slowAtpOmittedTaskCount report `shouldBe` 2
+ atpDurationNanoseconds
+ (slowAtpDuration
+ (fromMaybe
+ (error "slow report unexpectedly empty")
+ (listToMaybe (slowAtpTasks report))))
+ `shouldBe` 5000000013
+ , testCase "keeps earlier source positions on equal durations" do
+ prepared <- preparedTypedTask 0
+ let requestId =
+ preparedVerificationRequestId
+ (preparedTypedProverRequest prepared)
+ task position =
+ SlowAtpTask
+ { slowAtpDuration =
+ atpDurationFromNanoseconds 5000000000
+ , slowAtpOutcome = SlowAtpAccepted
+ , slowAtpPosition = workPosition 1 position
+ , slowAtpLocation = testLocation
+ , slowAtpRequestId = requestId
+ }
+ report = slowAtpReportFromTasks (task <$> [1..13])
+ (workPositionLocalRequestOrdinal . slowAtpPosition
+ <$> slowAtpTasks report)
+ `shouldBe` [1..12]
+ ]
+
+vampireExecutorTests :: TestTree
+vampireExecutorTests =
+ testGroup "bounded Vampire executor"
+ [ testCase "records completed qualifying tasks with runtime context" do
+ prepared <- preparedTypedTask 0
+ clock <- scriptedClock [10, 5000000010]
+ let position = workPosition 2 3
+ withFakeVampire
+ [ "cat >/dev/null"
+ , "printf '%s\n' '% SZS status Theorem for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutorUsingClock
+ clock
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ handle <- submitVampireRequest
+ owner
+ position
+ testLocation
+ (preparedTypedProverRequest prepared)
+ awaitVampireRequest handle
+ >>= assertAcceptedRequest prepared
+ report <- vampireExecutorSlowAtpReport executor
+ slowAtpQualifyingTaskCount report `shouldBe` 1
+ case slowAtpTasks report of
+ [task] -> do
+ atpDurationNanoseconds
+ (slowAtpDuration task)
+ `shouldBe` 5000000000
+ slowAtpOutcome task `shouldBe` SlowAtpAccepted
+ slowAtpPosition task `shouldBe` position
+ slowAtpLocation task `shouldBe` testLocation
+ slowAtpRequestId task `shouldBe`
+ preparedVerificationRequestId
+ (preparedTypedProverRequest prepared)
+ tasks ->
+ assertFailure
+ ("unexpected slow-task report: "
+ <> show tasks)
+ , testCase "does not report a cancelled partial task" do
+ prepared <- preparedTypedTask 0
+ clock <- scriptedClock [0, 6000000000]
+ withProcessGroupFake
+ defaultTimeLimit
+ []
+ \pidFile vampireCommand ->
+ withVampireExecutorUsingClock
+ clock
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ handle <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ processIds <- waitForProcessIds pidFile
+ cancelVampireRequest handle
+ report <- vampireExecutorSlowAtpReport executor
+ slowAtpQualifyingTaskCount report `shouldBe` 0
+ assertBool "cancelled report is empty"
+ (null (slowAtpTasks report))
+ assertProcessesGone processIds
+ , testCase "opaque handles complete out of submission order" do
+ prepared <- preparedTypedTask 0
+ firstStarted <- newEmptyMVar
+ releaseFirst <- newEmptyMVar
+ withFakeVampire
+ [ "cat >/dev/null"
+ , "printf '%s\n' '% SZS status Theorem for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 2)
+ vampireCommand
+ (\position _request ->
+ when
+ (workPositionLocalRequestOrdinal position == 1)
+ (putMVar firstStarted () >> takeMVar releaseFirst))
+ \executor -> withVampireRequestOwner executor \owner -> do
+ first <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ takeMVar firstStarted
+ second <- submitVampireRequest
+ owner
+ (workPosition 1 2)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ secondCompletion <- awaitVampireRequest second
+ assertAcceptedRequest prepared secondCompletion
+ putMVar releaseFirst ()
+ firstCompletion <- awaitVampireRequest first
+ assertAcceptedRequest prepared firstCompletion
+ , testCase "validates request identity before a rejection" do
+ submitted <- preparedTypedTask 0
+ mismatched <- preparedTypedTask 1
+ withFakeVampire
+ [ "cat >/dev/null"
+ , "printf '%s\n' '% SZS status CounterSatisfiable for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ handle <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ testLocation
+ (preparedTypedProverRequest submitted)
+ outcome <- Exception.try
+ (awaitPreparedVampireRequest
+ (preparedTypedProverRequest mismatched)
+ handle)
+ case outcome of
+ Left (failure :: VampireExecutorFault) ->
+ assertBool
+ "request mismatch is an integrity fault"
+ ("wrong request id"
+ `Text.isInfixOf`
+ Text.pack (show failure))
+ Right answer ->
+ assertFailure
+ ("mismatched rejection was accepted: "
+ <> show answer)
+ , testCase "runs requests through the bounded worker pool" do
+ prepared <- preparedTypedTask 0
+ withFakeVampire
+ [ "previous=''"
+ , "found=0"
+ , "for argument in \"$@\"; do"
+ , " if [ \"$previous\" = '--cores' ]; then"
+ , " [ \"$argument\" = '2' ] || exit 17"
+ , " found=1"
+ , " fi"
+ , " previous=$argument"
+ , "done"
+ , "[ \"$found\" = '1' ] || exit 18"
+ , "cat >/dev/null"
+ , "sleep 0.2"
+ , "printf '%s\n' '% SZS status Theorem for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 2)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ answers <- mapConcurrently
+ (\ordinal ->
+ runPreparedTypedProverWithExecutor
+ owner
+ (workPosition 1 ordinal)
+ testLocation
+ prepared)
+ [1..4]
+ traverse_ assertProved answers
+ , testCase "propagates observer failure to the submitter" do
+ prepared <- preparedTypedTask 0
+ withFakeVampire
+ [ "cat >/dev/null"
+ , "printf '%s\n' '% SZS status Theorem for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request ->
+ Exception.throwIO
+ (userError "observer failed"))
+ \executor -> withVampireRequestOwner executor \owner -> do
+ result <- Exception.try
+ (runPreparedTypedProverWithExecutor
+ owner
+ (workPosition 1 1)
+ testLocation
+ prepared)
+ case result of
+ Left (failure :: VampireExecutorFault) ->
+ assertBool
+ "global executor fault"
+ ("observer failed"
+ `Text.isInfixOf`
+ Text.pack (show failure))
+ Right answer ->
+ assertFailure
+ ("observer failure was lost: "
+ <> show answer)
+ , testCase "cancels queued and running jobs independently" do
+ prepared <- preparedTypedTask 0
+ withProcessGroupFake
+ defaultTimeLimit
+ []
+ \pidFile vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner ->
+ withAsync
+ (runPreparedTypedProverWithExecutor
+ owner
+ (workPosition 1 1)
+ testLocation
+ prepared)
+ \running -> do
+ processIds <- waitForProcessIds pidFile
+ queuedSubmitted <- newEmptyMVar
+ withAsync
+ (do
+ handle <- submitVampireRequest
+ owner
+ (workPosition 2 1)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ putMVar queuedSubmitted ()
+ awaitPreparedVampireRequest
+ (preparedTypedProverRequest prepared)
+ handle)
+ \queued -> do
+ takeMVar queuedSubmitted
+ cancel queued
+ cancel running
+ assertProcessesGone processIds
+ , testCase "explicit cancellation completes queued and running handles" do
+ prepared <- preparedTypedTask 0
+ withProcessGroupFake
+ defaultTimeLimit
+ []
+ \pidFile vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ running <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ processIds <- waitForProcessIds pidFile
+ queued <- submitVampireRequest
+ owner
+ (workPosition 1 2)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ cancelVampireRequest queued
+ awaitVampireRequest queued
+ >>= assertCancelled prepared
+ cancelVampireRequest running
+ awaitVampireRequest running
+ >>= assertCancelled prepared
+ assertProcessesGone processIds
+ , testCase "structured shutdown wakes waiter and full-queue submitter" do
+ prepared <- preparedTypedTask 0
+ withProcessGroupFake
+ defaultTimeLimit
+ []
+ \pidFile vampireCommand -> do
+ (waiter, blockedSubmit, processIds) <-
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor ->
+ withVampireRequestOwner executor \owner -> do
+ running <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ processIds <- waitForProcessIds pidFile
+ _queuedOne <- submitVampireRequest
+ owner
+ (workPosition 1 2)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ _queuedTwo <- submitVampireRequest
+ owner
+ (workPosition 1 3)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ waiter <- Async.async
+ (awaitVampireRequest running)
+ submitStarted <- newEmptyMVar
+ blockedSubmit <- Async.async do
+ putMVar submitStarted ()
+ submitVampireRequest
+ owner
+ (workPosition 1 4)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ takeMVar submitStarted
+ pure (waiter, blockedSubmit, processIds)
+ Async.waitCatch waiter >>= \case
+ Right completion ->
+ assertCancelled prepared completion
+ Left failure ->
+ assertFailure
+ ("shutdown waiter failed: " <> show failure)
+ Async.waitCatch blockedSubmit >>= \case
+ Left failure ->
+ assertBool
+ "backpressured submit observes owner shutdown"
+ ("VampireRequestOwnerClosed"
+ `Text.isInfixOf`
+ Text.pack (show failure))
+ Right _handle ->
+ assertFailure
+ "backpressured submit survived structured shutdown"
+ assertProcessesGone processIds
+ , testCase "worker fault wakes a waiter and a full-queue submitter" do
+ prepared <- preparedTypedTask 0
+ observerEntered <- newEmptyMVar
+ failObserver <- newEmptyMVar
+ withFakeVampire
+ [ "cat >/dev/null"
+ , "printf '%s\n' '% SZS status Theorem for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\position _request ->
+ when
+ (workPositionLocalRequestOrdinal position == 1)
+ (putMVar observerEntered ()
+ >> takeMVar failObserver
+ >> Exception.throwIO
+ (userError "fatal observer fault")))
+ \executor -> withVampireRequestOwner executor \owner -> do
+ first <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ takeMVar observerEntered
+ _second <- submitVampireRequest
+ owner
+ (workPosition 1 2)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ _third <- submitVampireRequest
+ owner
+ (workPosition 1 3)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ withAsync
+ (submitVampireRequest
+ owner
+ (workPosition 1 4)
+ testLocation
+ (preparedTypedProverRequest prepared))
+ \blockedSubmit -> do
+ putMVar failObserver ()
+ awaitFault (awaitVampireRequest first)
+ Async.waitCatch blockedSubmit >>= \case
+ Left failure ->
+ assertExecutorFault failure
+ Right _handle ->
+ assertFailure
+ "full-queue submission survived executor fault"
+ , testCase "declared launch failure remains request-local" do
+ prepared <- preparedTypedTask 0
+ let missing = vampire
+ "/definitely/missing/felix-vampire"
+ defaultTimeLimit
+ defaultMemoryLimit
+ withVampireExecutor
+ (positiveJobs 1)
+ missing
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ handle <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ completion <- awaitVampireRequest handle
+ assertCompletionRequest prepared completion
+ case vampireCompletionTerminal completion of
+ VampireProcessFailed ProverLaunchFailed{} -> pure ()
+ terminal ->
+ assertFailure
+ ("expected a local launch failure, got "
+ <> show terminal)
+ , testCase "protocol failure is distinct from ATP rejection" do
+ prepared <- preparedTypedTask 0
+ withFakeVampire
+ [ "cat >/dev/null"
+ , "printf '%s\n' 'completed without an SZS status'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ handle <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ completion <- awaitVampireRequest handle
+ assertCompletionRequest prepared completion
+ case vampireCompletionTerminal completion of
+ VampireProtocolFailed{} -> pure ()
+ terminal ->
+ assertFailure
+ ("expected a protocol terminal, got "
+ <> show terminal)
+ , testCase "completed rejection diagnostics are compact" do
+ prepared <- preparedTypedTask 0
+ let headMarker :: Text
+ headMarker = "HEAD-MARKER"
+ tailMarker :: Text
+ tailMarker = "TAIL-MARKER"
+ status :: Text
+ status = "% SZS status CounterSatisfiable for fake"
+ originalByteCount =
+ Text.length headMarker
+ + 1048576
+ + Text.length tailMarker + 1
+ + Text.length status + 1
+ withFakeVampire
+ [ "printf '%s' 'HEAD-MARKER'"
+ , "head -c 1048576 /dev/zero"
+ , "printf '%s\n' 'TAIL-MARKER'"
+ , "printf '%s\n' '% SZS status CounterSatisfiable for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ handle <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ testLocation
+ (preparedTypedProverRequest prepared)
+ completion <- awaitVampireRequest handle
+ case renderVampireTerminalDiagnostic
+ (vampireCompletionTerminal completion) of
+ Just diagnostic -> do
+ assertBool
+ "retained diagnostic is bounded"
+ (Text.length diagnostic < 70000)
+ assertBool
+ "truncation is reported"
+ ("retained first and last 16 KiB"
+ `Text.isInfixOf` diagnostic)
+ assertBool
+ "original byte count is reported"
+ (("of "
+ <> Text.pack
+ (show originalByteCount)
+ <> " bytes)")
+ `Text.isInfixOf` diagnostic)
+ assertBool
+ "diagnostic head is retained"
+ (headMarker `Text.isInfixOf` diagnostic)
+ assertBool
+ "diagnostic tail is retained"
+ (tailMarker `Text.isInfixOf` diagnostic)
+ Nothing ->
+ assertFailure "expected a rejected terminal"
+ ]
+
+positiveJobs :: Int -> EffectiveJobs
+positiveJobs amount =
+ fromMaybe
+ (error "test requested a non-positive job count")
+ (effectiveJobs amount)
+
+scriptedClock :: [Word64] -> IO (IO Word64)
+scriptedClock ticks = do
+ remaining <- newIORef ticks
+ pure
+ (atomicModifyIORef' remaining \case
+ next : rest -> (rest, next)
+ [] -> error "test monotonic clock exhausted")
+
+testLocation :: Location
+testLocation = Location maxBound
+
+vampireStatusParserTests :: TestTree
+vampireStatusParserTests =
+ testGroup "Vampire status parser"
+ [ testCase "parses canonical status lines" do
+ parseMaybe
+ vampireStatusParser
+ "% SZS status ContradictoryAxioms for 2260"
+ `shouldBe` Just StatusContradictoryAxioms
+ , testCase "parses worker-prefixed status lines" do
+ parseMaybe
+ vampireStatusParser
+ "% (2581105)SZS status Timeout for "
+ `shouldBe` Just StatusTimeout
+ , testCase "parses ResourceOut status" do
+ parseMaybe
+ vampireStatusParser
+ "% SZS status ResourceOut for 2260"
+ `shouldBe` Just StatusResourceOut
+ , testCase "retains unsupported status values" do
+ parseMaybe
+ vampireStatusParser
+ "% SZS status AlienResult for 2260"
+ `shouldBe` Just (UnsupportedStatus "AlienResult")
+ ]
+
+vampireClassifierTests :: TestTree
+vampireClassifierTests =
+ testGroup "Vampire completed transcript classifier"
+ [ testCase "maps each terminal status in both task modes" do
+ classify DirectTask [StatusTheorem]
+ `shouldBe` Right Proved
+ classify DirectTask [StatusCounterSatisfiable]
+ `shouldBe` Right Counterexample
+ classify DirectTask [StatusContradictoryAxioms]
+ `shouldBe` Right ContradictoryInput
+ classify IndirectTask [StatusContradictoryAxioms]
+ `shouldBe` Right Proved
+ , testCase "maps every resource status to indeterminate" do
+ for_ indeterminateStatuses \status ->
+ classify DirectTask [status]
+ `shouldBe` Right Indeterminate
+ , testCase "lets a unique terminal outcome override resource statuses" do
+ for_ indeterminateStatuses \status ->
+ classify DirectTask [status, StatusTheorem]
+ `shouldBe` Right Proved
+ , testCase "accepts duplicate and equivalent terminal statuses" do
+ classify DirectTask [StatusTheorem, StatusTheorem]
+ `shouldBe` Right Proved
+ classify
+ IndirectTask
+ [StatusTheorem, StatusContradictoryAxioms]
+ `shouldBe` Right Proved
+ , testCase "rejects every pair of different terminal outcomes" do
+ for_ conflictingTerminalCases
+ \(mode, statuses, outcomes) ->
+ classify mode statuses
+ `shouldBe`
+ Left (ConflictingTerminalOutcomes outcomes)
+ , testCase "is independent of status order" do
+ for_ orderCases \(mode, statuses) ->
+ classify mode statuses
+ `shouldBe` classify mode (reverse statuses)
+ , testCase "rejects unsupported status values" do
+ classify
+ DirectTask
+ [StatusTheorem, UnsupportedStatus "AlienResult"]
+ `shouldBe`
+ Left
+ (UnsupportedVampireStatuses
+ (Set.singleton "AlienResult"))
+ , testCase "rejects a successful exit without an outcome" do
+ classify DirectTask []
+ `shouldBe` Left MissingVampireOutcome
+ , testCase "rejects every status after a nonzero exit" do
+ classifyVampireProtocol
+ DirectTask
+ (ExitFailure 7)
+ [StatusTheorem]
+ `shouldBe`
+ Left (UnsuccessfulVampireExit (ExitFailure 7))
+ ]
+
+vampireProcessTests :: TestTree
+vampireProcessTests =
+ testGroup "Vampire process boundary"
+ [ testCase "classifies statuses from both completed streams" do
+ answer <- runFakeVampire
+ [ "printf '%s\\n' '% SZS status Timeout for fake'"
+ , "printf '%s\\n' '% SZS status Theorem for fake' >&2"
+ , "exit 0"
+ ]
+ assertProved answer
+ , testCase "rejects a split-stream terminal conflict" do
+ answer <- runFakeVampire
+ [ "printf '%s\\n' '% SZS status Theorem for fake'"
+ , "printf '%s\\n' '% SZS status CounterSatisfiable for fake' >&2"
+ , "exit 0"
+ ]
+ assertProtocolError "ConflictingTerminalOutcomes" answer
+ , testCase "rejects a theorem from a nonzero exit" do
+ answer <- runFakeVampire
+ [ "printf '%s\\n' '% SZS status Theorem for fake'"
+ , "exit 7"
+ ]
+ assertProtocolError "ExitFailure 7" answer
+ , testCase "rejects malformed UTF-8 output" do
+ answer <- runFakeVampire
+ [ "printf '\\377'"
+ , "exit 0"
+ ]
+ case answer of
+ Left
+ (ProverOutputMalformedUtf8
+ _
+ ProverOutputStdout
+ _) ->
+ pure ()
+ result ->
+ assertFailure
+ ("expected malformed stdout, got " <> show result)
+ , testCase "returns a broken stdin pipe" do
+ prepared <- preparedTypedTask 20000
+ result <- withFakeVampire
+ [ "exec 0<&-"
+ , "sleep 1"
+ ]
+ \vampireCommand ->
+ runPreparedTypedProver vampireCommand prepared
+ case result of
+ Left (ProverCommunicationFailed _ ProverStdin _) ->
+ pure ()
+ processResult ->
+ assertFailure
+ ("expected a communication failure, got "
+ <> show processResult)
+ , testCase "drains output while feeding prover input" do
+ prepared <- preparedTypedTask 20000
+ guardedAnswer <- Timeout.timeout
+ 30000000
+ (withFakeVampire
+ [ "head -c 1048576 /dev/zero &"
+ , "head -c 1048576 /dev/zero >&2 &"
+ , "wait"
+ , "printf '\\n'"
+ , "printf '\\n' >&2"
+ , "cat >/dev/null"
+ , "printf '%s\\n' '% SZS status Theorem for fake'"
+ , "exit 0"
+ ]
+ \vampireCommand -> do
+ runPreparedTypedProver vampireCommand prepared)
+ case guardedAnswer of
+ Nothing ->
+ assertFailure "prover communication did not finish"
+ Just answer ->
+ assertProved answer
+ , testCase "reports signal termination separately" do
+ prepared <- preparedTypedTask 0
+ result <- withFakeVampire
+ [ "kill -TERM $$"
+ ]
+ \vampireCommand ->
+ runPreparedTypedProver vampireCommand prepared
+ case result of
+ Left
+ (ProverTerminatedBySignal
+ _
+ signalNumber
+ _) ->
+ assertEqual
+ "termination signal"
+ (fromIntegral sigTERM)
+ signalNumber
+ processResult ->
+ assertFailure
+ ("expected signal termination, got "
+ <> show processResult)
+ , testCase "deadline terminates and reaps the process group" do
+ prepared <- preparedTypedTask 0
+ withProcessGroupFake
+ (Seconds 0)
+ []
+ \pidFile vampireCommand -> do
+ result <-
+ runPreparedTypedProver vampireCommand prepared
+ assertTimedOut result
+ processIds <- readProcessIds pidFile
+ assertProcessesGone processIds
+ , testCase "output exhaustion terminates the process group" do
+ prepared <- preparedTypedTask 0
+ withProcessGroupFake
+ defaultTimeLimit
+ [ "head -c 33554432 /dev/zero"
+ ]
+ \pidFile vampireCommand -> do
+ result <-
+ runPreparedTypedProver vampireCommand prepared
+ case result of
+ Left
+ (ProverOutputLimitExceeded
+ _
+ ProverOutputStdout
+ _) ->
+ pure ()
+ processResult ->
+ assertFailure
+ ("expected stdout limit exhaustion, got "
+ <> show processResult)
+ processIds <- readProcessIds pidFile
+ assertProcessesGone processIds
+ , testCase "cancellation terminates and reaps the process group" do
+ prepared <- preparedTypedTask 0
+ withProcessGroupFake
+ defaultTimeLimit
+ []
+ \pidFile vampireCommand ->
+ withAsync
+ (runPreparedTypedProver vampireCommand prepared)
+ \worker -> do
+ processIds <- waitForProcessIds pidFile
+ cancel worker
+ assertProcessesGone processIds
+ ]
+
+classify
+ :: VampireTaskMode
+ -> [VampireStatus]
+ -> Either VampireProtocolError CanonicalAtpOutcome
+classify mode =
+ classifyVampireProtocol mode ExitSuccess
+
+indeterminateStatuses :: [VampireStatus]
+indeterminateStatuses =
+ [ StatusTimeout
+ , StatusResourceOut
+ , StatusGaveUp
+ , StatusUnknown
+ ]
+
+conflictingTerminalCases
+ :: [(VampireTaskMode, [VampireStatus], Set CanonicalAtpOutcome)]
+conflictingTerminalCases =
+ [ ( DirectTask
+ , [StatusTheorem, StatusCounterSatisfiable]
+ , Set.fromList [Proved, Counterexample]
+ )
+ , ( DirectTask
+ , [StatusTheorem, StatusContradictoryAxioms]
+ , Set.fromList [Proved, ContradictoryInput]
+ )
+ , ( DirectTask
+ , [StatusCounterSatisfiable, StatusContradictoryAxioms]
+ , Set.fromList [Counterexample, ContradictoryInput]
+ )
+ , ( IndirectTask
+ , [StatusTheorem, StatusCounterSatisfiable]
+ , Set.fromList [Proved, Counterexample]
+ )
+ , ( IndirectTask
+ , [StatusCounterSatisfiable, StatusContradictoryAxioms]
+ , Set.fromList [Proved, Counterexample]
+ )
+ ]
+
+orderCases :: [(VampireTaskMode, [VampireStatus])]
+orderCases =
+ [ (DirectTask, StatusTheorem : indeterminateStatuses)
+ , (DirectTask, [StatusTheorem, StatusCounterSatisfiable])
+ , (IndirectTask, [StatusTheorem, StatusContradictoryAxioms])
+ , (DirectTask, [UnsupportedStatus "B", UnsupportedStatus "A"])
+ ]
+
+assertProved
+ :: Either ProverProcessError ProverAnswer
+ -> Assertion
+assertProved = \case
+ Right Yes ->
+ pure ()
+ answer ->
+ assertFailure ("expected a proof, got " <> show answer)
+
+assertAcceptedRequest
+ :: PreparedTypedProverTask ref local origin global
+ -> VampireCompletion
+ -> Assertion
+assertAcceptedRequest prepared completion = do
+ assertCompletionRequest prepared completion
+ case vampireCompletionTerminal completion of
+ VampireAccepted -> pure ()
+ terminal ->
+ assertFailure ("expected an accepted terminal, got " <> show terminal)
+
+assertCompletionRequest
+ :: PreparedTypedProverTask ref local origin global
+ -> VampireCompletion
+ -> Assertion
+assertCompletionRequest prepared completion =
+ vampireCompletionRequestId completion
+ `shouldBe`
+ preparedVerificationRequestId
+ (preparedTypedProverRequest prepared)
+
+assertCancelled
+ :: PreparedTypedProverTask ref local origin global
+ -> VampireCompletion
+ -> Assertion
+assertCancelled prepared completion = do
+ assertCompletionRequest prepared completion
+ vampireCompletionTerminal completion `shouldBe` VampireCancelled
+
+awaitFault :: IO value -> Assertion
+awaitFault action = do
+ result <- Exception.try action
+ case result of
+ Left failure -> assertExecutorFault failure
+ Right _value -> assertFailure "expected a global executor fault"
+
+assertExecutorFault :: Exception.SomeException -> Assertion
+assertExecutorFault failure =
+ case Exception.fromException failure :: Maybe VampireExecutorFault of
+ Just _fault -> pure ()
+ Nothing ->
+ assertFailure
+ ("expected VampireExecutorFault, got " <> show failure)
+
+assertProtocolError
+ :: Text
+ -> Either ProverProcessError ProverAnswer
+ -> Assertion
+assertProtocolError expected = \case
+ Right (Error _label diagnostic) ->
+ assertBool
+ ( "expected protocol error containing "
+ <> show expected
+ <> ", got "
+ <> show diagnostic
+ )
+ (expected `Text.isInfixOf` diagnostic)
+ answer ->
+ assertFailure ("expected a protocol error, got " <> show answer)
+
+assertTimedOut
+ :: Either ProverProcessError a
+ -> Assertion
+assertTimedOut = \case
+ Left ProverTimedOut{} ->
+ pure ()
+ result ->
+ assertFailure ("expected prover timeout, got " <> showResult result)
+ where
+ showResult = \case
+ Left err ->
+ show err
+ Right _ ->
+ "successful process result"
+
+withProcessGroupFake
+ :: TimeLimit
+ -> [String]
+ -> (FilePath -> Vampire -> IO a)
+ -> IO a
+withProcessGroupFake timeLimit body action =
+ withFakeVampireIn
+ (\temp ->
+ let pidFile = temp </> "process-ids"
+ in [ "trap '' TERM"
+ , "sleep 60 &"
+ , "printf '%s %s\\n' \"$$\" \"$!\" > " <> pidFile
+ ]
+ <> body
+ <> ["wait"])
+ timeLimit
+ \temp ->
+ action (temp </> "process-ids")
+
+readProcessIds :: FilePath -> IO [ProcessID]
+readProcessIds path = do
+ contents <- Text.readFile path
+ case traverse
+ (readMaybe . Text.unpack)
+ (Text.words contents) of
+ Just processIds@[_leader, _descendant] ->
+ pure processIds
+ _ ->
+ assertFailure
+ ("expected leader and descendant process ids, got "
+ <> show contents)
+
+waitForProcessIds :: FilePath -> IO [ProcessID]
+waitForProcessIds path = do
+ guarded <- Timeout.timeout 10000000 loop
+ case guarded of
+ Just processIds ->
+ pure processIds
+ Nothing ->
+ assertFailure "fake prover did not publish its process ids"
+ where
+ loop = do
+ exists <- Directory.doesFileExist path
+ if exists
+ then readProcessIds path
+ else do
+ threadDelay 10000
+ loop
+
+assertProcessesGone :: [ProcessID] -> Assertion
+assertProcessesGone processIds = do
+ guarded <- Timeout.timeout 10000000 loop
+ case guarded of
+ Just () ->
+ pure ()
+ Nothing ->
+ assertFailure
+ ("supervisor left processes running: "
+ <> show processIds)
+ where
+ loop = do
+ alive <- traverse processIsAlive processIds
+ if or alive
+ then do
+ threadDelay 10000
+ loop
+ else pure ()
+
+processIsAlive :: ProcessID -> IO Bool
+processIsAlive processId =
+ (signalProcess nullSignal processId >> pure True)
+ `Exception.catch` \(err :: Exception.IOException) ->
+ if isDoesNotExistError err
+ then pure False
+ else throwIO err
+
+runFakeVampire
+ :: [String]
+ -> IO (Either ProverProcessError ProverAnswer)
+runFakeVampire scriptLines =
+ withFakeVampire
+ (["cat >/dev/null"] <> scriptLines)
+ \vampireCommand -> do
+ prepared <- preparedTypedTask 0
+ runPreparedTypedProver vampireCommand prepared
+
+withFakeVampire
+ :: [String]
+ -> (Vampire -> IO a)
+ -> IO a
+withFakeVampire scriptLines action =
+ withFakeVampireIn
+ (const scriptLines)
+ defaultTimeLimit
+ (const action)
+
+withFakeVampireIn
+ :: (FilePath -> [String])
+ -> TimeLimit
+ -> (FilePath -> Vampire -> IO a)
+ -> IO a
+withFakeVampireIn makeScript timeLimit action =
+ withTemporaryDirectory "felix-fake-vampire" \temp -> do
+ let executablePath = temp </> "vampire"
+ writeFile executablePath
+ (unlines
+ ( [ "#!/bin/sh"
+ ]
+ <> makeScript temp
+ ))
+ permissions <- Directory.getPermissions executablePath
+ Directory.setPermissions executablePath
+ (Directory.setOwnerExecutable True permissions)
+ action
+ temp
+ (vampire
+ executablePath
+ timeLimit
+ defaultMemoryLimit)
+
+withTemporaryDirectory :: String -> (FilePath -> IO a) -> IO a
+withTemporaryDirectory template =
+ bracket create Directory.removePathForcibly
+ where
+ create = do
+ systemTemp <- Directory.getTemporaryDirectory
+ (path, handle) <- openTempFile systemTemp template
+ hClose handle
+ Directory.removeFile path
+ Directory.createDirectory path
+ pure path
+
+shouldBe :: (Eq a, Show a, HasCallStack) => a -> a -> Assertion
+shouldBe =
+ flip (assertEqual "")
+
+preparedTypedTask
+ :: Int
+ -> IO (PreparedTypedProverTask Int Void Void Void)
+preparedTypedTask factCount = do
+ checked <- expectRight
+ (checkScopedCanonicalCore
+ (const Nothing)
+ []
+ propositionTerm)
+ proposition <- expectRight
+ (supportedProposition Vector.empty checked)
+ capability <- expectRight
+ (classifySupportedProposition (const Nothing) proposition)
+ let facts =
+ Vector.generate
+ factCount
+ (\reference ->
+ typedBackendFact reference proposition capability)
+ problem <- expectRight
+ (planTypedProblem
+ (const Nothing)
+ facts
+ proposition
+ []
+ []
+ FirstOrderLocals
+ ExplicitHigherOrderJustification)
+ expectRight (prepareTypedProverTask DirectTask problem)
+ where
+ propositionTerm =
+ CEq TySet
+ (CIntrinsic Empty)
+ (CIntrinsic Empty)
+
+expectRight :: Show error => Either error value -> IO value
+expectRight = \case
+ Left failure ->
+ assertFailure (show failure) >> fail "unreachable"
+ Right value ->
+ pure value
diff --git a/source/Felix/Test/Unit/Semantic.hs b/source/Felix/Test/Unit/Semantic.hs
new file mode 100644
index 0000000..f62a003
--- /dev/null
+++ b/source/Felix/Test/Unit/Semantic.hs
@@ -0,0 +1,437 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Test.Unit.Semantic (unitTests) where
+
+import Base
+import Felix.Checking.Authority qualified as Authority
+import Felix.Checking.Core qualified as Core
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Checking.Identity qualified as Identity
+import Felix.Checking.Semantic qualified as Semantic
+import Felix.Cache.Codec
+import Felix.Math.Codec
+import Felix.Module
+import Felix.Parsed.Identity
+import Felix.Source
+import Felix.Source.Content
+import Felix.Syntax.Interface qualified as Syntax
+import Felix.Syntax.Abstract qualified as Raw
+
+import Data.ByteString (ByteString)
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Semantic interfaces"
+ [ testCase "separates syntax and semantic Merkle identities"
+ separatesSyntaxAndSemantics
+ , testCase "round-trips closed semantic declaration state"
+ roundTripsSemanticState
+ , testCase "round-trips exact semantic global keys"
+ roundTripsSemanticGlobalKeys
+ , testCase "round-trips canonical structure descriptors"
+ roundTripsStructureDescriptors
+ , testCase "keys exact proof and module inputs"
+ keysExactInputs
+ ]
+
+separatesSyntaxAndSemantics :: Assertion
+separatesSyntaxAndSemantics = do
+ fixture <- makeFixture
+ firstSyntax <- makeSyntax "first"
+ secondSyntax <- makeSyntax "second"
+ assertBool
+ "notation changes syntax identity"
+ ( Syntax.moduleSyntaxAssertedId firstSyntax
+ /= Syntax.moduleSyntaxAssertedId secondSyntax
+ )
+ assertEqual
+ "notation does not enter semantic prefix identity"
+ (fixtureNextPrefix fixture)
+ (Semantic.nextPrefixContextId
+ (fixtureInitialPrefix fixture)
+ (fixtureDelta fixture))
+
+roundTripsSemanticState :: Assertion
+roundTripsSemanticState = do
+ fixture <- makeFixture
+ assertEqual
+ "semantic interface cache round trip"
+ (Right (fixtureInterface fixture))
+ (decodeCache
+ Semantic.getSemanticInterfaceCache
+ (encodeCache
+ (Semantic.putSemanticInterfaceCache
+ (fixtureInterface fixture))))
+ assertEqual
+ "walking-subset environment delta round trip"
+ (Right Semantic.emptySemanticEnvironmentDelta)
+ (decodeCache
+ Semantic.getSemanticEnvironmentDeltaCache
+ (encodeCache
+ (Semantic.putSemanticEnvironmentDeltaCache
+ Semantic.emptySemanticEnvironmentDelta)))
+
+roundTripsSemanticGlobalKeys :: Assertion
+roundTripsSemanticGlobalKeys = do
+ fixture <- makeFixture
+ let unary = Raw.HoleCons (Raw.TokenCons (Raw.Command "f") Raw.End)
+ plural = Raw.HoleCons (Raw.TokenCons (Raw.Word "things") Raw.End)
+ keys =
+ [ Semantic.SemanticLeftAdjective unary
+ , Semantic.SemanticRightAdjective unary
+ , Semantic.SemanticFunctionPhrase unary plural
+ , Semantic.SemanticNoun unary plural
+ , Semantic.SemanticVerb unary plural
+ , Semantic.SemanticRelation
+ (Raw.Command "rel")
+ (Raw.ParameterArity 2)
+ , Semantic.SemanticExpressionFunction unary
+ , Semantic.SemanticPrefixPredicate "Pred" 3
+ ]
+ target =
+ Identity.intrinsicObjectId
+ (fixtureTheory fixture)
+ Core.Empty
+ Core.TySet
+ bindings =
+ List.sortOn Semantic.semanticGlobalBindingKey
+ ( case keys of
+ [] -> []
+ first : rest ->
+ Semantic.semanticGlobalBinding
+ first
+ (Semantic.ContextualTransparentExpansion
+ target
+ (Map.singleton
+ (Raw.StructSymbol "operation")
+ target))
+ : [ Semantic.semanticGlobalBinding
+ key
+ (Semantic.GlobalReference target)
+ | key <- rest
+ ]
+ )
+ delta <- expectRight (Semantic.semanticEnvironmentDelta bindings)
+ assertEqual "binding cache round trip"
+ (Right delta)
+ (decodeCache
+ Semantic.getSemanticEnvironmentDeltaCache
+ (encodeCache
+ (Semantic.putSemanticEnvironmentDeltaCache delta)))
+ case bindings of
+ first : second : _ -> do
+ assertEqual "rejects noncanonical order"
+ (Left Semantic.NonCanonicalSemanticGlobalBindingOrder)
+ (Semantic.semanticEnvironmentDelta
+ (second : first : drop 2 bindings))
+ assertEqual "rejects duplicate key"
+ (Left
+ (Semantic.DuplicateSemanticGlobalKey
+ (Semantic.semanticGlobalBindingKey first)))
+ (Semantic.semanticEnvironmentDelta [first, first])
+ _ -> assertFailure "semantic key fixture is unexpectedly empty"
+
+roundTripsStructureDescriptors :: Assertion
+roundTripsStructureDescriptors = do
+ fixture <- makeFixture
+ let structurePhrase marker word =
+ Semantic.semanticStructurePhrase
+ (Raw.LexicalItemSgPl
+ (Raw.SgPl
+ (Raw.TokenCons (Raw.Word word) Raw.End)
+ (Raw.TokenCons (Raw.Word (word <> "s")) Raw.End))
+ marker)
+ base = structurePhrase "onesorted_structure" "base"
+ child = structurePhrase "ordered_structure" "ordered"
+ object =
+ Identity.intrinsicObjectId
+ (fixtureTheory fixture)
+ Core.Empty
+ Core.TySet
+ operation =
+ Semantic.semanticStructureOperation
+ (Raw.StructSymbol "carrier")
+ object
+ descriptor <- expectRight
+ (Semantic.semanticStructureDescriptor
+ child
+ (Just object)
+ [base]
+ [operation])
+ delta <- expectRight
+ (Semantic.semanticEnvironmentWithStructures [] [descriptor])
+ assertEqual
+ "structure environment cache round trip"
+ (Right delta)
+ (decodeCache
+ Semantic.getSemanticEnvironmentDeltaCache
+ (encodeCache
+ (Semantic.putSemanticEnvironmentDeltaCache delta)))
+ assertEqual
+ "duplicate local operation is rejected"
+ (Left
+ (Semantic.DuplicateSemanticStructureOperation
+ (Raw.StructSymbol "carrier")))
+ (Semantic.semanticStructureDescriptor
+ child
+ (Just object)
+ [base]
+ [operation, operation])
+
+keysExactInputs :: Assertion
+keysExactInputs = do
+ fixture <- makeFixture
+ let authority =
+ Authority.factAuthority
+ (fixtureTheorem fixture)
+ Authority.cleanAuthoritySafety
+ certificate <- expectRight
+ (Authority.validationCertificate
+ authority
+ (Authority.CheckedSourceProof []))
+ let theorem =
+ Identity.theoremId
+ (fixtureTheorem fixture)
+ firstProof =
+ Semantic.proofValidationKey
+ theorem
+ (Semantic.proofSyntaxId "proof-a")
+ (fixtureInitialPrefix fixture)
+ secondProof =
+ Semantic.proofValidationKey
+ theorem
+ (Semantic.proofSyntaxId "proof-b")
+ (fixtureInitialPrefix fixture)
+ laterContext =
+ Semantic.proofValidationKey
+ theorem
+ (Semantic.proofSyntaxId "proof-a")
+ (fixtureNextPrefix fixture)
+ assertBool
+ "proof syntax is an exact validation input"
+ (firstProof /= secondProof)
+ assertBool
+ "semantic predecessor is an exact validation input"
+ (firstProof /= laterContext)
+ let proofRecord =
+ Semantic.proofValidationRecord
+ firstProof certificate
+ declarationKey =
+ Semantic.declarationValidationKey
+ (Semantic.declarationSyntaxId "declaration")
+ (fixtureInitialPrefix fixture)
+ []
+ [theorem, theorem]
+ declarationRecord =
+ Semantic.declarationValidationRecord
+ declarationKey
+ [certificate, certificate]
+ assertEqual
+ "proof validation record cache round trip"
+ (Right proofRecord)
+ (decodeCache
+ Semantic.getProofValidationRecordCache
+ (encodeCache
+ (Semantic.putProofValidationRecordCache
+ proofRecord)))
+ assertEqual
+ "ordered declaration certificates retain repetitions"
+ (Right declarationRecord)
+ (decodeCache
+ Semantic.getDeclarationValidationRecordCache
+ (encodeCache
+ (Semantic.putDeclarationValidationRecordCache
+ declarationRecord)))
+ firstParsedKey <- expectRight
+ (parsedModuleKey
+ (fixtureSourceContentId "source-a")
+ Syntax.baseSyntaxInterfaceId
+ [])
+ secondParsedKey <- expectRight
+ (parsedModuleKey
+ (fixtureSourceContentId "source-b")
+ Syntax.baseSyntaxInterfaceId
+ [])
+ directSyntax <- makeSyntax "direct"
+ let directSyntaxId =
+ Syntax.moduleSyntaxAssertedId directSyntax
+ assertEqual
+ "parsed identity rejects duplicate direct syntax"
+ (Left
+ (DuplicateParsedDirectSyntaxInput
+ directSyntaxId))
+ (parsedModuleKey
+ (fixtureSourceContentId "source-a")
+ Syntax.baseSyntaxInterfaceId
+ [directSyntaxId, directSyntaxId])
+ let
+ firstParsed =
+ parsedModuleId firstParsedKey "parsed"
+ secondParsed =
+ parsedModuleId secondParsedKey "parsed"
+ firstArtifactKey <- expectRight
+ (Semantic.moduleArtifactKey
+ (fixtureOwner fixture)
+ firstParsed
+ []
+ (fixtureTheory fixture))
+ secondArtifactKey <- expectRight
+ (Semantic.moduleArtifactKey
+ (fixtureOwner fixture)
+ secondParsed
+ []
+ (fixtureTheory fixture))
+ let firstArtifact =
+ Semantic.moduleArtifactId firstArtifactKey
+ secondArtifact =
+ Semantic.moduleArtifactId secondArtifactKey
+ assertBool
+ "module artifact binds parsed source identity"
+ (firstArtifact /= secondArtifact)
+ let semanticId =
+ Semantic.semanticInterfaceAssertedId
+ (fixtureInterface fixture)
+ assertEqual
+ "prefix identity rejects duplicate direct semantics"
+ (Left
+ (Semantic.DuplicateInitialPrefixSemanticInput
+ semanticId))
+ (Semantic.initialPrefixContextId
+ (fixtureTheory fixture)
+ (fixtureOwner fixture)
+ [semanticId, semanticId])
+ assertEqual
+ "module artifact key cache round trip"
+ (Right firstArtifactKey)
+ (decodeCache
+ Semantic.getModuleArtifactKeyCache
+ (encodeCache
+ (Semantic.putModuleArtifactKeyCache
+ firstArtifactKey)))
+ syntax <- makeSyntax "artifact"
+ let artifactResult =
+ Semantic.moduleArtifactResult
+ firstArtifactKey
+ (Syntax.moduleSyntaxAssertedId syntax)
+ (Semantic.semanticInterfaceAssertedId
+ (fixtureInterface fixture))
+ assertEqual
+ "module artifact root round trip"
+ (Right artifactResult)
+ (decodeCache
+ (Semantic.getModuleArtifactResultCache
+ firstArtifact)
+ (encodeCache
+ (Semantic.putModuleArtifactResultCache
+ artifactResult)))
+
+
+data Fixture = Fixture
+ { fixtureTheory :: !Identity.TheoryId
+ , fixtureOwner :: !ModuleName
+ , fixtureTheorem :: !Identity.TheoremRef
+ , fixtureDelta :: !Semantic.DeclarationInterfaceDelta
+ , fixtureInterface :: !Semantic.SemanticInterface
+ , fixtureInitialPrefix :: !Semantic.PrefixContextId
+ , fixtureNextPrefix :: !Semantic.PrefixContextId
+ }
+
+makeFixture :: IO Fixture
+makeFixture = do
+ foundation <- expectRight Foundation.checkedFoundation
+ namespaceDigest <- expectRight
+ (hashCanonicalFields
+ "semantic-test-namespace"
+ ["root"])
+ relative <- expectRight (safeRelativePath "module.tex")
+ closure <- expectRight
+ (Identity.validateObjectClosure
+ (Identity.theoryId foundation)
+ [])
+ proposition <- expectRight
+ (Identity.validatePropositionContent
+ closure
+ Core.CFalsum)
+ let theory =
+ Identity.theoryId foundation
+ owner =
+ moduleNameFromParts
+ (sourceNamespaceIdFromDigest namespaceDigest)
+ relative
+ reference =
+ Identity.theoremRef
+ theory
+ (Identity.checkedPropositionId proposition)
+ authority =
+ Authority.factAuthority
+ reference
+ Authority.cleanAuthoritySafety
+ occurrence =
+ Semantic.semanticFactOccurrence
+ (Semantic.factSlot owner (localFactOrdinal 0))
+ authority
+ Semantic.SearchEligible
+ slot =
+ Semantic.declarationSlot
+ owner
+ (localDeclarationOrdinal 0)
+ delta <- expectRight
+ (Semantic.declarationInterfaceDelta
+ slot
+ [occurrence]
+ [ Semantic.semanticAlias
+ (Semantic.semanticName "theorem")
+ (Semantic.semanticFactOccurrenceFingerprint
+ (Semantic.factSlot owner (localFactOrdinal 0))
+ authority)
+ ]
+ []
+ [Identity.checkedPropositionId proposition]
+ Semantic.emptySemanticEnvironmentDelta)
+ interface <- expectRight
+ (Semantic.semanticInterface owner [] [delta])
+ initial <- expectRight
+ (Semantic.initialPrefixContextId theory owner [])
+ pure
+ Fixture
+ { fixtureTheory = theory
+ , fixtureOwner = owner
+ , fixtureTheorem = reference
+ , fixtureDelta = delta
+ , fixtureInterface = interface
+ , fixtureInitialPrefix = initial
+ , fixtureNextPrefix =
+ Semantic.nextPrefixContextId initial delta
+ }
+
+makeSyntax :: Text -> IO Syntax.ModuleSyntaxInterface
+makeSyntax command = do
+ delta <- expectRight
+ (Syntax.canonicalSyntaxDelta
+ [Syntax.CanonicalStructureOperation command])
+ expectRight (Syntax.moduleSyntaxInterface [] delta)
+
+fixtureSourceContentId :: ByteString -> SourceContentId
+fixtureSourceContentId bytes =
+ either
+ (impossible . show)
+ id
+ (decodeCache
+ getSourceContentIdCache
+ (encodeCache
+ (putCacheDigest
+ (hashCacheFields
+ "semantic-test-source"
+ [bytes]))))
+
+expectRight :: Show error => Either error value -> IO value
+expectRight = \case
+ Left err ->
+ assertFailure (show err) >> fail "unreachable"
+ Right value ->
+ pure value
diff --git a/source/Felix/Test/Unit/Source.hs b/source/Felix/Test/Unit/Source.hs
new file mode 100644
index 0000000..6216381
--- /dev/null
+++ b/source/Felix/Test/Unit/Source.hs
@@ -0,0 +1,2581 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
+
+module Felix.Test.Unit.Source (unitTests) where
+
+import Base
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Checking.Identity qualified as Identity
+import Felix.Checking.Semantic qualified as Semantic
+import Felix.Cache.Codec qualified as Cache
+import Felix.Module qualified as Module
+import Felix.Parse qualified as Parse
+import Felix.Parsed.Identity qualified as ParsedIdentity
+import Felix.Parsed.Payload qualified as Parsed
+import Felix.Prelude qualified as Prelude
+import Felix.Source
+import Felix.Source.Content qualified as Content
+import Felix.Source.Graph
+import Felix.Store qualified as Store
+import Felix.Report.Location
+ ( FileId(..)
+ , FileIdAllocator(..)
+ , Location(..)
+ , LocationRegistrationError(..)
+ , allocateFileId
+ , locColumn
+ , locFile
+ , locFileId
+ , locLine
+ , lookupFileIdentityPath
+ )
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Adapt qualified as Adapt
+import Felix.Syntax.Interface qualified as Interface
+import Felix.Syntax.Token (runLexer)
+
+import Control.Exception (bracket, evaluate)
+import Data.ByteString qualified as ByteString
+import Data.IORef
+import Data.List qualified as List
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Text qualified as Text
+import Data.Word (Word8, Word16)
+import Database.SQLite.Simple qualified as SQLite
+import System.Directory qualified as Directory
+import System.FilePath.Posix qualified as Posix
+import System.Posix.Files qualified as PosixFiles
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+unitTests :: TestTree
+unitTests = testGroup "Source resolution"
+ [ testCase "validates mount-root-relative POSIX paths" validatesRelativePaths
+ , testCase "rejects duplicate source mount ids" rejectsDuplicateMountIds
+ , testCase "rejects duplicate canonical mount roots" rejectsDuplicateMountRoots
+ , testCase "permits missing and rejects non-directory mounts"
+ validatesMountRootTypes
+ , testCase "rejects relative exact roots" rejectsRelativeExactRoots
+ , testCase "retains exact root spelling as diagnostic trivia" retainsRootSpelling
+ , testCase "searched and exact roots share canonical identity" rootFormsShareIdentity
+ , testCase "attributes nested sources to the most specific mount" attributesNestedSources
+ , testCase "configured order selects searched candidates" candidateOrderSelectsWinner
+ , testCase "rejects a higher-priority special source"
+ rejectsHigherPrioritySpecialSource
+ , testCase "rejects exact roots outside configured mounts" rejectsOutsideExactRoot
+ , testCase "loads source text as strict UTF-8" loadsStrictUtf8
+ , testCase "reports malformed UTF-8 sequence starts"
+ reportsInvalidUtf8Offsets
+ , testCase "reserves the all-ones file identifier"
+ preservesReservedFileId
+ , testCase "builds an imported-before-importer source graph" buildsSourceGraph
+ , testCase "rejects the packaged prelude as ordinary source"
+ rejectsPackagedPreludeAsOrdinarySource
+ , testCase "orders sibling imports by textual occurrence"
+ ordersSiblingImports
+ , testCase "orders shared dependencies before their importers"
+ ordersSharedDependencies
+ , testCase "retains repeated import-edge occurrences" retainsRepeatedImports
+ , testCase "deduplicates canonical source nodes" deduplicatesCanonicalNodes
+ , testCase "reports missing imports at their source location" reportsMissingImports
+ , testCase "rejects unsafe imports at their source location" rejectsUnsafeImports
+ , testCase "reports the located import cycle chain" reportsImportCycles
+ , testCase "rejects malformed imported source before discovery" rejectsMalformedImportedSource
+ , testCase "builds empty modules through the ordinary pipeline"
+ buildsEmptyModules
+ , testCase "identifies owner-independent parsed modules"
+ identifiesOwnerIndependentParsedModules
+ , testCase "keys effective direct syntax inputs"
+ keysEffectiveDirectSyntaxInputs
+ , testCase "reuses exact parsed syntax on a warm pass"
+ reusesExactParsedSyntax
+ , testCase "invalidates exact parsed inputs transitively"
+ invalidatesExactParsedInputs
+ , testCase "rebinds relocated parsed artifacts"
+ rebindsRelocatedParsedArtifacts
+ , testCase "rejects a corrupted cached declaration anchor"
+ rejectsCorruptedCachedDeclarationAnchor
+ , testCase "parses source-local blocks in graph order" parsesSourceGraph
+ , testCase "does not leak syntax between sibling imports"
+ rejectsSiblingSyntaxLeakage
+ , testCase "parses source fixity levels and grouping"
+ parsesSourceFixities
+ , testCase "parses cdot and symdiff fixities"
+ parsesLibraryFixities
+ , testCase "validates source pragma associations"
+ validatesSourcePragmaAssociations
+ , testCase "rejects fixed-base category mismatches"
+ rejectsFixedBaseCategoryMismatch
+ , testCase "retains multi-item syntax occurrence order"
+ retainsMultiItemSyntaxOccurrences
+ , testCase "propagates and coalesces imported syntax"
+ propagatesImportedSyntax
+ , testCase "rejects unequal imported syntax"
+ rejectsUnequalImportedSyntax
+ , testCase "qualifies same-display cross-mount collisions"
+ distinguishesPhysicalSourceLocations
+ , testCase "retains each workspace location display path"
+ retainsWorkspaceLocationDisplayPath
+ , testCase "reports imported scanner errors before importer tokenizer errors"
+ reportsImportedScannerErrorFirst
+ , testCase "returns malformed lexical declarations as typed errors"
+ reportsMalformedLexicalDeclaration
+ , testCase "validates inductive function patterns during scanning"
+ rejectsMalformedInductivePattern
+ , testCase "scans and parses adjective signatures"
+ acceptsAdjectiveSignature
+ , testCase "rejects malformed math-led signature heads"
+ rejectsMalformedSignatureHead
+ , testCase "locates conflicting declarations within one environment"
+ reportsSameSourceLexiconCollision
+ , testCase "accepts the first source declaration of a built-in pattern"
+ acceptsBuiltinSourceDeclaration
+ , testCase "keeps the built-in marker for a prefix predicate declaration"
+ acceptsBuiltinPrefixPredicateDeclaration
+ , testCase "does not rescan repeated canonical imports"
+ avoidsAliasImportLexiconCollision
+ , testCase "parses loaded sources without rereading files" parsesWithoutRereading
+ , testCase "returns source-local failures after prior chunk callbacks"
+ returnsSourceParseFailures
+ , testCase "rejects guarded symbolic declarations before publication"
+ rejectsGuardedSymbolicDeclarations
+ ]
+
+validatesRelativePaths :: Assertion
+validatesRelativePaths = do
+ assertRight (safeRelativePath "theory/set.tex")
+ assertRight (safeRelativePath "theory\\set.tex")
+ assertLeft EmptyRelativePath (safeRelativePath "")
+ assertLeft AbsoluteRelativePath (safeRelativePath "/theory.tex")
+ assertLeft CurrentDirectoryComponent (safeRelativePath "./theory.tex")
+ assertLeft ParentDirectoryComponent (safeRelativePath "a/../theory.tex")
+ assertLeft EmptyPathComponent (safeRelativePath "a//theory.tex")
+ assertLeft EmptyPathComponent (safeRelativePath "a/")
+ assertLeft NullPathCharacter (safeRelativePath "a\0b")
+
+rejectsDuplicateMountIds :: Assertion
+rejectsDuplicateMountIds =
+ withTemporaryDirectory "felix-source-duplicate-id" \temp -> do
+ result <- prepareSourceMounts
+ [ (sourceMountId "same", temp Posix.</> "one")
+ , (sourceMountId "same", temp Posix.</> "two")
+ ]
+ assertEqual
+ "duplicate id"
+ (Left (DuplicateSourceMountId (sourceMountId "same")))
+ result
+
+rejectsDuplicateMountRoots :: Assertion
+rejectsDuplicateMountRoots =
+ withTemporaryDirectory "felix-source-duplicate-root" \temp -> do
+ result <- prepareSourceMounts
+ [ (sourceMountId "one", temp)
+ , (sourceMountId "two", temp Posix.</> ".")
+ ]
+ canonical <- Directory.canonicalizePath temp
+ case result of
+ Left (DuplicateCanonicalMountRoot root firstId secondId) -> do
+ assertEqual "canonical root" canonical (canonicalPathFilePath root)
+ assertEqual "first mount id" (sourceMountId "one") firstId
+ assertEqual "second mount id" (sourceMountId "two") secondId
+ Left err ->
+ assertFailure ("expected DuplicateCanonicalMountRoot, got " <> show err)
+ Right mounts ->
+ assertFailure ("expected duplicate-root rejection, got " <> show mounts)
+
+validatesMountRootTypes :: Assertion
+validatesMountRootTypes =
+ withTemporaryDirectory "felix-source-mount-type" \temp -> do
+ let ident = sourceMountId "project"
+ missing = temp Posix.</> "missing"
+ regularFile = temp Posix.</> "file"
+ assertRight =<< prepareSourceMounts [(ident, missing)]
+
+ writeFile regularFile ""
+ result <- prepareSourceMounts [(ident, regularFile)]
+ case result of
+ Left SourceMountNotDirectory{} ->
+ pure ()
+ Left err ->
+ assertFailure
+ ("expected SourceMountNotDirectory, got " <> show err)
+ Right mounts ->
+ assertFailure
+ ("expected non-directory rejection, got " <> show mounts)
+
+rejectsRelativeExactRoots :: Assertion
+rejectsRelativeExactRoots =
+ assertEqual
+ "relative exact roots are rejected"
+ (Left (ExistingRootNotAbsolute "entry.tex"))
+ =<< existingRoot "entry.tex"
+
+retainsRootSpelling :: Assertion
+retainsRootSpelling =
+ withTemporaryDirectory "felix-source-root-spelling" \temp -> do
+ let source = temp Posix.</> "entry.tex"
+ alias = temp Posix.</> "entry-alias.tex"
+ writeFile source ""
+ Directory.createFileLink source alias
+ direct <- expectRight =<< existingRoot source
+ throughAlias <- expectRight =<< existingRoot alias
+ assertEqual "canonical request identity" direct throughAlias
+ assertEqual "diagnostic spelling" alias
+ (rootRequestSpelling throughAlias)
+
+rootFormsShareIdentity :: Assertion
+rootFormsShareIdentity =
+ withTemporaryDirectory "felix-source-root-identity" \temp -> do
+ let source = temp Posix.</> "entry.tex"
+ writeFile source "source"
+ mounts <- oneMount "project" temp
+ searched <- expectRight (searchedRoot "entry.tex")
+ exact <- expectRight =<< existingRoot source
+ searchedLoaded <- expectRight =<< resolveAndLoadRoot mounts searched
+ exactLoaded <- expectRight =<< resolveAndLoadRoot mounts exact
+ assertEqual "loaded source" searchedLoaded exactLoaded
+ assertEqual "source mount"
+ (resolvedSourceMount (loadedSource searchedLoaded))
+ (resolvedSourceMount (loadedSource exactLoaded))
+ assertEqual "mount-relative source path"
+ (resolvedSourceRelativePath (loadedSource searchedLoaded))
+ (resolvedSourceRelativePath (loadedSource exactLoaded))
+
+rejectsPackagedPreludeAsOrdinarySource :: Assertion
+rejectsPackagedPreludeAsOrdinarySource = do
+ packaged <- expectRight =<< Prelude.loadReservedPreludeSourceInput
+ canonical <- expectJust "packaged canonical path"
+ (Prelude.reservedPreludeSourceCanonicalPath packaged)
+ let path = canonicalPathFilePath canonical
+ mounts <- oneMount "packaged" (Posix.takeDirectory path)
+ request <- expectRight (searchedRoot (Posix.takeFileName path))
+ let validate = Prelude.rejectOrdinaryPreludeSourceGraph packaged
+ syntaxInputs = const []
+ Parse.parseSourceWorkspaceWithSyntaxInputsAndGraphValidation
+ mounts request syntaxInputs validate >>= \case
+ Left
+ (Parse.SourceWorkspaceError
+ (PackagedPreludeSelectedAsOrdinarySource source)) ->
+ assertEqual "authority-free rejected path"
+ canonical
+ (resolvedSourceCanonicalPath source)
+ other ->
+ assertFailure
+ ("unexpected authority-free result: " <> show other)
+
+ withTemporaryDirectory "felix-reserved-parse-store" \temp -> do
+ foundation <- expectRight Foundation.checkedFoundation
+ store <- openTestStore
+ (temp Posix.</> "store.sqlite")
+ (Identity.theoryId foundation)
+ Parse.parseSourceWorkspaceWithStoreAndSyntaxInputsAndGraphValidation
+ store mounts request syntaxInputs validate >>= \case
+ Left
+ (Parse.ParseExecutionWorkspaceError
+ (Parse.SourceWorkspaceError
+ (PackagedPreludeSelectedAsOrdinarySource source))) ->
+ assertEqual "typed rejected path"
+ canonical
+ (resolvedSourceCanonicalPath source)
+ other ->
+ assertFailure
+ ("unexpected typed result: " <> show other)
+ Store.closeStore store
+
+attributesNestedSources :: Assertion
+attributesNestedSources =
+ withTemporaryDirectory "felix-source-nested-mount" \temp -> do
+ let nested = temp Posix.</> "library"
+ source = nested Posix.</> "entry.tex"
+ Directory.createDirectory nested
+ writeFile source "source"
+ exact <- expectRight =<< existingRoot source
+ outerFirst <- expectRight =<< prepareSourceMounts
+ [ (sourceMountId "project", temp)
+ , (sourceMountId "library", nested)
+ ]
+ innerFirst <- expectRight =<< prepareSourceMounts
+ [ (sourceMountId "library", nested)
+ , (sourceMountId "project", temp)
+ ]
+ searched <- expectRight (searchedRoot "library/entry.tex")
+ outerFirstSource <- loadedSource <$> (expectRight =<< resolveAndLoadRoot outerFirst exact)
+ innerFirstSource <- loadedSource <$> (expectRight =<< resolveAndLoadRoot innerFirst exact)
+ searchedSource <- loadedSource <$> (expectRight =<< resolveAndLoadRoot outerFirst searched)
+ assertEqual "order-independent attribution" outerFirstSource innerFirstSource
+ assertEqual "root-form-independent attribution" outerFirstSource searchedSource
+ assertEqual "most specific mount" (sourceMountId "library") (resolvedSourceMount outerFirstSource)
+ assertEqual "mount-relative identity" "entry.tex"
+ (safeRelativePathFilePath (resolvedSourceRelativePath outerFirstSource))
+
+candidateOrderSelectsWinner :: Assertion
+candidateOrderSelectsWinner =
+ withTemporaryDirectory "felix-source-precedence" \temp -> do
+ let firstRoot = temp Posix.</> "first"
+ secondRoot = temp Posix.</> "second"
+ firstSource = firstRoot Posix.</> "entry.tex"
+ secondSource = secondRoot Posix.</> "entry.tex"
+ Directory.createDirectory firstRoot
+ Directory.createDirectory secondRoot
+ writeFile firstSource "first"
+ writeFile secondSource "second"
+ request <- expectRight (searchedRoot "entry.tex")
+ firstMounts <- expectRight =<< prepareSourceMounts
+ [ (sourceMountId "first", firstRoot)
+ , (sourceMountId "second", secondRoot)
+ ]
+ secondMounts <- expectRight =<< prepareSourceMounts
+ [ (sourceMountId "second", secondRoot)
+ , (sourceMountId "first", firstRoot)
+ ]
+ firstWinner <- expectRight =<< resolveAndLoadRoot firstMounts request
+ secondWinner <- expectRight =<< resolveAndLoadRoot secondMounts request
+ assertEqual "first configured source" "first" (loadedText firstWinner)
+ assertEqual "reversed configured source" "second" (loadedText secondWinner)
+
+rejectsHigherPrioritySpecialSource :: Assertion
+rejectsHigherPrioritySpecialSource =
+ withTemporaryDirectory "felix-source-special-precedence" \temp -> do
+ let higherRoot = temp Posix.</> "higher"
+ lowerRoot = temp Posix.</> "lower"
+ higherSource = higherRoot Posix.</> "entry.tex"
+ lowerSource = lowerRoot Posix.</> "entry.tex"
+ Directory.createDirectory higherRoot
+ Directory.createDirectory lowerRoot
+ PosixFiles.createNamedPipe higherSource PosixFiles.ownerModes
+ writeFile lowerSource "ordinary source"
+ mounts <- expectRight =<< prepareSourceMounts
+ [ (sourceMountId "higher", higherRoot)
+ , (sourceMountId "lower", lowerRoot)
+ ]
+ request <- expectRight (searchedRoot "entry.tex")
+ result <- resolveRoot mounts request
+ case result of
+ Left
+ (SelectedSourceNotRegular
+ (SearchedRootLookup relative)
+ selectedPath
+ canonical) -> do
+ assertEqual "searched path"
+ "entry.tex"
+ (safeRelativePathFilePath relative)
+ assertEqual "selected higher candidate"
+ higherSource
+ selectedPath
+ canonicalHigher <-
+ Directory.canonicalizePath higherSource
+ assertEqual "selected canonical target"
+ canonicalHigher
+ (canonicalPathFilePath canonical)
+ Left err ->
+ assertFailure
+ ("expected SelectedSourceNotRegular, got " <> show err)
+ Right source ->
+ assertFailure
+ ("expected special-source rejection, got " <> show source)
+
+rejectsOutsideExactRoot :: Assertion
+rejectsOutsideExactRoot =
+ withTemporaryDirectory "felix-source-outside-root" \temp -> do
+ let mountRoot = temp Posix.</> "mount"
+ outsideRoot = temp Posix.</> "outside"
+ source = outsideRoot Posix.</> "entry.tex"
+ Directory.createDirectory mountRoot
+ Directory.createDirectory outsideRoot
+ writeFile source "source"
+ mounts <- oneMount "project" mountRoot
+ exact <- expectRight =<< existingRoot source
+ result <- resolveAndLoadRoot mounts exact
+ case result of
+ Left (RootOutsideConfiguredMount spelling _canonical) ->
+ assertEqual "exact-root diagnostic spelling" source spelling
+ Left err ->
+ assertFailure ("expected RootOutsideConfiguredMount, got " <> show err)
+ Right loaded ->
+ assertFailure ("expected outside-root rejection, got " <> show loaded)
+
+loadsStrictUtf8 :: Assertion
+loadsStrictUtf8 =
+ withTemporaryDirectory "felix-source-utf8" \temp -> do
+ let source = temp Posix.</> "unicode.tex"
+ bytes =
+ ByteString.pack
+ [ 0xCE, 0xB1, 0x20, 0xE2
+ , 0x88, 0x88, 0x20, 0x41
+ ]
+ ByteString.writeFile source bytes
+ mounts <- oneMount "project" temp
+ request <- expectRight (searchedRoot "unicode.tex")
+ loaded <- expectRight =<< resolveAndLoadRoot mounts request
+ assertEqual "exact bytes" bytes (loadedBytes loaded)
+ assertEqual "decoded text" ("α ∈ A" :: Text) (loadedText loaded)
+ assertEqual
+ "byte count"
+ (fromIntegral (ByteString.length bytes))
+ (loadedByteCount loaded)
+ let identifier =
+ Content.sourceContentId loaded
+ assertEqual
+ "content identity cache round trip"
+ (Right identifier)
+ (Cache.decodeCache
+ Content.getSourceContentIdCache
+ (Cache.encodeCache
+ (Content.putSourceContentIdCache
+ identifier)))
+ ByteString.writeFile source (bytes <> "\n")
+ changed <- expectRight
+ =<< loadResolvedSource (loadedSource loaded)
+ assertBool
+ "exact byte edits change source identity"
+ (identifier /= Content.sourceContentId changed)
+
+reportsInvalidUtf8Offsets :: Assertion
+reportsInvalidUtf8Offsets =
+ withTemporaryDirectory "felix-source-invalid-utf8" \temp -> do
+ let source = temp Posix.</> "invalid.tex"
+ mounts <- oneMount "project" temp
+ request <- expectRight (searchedRoot "invalid.tex")
+ let assertOffset label bytes expected = do
+ ByteString.writeFile source (ByteString.pack bytes)
+ result <- resolveAndLoadRoot mounts request
+ case result of
+ Left (SourceDecodeError _source offset) ->
+ assertEqual label expected offset
+ Left err ->
+ assertFailure
+ ("expected SourceDecodeError, got " <> show err)
+ Right loaded ->
+ assertFailure
+ ("expected malformed UTF-8 rejection, got "
+ <> show loaded)
+ assertOffset "malformed sequence start" [0x61, 0xC3, 0x28] 1
+ assertOffset "incomplete sequence start" [0x61, 0xC3] 1
+
+preservesReservedFileId :: Assertion
+preservesReservedFileId =
+ case allocateFileId boundaryAllocator of
+ Left err ->
+ assertFailure
+ ("could not allocate last available file id: " <> show err)
+ Right (fileId, exhaustedAllocator) -> do
+ assertEqual "last available file id"
+ (maxBound - 1)
+ (unFileId fileId)
+ assertBool "allocator returned reserved file id"
+ (unFileId fileId /= maxBound)
+ assertEqual "allocator reports exhaustion"
+ (Left FileIdSpaceExhausted)
+ (allocateFileId exhaustedAllocator)
+ where
+ boundaryAllocator =
+ FileIdAllocator
+ (fromIntegral (maxBound :: Word16) - 1)
+
+buildsSourceGraph :: Assertion
+buildsSourceGraph =
+ withTemporaryDirectory "felix-source-graph" \temp -> do
+ writeTheory (temp Posix.</> "shared.tex") [] "shared"
+ writeTheory (temp Posix.</> "entry.tex") ["shared.tex"] "entry"
+ mounts <- oneMount "project" temp
+ request <- expectRight (searchedRoot "entry.tex")
+ graph <- expectRight =<< buildResolvedSourceGraph mounts request
+ assertEqual "two source nodes" 2 (length (sourceGraphNodes graph))
+ case sourceGraphImportEdges graph of
+ [edge] -> do
+ assertEqual "root imports" (sourceGraphRoot graph) (sourceImportingNode edge)
+ assertEqual
+ "imported-before-importer order"
+ [sourceImportedNode edge, sourceGraphRoot graph]
+ ( sourceNodeCanonicalPathForTest
+ <$> toList
+ (sourceGraphImportedBeforeImporter graph)
+ )
+ assertEqual "import location line" 1
+ (locLine (importLocation (sourceImportReference edge)))
+ assertEqual "selected location path" "entry.tex"
+ (locFile (importLocation (sourceImportReference edge)))
+ edges ->
+ assertFailure ("expected one import edge, got " <> show edges)
+
+ordersSiblingImports :: Assertion
+ordersSiblingImports =
+ withTemporaryDirectory "felix-source-sibling-order" \temp -> do
+ writeTheory (temp Posix.</> "a.tex") [] "a"
+ writeTheory (temp Posix.</> "b.tex") [] "b"
+ writeTheory
+ (temp Posix.</> "entry.tex")
+ ["a.tex", "b.tex"]
+ "entry"
+ graph <- buildSearchedGraph temp "entry.tex"
+ order <- sourceGraphOrderPaths graph
+ assertEqual "DFS completion order"
+ ["a.tex", "b.tex", "entry.tex"]
+ order
+
+ordersSharedDependencies :: Assertion
+ordersSharedDependencies =
+ withTemporaryDirectory "felix-source-shared-order" \temp -> do
+ writeTheory (temp Posix.</> "shared.tex") [] "shared"
+ writeTheory (temp Posix.</> "a.tex") ["shared.tex"] "a"
+ writeTheory (temp Posix.</> "b.tex") ["shared.tex"] "b"
+ writeTheory
+ (temp Posix.</> "entry.tex")
+ ["a.tex", "b.tex"]
+ "entry"
+ graph <- buildSearchedGraph temp "entry.tex"
+ order <- sourceGraphOrderPaths graph
+ assertEqual "shared dependency occurs once before both importers"
+ ["shared.tex", "a.tex", "b.tex", "entry.tex"]
+ order
+
+retainsRepeatedImports :: Assertion
+retainsRepeatedImports =
+ withTemporaryDirectory "felix-source-repeated-import" \temp -> do
+ writeTheory (temp Posix.</> "shared.tex") [] "shared"
+ writeTheory
+ (temp Posix.</> "entry.tex")
+ ["shared.tex", "shared.tex"]
+ "entry"
+ graph <- buildSearchedGraph temp "entry.tex"
+ assertEqual "canonical node count" 2 (length (sourceGraphNodes graph))
+ assertEqual "repeated edge count" 2 (length (sourceGraphImportEdges graph))
+
+deduplicatesCanonicalNodes :: Assertion
+deduplicatesCanonicalNodes =
+ withTemporaryDirectory "felix-source-canonical-dedup" \temp -> do
+ let shared = temp Posix.</> "shared.tex"
+ alias = temp Posix.</> "alias.tex"
+ writeTheory shared [] "shared"
+ Directory.createFileLink shared alias
+ writeTheory
+ (temp Posix.</> "entry.tex")
+ ["shared.tex", "alias.tex"]
+ "entry"
+ graph <- buildSearchedGraph temp "entry.tex"
+ assertEqual "one node for symlink aliases" 2 (length (sourceGraphNodes graph))
+ case sourceGraphImportEdges graph of
+ [firstEdge, secondEdge] ->
+ assertEqual
+ "both occurrences reach one node"
+ (sourceImportedNode firstEdge)
+ (sourceImportedNode secondEdge)
+ edges ->
+ assertFailure ("expected two import edges, got " <> show edges)
+
+reportsMissingImports :: Assertion
+reportsMissingImports =
+ withTemporaryDirectory "felix-source-missing-import" \temp -> do
+ writeFile
+ (temp Posix.</> "entry.tex")
+ (unlines
+ [ "% heading"
+ , "\\import{missing.tex}"
+ , theoryBlock "entry"
+ ])
+ mounts <- oneMount "project" temp
+ request <- expectRight (searchedRoot "entry.tex")
+ result <- buildResolvedSourceGraph mounts request
+ case result of
+ Left (SourceNotFound (ImportedSourceLookup _ reference) _candidates) -> do
+ assertEqual "missing import line" 2 (locLine (importLocation reference))
+ assertEqual "missing import source" "entry.tex"
+ (locFile (importLocation reference))
+ Left err ->
+ assertFailure ("expected located SourceNotFound, got " <> show err)
+ Right graph ->
+ assertFailure ("expected missing-import rejection, got " <> show graph)
+
+rejectsUnsafeImports :: Assertion
+rejectsUnsafeImports =
+ withTemporaryDirectory "felix-source-unsafe-import" \temp -> do
+ writeTheory (temp Posix.</> "entry.tex") ["./shared.tex"] "entry"
+ mounts <- oneMount "project" temp
+ request <- expectRight (searchedRoot "entry.tex")
+ result <- buildResolvedSourceGraph mounts request
+ case result of
+ Left (InvalidImportPath _source location raw CurrentDirectoryComponent) -> do
+ assertEqual "raw import" "./shared.tex" raw
+ assertEqual "unsafe import line" 1 (locLine location)
+ assertEqual "unsafe import source" "entry.tex" (locFile location)
+ Left err ->
+ assertFailure ("expected InvalidImportPath, got " <> show err)
+ Right graph ->
+ assertFailure ("expected unsafe-import rejection, got " <> show graph)
+
+reportsImportCycles :: Assertion
+reportsImportCycles =
+ withTemporaryDirectory "felix-source-cycle" \temp -> do
+ writeTheory (temp Posix.</> "a.tex") ["b.tex"] "a"
+ writeTheory (temp Posix.</> "b.tex") ["a.tex"] "b"
+ mounts <- oneMount "project" temp
+ request <- expectRight (searchedRoot "a.tex")
+ result <- buildResolvedSourceGraph mounts request
+ case result of
+ Left (SourceImportCycle steps) -> do
+ assertEqual "cycle length" 2 (length steps)
+ assertEqual
+ "cycle importer sequence"
+ ["a.tex", "b.tex"]
+ [ safeRelativePathFilePath
+ (resolvedSourceRelativePath (cycleImporter step))
+ | step <- toList steps
+ ]
+ assertEqual
+ "cycle import locations"
+ ["a.tex", "b.tex"]
+ [ locFile (importLocation (cycleImport step))
+ | step <- toList steps
+ ]
+ Left err ->
+ assertFailure ("expected SourceImportCycle, got " <> show err)
+ Right graph ->
+ assertFailure ("expected cycle rejection, got " <> show graph)
+
+rejectsMalformedImportedSource :: Assertion
+rejectsMalformedImportedSource =
+ withTemporaryDirectory "felix-source-import-utf8" \temp -> do
+ writeTheory (temp Posix.</> "entry.tex") ["bad.tex"] "entry"
+ ByteString.writeFile
+ (temp Posix.</> "bad.tex")
+ (ByteString.pack [0x61, 0xFF])
+ mounts <- oneMount "project" temp
+ request <- expectRight (searchedRoot "entry.tex")
+ result <- buildResolvedSourceGraph mounts request
+ case result of
+ Left (SourceDecodeError source offset) -> do
+ assertEqual "bad source" "bad.tex"
+ (safeRelativePathFilePath (resolvedSourceRelativePath source))
+ assertEqual "bad byte offset" 1 offset
+ Left err ->
+ assertFailure ("expected SourceDecodeError, got " <> show err)
+ Right graph ->
+ assertFailure ("expected malformed-source rejection, got " <> show graph)
+
+buildsEmptyModules :: Assertion
+buildsEmptyModules =
+ withTemporaryDirectory "felix-source-empty" \temp ->
+ forM_
+ [ ("empty.tex", "")
+ , ("comments.tex", "% heading\n% body")
+ ]
+ \(relative, contents) -> do
+ writeFile (temp Posix.</> relative) contents
+ graph <- buildSearchedGraph temp relative
+ assertEqual
+ "one ordinary graph node"
+ 1
+ (length (sourceGraphNodes graph))
+ emittedRef <- newIORef (0 :: Int)
+ workspace <-
+ expectRight
+ =<< Parse.parseResolvedSourceGraphWith
+ graph
+ (\_source _block ->
+ modifyIORef' emittedRef (+ 1))
+ assertEqual
+ "no block callbacks"
+ 0
+ =<< readIORef emittedRef
+ assertEqual
+ "empty parsed projection"
+ []
+ (Parse.importedBeforeImporterBlocks workspace)
+ assertBool
+ "empty syntax declarations"
+ (null
+ (Interface.canonicalSyntaxDeltaEntries
+ (Interface.moduleSyntaxLocalDelta
+ (Parse.parsedModuleSyntaxInterface
+ (Parse.parsedWorkspaceRootModule
+ workspace)))))
+
+identifiesOwnerIndependentParsedModules :: Assertion
+identifiesOwnerIndependentParsedModules =
+ withTemporaryDirectory "felix-parsed-identity" \temp -> do
+ let firstRoot = temp Posix.</> "first"
+ secondRoot = temp Posix.</> "second"
+ bytes = axiomBlock "same" "x = x"
+ Directory.createDirectory firstRoot
+ Directory.createDirectory secondRoot
+ writeFile (firstRoot Posix.</> "entry.tex") bytes
+ writeFile (secondRoot Posix.</> "entry.tex") bytes
+ firstWorkspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph
+ =<< buildSearchedGraph firstRoot "entry.tex"
+ secondWorkspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph
+ =<< buildSearchedGraph secondRoot "entry.tex"
+ let first = Parse.parsedWorkspaceRootModule firstWorkspace
+ second = Parse.parsedWorkspaceRootModule secondWorkspace
+ assertBool
+ "logical owners remain distinct"
+ ( Module.moduleName (Parse.parsedModuleAddress first)
+ /= Module.moduleName (Parse.parsedModuleAddress second)
+ )
+ assertEqual
+ "equal bytes retain one content identity"
+ (Parse.parsedModuleSourceContentId first)
+ (Parse.parsedModuleSourceContentId second)
+ assertEqual
+ "physical source registration is outside parsed identity"
+ (Parse.parsedModuleId first)
+ (Parse.parsedModuleId second)
+ assertEqual
+ "canonical payload is owner-independent"
+ (Parse.parsedModulePayload first)
+ (Parse.parsedModulePayload second)
+ let payload = Parse.parsedModulePayload first
+ assertEqual
+ "canonical parsed payload cache round trip"
+ (Right payload)
+ (Cache.decodeCache
+ Parsed.getCanonicalParsedPayloadCache
+ (Cache.encodeCache
+ (Parsed.putCanonicalParsedPayloadCache payload)))
+ rebound <- expectRight
+ (Parsed.decodeCanonicalParsedPayload
+ (FileId 123)
+ payload)
+ case Parsed.decodedParsedBlocks rebound of
+ Raw.BlockAxiom location _title _marker _axiom : _ ->
+ assertEqual
+ "decoded locations bind only to the current live file"
+ (Just (FileId 123))
+ (locFileId location)
+ blocks ->
+ assertFailure
+ ("expected decoded axiom, got " <> show blocks)
+ writeFile
+ (secondRoot Posix.</> "entry.tex")
+ (bytes <> "% content identity change\n")
+ changedWorkspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph
+ =<< buildSearchedGraph secondRoot "entry.tex"
+ assertBool
+ "exact source changes parsed identity"
+ ( Parse.parsedModuleId first
+ /= Parse.parsedModuleId
+ (Parse.parsedWorkspaceRootModule changedWorkspace)
+ )
+
+keysEffectiveDirectSyntaxInputs :: Assertion
+keysEffectiveDirectSyntaxInputs =
+ withTemporaryDirectory "felix-parsed-syntax-input" \temp -> do
+ let firstRoot = temp Posix.</> "first"
+ secondRoot = temp Posix.</> "second"
+ rootBytes = "\\import{notation.tex}\n"
+ Directory.createDirectory firstRoot
+ Directory.createDirectory secondRoot
+ writeFile (firstRoot Posix.</> "entry.tex") rootBytes
+ writeFile (secondRoot Posix.</> "entry.tex") rootBytes
+ writeFile
+ (firstRoot Posix.</> "notation.tex")
+ (syntaxFunctionDefinition
+ "first_notation"
+ "firstop"
+ (Just "%! infixl 1"))
+ writeFile
+ (secondRoot Posix.</> "notation.tex")
+ (syntaxFunctionDefinition
+ "second_notation"
+ "secondop"
+ (Just "%! infixl 1"))
+ firstWorkspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph
+ =<< buildSearchedGraph firstRoot "entry.tex"
+ secondWorkspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph
+ =<< buildSearchedGraph secondRoot "entry.tex"
+ let first = Parse.parsedWorkspaceRootModule firstWorkspace
+ second = Parse.parsedWorkspaceRootModule secondWorkspace
+ assertEqual
+ "root source bytes are unchanged"
+ (Parse.parsedModuleSourceContentId first)
+ (Parse.parsedModuleSourceContentId second)
+ assertBool
+ "effective syntax changes the parsed key"
+ (Parse.parsedModuleKey first /= Parse.parsedModuleKey second)
+ assertBool
+ "effective syntax changes parsed identity"
+ (Parse.parsedModuleId first /= Parse.parsedModuleId second)
+
+reusesExactParsedSyntax :: Assertion
+reusesExactParsedSyntax =
+ withTemporaryDirectory "felix-parsed-warm" \temp -> do
+ let datatype = unlines
+ [ "\\begin{datatype}\\label{multi_item}"
+ , " Define $\\itemkind$ inductively as follows."
+ , " \\begin{enumerate}"
+ , " \\item $\\itemzero \\in \\itemkind$."
+ , " \\item $\\itemsucc{x} \\in \\itemkind$ for $x \\in \\itemkind$."
+ , " \\end{enumerate}"
+ , "\\end{datatype}"
+ ]
+ writeFile
+ (temp Posix.</> "entry.tex")
+ (builtinZeroDefinition "source_zero" <> datatype)
+ mounts <- oneMount "project" temp
+ request <- expectRight (searchedRoot "entry.tex")
+ foundation <- expectRight Foundation.checkedFoundation
+ store <- openTestStore
+ (temp Posix.</> "store.sqlite")
+ (Identity.theoryId foundation)
+ coldCallbacks <- newIORef (0 :: Int)
+ cold <- expectParseExecution
+ =<< Parse.parseSourceWorkspaceWithStoreAndSyntaxInputsAndCallback
+ store mounts request (const [])
+ (\_source _block -> modifyIORef' coldCallbacks (+ 1))
+ warmCallbacks <- newIORef (0 :: Int)
+ warm <- expectParseExecution
+ =<< Parse.parseSourceWorkspaceWithStoreAndSyntaxInputsAndCallback
+ store mounts request (const [])
+ (\_source _block -> modifyIORef' warmCallbacks (+ 1))
+ let coldRoot = Parse.parsedWorkspaceRootModule cold
+ warmRoot = Parse.parsedWorkspaceRootModule warm
+ let expectedChunkCount =
+ length (Parse.parsedModuleBlocks warmRoot)
+ assertBool "fixture has source chunks" (expectedChunkCount > 0)
+ assertEqual "cold callbacks" expectedChunkCount
+ =<< readIORef coldCallbacks
+ assertEqual "warm callbacks" expectedChunkCount
+ =<< readIORef warmCallbacks
+ assertEqual "warm blocks" (Parse.parsedModuleBlocks coldRoot)
+ (Parse.parsedModuleBlocks warmRoot)
+ assertEqual "warm occurrences"
+ (Parse.parsedModuleSyntaxOccurrences coldRoot)
+ (Parse.parsedModuleSyntaxOccurrences warmRoot)
+ assertEqual "warm syntax interface"
+ (Parse.parsedModuleSyntaxInterface coldRoot)
+ (Parse.parsedModuleSyntaxInterface warmRoot)
+ assertEqual "warm parsed identity"
+ (Parse.parsedModuleId coldRoot)
+ (Parse.parsedModuleId warmRoot)
+ case Parse.parsedModuleSyntaxOccurrences warmRoot of
+ first : second : third : fourth : [] -> do
+ assertEqual "fixed source marker" "source_zero"
+ (Parse.parsedSyntaxOccurrenceMarker first)
+ case Parse.parsedSyntaxOccurrenceEntry first of
+ Interface.CanonicalExpressionFunction
+ _pattern marker _fixity ->
+ assertEqual "fixed authoritative marker" "zero" marker
+ entry ->
+ assertFailure
+ ("unexpected fixed cached entry: " <> show entry)
+ assertEqual "multi-item block order" [1, 1, 1]
+ (Parse.parsedSyntaxOccurrenceBlockIndex
+ <$> [second, third, fourth])
+ assertEqual "multi-item scanner order"
+ ["multi_item", "itemzero", "itemsucc"]
+ (Parse.parsedSyntaxOccurrenceMarker
+ <$> [second, third, fourth])
+ case drop 1 (Parse.parsedModuleBlocks warmRoot) of
+ block : _ ->
+ case block of
+ Raw.BlockData _location _title marker _datatype ->
+ assertEqual "cached declaration-head anchor"
+ marker
+ (Parse.parsedSyntaxOccurrenceMarker second)
+ other ->
+ assertFailure
+ ("expected cached datatype block, got "
+ <> show other)
+ [] ->
+ assertFailure "cached datatype block is absent"
+ occurrences ->
+ assertFailure
+ ("unexpected cached syntax occurrences: "
+ <> show occurrences)
+ assertEqual "cold callback projection" 2
+ =<< readIORef coldCallbacks
+ assertEqual "warm callback projection" 2
+ =<< readIORef warmCallbacks
+ Store.closeStore store
+
+invalidatesExactParsedInputs :: Assertion
+invalidatesExactParsedInputs =
+ withTemporaryDirectory "felix-parsed-invalidation" \temp -> do
+ let notationPath = temp Posix.</> "notation.tex"
+ entryPath = temp Posix.</> "entry.tex"
+ notation associativity level =
+ syntaxFunctionDefinition
+ "join"
+ "join"
+ (Just
+ ("%! " <> associativity <> " " <> show level))
+ entry suffix =
+ "\\import{notation.tex}\n"
+ <> axiomBlock
+ "imported_syntax_use"
+ "a\\join b\\join c = a"
+ <> suffix
+ writeFile notationPath (notation "infixl" (1 :: Int))
+ writeFile entryPath (entry "")
+ mounts <- oneMount "project" temp
+ request <- expectRight (searchedRoot "entry.tex")
+ foundation <- expectRight Foundation.checkedFoundation
+ store <- openTestStore
+ (temp Posix.</> "store.sqlite")
+ (Identity.theoryId foundation)
+ let parse =
+ expectParseExecution
+ =<< Parse.parseSourceWorkspaceWithStoreAndSyntaxInputs
+ store mounts request (const [])
+ coldWorkspace <- parse
+ warmWorkspace <- parse
+ coldNotation <- findParsedModule "notation.tex" coldWorkspace
+ warmNotation <- findParsedModule "notation.tex" warmWorkspace
+ let coldRoot = Parse.parsedWorkspaceRootModule coldWorkspace
+ warmRoot = Parse.parsedWorkspaceRootModule warmWorkspace
+ assertEqual "unchanged import identity"
+ (Parse.parsedModuleId coldNotation)
+ (Parse.parsedModuleId warmNotation)
+ assertEqual "unchanged importer identity"
+ (Parse.parsedModuleId coldRoot)
+ (Parse.parsedModuleId warmRoot)
+
+ writeFile entryPath (entry "% formatting-only edit\n")
+ editedWorkspace <- parse
+ editedNotation <- findParsedModule "notation.tex" editedWorkspace
+ let editedRoot = Parse.parsedWorkspaceRootModule editedWorkspace
+ assertEqual "cached import retains identity"
+ (Parse.parsedModuleId warmNotation)
+ (Parse.parsedModuleId editedNotation)
+ assertBool "exact source edit changes importer key"
+ (Parse.parsedModuleKey warmRoot
+ /= Parse.parsedModuleKey editedRoot)
+ assertEqual "formatting retains parsed projection"
+ (Parse.parsedModulePayload warmRoot)
+ (Parse.parsedModulePayload editedRoot)
+
+ writeFile notationPath (notation "infixr" (2 :: Int))
+ syntaxWorkspace <- parse
+ syntaxNotation <- findParsedModule "notation.tex" syntaxWorkspace
+ let syntaxRoot = Parse.parsedWorkspaceRootModule syntaxWorkspace
+ assertBool "local syntax identity changes"
+ ( Interface.moduleSyntaxAssertedId
+ (Parse.parsedModuleSyntaxInterface editedNotation)
+ /= Interface.moduleSyntaxAssertedId
+ (Parse.parsedModuleSyntaxInterface syntaxNotation)
+ )
+ assertEqual "importer source is unchanged"
+ (Parse.parsedModuleSourceContentId editedRoot)
+ (Parse.parsedModuleSourceContentId syntaxRoot)
+ assertBool "direct syntax invalidates importer key"
+ (Parse.parsedModuleKey editedRoot
+ /= Parse.parsedModuleKey syntaxRoot)
+ Store.closeStore store
+
+rebindsRelocatedParsedArtifacts :: Assertion
+rebindsRelocatedParsedArtifacts =
+ withTemporaryDirectory "felix-parsed-relocation" \temp -> do
+ let firstRoot = temp Posix.</> "first"
+ secondRoot = temp Posix.</> "second"
+ sourceBytes = axiomBlock "same" "x = x"
+ Directory.createDirectory firstRoot
+ Directory.createDirectory secondRoot
+ writeFile (firstRoot Posix.</> "entry.tex") sourceBytes
+ writeFile (secondRoot Posix.</> "entry.tex") sourceBytes
+ firstMounts <- oneMount "first" firstRoot
+ secondMounts <- oneMount "second" secondRoot
+ request <- expectRight (searchedRoot "entry.tex")
+ foundation <- expectRight Foundation.checkedFoundation
+ let theory = Identity.theoryId foundation
+ store <- openTestStore (temp Posix.</> "store.sqlite") theory
+ firstWorkspace <- expectParseExecution
+ =<< Parse.parseSourceWorkspaceWithStoreAndSyntaxInputs
+ store firstMounts request (const [])
+ secondWorkspace <- expectParseExecution
+ =<< Parse.parseSourceWorkspaceWithStoreAndSyntaxInputs
+ store secondMounts request (const [])
+ let first = Parse.parsedWorkspaceRootModule firstWorkspace
+ second = Parse.parsedWorkspaceRootModule secondWorkspace
+ firstSource = Parse.parsedModuleResolved first
+ secondSource = Parse.parsedModuleResolved second
+ assertEqual "relocation retains parsed identity"
+ (Parse.parsedModuleId first)
+ (Parse.parsedModuleId second)
+ assertEqual "relocation retains canonical payload"
+ (Parse.parsedModulePayload first)
+ (Parse.parsedModulePayload second)
+ assertBool "relocation rebinds the physical source"
+ (resolvedSourceCanonicalPath firstSource
+ /= resolvedSourceCanonicalPath secondSource)
+ assertBool "relocation rebinds the logical owner"
+ (Parse.parsedModuleAddress first
+ /= Parse.parsedModuleAddress second)
+ firstFileId <- expectJust "first location file id"
+ (locFileId (onlyAxiomLocation first))
+ secondFileId <- expectJust "second location file id"
+ (locFileId (onlyAxiomLocation second))
+ assertBool "relocation rebinds locations"
+ (firstFileId /= secondFileId)
+ firstArtifactKey <- expectRight
+ (Semantic.moduleArtifactKey
+ (Module.moduleName (Parse.parsedModuleAddress first))
+ (Parse.parsedModuleId first)
+ []
+ theory)
+ secondArtifactKey <- expectRight
+ (Semantic.moduleArtifactKey
+ (Module.moduleName (Parse.parsedModuleAddress second))
+ (Parse.parsedModuleId second)
+ []
+ theory)
+ assertBool "module artifact remains owner-dependent"
+ (Semantic.moduleArtifactId firstArtifactKey
+ /= Semantic.moduleArtifactId secondArtifactKey)
+ Store.closeStore store
+
+rejectsCorruptedCachedDeclarationAnchor :: Assertion
+rejectsCorruptedCachedDeclarationAnchor =
+ withTemporaryDirectory "felix-parsed-corrupt-anchor" \temp -> do
+ writeBuiltinZeroDefinition
+ (temp Posix.</> "entry.tex")
+ "source_zero"
+ mounts <- oneMount "project" temp
+ request <- expectRight (searchedRoot "entry.tex")
+ foundation <- expectRight Foundation.checkedFoundation
+ let storePath = temp Posix.</> "store.sqlite"
+ theory = Identity.theoryId foundation
+ store <- openTestStore storePath theory
+ cold <- expectParseExecution
+ =<< Parse.parseSourceWorkspaceWithStoreAndSyntaxInputs
+ store mounts request (const [])
+ let parsed = Parse.parsedWorkspaceRootModule cold
+ key = Parse.parsedModuleKey parsed
+ fileId <- case Parse.parsedModuleSyntaxOccurrences parsed of
+ occurrence : _ ->
+ expectJust
+ "parsed occurrence file id"
+ (locFileId
+ (Parse.parsedSyntaxOccurrenceLocation occurrence))
+ [] ->
+ assertFailure "parsed fixed occurrence is absent"
+ >> fail "unreachable"
+ decoded <- expectRight
+ (Parsed.decodeCanonicalParsedPayload
+ fileId
+ (Parse.parsedModulePayload parsed))
+ Store.closeStore store
+ let corruptedOccurrences = case Parsed.decodedParsedOccurrences decoded of
+ (blockIndex, location, _marker, entry) : rest ->
+ (blockIndex, location, "corrupted_anchor", entry) : rest
+ [] ->
+ []
+ corruptedPayload =
+ Parsed.canonicalParsedPayload
+ (Parsed.decodedParsedImports decoded)
+ (Parsed.decodedParsedBlocks decoded)
+ corruptedOccurrences
+ (Parsed.decodedParsedSyntaxInterface decoded)
+ corruptedId =
+ ParsedIdentity.parsedModuleId
+ key
+ (Parsed.canonicalParsedPayloadBytes corruptedPayload)
+ connection <- SQLite.open storePath
+ SQLite.execute connection
+ "UPDATE parsed_artifacts \
+ \SET parsed_module_id = ?, payload = ? \
+ \WHERE parsed_module_key = ?"
+ ( Cache.cacheDigestBytes
+ (ParsedIdentity.parsedModuleIdDigest corruptedId)
+ , Parsed.canonicalParsedPayloadBytes corruptedPayload
+ , Cache.cacheDigestBytes
+ (ParsedIdentity.parsedModuleKeyDigest key)
+ )
+ SQLite.close connection
+ current <- openTestStore storePath theory
+ callbacks <- newIORef (0 :: Int)
+ Parse.parseSourceWorkspaceWithStoreAndSyntaxInputsAndCallback
+ current mounts request (const [])
+ (\_source _block -> modifyIORef' callbacks (+ 1)) >>= \case
+ Left
+ (Parse.ParseExecutionArtifactIntegrityFailure
+ _source
+ (Parse.ParsedArtifactAssociationFailure
+ Parse.SyntaxOccurrenceMarkerMismatch{})) ->
+ pure ()
+ other ->
+ assertFailure
+ ("unexpected corrupted parsed result: " <> show other)
+ assertEqual "corrupt hit invokes no parse callback" 0
+ =<< readIORef callbacks
+ Store.closeStore current
+
+openTestStore :: FilePath -> Identity.TheoryId -> IO Store.Store
+openTestStore path theory =
+ Store.openStore path theory >>= \case
+ Left failure ->
+ assertFailure (show failure) >> fail "unreachable"
+ Right (_startup, store) ->
+ pure store
+
+expectParseExecution
+ :: Either Parse.ParseExecutionError value
+ -> IO value
+expectParseExecution = \case
+ Left failure ->
+ assertFailure (show failure) >> fail "unreachable"
+ Right value ->
+ pure value
+
+parsesSourceGraph :: Assertion
+parsesSourceGraph =
+ withTemporaryDirectory "felix-source-parse" \temp -> do
+ writeTheory (temp Posix.</> "shared.tex") [] "shared"
+ writeTheory (temp Posix.</> "entry.tex") ["shared.tex"] "entry"
+ graph <- buildSearchedGraph temp "entry.tex"
+ emittedRef <- newIORef []
+ workspace <- expectRight =<<
+ Parse.parseResolvedSourceGraphWith graph
+ (\source _block ->
+ modifyIORef'
+ emittedRef
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source) :))
+ assertEqual "two parsed source nodes" 2
+ (length (Parse.parsedWorkspaceModules workspace))
+ assertEqual "one source-local block per node" [1, 1]
+ (toList
+ (length . Parse.parsedModuleBlocks
+ <$> Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ assertEqual "imported-before-importer source order"
+ ["shared.tex", "entry.tex"]
+ (toList
+ (safeRelativePathFilePath
+ . resolvedSourceRelativePath
+ . Parse.parsedModuleResolved
+ <$> Parse.parsedWorkspaceImportedBeforeImporter workspace))
+ assertEqual "flattened block view" 2
+ (length (Parse.importedBeforeImporterBlocks workspace))
+ emitted <- reverse <$> readIORef emittedRef
+ assertEqual "streamed block order"
+ ["shared.tex", "entry.tex"]
+ emitted
+
+rejectsSiblingSyntaxLeakage :: Assertion
+rejectsSiblingSyntaxLeakage =
+ withTemporaryDirectory "felix-source-syntax-world" \temp -> do
+ writeFile
+ (temp Posix.</> "use.tex")
+ (unlines
+ [ "\\begin{axiom}\\label{use}"
+ , " $x$ is special."
+ , "\\end{axiom}"
+ ])
+ writeAdjectiveDefinition
+ (temp Posix.</> "declare.tex")
+ "shared_special"
+ writeTheory
+ (temp Posix.</> "entry.tex")
+ ["use.tex", "declare.tex"]
+ "entry"
+ graph <- buildSearchedGraph temp "entry.tex"
+ result <- Parse.parseResolvedSourceGraph graph
+ case result of
+ Left (Parse.SourceParseError source _parseError) ->
+ assertEqual
+ "syntax consumer fails in its own module"
+ "use.tex"
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source))
+ Left err ->
+ assertFailure
+ ("expected a source-local parse error, got "
+ <> show err)
+ Right workspace ->
+ assertFailure
+ ("sibling syntax leaked into use.tex: "
+ <> show workspace)
+
+parsesSourceFixities :: Assertion
+parsesSourceFixities =
+ withTemporaryDirectory "felix-source-fixity" \temp -> do
+ writeFile
+ (temp Posix.</> "entry.tex")
+ ( syntaxFunctionDefinition
+ "loose"
+ "loose"
+ (Just "%! infixl 0")
+ <> syntaxFunctionDefinition
+ "tight"
+ "tight"
+ (Just "%! infixr 7")
+ <> axiomBlock
+ "loose_associativity"
+ "a\\loose b\\loose c = a"
+ <> axiomBlock
+ "tight_associativity"
+ "a\\tight b\\tight c = a"
+ <> axiomBlock
+ "mixed_precedence"
+ "a\\loose b\\tight c = a"
+ <> axiomBlock
+ "parenthesized_precedence"
+ "(a\\loose b)\\tight c = a"
+ )
+ graph <- buildSearchedGraph temp "entry.tex"
+ workspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph graph
+ let root =
+ Parse.parsedWorkspaceRootModule workspace
+ localEntries =
+ Interface.canonicalSyntaxDeltaEntries
+ (Interface.moduleSyntaxLocalDelta
+ (Parse.parsedModuleSyntaxInterface root))
+ assertExpressionFixity
+ "loose"
+ Raw.LeftAssoc
+ 0
+ localEntries
+ assertExpressionFixity
+ "tight"
+ Raw.RightAssoc
+ 7
+ localEntries
+ assertEqual
+ "source declaration occurrences"
+ [0, 1]
+ (Parse.parsedSyntaxOccurrenceBlockIndex
+ <$> Parse.parsedModuleSyntaxOccurrences root)
+ case drop 2 (Parse.parsedModuleBlocks root) of
+ [ looseAssociativity
+ , tightAssociativity
+ , mixedPrecedence
+ , parenthesizedPrecedence
+ ] -> do
+ assertAxiomLeftShape
+ "left associativity"
+ "loose(loose(a,b),c)"
+ looseAssociativity
+ assertAxiomLeftShape
+ "right associativity"
+ "tight(a,tight(b,c))"
+ tightAssociativity
+ assertAxiomLeftShape
+ "mixed precedence"
+ "loose(a,tight(b,c))"
+ mixedPrecedence
+ assertAxiomLeftShape
+ "parentheses override precedence"
+ "tight(loose(a,b),c)"
+ parenthesizedPrecedence
+ blocks ->
+ assertFailure
+ ("expected four fixity axioms, got "
+ <> show blocks)
+
+parsesLibraryFixities :: Assertion
+parsesLibraryFixities =
+ withTemporaryDirectory "felix-source-library-fixity" \temp -> do
+ writeFile
+ (temp Posix.</> "entry.tex")
+ ( syntaxFunctionDefinition
+ "cdot"
+ "cdot"
+ (Just "%! infixl 4")
+ <> syntaxFunctionDefinition
+ "symdiff"
+ "symdiff"
+ (Just "%! infixl 1")
+ <> axiomBlock
+ "cdot_associativity"
+ "a\\cdot b\\cdot c = a"
+ <> axiomBlock
+ "symdiff_associativity"
+ "a\\symdiff b\\symdiff c = a"
+ <> axiomBlock
+ "library_mixed_precedence"
+ "a\\symdiff b\\cdot c = a"
+ <> axiomBlock
+ "library_parentheses"
+ "(a\\symdiff b)\\cdot c = a"
+ )
+ graph <- buildSearchedGraph temp "entry.tex"
+ workspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph graph
+ case drop 2
+ (Parse.parsedModuleBlocks
+ (Parse.parsedWorkspaceRootModule workspace)) of
+ [ cdotAssociativity
+ , symdiffAssociativity
+ , mixedPrecedence
+ , parenthesizedPrecedence
+ ] -> do
+ assertAxiomLeftShape
+ "cdot left associativity"
+ "cdot(cdot(a,b),c)"
+ cdotAssociativity
+ assertAxiomLeftShape
+ "symdiff left associativity"
+ "symdiff(symdiff(a,b),c)"
+ symdiffAssociativity
+ assertAxiomLeftShape
+ "cdot binds tighter than symdiff"
+ "symdiff(a,cdot(b,c))"
+ mixedPrecedence
+ assertAxiomLeftShape
+ "library parentheses override precedence"
+ "cdot(symdiff(a,b),c)"
+ parenthesizedPrecedence
+ blocks ->
+ assertFailure
+ ("expected four library-fixity axioms, got "
+ <> show blocks)
+
+validatesSourcePragmaAssociations :: Assertion
+validatesSourcePragmaAssociations =
+ forM_ cases \(description, contents, checkProblem) ->
+ withTemporaryDirectory
+ ("felix-source-pragma-" <> description)
+ \temp -> do
+ writeFile
+ (temp Posix.</> "entry.tex")
+ contents
+ graph <- buildSearchedGraph temp "entry.tex"
+ result <- Parse.parseResolvedSourceGraph graph
+ case result of
+ Left
+ (Parse.SourceSyntaxDeclarationError
+ source
+ problem) -> do
+ assertEqual
+ "pragma source"
+ "entry.tex"
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source))
+ checkProblem problem
+ Left err ->
+ assertFailure
+ ("expected source pragma error, got "
+ <> show err)
+ Right workspace ->
+ assertFailure
+ ("expected source pragma rejection, got "
+ <> show workspace)
+ where
+ cases
+ :: [ ( String
+ , String
+ , Parse.SyntaxDeclarationError -> Assertion
+ )
+ ]
+ cases =
+ [ ( "outside"
+ , "%! infixl 1\n" <> theoryBlock "outside"
+ , \case
+ Parse.SyntaxPragmaOutsideDeclaration{} ->
+ pure ()
+ problem ->
+ unexpected "outside-declaration pragma" problem
+ )
+ , ( "inside-nonsyntax"
+ , unlines
+ [ "\\begin{axiom}\\label{inside_nonsyntax}"
+ , " %! infixl 1"
+ , " $x = x$."
+ , "\\end{axiom}"
+ ]
+ , \case
+ Parse.SyntaxPragmaOutsideDeclaration location ->
+ assertEqual
+ "pragma in non-syntax chunk"
+ 2
+ (locLine location)
+ problem ->
+ unexpected "non-syntax declaration pragma" problem
+ )
+ , ( "missing"
+ , syntaxFunctionDefinition
+ "missing"
+ "missing"
+ Nothing
+ , \case
+ Parse.MissingSyntaxPragma{} ->
+ pure ()
+ problem ->
+ unexpected "missing pragma" problem
+ )
+ , ( "duplicate"
+ , unlines
+ [ "\\begin{abbreviation}\\label{duplicate}"
+ , " %! infixl 1"
+ , " %! infixl 1"
+ , " $x\\duplicate y = x$."
+ , "\\end{abbreviation}"
+ ]
+ , \case
+ Parse.DuplicateSyntaxPragma{} ->
+ pure ()
+ problem ->
+ unexpected "duplicate pragma" problem
+ )
+ , ( "irrelevant"
+ , unlines
+ [ "\\begin{definition}\\label{irrelevant}"
+ , " %! infixl 1"
+ , " $x$ is irrelevant iff $x = x$."
+ , "\\end{definition}"
+ ]
+ , \case
+ Parse.IrrelevantSyntaxPragma{} ->
+ pure ()
+ problem ->
+ unexpected "irrelevant pragma" problem
+ )
+ , ( "multiple-without-pragma"
+ , unlines
+ [ "\\begin{datatype}\\label{multiple_patterns}"
+ , " Define $\\patternkind$ inductively as follows."
+ , " \\begin{enumerate}"
+ , " \\item $(x \\firstpattern y) \\in \\patternkind$."
+ , " \\item $(x \\secondpattern y) \\in \\patternkind$."
+ , " \\end{enumerate}"
+ , "\\end{datatype}"
+ ]
+ , \case
+ problem@(Parse.MultipleNewSyntaxPatternsWithoutPragma
+ location
+ patterns) -> do
+ assertEqual
+ "first new pattern location"
+ 4
+ (locLine location)
+ assertEqual
+ "new pattern count"
+ 2
+ (NonEmpty.length patterns)
+ assertBool
+ "accurate multiple-pattern message"
+ ("several new eligible patterns that V1 cannot select between"
+ `List.isInfixOf` show problem)
+ assertBool
+ "message requires an unambiguous declaration"
+ ("make the declaration unambiguous"
+ `List.isInfixOf` show problem)
+ problem ->
+ unexpected "multiple unannotated patterns" problem
+ )
+ , ( "fixed"
+ , unlines
+ [ "\\begin{abbreviation}\\label{local_addition}"
+ , " %! infixl 1"
+ , " $x + y = x$."
+ , "\\end{abbreviation}"
+ ]
+ , \case
+ Parse.SyntaxPragmaOnFixedReuse{} ->
+ pure ()
+ problem ->
+ unexpected "fixed-base pragma" problem
+ )
+ ]
+
+ unexpected expected problem =
+ assertFailure
+ ("expected " <> expected <> ", got " <> show problem)
+
+rejectsFixedBaseCategoryMismatch :: Assertion
+rejectsFixedBaseCategoryMismatch =
+ withTemporaryDirectory "felix-source-fixed-category" \temp -> do
+ writeFile
+ (temp Posix.</> "entry.tex")
+ (unlines
+ [ "\\begin{definition}\\label{local_add_relation}"
+ , " $x + y$ iff $x = y$."
+ , "\\end{definition}"
+ ])
+ graph <- buildSearchedGraph temp "entry.tex"
+ collision <- expectLexiconCollision
+ =<< Parse.parseResolvedSourceGraph graph
+ assertEqual
+ "fixed collision pattern"
+ (Raw.HoleCons
+ (Raw.TokenCons
+ (Raw.Symbol "+")
+ (Raw.HoleCons Raw.End)))
+ (Parse.lexiconCollisionPattern collision)
+ case toList (Parse.lexiconCollisionOrigins collision) of
+ [ Parse.FixedLexiconOrigin
+ Interface.CanonicalExpressionFunction{}
+ , Parse.SourceLexiconOrigin
+ Interface.CanonicalRelation{}
+ source
+ location
+ ] -> do
+ assertEqual
+ "local collision source"
+ "entry.tex"
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source))
+ assertLocation
+ "local collision declaration"
+ "entry.tex"
+ 1
+ location
+ origins ->
+ assertFailure
+ ("expected fixed/source category origins, got "
+ <> show origins)
+
+retainsMultiItemSyntaxOccurrences :: Assertion
+retainsMultiItemSyntaxOccurrences =
+ withTemporaryDirectory "felix-source-multi-item-syntax" \temp -> do
+ writeFile
+ (temp Posix.</> "entry.tex")
+ (unlines
+ [ "\\begin{datatype}\\label{multi_item}"
+ , " Define $\\itemkind$ inductively as follows."
+ , " \\begin{enumerate}"
+ , " \\item $\\itemzero \\in \\itemkind$."
+ , " \\item $\\itemsucc{x} \\in \\itemkind$ for $x \\in \\itemkind$."
+ , " \\end{enumerate}"
+ , "\\end{datatype}"
+ ])
+ graph <- buildSearchedGraph temp "entry.tex"
+ workspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph graph
+ let root =
+ Parse.parsedWorkspaceRootModule workspace
+ occurrences =
+ Parse.parsedModuleSyntaxOccurrences root
+ summarize occurrence =
+ case Parse.parsedSyntaxOccurrenceEntry occurrence of
+ Interface.CanonicalExpressionFunction
+ _pattern
+ marker
+ _fixity ->
+ Right
+ ( Parse.parsedSyntaxOccurrenceBlockIndex
+ occurrence
+ , locLine
+ (Parse.parsedSyntaxOccurrenceLocation
+ occurrence)
+ , Parse.parsedSyntaxOccurrenceMarker occurrence
+ , marker
+ )
+ entry ->
+ Left entry
+ case traverse summarize occurrences of
+ Right summaries ->
+ assertEqual
+ "block association and scanner order"
+ [ (0, 2, "multi_item", "multi_item")
+ , (0, 4, "itemzero", "itemzero")
+ , (0, 5, "itemsucc", "itemsucc")
+ ]
+ summaries
+ Left entry ->
+ assertFailure
+ ("expected an expression occurrence, got "
+ <> show entry)
+ case (Parse.parsedModuleBlocks root, occurrences) of
+ ( Raw.BlockData _location _title blockMarker _datatype : _
+ , firstOccurrence : _
+ ) ->
+ assertEqual
+ "first occurrence is the declaration-head anchor"
+ blockMarker
+ (Parse.parsedSyntaxOccurrenceMarker firstOccurrence)
+ _ ->
+ assertFailure "expected a datatype block and its occurrences"
+
+propagatesImportedSyntax :: Assertion
+propagatesImportedSyntax =
+ withTemporaryDirectory "felix-source-syntax-diamond" \temp -> do
+ writeFile
+ (temp Posix.</> "base.tex")
+ (syntaxFunctionDefinition
+ "star"
+ "star"
+ (Just "%! infixl 3"))
+ writeFile
+ (temp Posix.</> "left.tex")
+ ("\\import{base.tex}\n"
+ <> syntaxFunctionDefinition
+ "star"
+ "star"
+ Nothing)
+ writeTheory
+ (temp Posix.</> "right.tex")
+ ["base.tex"]
+ "right"
+ writeFile
+ (temp Posix.</> "entry.tex")
+ (unlines
+ [ "\\import{left.tex}"
+ , "\\import{right.tex}"
+ ]
+ <> axiomBlock
+ "imported_use"
+ "a\\star b\\star c = a")
+ graph <- buildSearchedGraph temp "entry.tex"
+ workspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph graph
+ baseModule <- findParsedModule "base.tex" workspace
+ leftModule <- findParsedModule "left.tex" workspace
+ rightModule <- findParsedModule "right.tex" workspace
+ let root =
+ Parse.parsedWorkspaceRootModule workspace
+ interface =
+ Parse.parsedModuleSyntaxInterface
+ localEntries parsed =
+ Interface.canonicalSyntaxDeltaEntries
+ (Interface.moduleSyntaxLocalDelta
+ (interface parsed))
+ assertEqual "base exports one syntax entry"
+ 1
+ (length (localEntries baseModule))
+ assertEqual "imported reuse emits no local entry"
+ []
+ (localEntries leftModule)
+ assertEqual "imported reuse retains its occurrence"
+ 1
+ (length
+ (Parse.parsedModuleSyntaxOccurrences leftModule))
+ assertEqual "empty diamond branch has no occurrence"
+ []
+ (Parse.parsedModuleSyntaxOccurrences rightModule)
+ assertEqual "equal diamond interfaces"
+ (Interface.moduleSyntaxAssertedId
+ (interface leftModule))
+ (Interface.moduleSyntaxAssertedId
+ (interface rightModule))
+ assertEqual "root coalesces equal direct interfaces"
+ 1
+ (length
+ (Interface.moduleSyntaxDirectInputs
+ (interface root)))
+ case Parse.parsedModuleBlocks root of
+ [block] ->
+ assertAxiomLeftShape
+ "imported left associativity"
+ "star(star(a,b),c)"
+ block
+ blocks ->
+ assertFailure
+ ("expected one imported-syntax axiom, got "
+ <> show blocks)
+ writeFile
+ (temp Posix.</> "left.tex")
+ ("\\import{base.tex}\n"
+ <> syntaxFunctionDefinition
+ "star"
+ "star"
+ (Just "%! infixl 3"))
+ reuseGraph <- buildSearchedGraph temp "left.tex"
+ reuseResult <-
+ Parse.parseResolvedSourceGraph reuseGraph
+ case reuseResult of
+ Left
+ (Parse.SourceSyntaxDeclarationError
+ _source
+ Parse.SyntaxPragmaOnImportedReuse{}) ->
+ pure ()
+ Left err ->
+ assertFailure
+ ("expected imported-reuse pragma rejection, got "
+ <> show err)
+ Right reused ->
+ assertFailure
+ ("expected imported-reuse pragma rejection, got "
+ <> show reused)
+
+rejectsUnequalImportedSyntax :: Assertion
+rejectsUnequalImportedSyntax =
+ forM_ cases \(description, leftDefinition, rightDefinition) ->
+ withTemporaryDirectory
+ ("felix-source-imported-collision-" <> description)
+ \temp -> do
+ writeFile
+ (temp Posix.</> "a.tex")
+ leftDefinition
+ writeFile
+ (temp Posix.</> "b.tex")
+ rightDefinition
+ writeTheory
+ (temp Posix.</> "entry.tex")
+ ["a.tex", "b.tex"]
+ "entry"
+ graph <- buildSearchedGraph temp "entry.tex"
+ collision <- expectLexiconCollision
+ =<< Parse.parseResolvedSourceGraph graph
+ (firstLocation, secondLocation) <-
+ expectTwoCollisionLocations collision
+ assertLocation
+ "first imported declaration"
+ "a.tex"
+ 1
+ firstLocation
+ assertLocation
+ "second imported declaration"
+ "b.tex"
+ 1
+ secondLocation
+ where
+ cases =
+ [ ( "marker"
+ , syntaxFunctionDefinition
+ "clash_left"
+ "clash"
+ (Just "%! infixl 2")
+ , syntaxFunctionDefinition
+ "clash_right"
+ "clash"
+ (Just "%! infixl 2")
+ )
+ , ( "fixity"
+ , syntaxFunctionDefinition
+ "clash"
+ "clash"
+ (Just "%! infixl 2")
+ , syntaxFunctionDefinition
+ "clash"
+ "clash"
+ (Just "%! infixr 2")
+ )
+ ]
+
+distinguishesPhysicalSourceLocations :: Assertion
+distinguishesPhysicalSourceLocations =
+ withTemporaryDirectory "felix-source-location-identity" \temp -> do
+ let projectRoot = temp Posix.</> "project"
+ libraryRoot = temp Posix.</> "library"
+ projectEntry = projectRoot Posix.</> "entry.tex"
+ libraryEntry = libraryRoot Posix.</> "entry.tex"
+ Directory.createDirectory projectRoot
+ Directory.createDirectory libraryRoot
+ writeFile projectEntry
+ ("\\import{entry.tex}\n"
+ <> adjectiveDefinition "project_adjective")
+ writeNounDefinition libraryEntry "library_noun"
+ mounts <- expectRight =<< prepareSourceMounts
+ [ (sourceMountId "library", libraryRoot)
+ , (sourceMountId "project", projectRoot)
+ ]
+ request <- expectRight =<< existingRoot projectEntry
+ graph <- expectRight =<< buildResolvedSourceGraph mounts request
+ collision <- expectLexiconCollision
+ =<< Parse.parseResolvedSourceGraph graph
+ assertEqual "normalized cross-category pattern"
+ (Raw.TokenCons (Raw.Word "special") Raw.End)
+ (Parse.lexiconCollisionPattern collision)
+ (libraryLocation, projectLocation) <-
+ expectTwoCollisionLocations collision
+ assertEqual "accepted display path"
+ "entry.tex"
+ (locFile libraryLocation)
+ assertEqual "accepted declaration line"
+ 1
+ (locLine libraryLocation)
+ assertEqual "colliding display path"
+ "entry.tex"
+ (locFile projectLocation)
+ assertEqual "colliding declaration line"
+ 2
+ (locLine projectLocation)
+ canonicalProject <- Directory.canonicalizePath projectEntry
+ canonicalLibrary <- Directory.canonicalizePath libraryEntry
+ let rendered = show collision
+ quotedLibrary = show canonicalLibrary
+ quotedProject = show canonicalProject
+ assertBool "rendered error includes accepted canonical path"
+ (quotedLibrary `List.isInfixOf` rendered)
+ assertBool "rendered error includes colliding canonical path"
+ (quotedProject `List.isInfixOf` rendered)
+ assertBool "canonical locations render in declaration order"
+ (substringIndex quotedLibrary rendered
+ < substringIndex quotedProject rendered)
+
+retainsWorkspaceLocationDisplayPath :: Assertion
+retainsWorkspaceLocationDisplayPath =
+ withTemporaryDirectory "felix-source-location-display" \temp -> do
+ let nested = temp Posix.</> "nested"
+ entry = nested Posix.</> "entry.tex"
+ Directory.createDirectory nested
+ writeTheory entry [] "entry"
+ outerMounts <- oneMount "project" temp
+ outerRequest <- expectRight (searchedRoot "nested/entry.tex")
+ outerGraph <- expectRight =<<
+ buildResolvedSourceGraph outerMounts outerRequest
+ outerWorkspace <- expectRight =<<
+ Parse.parseResolvedSourceGraph outerGraph
+ innerMounts <- oneMount "library" nested
+ innerRequest <- expectRight (searchedRoot "entry.tex")
+ innerGraph <- expectRight =<<
+ buildResolvedSourceGraph innerMounts innerRequest
+ innerWorkspace <- expectRight =<<
+ Parse.parseResolvedSourceGraph innerGraph
+ let outerLocation =
+ onlyAxiomLocation
+ (Parse.parsedWorkspaceRootModule outerWorkspace)
+ innerLocation =
+ onlyAxiomLocation
+ (Parse.parsedWorkspaceRootModule innerWorkspace)
+ assertEqual "outer-mount display path"
+ "nested/entry.tex"
+ (locFile outerLocation)
+ assertEqual "more-specific-mount display path"
+ "entry.tex"
+ (locFile innerLocation)
+ outerFileId <- expectJust "outer workspace file id"
+ (locFileId outerLocation)
+ innerFileId <- expectJust "inner workspace file id"
+ (locFileId innerLocation)
+ assertBool "distinct display registrations use distinct file ids"
+ (outerFileId /= innerFileId)
+ canonicalEntry <- Directory.canonicalizePath entry
+ assertEqual "outer physical location key"
+ (Just canonicalEntry)
+ (lookupFileIdentityPath outerFileId)
+ assertEqual "inner physical location key"
+ (Just canonicalEntry)
+ (lookupFileIdentityPath innerFileId)
+
+reportsImportedScannerErrorFirst :: Assertion
+reportsImportedScannerErrorFirst =
+ withTemporaryDirectory "felix-source-lexer-error-order" \temp -> do
+ let scannerFailure = unlines
+ [ "\\begin{abbreviation}\\label{malformed_function}"
+ , " $x = \\emptyset$."
+ , "\\end{abbreviation}"
+ ]
+ tokenizerFailure = unlines
+ [ "\\begin{axiom}"
+ , "#"
+ , "\\end{axiom}"
+ ]
+ writeFile
+ (temp Posix.</> "imported.tex")
+ scannerFailure
+ writeFile
+ (temp Posix.</> "entry.tex")
+ ("\\import{imported.tex}\n" <> tokenizerFailure)
+ graph <- buildSearchedGraph temp "entry.tex"
+ result <- Parse.parseResolvedSourceGraph graph
+ case result of
+ Left
+ (Parse.SourceParseError
+ source
+ (Parse.LexicalScanFailure
+ Adapt.InvalidFunctionPattern{})) ->
+ assertEqual
+ "dependency scanner error"
+ "imported.tex"
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source))
+ Left err ->
+ assertFailure
+ ("expected imported scanner error, got " <> show err)
+ Right workspace ->
+ assertFailure
+ ("expected imported scanner error, got "
+ <> show workspace)
+
+reportsMalformedLexicalDeclaration :: Assertion
+reportsMalformedLexicalDeclaration =
+ withTemporaryDirectory "felix-source-malformed-lexical" \temp -> do
+ writeFile
+ (temp Posix.</> "entry.tex")
+ (unlines
+ [ "\\begin{abbreviation}\\label{malformed_function}"
+ , " $x = \\emptyset$."
+ , "\\end{abbreviation}"
+ ])
+ graph <- buildSearchedGraph temp "entry.tex"
+ result <- Parse.parseResolvedSourceGraph graph
+ void (evaluate (length (show result)))
+ case result of
+ Left
+ (Parse.SourceParseError
+ source
+ (Parse.LexicalScanFailure
+ (Adapt.InvalidFunctionPattern
+ location
+ Adapt.FunctionPatternBareVariable))) -> do
+ assertEqual "malformed source"
+ "entry.tex"
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source))
+ assertLocation
+ "malformed declaration"
+ "entry.tex"
+ 1
+ location
+ Left err ->
+ assertFailure
+ ("expected typed lexical scan failure, got " <> show err)
+ Right workspace ->
+ assertFailure
+ ("expected typed lexical scan failure, got "
+ <> show workspace)
+
+rejectsMalformedInductivePattern :: Assertion
+rejectsMalformedInductivePattern =
+ case runLexer (FileId 0) "inductive.tex" source of
+ Left err ->
+ assertFailure ("could not tokenize fixture: " <> show err)
+ Right (_imports, [chunk]) ->
+ case Adapt.scanChunk chunk of
+ Left
+ (Adapt.InvalidFunctionPattern
+ _location
+ Adapt.FunctionPatternBareVariable) ->
+ pure ()
+ Left err ->
+ assertFailure
+ ("expected bare-variable scan failure, got "
+ <> show err)
+ Right scans ->
+ assertFailure
+ ("expected bare-variable scan failure, got "
+ <> show scans)
+ Right (_imports, chunks) ->
+ assertFailure
+ ("expected one lexical chunk, got " <> show (length chunks))
+ where
+ source =
+ Text.pack
+ (unlines
+ [ "\\begin{inductive}\\label{malformed_inductive}"
+ , " Define $x\\subseteq\\pow{x}$ inductively."
+ , "\\end{inductive}"
+ ])
+
+acceptsAdjectiveSignature :: Assertion
+acceptsAdjectiveSignature =
+ withTemporaryDirectory "felix-source-signature-adjective" \temp -> do
+ writeFile
+ (temp Posix.</> "entry.tex")
+ (unlines
+ [ "\\begin{signature}\\label{reflexive_signature}"
+ , " Suppose $A$ is a set."
+ , " Then $x$ can be reflexive."
+ , "\\end{signature}"
+ , "\\begin{axiom}\\label{reflexive_use}"
+ , " $x$ is reflexive."
+ , "\\end{axiom}"
+ ])
+ graph <- buildSearchedGraph temp "entry.tex"
+ workspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph graph
+ let blocks = Parse.importedBeforeImporterBlocks workspace
+ case blocks of
+ [ Raw.BlockSig
+ _signatureLocation
+ _signatureTitle
+ _signatureMarker
+ [_signatureAssumption]
+ (Raw.SignatureAdj
+ _variable
+ (Raw.Adj _adjectiveLocation declaredAdjective []))
+ , Raw.BlockAxiom{}
+ ] -> do
+ assertEqual
+ "signature marker enters the lexicon"
+ "reflexive_signature"
+ (Raw.lexicalItemMarker declaredAdjective)
+ _ ->
+ assertFailure
+ ("unexpected adjective-signature blocks: " <> show blocks)
+
+rejectsMalformedSignatureHead :: Assertion
+rejectsMalformedSignatureHead = do
+ case runLexer
+ (FileId 49)
+ "malformed-signature.tex"
+ (Text.unlines
+ [ "\\begin{signature}\\label{bad_signature}"
+ , " $x$ can be."
+ , "\\end{signature}"
+ ]) of
+ Left err ->
+ assertFailure ("unexpected token error: " <> show err)
+ Right (_imports, [chunk]) ->
+ case Adapt.scanChunk chunk of
+ Left
+ (Adapt.InvalidFunctionPattern
+ location
+ Adapt.FunctionPatternBareVariable) -> do
+ assertEqual "error line" 1 (locLine location)
+ assertEqual "error column" 1 (locColumn location)
+ Left err ->
+ assertFailure
+ ("expected malformed signature error, got " <> show err)
+ Right scans ->
+ assertFailure
+ ("expected malformed signature rejection, got "
+ <> show scans)
+ Right (_imports, chunks) ->
+ assertFailure
+ ("expected one malformed signature chunk, got "
+ <> show (length chunks))
+
+reportsSameSourceLexiconCollision :: Assertion
+reportsSameSourceLexiconCollision =
+ withTemporaryDirectory "felix-source-local-lexicon-collision" \temp -> do
+ writeFile
+ (temp Posix.</> "entry.tex")
+ (unlines
+ [ "\\begin{struct}\\label{duplicate_operations}"
+ , " A \\duplicateop $X$ is equipped with"
+ , " \\begin{enumerate}"
+ , " \\item $\\duplicateop$"
+ , " \\end{enumerate}"
+ , "\\end{struct}"
+ ])
+ graph <- buildSearchedGraph temp "entry.tex"
+ collision <- expectLexiconCollision
+ =<< Parse.parseResolvedSourceGraph graph
+ (firstLocation, secondLocation) <-
+ expectTwoCollisionLocations collision
+ assertEqual "first declaration file"
+ "entry.tex"
+ (locFile firstLocation)
+ assertEqual "first declaration line" 1 (locLine firstLocation)
+ assertEqual "colliding declaration file"
+ "entry.tex"
+ (locFile secondLocation)
+ assertEqual "colliding declaration line" 4 (locLine secondLocation)
+ assertBool "declarations have distinct locations"
+ (firstLocation /= secondLocation)
+
+acceptsBuiltinSourceDeclaration :: Assertion
+acceptsBuiltinSourceDeclaration =
+ withTemporaryDirectory "felix-source-builtin-declaration" \temp -> do
+ writeBuiltinZeroDefinition
+ (temp Posix.</> "entry.tex")
+ "source_zero"
+ graph <- buildSearchedGraph temp "entry.tex"
+ workspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph graph
+ let root =
+ Parse.parsedWorkspaceRootModule workspace
+ assertEqual
+ "fixed reuse emits no local syntax"
+ []
+ (Interface.canonicalSyntaxDeltaEntries
+ (Interface.moduleSyntaxLocalDelta
+ (Parse.parsedModuleSyntaxInterface root)))
+ case Parse.parsedModuleBlocks root of
+ [Raw.BlockAbbr
+ _location
+ _title
+ blockMarker
+ (Raw.AbbreviationEq
+ (Raw.SymbolPattern
+ (Raw.MixfixItem
+ _pattern
+ symbolMarker
+ _associativity)
+ [])
+ _expression)] -> do
+ assertEqual
+ "declaration label remains independent"
+ "source_zero"
+ blockMarker
+ assertEqual
+ "built-in marker remains authoritative"
+ "zero"
+ symbolMarker
+ case Parse.parsedModuleSyntaxOccurrences root of
+ [occurrence] ->
+ case Parse.parsedSyntaxOccurrenceEntry occurrence of
+ Interface.CanonicalExpressionFunction
+ _pattern
+ occurrenceMarker
+ _fixity -> do
+ assertEqual
+ "occurrence retains source marker"
+ "source_zero"
+ (Parse.parsedSyntaxOccurrenceMarker
+ occurrence)
+ assertEqual
+ "occurrence uses fixed marker"
+ "zero"
+ occurrenceMarker
+ fileId <- expectJust
+ "fixed occurrence file id"
+ (locFileId
+ (Parse.parsedSyntaxOccurrenceLocation
+ occurrence))
+ decoded <- expectRight
+ (Parsed.decodeCanonicalParsedPayload
+ fileId
+ (Parse.parsedModulePayload root))
+ case Parsed.decodedParsedOccurrences decoded of
+ [ ( _blockIndex
+ , _location
+ , storedMarker
+ , Interface.CanonicalExpressionFunction
+ _storedPattern
+ storedEntryMarker
+ _storedFixity
+ )
+ ] -> do
+ assertEqual
+ "payload source marker"
+ "source_zero"
+ storedMarker
+ assertEqual
+ "payload authoritative marker"
+ "zero"
+ storedEntryMarker
+ stored ->
+ assertFailure
+ ("unexpected decoded fixed occurrence: "
+ <> show stored)
+ entry ->
+ assertFailure
+ ("unexpected fixed occurrence: "
+ <> show entry)
+ occurrences ->
+ assertFailure
+ ("unexpected fixed occurrences: "
+ <> show occurrences)
+ blocks ->
+ assertFailure
+ ("unexpected built-in declaration parse: "
+ <> show blocks)
+
+acceptsBuiltinPrefixPredicateDeclaration :: Assertion
+acceptsBuiltinPrefixPredicateDeclaration =
+ withTemporaryDirectory "felix-source-builtin-prefix" \temp -> do
+ writeBuiltinCongDefinition
+ (temp Posix.</> "entry.tex")
+ graph <- buildSearchedGraph temp "entry.tex"
+ workspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph graph
+ case Parse.parsedModuleBlocks
+ (Parse.parsedWorkspaceRootModule workspace) of
+ [Raw.BlockDefn
+ _location
+ _title
+ _blockMarker
+ (Raw.Defn
+ _assumptions
+ (Raw.DefnSymbolicPredicate
+ predicate
+ predicateMarker
+ _variables)
+ _statement)] -> do
+ assertEqual "built-in prefix predicate"
+ (Raw.PrefixPredicate "Cong" 4)
+ predicate
+ assertEqual
+ "built-in prefix marker remains authoritative"
+ "cong"
+ predicateMarker
+ blocks ->
+ assertFailure
+ ("unexpected built-in prefix declaration parse: "
+ <> show blocks)
+
+avoidsAliasImportLexiconCollision :: Assertion
+avoidsAliasImportLexiconCollision =
+ withTemporaryDirectory "felix-source-alias-lexicon" \temp -> do
+ let shared = temp Posix.</> "shared.tex"
+ alias = temp Posix.</> "alias.tex"
+ writeAdjectiveDefinition shared "shared_special"
+ Directory.createFileLink shared alias
+ writeFile
+ (temp Posix.</> "entry.tex")
+ (unlines
+ [ "\\import{shared.tex}"
+ , "\\import{shared.tex}"
+ , "\\import{alias.tex}"
+ , "\\begin{axiom}\\label{root}"
+ , " $x$ is special."
+ , "\\end{axiom}"
+ ])
+ graph <- buildSearchedGraph temp "entry.tex"
+ workspace <- expectRight
+ =<< Parse.parseResolvedSourceGraph graph
+ assertEqual "canonical source is parsed once"
+ ["shared.tex", "entry.tex"]
+ (toList
+ ( safeRelativePathFilePath
+ . resolvedSourceRelativePath
+ . Parse.parsedModuleResolved
+ <$> Parse.parsedWorkspaceImportedBeforeImporter workspace
+ ))
+
+parsesWithoutRereading :: Assertion
+parsesWithoutRereading =
+ withTemporaryDirectory "felix-source-no-reread" \temp -> do
+ let shared = temp Posix.</> "shared.tex"
+ entry = temp Posix.</> "entry.tex"
+ writeTheory shared [] "shared"
+ writeTheory entry ["shared.tex"] "entry"
+ graph <- buildSearchedGraph temp "entry.tex"
+ Directory.removeFile entry
+ Directory.removeFile shared
+ firstWorkspace <- expectRight =<< Parse.parseResolvedSourceGraph graph
+ secondWorkspace <- expectRight =<< Parse.parseResolvedSourceGraph graph
+ assertEqual "first flat projection" 2
+ (length (Parse.importedBeforeImporterBlocks firstWorkspace))
+ assertEqual "repeated downstream projection" 2
+ (length (Parse.importedBeforeImporterBlocks secondWorkspace))
+
+returnsSourceParseFailures :: Assertion
+returnsSourceParseFailures =
+ withTemporaryDirectory "felix-source-parse-error" \temp -> do
+ writeFile
+ (temp Posix.</> "entry.tex")
+ (theoryBlock "accepted"
+ <> "\\begin{axiom}\\label{late_failure}\n")
+ graph <- buildSearchedGraph temp "entry.tex"
+ emittedRef <- newIORef []
+ result <-
+ Parse.parseResolvedSourceGraphWith graph
+ (\_source block ->
+ case block of
+ Raw.BlockAxiom _location _title marker _axiom ->
+ modifyIORef' emittedRef (marker :)
+ _ ->
+ assertFailure
+ ("unexpected emitted block: " <> show block))
+ case result of
+ Left (Parse.SourceParseError source _err) -> do
+ assertEqual "failed source" "entry.tex"
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath source))
+ emitted <- reverse <$> readIORef emittedRef
+ assertEqual "completed callbacks before later failure"
+ ["accepted"]
+ emitted
+ Left err ->
+ assertFailure ("expected SourceParseError, got " <> show err)
+ Right workspace ->
+ assertFailure ("expected parse failure, got " <> show workspace)
+
+rejectsGuardedSymbolicDeclarations :: Assertion
+rejectsGuardedSymbolicDeclarations =
+ for_ [("definition", 3 :: Int), ("abbreviation", 2)]
+ \(kind, failureLine) ->
+ withTemporaryDirectory
+ ("felix-guarded-symbolic-" <> kind)
+ \temp -> do
+ let relative = "entry.tex"
+ source = unlines
+ [ "\\begin{" <> kind <> "}\\label{guarded_symbolic}"
+ , " Suppose $\\top$."
+ , " $\\guardedsymbolic{X} = X$."
+ , "\\end{" <> kind <> "}"
+ ]
+ writeFile (temp Posix.</> relative) source
+ graph <- buildSearchedGraph temp relative
+ emittedRef <- newIORef ([] :: [Raw.Block])
+ result <-
+ Parse.parseResolvedSourceGraphWith graph
+ (\_source block -> modifyIORef' emittedRef (block :))
+ case result of
+ Left (Parse.SourceParseError failed parseFailure) -> do
+ assertEqual (kind <> " source") relative
+ (safeRelativePathFilePath
+ (resolvedSourceRelativePath failed))
+ assertBool
+ (kind <> " parse failure retains a located source position: "
+ <> show parseFailure)
+ (("entry.tex " <> show failureLine <> ":")
+ `List.isInfixOf` show parseFailure)
+ assertEqual
+ (kind <> " publishes no completed source block")
+ []
+ =<< readIORef emittedRef
+ Left failure ->
+ assertFailure
+ ("expected guarded-symbolic parse failure, got "
+ <> show failure)
+ Right workspace ->
+ assertFailure
+ ("guarded symbolic " <> kind
+ <> " was silently accepted: " <> show workspace)
+
+buildSearchedGraph :: FilePath -> FilePath -> IO ResolvedSourceGraph
+buildSearchedGraph root path = do
+ mounts <- oneMount "project" root
+ request <- expectRight (searchedRoot path)
+ expectRight =<< buildResolvedSourceGraph mounts request
+
+sourceGraphOrderPaths :: ResolvedSourceGraph -> IO [FilePath]
+sourceGraphOrderPaths graph =
+ pure
+ [ safeRelativePathFilePath
+ (resolvedSourceRelativePath (sourceNodeResolved node))
+ | node <- toList (sourceGraphImportedBeforeImporter graph)
+ ]
+
+sourceNodeCanonicalPathForTest :: SourceNode -> CanonicalPath
+sourceNodeCanonicalPathForTest =
+ resolvedSourceCanonicalPath . sourceNodeResolved
+
+onlyAxiomLocation :: Parse.ParsedModule -> Location
+onlyAxiomLocation node =
+ case Parse.parsedModuleBlocks node of
+ [Raw.BlockAxiom location _title _marker _axiom] ->
+ location
+ blocks ->
+ error ("expected one axiom block, got " <> show blocks)
+
+syntaxFunctionDefinition
+ :: String
+ -> String
+ -> Maybe String
+ -> String
+syntaxFunctionDefinition marker command pragma =
+ unlines
+ ( [ "\\begin{abbreviation}\\label{" <> marker <> "}"
+ ]
+ <> maybe [] (\line -> [" " <> line]) pragma
+ <> [ " $x\\" <> command <> " y = x$."
+ , "\\end{abbreviation}"
+ ]
+ )
+
+axiomBlock :: String -> String -> String
+axiomBlock marker statement =
+ unlines
+ [ "\\begin{axiom}\\label{" <> marker <> "}"
+ , " $" <> statement <> "$."
+ , "\\end{axiom}"
+ ]
+
+assertExpressionFixity
+ :: Text
+ -> Raw.Associativity
+ -> Word8
+ -> [Interface.CanonicalLexicalEntry]
+ -> Assertion
+assertExpressionFixity marker associativity level entries =
+ case
+ [ fixity
+ | Interface.CanonicalExpressionFunction
+ _pattern
+ (Raw.Marker candidate)
+ fixity <-
+ entries
+ , candidate == marker
+ ] of
+ [Interface.Fixity actualAssociativity actualLevel] -> do
+ assertEqual
+ (Text.unpack marker <> " associativity")
+ associativity
+ actualAssociativity
+ assertEqual
+ (Text.unpack marker <> " level")
+ level
+ (Interface.mixfixLevelValue actualLevel)
+ actual ->
+ assertFailure
+ ("expected one fixity for "
+ <> Text.unpack marker
+ <> ", got "
+ <> show actual)
+
+assertAxiomLeftShape
+ :: String
+ -> String
+ -> Raw.Block
+ -> Assertion
+assertAxiomLeftShape description expected block =
+ case block of
+ Raw.BlockAxiom
+ _location
+ _title
+ _marker
+ (Raw.Axiom
+ _assumptions
+ (Raw.StmtFormula
+ (Raw.FormulaChain
+ (Raw.ChainBase
+ (expression :| [])
+ _sign
+ _relation
+ _right)))) ->
+ assertEqual
+ description
+ expected
+ (expressionShape expression)
+ _ ->
+ assertFailure
+ ("expected an axiom with one left expression, got "
+ <> show block)
+
+expressionShape :: Raw.Expr -> String
+expressionShape = \case
+ Raw.ExprVar (Raw.NamedVarAt _location name) ->
+ Text.unpack name
+ Raw.ExprOp
+ _location
+ symbol
+ arguments ->
+ let Raw.Marker marker =
+ Raw.mixfixMarker symbol
+ in
+ Text.unpack marker
+ <> "("
+ <> List.intercalate ","
+ (expressionShape <$> arguments)
+ <> ")"
+ expression ->
+ show expression
+
+findParsedModule
+ :: FilePath
+ -> Parse.ParsedSourceWorkspace
+ -> IO Parse.ParsedModule
+findParsedModule relative workspace =
+ case List.find hasPath
+ (Parse.parsedWorkspaceModules workspace) of
+ Just parsed ->
+ pure parsed
+ Nothing ->
+ assertFailure
+ ("could not find parsed module " <> relative)
+ where
+ hasPath parsed =
+ safeRelativePathFilePath
+ (resolvedSourceRelativePath
+ (Parse.parsedModuleResolved parsed))
+ == relative
+
+writeAdjectiveDefinition :: FilePath -> String -> IO ()
+writeAdjectiveDefinition path marker =
+ writeFile path (adjectiveDefinition marker)
+
+adjectiveDefinition :: String -> String
+adjectiveDefinition marker =
+ unlines
+ [ "\\begin{definition}\\label{" <> marker <> "}"
+ , " $x$ is special iff $x = x$."
+ , "\\end{definition}"
+ ]
+
+writeNounDefinition :: FilePath -> String -> IO ()
+writeNounDefinition path marker =
+ writeFile path
+ (unlines
+ [ "\\begin{definition}\\label{" <> marker <> "}"
+ , " $x$ is a special iff $x = x$."
+ , "\\end{definition}"
+ ])
+
+writeBuiltinZeroDefinition :: FilePath -> String -> IO ()
+writeBuiltinZeroDefinition path marker =
+ writeFile path (builtinZeroDefinition marker)
+
+builtinZeroDefinition :: String -> String
+builtinZeroDefinition marker =
+ unlines
+ [ "\\begin{abbreviation}\\label{" <> marker <> "}"
+ , " $\\zero = \\emptyset$."
+ , "\\end{abbreviation}"
+ ]
+
+writeBuiltinCongDefinition :: FilePath -> IO ()
+writeBuiltinCongDefinition path =
+ writeFile path
+ (unlines
+ [ "\\begin{definition}\\label{source_cong}"
+ , " $\\Cong{x}{y}{z}{w}$ iff $x = x$."
+ , "\\end{definition}"
+ ])
+
+writeTheory :: FilePath -> [FilePath] -> String -> IO ()
+writeTheory path imports label =
+ writeFile path
+ (unlines
+ (["\\import{" <> imported <> "}" | imported <- imports]
+ <> [theoryBlock label]))
+
+theoryBlock :: String -> String
+theoryBlock label =
+ unlines
+ [ "\\begin{axiom}\\label{" <> label <> "}"
+ , " $x = x$."
+ , "\\end{axiom}"
+ ]
+
+oneMount :: Text -> FilePath -> IO SourceMounts
+oneMount ident root =
+ expectRight =<< prepareSourceMounts [(sourceMountId ident, root)]
+
+withTemporaryDirectory :: String -> (FilePath -> IO a) -> IO a
+withTemporaryDirectory template =
+ bracket create Directory.removePathForcibly
+ where
+ create = do
+ systemTemp <- Directory.getTemporaryDirectory
+ (path, handle) <- openTempFile systemTemp template
+ hClose handle
+ Directory.removeFile path
+ Directory.createDirectory path
+ pure path
+
+assertRight :: (Show e, HasCallStack) => Either e a -> Assertion
+assertRight = void . expectRight
+
+expectRight :: (Show e, HasCallStack) => Either e a -> IO a
+expectRight = \case
+ Left err ->
+ assertFailure ("expected Right, got Left " <> show err)
+ Right value ->
+ pure value
+
+expectJust :: HasCallStack => String -> Maybe a -> IO a
+expectJust description = \case
+ Nothing ->
+ assertFailure ("expected " <> description)
+ Just value ->
+ pure value
+
+expectLexiconCollision
+ :: Either Parse.ParseWorkspaceError a
+ -> IO Parse.LexiconCollision
+expectLexiconCollision = \case
+ Left (Parse.SourceLexiconCollision collision) ->
+ pure collision
+ Left err ->
+ assertFailure
+ ("expected SourceLexiconCollision, got " <> show err)
+ Right _value ->
+ assertFailure "expected SourceLexiconCollision, got Right"
+
+expectTwoCollisionLocations
+ :: Parse.LexiconCollision
+ -> IO (Location, Location)
+expectTwoCollisionLocations collision =
+ case Parse.lexiconCollisionDeclarations collision of
+ firstLocation : secondLocation : _ ->
+ pure (firstLocation, secondLocation)
+ locations ->
+ assertFailure
+ ("expected two source collision locations, got "
+ <> show locations)
+
+assertLocation :: String -> FilePath -> Int -> Location -> Assertion
+assertLocation description expectedFile expectedLine location = do
+ assertEqual (description <> " file")
+ expectedFile
+ (locFile location)
+ assertEqual (description <> " line")
+ expectedLine
+ (locLine location)
+ assertEqual (description <> " column")
+ 1
+ (locColumn location)
+
+substringIndex :: String -> String -> Int
+substringIndex needle haystack =
+ fromMaybe maxBound
+ (List.findIndex
+ (List.isPrefixOf needle)
+ (List.tails haystack))
+
+assertLeft :: (Eq e, Eq a, Show e, Show a, HasCallStack) => e -> Either e a -> Assertion
+assertLeft expected actual =
+ assertEqual "expected Left value" (Left expected) actual
diff --git a/source/Felix/Test/Unit/Store.hs b/source/Felix/Test/Unit/Store.hs
new file mode 100644
index 0000000..423204c
--- /dev/null
+++ b/source/Felix/Test/Unit/Store.hs
@@ -0,0 +1,1675 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Felix.Test.Unit.Store (unitTests) where
+
+import Base
+import Felix.Checking.Authority qualified as Authority
+import Felix.Checking.Core qualified as Core
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Checking.Identity qualified as Identity
+import Felix.Checking.Module qualified as Typed
+import Felix.Checking.Semantic qualified as Semantic
+import Felix.Cache.Codec qualified as Cache
+import Felix.Math.Codec
+import Felix.Module
+import Felix.Parsed.Identity qualified as Parsed
+import Felix.Parsed.Payload qualified as ParsedPayload
+import Felix.Source.Content qualified as Content
+import Felix.Source
+import Felix.Store qualified as Store
+import Felix.Provers qualified as Provers
+import Felix.Syntax.Interface qualified as Syntax
+
+import Control.Concurrent (threadDelay)
+import Control.Exception qualified as Exception
+import Data.ByteString qualified as ByteString
+import Data.ByteString.Char8 qualified as ByteString.Char8
+import Data.IORef qualified as IORef
+import Database.SQLite.Simple qualified as SQLite
+import Database.SQLite.Simple.Types (Only(..))
+import System.Directory qualified as Directory
+import System.Environment qualified as Environment
+import System.FilePath.Posix qualified as Posix
+import System.IO.Temp qualified as Temp
+import Test.Tasty
+import Test.Tasty.HUnit
+import UnliftIO.Async (concurrently)
+
+
+unitTests :: TestTree
+unitTests =
+ testGroup "SQLite store"
+ [ testCase "initializes and reopens the current schema"
+ initializesAndReopensCurrentSchema
+ , testCase "serializes invocation-local coordinator access"
+ serializesCoordinatorAccess
+ , testCase "rejects incompatibility without configuring the store"
+ rejectsIncompatibilityWithoutConfiguration
+ , testCase "rejects malformed compatibility metadata"
+ rejectsMalformedCompatibilityMetadata
+ , testCase "rejects a compatible incomplete schema"
+ rejectsCompatibleIncompleteSchema
+ , testCase "round-trips exact parsed artifacts"
+ roundTripsExactParsedArtifacts
+ , testCase "rejects malformed parsed payloads"
+ rejectsMalformedParsedPayloads
+ , testCase "rejects disagreeing parsed artifact identities"
+ rejectsDisagreeingParsedArtifactIdentities
+ , testCase "rejects malformed typed validation rows"
+ rejectsMalformedTypedValidationRows
+ , testCase "publishes a completed prefix before readiness"
+ publishesCompletedPrefixBeforeReadiness
+ , testCase "installs a sealed producer for a cached importer"
+ installsSealedProducerForCachedImporter
+ , testCase "validates exact cached installation inputs"
+ validatesExactCachedInstallationInputs
+ , testCase "rejects invalid cached root authority and closure"
+ rejectsInvalidCachedRootAuthorityAndClosure
+ , testCase "rejects disagreeing module artifact columns"
+ rejectsDisagreeingModuleArtifactColumns
+ , testCase "validates shared closures once per invocation"
+ validatesSharedClosuresOncePerInvocation
+ , testCase "rolls back a failed readiness transaction"
+ rollsBackFailedReadiness
+ , testCase "rolls back an unequal duplicate batch"
+ rollsBackUnequalDuplicateBatch
+ , testCase "rejects malformed canonical payloads"
+ rejectsMalformedCanonicalPayloads
+ , testCase "plans default and explicit persistent stores"
+ plansPersistentStores
+ , testCase "cleans fresh stores on return and exceptions"
+ cleansFreshStores
+ , testCase "does not fall back after fatal startup"
+ doesNotFallBackAfterFatalStartup
+ ]
+
+serializesCoordinatorAccess :: Assertion
+serializesCoordinatorAccess = do
+ coordinator <- Store.newStoreCoordinator
+ active <- IORef.newIORef (0 :: Int)
+ maximumActive <- IORef.newIORef (0 :: Int)
+ let operation =
+ Store.withStoreCoordinator coordinator
+ (Exception.bracket_
+ (IORef.atomicModifyIORef' active
+ (\current ->
+ let next = current + 1
+ in (next, ())))
+ (IORef.atomicModifyIORef' active
+ (\current -> (current - 1, ())))
+ (do
+ current <- IORef.readIORef active
+ IORef.atomicModifyIORef' maximumActive
+ (\observed -> (max current observed, ()))
+ threadDelay 50000))
+ void (concurrently operation operation)
+ IORef.readIORef maximumActive >>= assertEqual "maximum owner count" 1
+
+roundTripsExactParsedArtifacts :: Assertion
+roundTripsExactParsedArtifacts =
+ withStoreFixture "felix-store-parsed" \path theory _fixture -> do
+ (_startup, store) <- expectOpen path theory
+ (key, artifact, unequal) <- makeParsedArtifacts
+ assertEqual "initial exact lookup misses" (Right Nothing)
+ =<< Store.loadParsedArtifact store key
+ assertEqual "published parsed artifact"
+ (Right artifact)
+ =<< Store.writeParsedArtifact store key artifact
+ assertEqual "exact parsed round trip"
+ (Right (Just artifact))
+ =<< Store.loadParsedArtifact store key
+ assertEqual "equal publication is idempotent"
+ (Right artifact)
+ =<< Store.writeParsedArtifact store key artifact
+ Store.writeParsedArtifact store key unequal >>= \case
+ Left Store.StoreRowPayloadMismatch{} ->
+ pure ()
+ other ->
+ assertFailure
+ ("unexpected unequal parsed publication: " <> show other)
+ Store.closeStore store
+
+rejectsMalformedParsedPayloads :: Assertion
+rejectsMalformedParsedPayloads = do
+ check "malformed" (ByteString.singleton 0xff)
+ check "noncanonical" . (<> ByteString.singleton 0x00)
+ =<< parsedPayloadBytes
+ where
+ check label corrupted =
+ withStoreFixture ("felix-store-parsed-" <> label)
+ \path theory _fixture -> do
+ (_startup, store) <- expectOpen path theory
+ (key, artifact, _unequal) <- makeParsedArtifacts
+ _ <- expectRightIO
+ (Store.writeParsedArtifact store key artifact)
+ Store.closeStore store
+ updateParsedPayload path key corrupted
+ (_reopened, current) <- expectOpen path theory
+ Store.loadParsedArtifact current key >>= \case
+ Left Store.StoreRowDecodeFailure{} ->
+ pure ()
+ other ->
+ assertFailure
+ ("unexpected " <> label
+ <> " parsed row result: " <> show other)
+ Store.closeStore current
+
+ parsedPayloadBytes = do
+ (_key, artifact, _unequal) <- makeParsedArtifacts
+ pure
+ (ParsedPayload.canonicalParsedPayloadBytes
+ (ParsedPayload.parsedArtifactPayload artifact))
+
+rejectsDisagreeingParsedArtifactIdentities :: Assertion
+rejectsDisagreeingParsedArtifactIdentities =
+ withStoreFixture "felix-store-parsed-id" \path theory _fixture -> do
+ (_startup, store) <- expectOpen path theory
+ (key, artifact, _unequal) <- makeParsedArtifacts
+ _ <- expectRightIO (Store.writeParsedArtifact store key artifact)
+ Store.closeStore store
+ connection <- SQLite.open path
+ SQLite.execute connection
+ "UPDATE parsed_artifacts SET parsed_module_id = ? \
+ \WHERE parsed_module_key = ?"
+ ( ByteString.replicate 32 0
+ , Cache.cacheDigestBytes (Parsed.parsedModuleKeyDigest key)
+ )
+ SQLite.close connection
+ (_reopened, current) <- expectOpen path theory
+ Store.loadParsedArtifact current key >>= \case
+ Left Store.StoreParsedArtifactIdMismatch ->
+ pure ()
+ other ->
+ assertFailure
+ ("unexpected parsed identity result: " <> show other)
+ Store.closeStore current
+
+makeParsedArtifacts
+ :: IO
+ ( Parsed.ParsedModuleKey
+ , ParsedPayload.ParsedArtifact
+ , ParsedPayload.ParsedArtifact
+ )
+makeParsedArtifacts = do
+ key <- expectRight
+ (Parsed.parsedModuleKey
+ (Content.sourceContentIdBytes "parsed-source")
+ Syntax.baseSyntaxInterfaceId
+ [])
+ emptyDelta <- expectRight (Syntax.canonicalSyntaxDelta [])
+ emptySyntax <- expectRight (Syntax.moduleSyntaxInterface [] emptyDelta)
+ otherDelta <- expectRight
+ (Syntax.canonicalSyntaxDelta
+ [Syntax.CanonicalStructureOperation "other"])
+ otherSyntax <- expectRight
+ (Syntax.moduleSyntaxInterface [] otherDelta)
+ let payload syntax =
+ ParsedPayload.canonicalParsedPayload
+ [] [] [] (Syntax.moduleSyntaxAssertedId syntax)
+ pure
+ ( key
+ , ParsedPayload.parsedArtifact key (payload emptySyntax)
+ , ParsedPayload.parsedArtifact key (payload otherSyntax)
+ )
+
+updateParsedPayload
+ :: FilePath
+ -> Parsed.ParsedModuleKey
+ -> ByteString.ByteString
+ -> IO ()
+updateParsedPayload path key payload = do
+ connection <- SQLite.open path
+ SQLite.execute connection
+ "UPDATE parsed_artifacts SET payload = ? \
+ \WHERE parsed_module_key = ?"
+ ( payload
+ , Cache.cacheDigestBytes (Parsed.parsedModuleKeyDigest key)
+ )
+ SQLite.close connection
+
+rejectsMalformedTypedValidationRows :: Assertion
+rejectsMalformedTypedValidationRows =
+ withStoreFixture "felix-store-malformed-typed" \path theory fixture -> do
+ (_startup, store) <- expectOpen path theory
+ (_owner, prefix, _syntax, _semantic, _key, _artifact, _proposition) <-
+ makeCommittedModule theory fixture
+ batch <- case Declaration.pendingModulePrefixBatches prefix of
+ [one] -> pure one
+ batches ->
+ assertFailure
+ ("unexpected prefix batch count: " <> show (length batches))
+ >> fail "unreachable"
+ proof <- case Declaration.committedBatchProofValidations batch of
+ [one] -> pure one
+ proofs ->
+ assertFailure
+ ("unexpected proof validation count: " <> show (length proofs))
+ >> fail "unreachable"
+ let key = Semantic.proofValidationRecordKey proof
+ certificate = Semantic.proofValidationRecordCertificate proof
+ theorem = Identity.theoremId
+ (Authority.factAuthorityTheorem
+ (Authority.validationTarget certificate))
+ wrongKey = Semantic.proofValidationKey
+ theorem
+ (Semantic.proofSyntaxId "other-row")
+ (Declaration.committedBatchPreviousPrefix batch)
+ wrongProof =
+ Semantic.proofValidationRecord wrongKey certificate
+ expectRightIO (Store.writePendingModulePrefix store prefix)
+ Store.closeStore store
+ connection <- SQLite.open path
+ SQLite.execute connection
+ "UPDATE proof_validations SET payload = ? \
+ \WHERE validation_key = ?"
+ ( Cache.encodeCache
+ (Semantic.putProofValidationRecordCache wrongProof)
+ , Cache.cacheDigestBytes
+ (Semantic.proofValidationKeyDigest key)
+ )
+ SQLite.close connection
+ (_reopened, current) <- expectOpen path theory
+ Store.loadProofValidation current key >>= \case
+ Left Store.StoreValidationRecordKeyMismatch{} -> pure ()
+ Left other ->
+ assertFailure
+ ("unexpected typed key mismatch: " <> show other)
+ Right _ ->
+ assertFailure "typed key mismatch was accepted"
+ Store.closeStore current
+ connection' <- SQLite.open path
+ SQLite.execute connection'
+ "UPDATE proof_validations SET payload = ? \
+ \WHERE validation_key = ?"
+ ( ByteString.singleton 0xff
+ , Cache.cacheDigestBytes
+ (Semantic.proofValidationKeyDigest key)
+ )
+ SQLite.close connection'
+ (_reopenedMalformed, malformed) <- expectOpen path theory
+ Store.loadProofValidation malformed key >>= \case
+ Left Store.StoreRowDecodeFailure{} -> pure ()
+ Left other ->
+ assertFailure
+ ("unexpected malformed typed row: " <> show other)
+ Right _ ->
+ assertFailure "malformed typed row was accepted"
+ Store.closeStore malformed
+
+publishesCompletedPrefixBeforeReadiness :: Assertion
+publishesCompletedPrefixBeforeReadiness =
+ withStoreFixture "felix-store-prefix" \path theory fixture -> do
+ (_startup, store) <- expectOpen path theory
+ (_owner, prefix, syntax, semantic, artifactKey, artifact, proposition) <-
+ makeCommittedModule theory fixture
+ expectRightIO (Store.writePendingModulePrefix store prefix)
+ connection <- SQLite.open path
+ [Only propositionRows] <- SQLite.query connection
+ "SELECT COUNT(*) FROM canonical_propositions \
+ \WHERE proposition_id = ?"
+ (Only
+ (Cache.encodeCache
+ (Identity.putPropositionIdCache
+ (Identity.checkedPropositionId proposition))))
+ :: IO [Only Int]
+ [Only artifactRowsBefore] <- SQLite.query_ connection
+ "SELECT COUNT(*) FROM module_artifacts"
+ :: IO [Only Int]
+ SQLite.close connection
+ assertEqual "completed prefix proposition is visible" 1 propositionRows
+ assertEqual "prefix publication does not publish readiness"
+ 0 artifactRowsBefore
+ expectRightIO
+ (Store.writeSealedModule
+ store
+ prefix
+ [syntax]
+ [semantic]
+ artifact)
+ memo <- Store.newStoreMemo store
+ installation <- expectRightIO
+ (Store.loadCachedModuleInstallation
+ memo
+ store
+ artifactKey
+ (Syntax.moduleSyntaxAssertedId syntax))
+ case installation of
+ Just loaded -> do
+ assertEqual "validated semantic interface" semantic
+ (Store.cachedInstallationSemantic loaded)
+ assertEqual "validated imported proposition count" 1
+ (length (Store.cachedInstallationPropositions loaded))
+ Nothing ->
+ assertFailure "validated module installation was absent"
+ Store.closeStore store
+
+installsSealedProducerForCachedImporter :: Assertion
+installsSealedProducerForCachedImporter =
+ withStoreFixture "felix-store-cached-import" \path theory fixture -> do
+ (_startup, store) <- expectOpen path theory
+ foundation <- expectRight Foundation.checkedFoundation
+ (_owner, prefix, syntax, semantic, artifactKey, artifact, proposition) <-
+ makeCommittedModule theory fixture
+ expectRightIO
+ (Store.writeSealedModule
+ store
+ prefix
+ [syntax]
+ [semantic]
+ artifact)
+ memo <- Store.newStoreMemo store
+ installation <-
+ expectRightIO
+ (Store.loadCachedModuleInstallation
+ memo
+ store
+ artifactKey
+ (Syntax.moduleSyntaxAssertedId syntax))
+ >>= \case
+ Nothing ->
+ assertFailure "sealed producer was not loadable"
+ >> fail "unreachable"
+ Just loaded ->
+ pure loaded
+ cached <- expectRight
+ (Typed.cachedSealedTypedModule
+ foundation
+ []
+ installation)
+ let loadedSemantic = Store.cachedInstallationSemantic installation
+ fingerprint <-
+ case concatMap
+ Semantic.declarationDeltaFacts
+ (Semantic.semanticInterfaceDeclarations loadedSemantic) of
+ [occurrence] ->
+ pure (Semantic.semanticFactFingerprint occurrence)
+ occurrences ->
+ assertFailure
+ ("unexpected cached producer facts: "
+ <> show (length occurrences))
+ >> fail "unreachable"
+ namespaceDigest <- expectRight
+ (hashCanonicalFields
+ "store-cached-import-consumer"
+ ["consumer"])
+ relative <- expectRight (safeRelativePath "consumer.tex")
+ let consumerOwner =
+ moduleNameFromParts
+ (sourceNamespaceIdFromDigest namespaceDigest)
+ relative
+ resolver = Declaration.vampireResolver \_ ->
+ pure
+ (Left
+ (Provers.ProverLaunchFailed
+ "unused"
+ "cached importer does not run Vampire"))
+ result <-
+ (Declaration.runModuleDriver
+ foundation
+ consumerOwner
+ [Semantic.semanticInterfaceAssertedId loadedSemantic]
+ resolver
+ Declaration.FreshValidation
+ do
+ Declaration.importSealedModuleDriver
+ (Typed.sealedTypedModuleEvidence cached)
+ (_value, batch) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "cached-import-consumer") do
+ candidate <- Declaration.reserveCandidate
+ (Declaration.candidateSpec
+ proposition
+ Semantic.SearchIneligible
+ [])
+ Declaration.authorizeOmittedCandidate candidate do
+ _ <- Declaration.useAuthorizedFact fingerprint
+ Declaration.recordOmittedUse
+ pure batch
+ :: IO
+ (Either
+ Declaration.DriverOpenError
+ (Declaration.DriverResult Text
+ Declaration.CommittedDeclarationBatch)))
+ case result of
+ Left failure ->
+ assertFailure ("cached importer could not open: " <> show failure)
+ Right (Declaration.DriverSucceeded _ _ _ _closure) ->
+ pure ()
+ Right (Declaration.DriverFailed failure _prefix) ->
+ assertFailure ("cached importer failed: " <> show failure)
+ Right (Declaration.DriverSealFailed failure _prefix) ->
+ assertFailure ("cached importer did not seal: " <> show failure)
+ Store.closeStore store
+
+validatesExactCachedInstallationInputs :: Assertion
+validatesExactCachedInstallationInputs =
+ withStoreFixture "felix-store-exact-install" \path theory fixture -> do
+ (_startup, store) <- expectOpen path theory
+ (owner, prefix, syntax, semantic, artifactKey, artifact, proposition) <-
+ makeCommittedModule theory fixture
+ _ <- expectRightIO
+ (Store.writeSealedModule
+ store prefix [syntax] [semantic] artifact)
+ otherDelta <- expectRight
+ (Syntax.canonicalSyntaxDelta
+ [Syntax.CanonicalStructureOperation "other-syntax"])
+ otherSyntax <- expectRight
+ (Syntax.moduleSyntaxInterface [] otherDelta)
+ wrongSyntaxMemo <- Store.newStoreMemo store
+ Store.loadCachedModuleInstallation
+ wrongSyntaxMemo
+ store
+ artifactKey
+ (Syntax.moduleSyntaxAssertedId otherSyntax)
+ >>= \case
+ Left Store.StoreModuleArtifactSyntaxMismatch{} -> pure ()
+ _ ->
+ assertFailure "unexpected syntax-input result"
+
+ parent <- expectRight
+ (Semantic.semanticInterface preludeModuleName [] [])
+ mismatched <- expectRight
+ (Semantic.semanticInterface
+ owner
+ [Semantic.semanticInterfaceAssertedId parent]
+ (Semantic.semanticInterfaceDeclarations semantic))
+ mismatchKey <- makeArtifactKey owner theory "direct-mismatch"
+ let mismatchArtifact =
+ Semantic.moduleArtifactResult
+ mismatchKey
+ (Syntax.moduleSyntaxAssertedId syntax)
+ (Semantic.semanticInterfaceAssertedId mismatched)
+ writeRawModuleRows
+ path
+ [fixtureFirstObject fixture]
+ [proposition]
+ syntax
+ [parent, mismatched]
+ mismatchArtifact
+ directMemo <- Store.newStoreMemo store
+ Store.loadCachedModuleInstallation
+ directMemo
+ store
+ mismatchKey
+ (Syntax.moduleSyntaxAssertedId syntax)
+ >>= \case
+ Left Store.StoreModuleArtifactDirectMismatch{} -> pure ()
+ _ ->
+ assertFailure "unexpected direct-input result"
+ Store.closeStore store
+
+rejectsInvalidCachedRootAuthorityAndClosure :: Assertion
+rejectsInvalidCachedRootAuthorityAndClosure =
+ withStoreFixture "felix-store-invalid-install" \path theory fixture -> do
+ (_startup, store) <- expectOpen path theory
+ (owner, _prefix, syntax, semantic, _artifactKey, _artifact, proposition) <-
+ makeCommittedModule theory fixture
+ otherTheory <- expectRight
+ (Cache.decodeCache
+ Identity.getTheoryIdCache
+ (ByteString.replicate 32 0x5a))
+ original <- case Semantic.semanticInterfaceDeclarations semantic of
+ [delta] -> pure delta
+ deltas ->
+ assertFailure
+ ("unexpected declaration count: " <> show (length deltas))
+ >> fail "unreachable"
+ occurrence <- case Semantic.declarationDeltaFacts original of
+ [fact] -> pure fact
+ facts ->
+ assertFailure
+ ("unexpected fact count: " <> show (length facts))
+ >> fail "unreachable"
+ let badAuthority =
+ Authority.factAuthority
+ (Identity.theoremRef
+ otherTheory
+ (Semantic.semanticFactProposition occurrence))
+ (Authority.factAuthoritySafety
+ (Semantic.semanticFactAuthority occurrence))
+ badOccurrence =
+ Semantic.semanticFactOccurrence
+ (Semantic.semanticFactSlot occurrence)
+ badAuthority
+ (Semantic.semanticFactSearchEligibility occurrence)
+ badDelta <- expectRight
+ (Semantic.declarationInterfaceDelta
+ (Semantic.declarationDeltaSlot original)
+ [badOccurrence]
+ (Semantic.declarationDeltaAliases original)
+ (Semantic.declarationDeltaObjects original)
+ (Semantic.declarationDeltaPropositions original)
+ (Semantic.declarationDeltaEnvironment original))
+ badSemantic <- expectRight
+ (Semantic.semanticInterface owner [] [badDelta])
+ badKey <- makeArtifactKey owner theory "bad-authority"
+ let badArtifact =
+ Semantic.moduleArtifactResult
+ badKey
+ (Syntax.moduleSyntaxAssertedId syntax)
+ (Semantic.semanticInterfaceAssertedId badSemantic)
+ writeRawModuleRows
+ path
+ [fixtureFirstObject fixture]
+ [proposition]
+ syntax
+ [badSemantic]
+ badArtifact
+ badMemo <- Store.newStoreMemo store
+ Store.loadCachedModuleInstallation
+ badMemo store badKey (Syntax.moduleSyntaxAssertedId syntax)
+ >>= \case
+ Left Store.StoreImportedOccurrenceValidationFailure{} -> pure ()
+ _ ->
+ assertFailure "unexpected root-authority result"
+
+ childKey <- makeArtifactKey owner theory "missing-late-child"
+ let childArtifact =
+ Semantic.moduleArtifactResult
+ childKey
+ (Syntax.moduleSyntaxAssertedId syntax)
+ (Semantic.semanticInterfaceAssertedId semantic)
+ writeRawModuleRows
+ path
+ [fixtureFirstObject fixture]
+ [proposition]
+ syntax
+ [semantic]
+ childArtifact
+ connection <- SQLite.open path
+ SQLite.execute connection
+ "DELETE FROM canonical_objects WHERE object_id = ?"
+ (Only
+ (Cache.encodeCache
+ (Identity.putObjectIdCache
+ (Identity.assertedObjectId
+ (fixtureFirstObject fixture)))))
+ SQLite.close connection
+ childMemo <- Store.newStoreMemo store
+ Store.loadCachedModuleInstallation
+ childMemo store childKey (Syntax.moduleSyntaxAssertedId syntax)
+ >>= \case
+ Left Store.StoreAssertedChildMissing{} -> pure ()
+ _ ->
+ assertFailure "unexpected missing-child result"
+ Store.closeStore store
+
+rejectsDisagreeingModuleArtifactColumns :: Assertion
+rejectsDisagreeingModuleArtifactColumns =
+ withStoreFixture "felix-store-artifact-columns" \path theory fixture -> do
+ (_startup, store) <- expectOpen path theory
+ (_owner, prefix, syntax, semantic, key, artifact, _proposition) <-
+ makeCommittedModule theory fixture
+ _ <- expectRightIO
+ (Store.writeSealedModule
+ store prefix [syntax] [semantic] artifact)
+ connection <- SQLite.open path
+ SQLite.execute_ connection "PRAGMA foreign_keys = OFF"
+ SQLite.execute connection
+ "UPDATE module_artifacts SET syntax_interface_id = ? \
+ \WHERE module_artifact_id = ?"
+ ( ByteString.replicate 32 0x3c
+ , Cache.cacheDigestBytes
+ (Semantic.moduleArtifactIdDigest
+ (Semantic.moduleArtifactResultId artifact))
+ )
+ SQLite.close connection
+ memo <- Store.newStoreMemo store
+ Store.loadCachedModuleInstallation
+ memo
+ store
+ key
+ (Syntax.moduleSyntaxAssertedId syntax)
+ >>= \case
+ Left Store.StoreModuleArtifactColumnsMismatch -> pure ()
+ Left other ->
+ assertFailure
+ ("unexpected artifact-column load: " <> show other)
+ Right _ ->
+ assertFailure "disagreeing artifact columns were accepted"
+ Store.writeSealedModule
+ store prefix [syntax] [semantic] artifact >>= \case
+ Left Store.StoreRowPayloadMismatch{} -> pure ()
+ other ->
+ assertFailure
+ ("unexpected artifact-column rewrite: " <> show other)
+ Store.closeStore store
+
+validatesSharedClosuresOncePerInvocation :: Assertion
+validatesSharedClosuresOncePerInvocation =
+ withStoreFixture "felix-store-linear-closure" \path theory fixture -> do
+ (_startup, store) <- expectOpen path theory
+ let baseObject = fixtureFirstObject fixture
+ first = transparentSetObject theory baseObject
+ second = transparentSetObject theory first
+ third = transparentSetObject theory second
+ objects = [baseObject, first, second, third]
+ closure <- expectRight
+ (Identity.validateObjectClosure theory objects)
+ proposition <- expectRight
+ (Identity.validatePropositionContent
+ closure
+ (Core.CEq
+ Core.TySet
+ (Core.CGlobal (Identity.assertedObjectId third))
+ (Core.CGlobal (Identity.assertedObjectId third))))
+ baseOwner <- testModuleName "linear-base.tex"
+ leftOwner <- testModuleName "linear-left.tex"
+ rightOwner <- testModuleName "linear-right.tex"
+ rootOwner <- testModuleName "linear-root.tex"
+ let theorem = Identity.theoremRef
+ theory
+ (Identity.checkedPropositionId proposition)
+ occurrence = Semantic.semanticFactOccurrence
+ (Semantic.factSlot baseOwner (localFactOrdinal 0))
+ (Authority.factAuthority
+ theorem Authority.cleanAuthoritySafety)
+ Semantic.SearchEligible
+ baseDelta <- expectRight
+ (Semantic.declarationInterfaceDelta
+ (Semantic.declarationSlot
+ baseOwner
+ (localDeclarationOrdinal 0))
+ [occurrence]
+ []
+ (Identity.assertedObjectId <$> objects)
+ [Identity.checkedPropositionId proposition]
+ Semantic.emptySemanticEnvironmentDelta)
+ baseSemantic <- expectRight
+ (Semantic.semanticInterface baseOwner [] [baseDelta])
+ leftSemantic <- expectRight
+ (Semantic.semanticInterface
+ leftOwner
+ [Semantic.semanticInterfaceAssertedId baseSemantic]
+ [])
+ rightSemantic <- expectRight
+ (Semantic.semanticInterface
+ rightOwner
+ [Semantic.semanticInterfaceAssertedId baseSemantic]
+ [])
+ rootSemantic <- expectRight
+ (Semantic.semanticInterface
+ rootOwner
+ [ Semantic.semanticInterfaceAssertedId leftSemantic
+ , Semantic.semanticInterfaceAssertedId rightSemantic
+ ]
+ [])
+
+ emptyDelta <- expectRight (Syntax.canonicalSyntaxDelta [])
+ leftDelta <- expectRight
+ (Syntax.canonicalSyntaxDelta
+ [Syntax.CanonicalStructureOperation "linear-left"])
+ rightDelta <- expectRight
+ (Syntax.canonicalSyntaxDelta
+ [Syntax.CanonicalStructureOperation "linear-right"])
+ baseSyntax <- expectRight
+ (Syntax.moduleSyntaxInterface [] emptyDelta)
+ leftSyntax <- expectRight
+ (Syntax.moduleSyntaxInterface
+ [Syntax.moduleSyntaxAssertedId baseSyntax]
+ leftDelta)
+ rightSyntax <- expectRight
+ (Syntax.moduleSyntaxInterface
+ [Syntax.moduleSyntaxAssertedId baseSyntax]
+ rightDelta)
+ rootSyntax <- expectRight
+ (Syntax.moduleSyntaxInterface
+ [ Syntax.moduleSyntaxAssertedId leftSyntax
+ , Syntax.moduleSyntaxAssertedId rightSyntax
+ ]
+ emptyDelta)
+
+ rootKey <- makeArtifactKeyWithDirect
+ rootOwner
+ theory
+ [ Semantic.semanticInterfaceAssertedId leftSemantic
+ , Semantic.semanticInterfaceAssertedId rightSemantic
+ ]
+ "linear-root"
+ leftKey <- makeArtifactKeyWithDirect
+ leftOwner
+ theory
+ [Semantic.semanticInterfaceAssertedId baseSemantic]
+ "linear-left"
+ let rootArtifact = Semantic.moduleArtifactResult
+ rootKey
+ (Syntax.moduleSyntaxAssertedId rootSyntax)
+ (Semantic.semanticInterfaceAssertedId rootSemantic)
+ leftArtifact = Semantic.moduleArtifactResult
+ leftKey
+ (Syntax.moduleSyntaxAssertedId leftSyntax)
+ (Semantic.semanticInterfaceAssertedId leftSemantic)
+ connection <- SQLite.open path
+ SQLite.withTransaction connection do
+ traverse_ (insertRawObject connection) objects
+ insertRawProposition connection proposition
+ traverse_
+ (insertRawSyntaxInterface connection)
+ [baseSyntax, leftSyntax, rightSyntax, rootSyntax]
+ traverse_
+ (insertRawSemanticInterface connection)
+ [baseSemantic, leftSemantic, rightSemantic, rootSemantic]
+ insertRawModuleArtifact connection rootArtifact
+ insertRawModuleArtifact connection leftArtifact
+ SQLite.close connection
+
+ memo <- Store.newStoreMemo store
+ expectInstallation memo store rootKey rootSyntax
+ expectInstallation memo store leftKey leftSyntax
+ expectInstallation memo store rootKey rootSyntax
+ visits <- Store.storeMemoVisits memo
+ assertEqual "unique artifact rows" 2
+ (Store.storeArtifactRowsDecoded visits)
+ assertEqual "unique artifact validations" 2
+ (Store.storeArtifactsValidated visits)
+ assertEqual "syntax diamond rows" 4
+ (Store.storeSyntaxRowsDecoded visits)
+ assertEqual "syntax diamond validations" 4
+ (Store.storeSyntaxRowsValidated visits)
+ assertEqual "semantic diamond rows" 4
+ (Store.storeSemanticRowsDecoded visits)
+ assertEqual "semantic diamond validations" 4
+ (Store.storeSemanticRowsValidated visits)
+ assertEqual "transparent-chain rows" 4
+ (Store.storeObjectRowsDecoded visits)
+ assertEqual "transparent-chain validations" 4
+ (Store.storeObjectRowsValidated visits)
+ assertEqual "proposition rows" 1
+ (Store.storePropositionRowsDecoded visits)
+ assertEqual "proposition validations" 1
+ (Store.storePropositionRowsValidated visits)
+ Store.closeStore store
+ where
+ expectInstallation memo store key syntax =
+ Store.loadCachedModuleInstallation
+ memo store key (Syntax.moduleSyntaxAssertedId syntax)
+ >>= \case
+ Right (Just _installation) -> pure ()
+ _ -> assertFailure "cached closure installation failed"
+
+transparentSetObject
+ :: Identity.TheoryId
+ -> Identity.AssertedObject
+ -> Identity.AssertedObject
+transparentSetObject theory dependency =
+ Identity.assertedObject identity content
+ where
+ content = Identity.TransparentObjectContent
+ theory
+ Core.TySet
+ (Core.CGlobal (Identity.assertedObjectId dependency))
+ identity = Identity.transparentObjectId
+ theory
+ Core.TySet
+ (Core.CGlobal (Identity.assertedObjectId dependency))
+
+testModuleName :: FilePath -> IO ModuleName
+testModuleName path = do
+ digest <- expectRight
+ (hashCanonicalFields
+ "store-linear-module"
+ [ByteString.Char8.pack path])
+ relative <- expectRight (safeRelativePath path)
+ pure
+ (moduleNameFromParts
+ (sourceNamespaceIdFromDigest digest)
+ relative)
+
+makeArtifactKey
+ :: ModuleName
+ -> Identity.TheoryId
+ -> ByteString.ByteString
+ -> IO Semantic.ModuleArtifactKey
+makeArtifactKey owner theory label = do
+ makeArtifactKeyWithDirect owner theory [] label
+
+makeArtifactKeyWithDirect
+ :: ModuleName
+ -> Identity.TheoryId
+ -> [Semantic.SemanticInterfaceId]
+ -> ByteString.ByteString
+ -> IO Semantic.ModuleArtifactKey
+makeArtifactKeyWithDirect owner theory direct label = do
+ parsedKey <- expectRight
+ (Parsed.parsedModuleKey
+ (Content.sourceContentIdBytes label)
+ Syntax.baseSyntaxInterfaceId
+ [])
+ expectRight
+ (Semantic.moduleArtifactKey
+ owner
+ (Parsed.parsedModuleId parsedKey label)
+ direct
+ theory)
+
+writeRawModuleRows
+ :: FilePath
+ -> [Identity.AssertedObject]
+ -> [Identity.CheckedPropositionContent]
+ -> Syntax.ModuleSyntaxInterface
+ -> [Semantic.SemanticInterface]
+ -> Semantic.ModuleArtifactResult
+ -> IO ()
+writeRawModuleRows path objects propositions syntax semantics artifact = do
+ connection <- SQLite.open path
+ SQLite.withTransaction connection do
+ traverse_ (insertRawObject connection) objects
+ traverse_ (insertRawProposition connection) propositions
+ insertRawSyntaxInterface connection syntax
+ traverse_ (insertRawSemanticInterface connection) semantics
+ insertRawModuleArtifact connection artifact
+ SQLite.close connection
+
+insertRawObject :: SQLite.Connection -> Identity.AssertedObject -> IO ()
+insertRawObject connection object =
+ SQLite.execute connection
+ "INSERT OR IGNORE INTO canonical_objects (object_id, payload) \
+ \VALUES (?, ?)"
+ ( Cache.encodeCache
+ (Identity.putObjectIdCache
+ (Identity.assertedObjectId object))
+ , Cache.encodeCache
+ (Identity.putObjectContentCache
+ (Identity.assertedObjectContent object))
+ )
+
+insertRawProposition
+ :: SQLite.Connection
+ -> Identity.CheckedPropositionContent
+ -> IO ()
+insertRawProposition connection proposition =
+ SQLite.execute connection
+ "INSERT OR IGNORE INTO canonical_propositions \
+ \(proposition_id, payload) VALUES (?, ?)"
+ ( Cache.encodeCache
+ (Identity.putPropositionIdCache
+ (Identity.checkedPropositionId proposition))
+ , Cache.encodeCache
+ (Cache.putCanonicalTermCache
+ Identity.putObjectIdCache
+ (Core.frozenCoreTerm
+ (Identity.checkedPropositionTerm proposition)))
+ )
+
+insertRawSyntaxInterface
+ :: SQLite.Connection
+ -> Syntax.ModuleSyntaxInterface
+ -> IO ()
+insertRawSyntaxInterface connection interface =
+ SQLite.execute connection
+ "INSERT OR IGNORE INTO syntax_interfaces \
+ \(syntax_interface_id, payload) VALUES (?, ?)"
+ ( Cache.cacheDigestBytes
+ (Syntax.syntaxInterfaceIdDigest
+ (Syntax.moduleSyntaxAssertedId interface))
+ , Cache.encodeCache
+ (Syntax.putModuleSyntaxInterfaceCache interface)
+ )
+
+insertRawSemanticInterface
+ :: SQLite.Connection
+ -> Semantic.SemanticInterface
+ -> IO ()
+insertRawSemanticInterface connection interface =
+ SQLite.execute connection
+ "INSERT OR IGNORE INTO semantic_interfaces \
+ \(semantic_interface_id, payload) VALUES (?, ?)"
+ ( Cache.cacheDigestBytes
+ (Semantic.semanticInterfaceIdDigest
+ (Semantic.semanticInterfaceAssertedId interface))
+ , Cache.encodeCache
+ (Semantic.putSemanticInterfaceCache interface)
+ )
+
+insertRawModuleArtifact
+ :: SQLite.Connection
+ -> Semantic.ModuleArtifactResult
+ -> IO ()
+insertRawModuleArtifact connection artifact =
+ SQLite.execute connection
+ "INSERT OR IGNORE INTO module_artifacts \
+ \(module_artifact_id, syntax_interface_id, \
+ \semantic_interface_id, payload) VALUES (?, ?, ?, ?)"
+ ( Cache.cacheDigestBytes
+ (Semantic.moduleArtifactIdDigest
+ (Semantic.moduleArtifactResultId artifact))
+ , Cache.cacheDigestBytes
+ (Syntax.syntaxInterfaceIdDigest
+ (Semantic.moduleArtifactResultSyntax artifact))
+ , Cache.cacheDigestBytes
+ (Semantic.semanticInterfaceIdDigest
+ (Semantic.moduleArtifactResultSemantic artifact))
+ , Cache.encodeCache
+ (Semantic.putModuleArtifactResultCache artifact)
+ )
+
+storedPropositionCount
+ :: FilePath
+ -> Identity.PropositionId
+ -> IO Int
+storedPropositionCount path identity = do
+ connection <- SQLite.open path
+ [Only rowCount] <- SQLite.query connection
+ "SELECT COUNT(*) FROM canonical_propositions \
+ \WHERE proposition_id = ?"
+ (Only
+ (Cache.encodeCache
+ (Identity.putPropositionIdCache identity)))
+ SQLite.close connection
+ pure rowCount
+
+storedObjectCount
+ :: FilePath
+ -> Identity.ObjectId
+ -> IO Int
+storedObjectCount path identity = do
+ connection <- SQLite.open path
+ [Only rowCount] <- SQLite.query connection
+ "SELECT COUNT(*) FROM canonical_objects WHERE object_id = ?"
+ (Only
+ (Cache.encodeCache
+ (Identity.putObjectIdCache identity)))
+ SQLite.close connection
+ pure rowCount
+
+storedArtifactCount
+ :: FilePath
+ -> Semantic.ModuleArtifactId
+ -> IO Int
+storedArtifactCount path identity = do
+ connection <- SQLite.open path
+ [Only rowCount] <- SQLite.query connection
+ "SELECT COUNT(*) FROM module_artifacts \
+ \WHERE module_artifact_id = ?"
+ (Only
+ (Cache.cacheDigestBytes
+ (Semantic.moduleArtifactIdDigest identity)))
+ SQLite.close connection
+ pure rowCount
+
+rollsBackFailedReadiness :: Assertion
+rollsBackFailedReadiness =
+ withStoreFixture "felix-store-readiness-rollback" \path theory fixture -> do
+ (_startup, store) <- expectOpen path theory
+ (owner, prefix, _syntax, semantic, _artifactKey, artifact, proposition) <-
+ makeCommittedModule theory fixture
+ missingDelta <- expectRight
+ (Syntax.canonicalSyntaxDelta
+ [Syntax.CanonicalStructureOperation "missing-child"])
+ missingInterface <- expectRight
+ (Syntax.moduleSyntaxInterface [] missingDelta)
+ syntaxDelta <- expectRight
+ (Syntax.canonicalSyntaxDelta [])
+ brokenSyntax <- expectRight
+ (Syntax.moduleSyntaxInterface
+ [Syntax.moduleSyntaxAssertedId missingInterface]
+ syntaxDelta)
+ brokenParsedKey <- expectRight
+ (Parsed.parsedModuleKey
+ (Content.sourceContentIdBytes "rollback-source")
+ Syntax.baseSyntaxInterfaceId
+ [])
+ brokenArtifactKey <- expectRight
+ (Semantic.moduleArtifactKey
+ owner
+ (Parsed.parsedModuleId
+ brokenParsedKey
+ "rollback-parsed")
+ []
+ theory)
+ let brokenArtifact =
+ Semantic.moduleArtifactResult
+ brokenArtifactKey
+ (Syntax.moduleSyntaxAssertedId brokenSyntax)
+ (Semantic.semanticInterfaceAssertedId semantic)
+ result <- Store.writeSealedModule
+ store
+ prefix
+ [brokenSyntax]
+ [semantic]
+ brokenArtifact
+ case result of
+ Left Store.StoreAssertedChildMissing{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected readiness failure: " <> show other)
+ Right _ ->
+ assertFailure "broken readiness transaction was accepted"
+ assertEqual "failed readiness did not publish the prefix" 0
+ =<< storedPropositionCount
+ path
+ (Identity.checkedPropositionId proposition)
+ assertEqual "failed readiness leaves no module root" 0
+ =<< storedArtifactCount
+ path
+ (Semantic.moduleArtifactResultId artifact)
+ -- A later failed seal must not erase a prefix published by an
+ -- earlier successful source prefix flush.
+ expectRightIO (Store.writePendingModulePrefix store prefix)
+ assertEqual "successful prefix is visible before retry" 1
+ =<< storedPropositionCount
+ path
+ (Identity.checkedPropositionId proposition)
+ retry <- Store.writeSealedModule
+ store
+ prefix
+ [brokenSyntax]
+ [semantic]
+ brokenArtifact
+ case retry of
+ Left Store.StoreAssertedChildMissing{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected retry readiness failure: " <> show other)
+ Right _ ->
+ assertFailure "broken readiness retry was accepted"
+ assertEqual "failed retry retains successful prefix" 1
+ =<< storedPropositionCount
+ path
+ (Identity.checkedPropositionId proposition)
+ assertEqual "failed retry still leaves no module root" 0
+ =<< storedArtifactCount
+ path
+ (Semantic.moduleArtifactResultId artifact)
+ Store.closeStore store
+makeCommittedModule
+ :: Identity.TheoryId
+ -> StoreFixture
+ -> IO
+ ( ModuleName
+ , Declaration.PendingModulePrefix
+ , Syntax.ModuleSyntaxInterface
+ , Semantic.SemanticInterface
+ , Semantic.ModuleArtifactKey
+ , Semantic.ModuleArtifactResult
+ , Identity.CheckedPropositionContent
+ )
+makeCommittedModule theory fixture = do
+ foundation <- expectRight Foundation.checkedFoundation
+ namespaceDigest <- expectRight
+ (hashCanonicalFields "store-module-test" ["prefix"])
+ relative <- expectRight (safeRelativePath "module.tex")
+ let owner =
+ moduleNameFromParts
+ (sourceNamespaceIdFromDigest namespaceDigest)
+ relative
+ proposition = fixtureProposition fixture
+ resolver = Declaration.vampireResolver \_ ->
+ pure
+ (Left
+ (Provers.ProverLaunchFailed
+ "unused"
+ "store fixture does not run Vampire"))
+ driver <- Declaration.runModuleDriver
+ foundation
+ owner
+ []
+ resolver
+ Declaration.FreshValidation
+ do
+ (_value, _batch) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "store-prefix") do
+ Declaration.addDeclarationObject
+ (fixtureFirstObject fixture)
+ candidate <- Declaration.reserveCandidate
+ (Declaration.candidateSpec
+ proposition
+ Semantic.SearchIneligible
+ [])
+ Declaration.authorizeOmittedCandidate candidate
+ Declaration.recordOmittedUse
+ pure ()
+ (_value, prefix, semantic) <-
+ case driver of
+ Right (Declaration.DriverSucceeded value interface pending _closure) ->
+ pure (value, pending, interface)
+ Right (Declaration.DriverFailed failure _prefix) ->
+ assertFailure
+ ("unexpected declaration failure: "
+ <> show
+ (failure
+ :: Declaration.DriverFailure
+ Declaration.DeclarationError))
+ >> fail "unreachable"
+ Right (Declaration.DriverSealFailed failure _prefix) ->
+ assertFailure ("unexpected seal failure: " <> show failure)
+ >> fail "unreachable"
+ Left failure ->
+ assertFailure ("unexpected driver-open failure: " <> show failure)
+ >> fail "unreachable"
+ delta <- expectRight (Syntax.canonicalSyntaxDelta [])
+ syntax <- expectRight (Syntax.moduleSyntaxInterface [] delta)
+ parsedKey <- expectRight
+ (Parsed.parsedModuleKey
+ (Content.sourceContentIdBytes "store-module-source")
+ Syntax.baseSyntaxInterfaceId
+ [])
+ artifactKey <- expectRight
+ (Semantic.moduleArtifactKey
+ owner
+ (Parsed.parsedModuleId parsedKey "store-module-parsed")
+ []
+ theory)
+ let artifact =
+ Semantic.moduleArtifactResult
+ artifactKey
+ (Syntax.moduleSyntaxAssertedId syntax)
+ (Semantic.semanticInterfaceAssertedId semantic)
+ pure
+ ( owner
+ , prefix
+ , syntax
+ , semantic
+ , artifactKey
+ , artifact
+ , proposition
+ )
+
+makePendingPrefix
+ :: StoreFixture
+ -> [Identity.AssertedObject]
+ -> IO Declaration.PendingModulePrefix
+makePendingPrefix fixture objects = do
+ foundation <- expectRight Foundation.checkedFoundation
+ owner <- testModuleName "rollback-prefix.tex"
+ let proposition = fixtureProposition fixture
+ resolver = Declaration.vampireResolver \_ ->
+ pure
+ (Left
+ (Provers.ProverLaunchFailed
+ "unused"
+ "store fixture does not run Vampire"))
+ driver <- Declaration.runModuleDriver
+ foundation
+ owner
+ []
+ resolver
+ Declaration.FreshValidation
+ do
+ (_value, _batch) <- Declaration.commitProofDeclaration
+ (Semantic.proofSyntaxId "store-rollback") do
+ traverse_ Declaration.addDeclarationObject objects
+ candidate <- Declaration.reserveCandidate
+ (Declaration.candidateSpec
+ proposition
+ Semantic.SearchIneligible
+ [])
+ Declaration.authorizeOmittedCandidate candidate
+ Declaration.recordOmittedUse
+ pure ()
+ case driver of
+ Right (Declaration.DriverSucceeded _value _interface prefix _closure) ->
+ pure prefix
+ Right (Declaration.DriverFailed failure _prefix) ->
+ assertFailure
+ ("unexpected declaration failure: "
+ <> show
+ (failure
+ :: Declaration.DriverFailure
+ Declaration.DeclarationError))
+ >> fail "unreachable"
+ Right (Declaration.DriverSealFailed failure _prefix) ->
+ assertFailure ("unexpected seal failure: " <> show failure)
+ >> fail "unreachable"
+ Left failure ->
+ assertFailure ("unexpected driver-open failure: " <> show failure)
+ >> fail "unreachable"
+
+initializesAndReopensCurrentSchema :: Assertion
+initializesAndReopensCurrentSchema =
+ withStoreFixture "felix-store-startup" \path theory _fixture -> do
+ (startup, store) <- expectOpen path theory
+ assertEqual "new store status"
+ Store.InitializedNewStore startup
+ Store.closeStore store
+
+ (reopened, current) <- expectOpen path theory
+ assertEqual "current store status"
+ Store.OpenedCurrentStore reopened
+ Store.closeStore current
+
+ connection <- SQLite.open path
+ names <- SQLite.query_ connection
+ "SELECT name FROM sqlite_master \
+ \WHERE type = 'table' AND name NOT LIKE 'sqlite_%' \
+ \ORDER BY name"
+ :: IO [Only Text]
+ journal <- SQLite.query_ connection
+ "PRAGMA journal_mode"
+ :: IO [Only Text]
+ SQLite.close connection
+ assertEqual "complete schema table count" 9 (length names)
+ assertEqual "rollback journal persists"
+ [Only "delete"] journal
+
+rejectsIncompatibilityWithoutConfiguration :: Assertion
+rejectsIncompatibilityWithoutConfiguration =
+ withStoreFixture "felix-store-incompatible" \path theory _fixture -> do
+ connection <- SQLite.open path
+ _ <- SQLite.query_ connection
+ "PRAGMA journal_mode = WAL"
+ :: IO [Only Text]
+ SQLite.execute_ connection
+ "CREATE TABLE store_compatibility ( \
+ \singleton INTEGER, cache_epoch INTEGER, theory_id BLOB )"
+ SQLite.execute connection
+ "INSERT INTO store_compatibility VALUES (1, ?, ?)"
+ ( 999 :: Int
+ , Cache.encodeCache (Identity.putTheoryIdCache theory)
+ )
+ SQLite.execute_ connection
+ "CREATE TABLE untouched (value INTEGER)"
+ SQLite.close connection
+
+ result <- Store.openStore path theory
+ case result of
+ Left
+ (Store.IncompatibleStore
+ Store.StoreCompatibilityMismatch{}) ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected incompatibility result: " <> show other)
+ Right (_startup, store) -> do
+ Store.closeStore store
+ assertFailure "incompatible store was accepted"
+
+ inspected <- SQLite.open path
+ names <- SQLite.query_ inspected
+ "SELECT name FROM sqlite_master \
+ \WHERE type = 'table' AND name NOT LIKE 'sqlite_%' \
+ \ORDER BY name"
+ :: IO [Only Text]
+ journal <- SQLite.query_ inspected
+ "PRAGMA journal_mode"
+ :: IO [Only Text]
+ SQLite.close inspected
+ assertEqual "startup creates no schema"
+ [Only "store_compatibility", Only "untouched"] names
+ assertEqual "startup applies no journal configuration"
+ [Only "wal"] journal
+
+rejectsMalformedCompatibilityMetadata :: Assertion
+rejectsMalformedCompatibilityMetadata =
+ withStoreFixture "felix-store-malformed" \path theory _fixture -> do
+ connection <- SQLite.open path
+ SQLite.execute_ connection
+ "CREATE TABLE store_compatibility ( \
+ \singleton INTEGER, cache_epoch, theory_id )"
+ SQLite.execute connection
+ "INSERT INTO store_compatibility VALUES (1, ?, ?)"
+ ( "not-an-epoch" :: Text
+ , Cache.encodeCache (Identity.putTheoryIdCache theory)
+ )
+ SQLite.close connection
+
+ result <- Store.openStore path theory
+ case result of
+ Left
+ (Store.IncompatibleStore
+ Store.StoreCompatibilityMalformed{}) ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected malformed result: " <> show other)
+ Right (_startup, store) -> do
+ Store.closeStore store
+ assertFailure "malformed metadata was accepted"
+
+rejectsCompatibleIncompleteSchema :: Assertion
+rejectsCompatibleIncompleteSchema =
+ withStoreFixture "felix-store-incomplete" \path theory _fixture -> do
+ (_startup, store) <- expectOpen path theory
+ Store.closeStore store
+ connection <- SQLite.open path
+ SQLite.execute_ connection
+ "DROP TABLE canonical_propositions"
+ SQLite.close connection
+
+ result <- Store.openStore path theory
+ case result of
+ Left
+ (Store.FatalStoreStartup
+ Store.StoreSchemaIntegrityFailure{}) ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected incomplete-schema result: " <> show other)
+ Right (_startup, current) -> do
+ Store.closeStore current
+ assertFailure "incomplete current schema was accepted"
+
+rollsBackUnequalDuplicateBatch :: Assertion
+rollsBackUnequalDuplicateBatch =
+ withStoreFixture "felix-store-rollback" \path theory fixture -> do
+ (_startup, store) <- expectOpen path theory
+ let first = fixtureFirstObject fixture
+ second = fixtureSecondObject fixture
+ prefix <- makePendingPrefix fixture [first, second]
+ Store.closeStore store
+
+ connection <- SQLite.open path
+ SQLite.execute connection
+ "INSERT INTO canonical_objects (object_id, payload) VALUES (?, ?)"
+ ( Cache.encodeCache
+ (Identity.putObjectIdCache
+ (Identity.assertedObjectId second))
+ , Cache.encodeCache
+ (Identity.putObjectContentCache
+ (Identity.assertedObjectContent
+ first))
+ )
+ SQLite.close connection
+
+ (_reopened, current) <- expectOpen path theory
+ result <- Store.writePendingModulePrefix current prefix
+ case result of
+ Left Store.StoreRowPayloadMismatch{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected duplicate result: " <> show other)
+ Right () ->
+ assertFailure "unequal duplicate was accepted"
+ assertEqual "earlier insertion was rolled back" 0
+ =<< storedObjectCount path (Identity.assertedObjectId first)
+ Store.closeStore current
+
+rejectsMalformedCanonicalPayloads :: Assertion
+rejectsMalformedCanonicalPayloads =
+ withStoreFixture "felix-store-decode" \path theory fixture -> do
+ (_startup, store) <- expectOpen path theory
+ let object = fixtureFirstObject fixture
+ (_owner, prefix, syntax, semantic, key, artifact, _proposition) <-
+ makeCommittedModule theory fixture
+ expectRightIO
+ (Store.writeSealedModule
+ store prefix [syntax] [semantic] artifact)
+ Store.closeStore store
+
+ connection <- SQLite.open path
+ SQLite.execute connection
+ "UPDATE canonical_objects SET payload = ? \
+ \WHERE object_id = ?"
+ ( ByteString.singleton 0xff
+ , Cache.encodeCache
+ (Identity.putObjectIdCache
+ (Identity.assertedObjectId object))
+ )
+ SQLite.close connection
+
+ (_reopened, current) <- expectOpen path theory
+ memo <- Store.newStoreMemo current
+ result <- Store.loadCachedModuleInstallation
+ memo current key (Syntax.moduleSyntaxAssertedId syntax)
+ case result of
+ Left Store.StoreRowDecodeFailure{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected malformed-row result: " <> show other)
+ Right _ ->
+ assertFailure "malformed canonical payload was accepted"
+ Store.closeStore current
+
+plansPersistentStores :: Assertion
+plansPersistentStores =
+ Temp.withSystemTempDirectory "felix-store-planning" \root -> do
+ (theory, _fixture) <- makeStoreFixture
+ let cacheRoot = root Posix.</> "cache"
+ expectedDefault =
+ cacheRoot Posix.</> "felix" Posix.</> "store.sqlite"
+ explicitParent = root Posix.</> "explicit"
+ explicitPath = explicitParent Posix.</> "selected.sqlite"
+ Directory.createDirectory cacheRoot
+ Directory.createDirectory explicitParent
+ withEnvironment "XDG_CACHE_HOME" cacheRoot do
+ defaultPlan <- expectRightIO
+ (Store.planStore Store.DefaultStore)
+ defaultResult <- Store.withStoreLease defaultPlan \lease -> do
+ assertEqual "default store path"
+ expectedDefault
+ (Store.storePathFilePath
+ (Store.storeLeasePath lease))
+ assertBool "planning does not create the default parent"
+ . not
+ =<< Directory.doesPathExist
+ (cacheRoot Posix.</> "felix")
+ Store.withOpenStore lease theory \_startup _store ->
+ Directory.doesFileExist expectedDefault
+ assertEqual "default store opens at the XDG path"
+ (Right True) defaultResult
+
+ explicitPlan <- expectRightIO
+ (Store.planStore
+ (Store.ExplicitStore explicitPath))
+ explicitResult <- Store.withStoreLease explicitPlan \lease -> do
+ assertEqual "explicit store path"
+ explicitPath
+ (Store.storePathFilePath
+ (Store.storeLeasePath lease))
+ Store.withOpenStore lease theory \_startup _store ->
+ Directory.doesFileExist explicitPath
+ assertEqual "explicit store opens without creating its parent"
+ (Right True) explicitResult
+
+ missing <- Store.planStore
+ (Store.ExplicitStore
+ (root Posix.</> "missing" Posix.</> "store.sqlite"))
+ case missing of
+ Left Store.ExplicitStoreParentMissing{} ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected missing-parent result: " <> show other)
+ Right _ ->
+ assertFailure "missing explicit parent was accepted"
+
+cleansFreshStores :: Assertion
+cleansFreshStores = do
+ (theory, _fixture) <- makeStoreFixture
+ plan <- expectRightIO
+ (Store.planStore Store.FreshTemporaryStore)
+
+ successPath <- IORef.newIORef Nothing
+ success <- Store.withStoreLease plan \lease -> do
+ let path = Store.storePathFilePath
+ (Store.storeLeasePath lease)
+ IORef.writeIORef successPath (Just path)
+ Store.withOpenStore lease theory \_startup _store ->
+ Directory.doesFileExist path
+ assertEqual "fresh store opened" (Right True) success
+ assertFreshRemoved successPath
+
+ failurePath <- IORef.newIORef Nothing
+ failed <- Exception.try
+ (Store.withStoreLease plan \lease -> do
+ let path = Store.storePathFilePath
+ (Store.storeLeasePath lease)
+ IORef.writeIORef failurePath (Just path)
+ void
+ (Store.withOpenStore lease theory \_startup _store ->
+ ioError (userError "fresh action failed")))
+ :: IO (Either IOError ())
+ case failed of
+ Left _ ->
+ pure ()
+ Right () ->
+ assertFailure "fresh-store action exception did not escape"
+ assertFreshRemoved failurePath
+
+doesNotFallBackAfterFatalStartup :: Assertion
+doesNotFallBackAfterFatalStartup =
+ Temp.withSystemTempDirectory "felix-store-no-fallback" \root -> do
+ (theory, _fixture) <- makeStoreFixture
+ let persistentParent = root Posix.</> "persistent"
+ persistentPath = persistentParent Posix.</> "store.sqlite"
+ cacheRoot = root Posix.</> "cache"
+ Directory.createDirectory persistentParent
+ Directory.createDirectory cacheRoot
+ plan <- expectRightIO
+ (Store.planStore
+ (Store.ExplicitStore persistentPath))
+ initialized <- Store.withStoreLease plan \lease ->
+ Store.withOpenStore lease theory \_startup _store ->
+ pure ()
+ assertEqual "fixture store initialized"
+ (Right ()) initialized
+ connection <- SQLite.open persistentPath
+ SQLite.execute_ connection
+ "DROP TABLE canonical_objects"
+ SQLite.close connection
+
+ withEnvironment "XDG_CACHE_HOME" cacheRoot do
+ result <- Store.withStoreLease plan \lease ->
+ Store.withOpenStore lease theory \_startup _store ->
+ pure ()
+ case result of
+ Left
+ (Store.StoreLifecycleOpenFailed
+ (Store.FatalStoreStartup
+ Store.StoreSchemaIntegrityFailure{})) ->
+ pure ()
+ Left other ->
+ assertFailure
+ ("unexpected fatal-startup result: " <> show other)
+ Right () ->
+ assertFailure "corrupt persistent store was accepted"
+ assertBool "fatal startup creates no default fallback"
+ . not
+ =<< Directory.doesPathExist
+ (cacheRoot Posix.</> "felix")
+
+
+data StoreFixture = StoreFixture
+ !Identity.AssertedObject
+ !Identity.AssertedObject
+ !Identity.CheckedPropositionContent
+
+fixtureFirstObject :: StoreFixture -> Identity.AssertedObject
+fixtureFirstObject (StoreFixture object _second _proposition) =
+ object
+
+fixtureSecondObject :: StoreFixture -> Identity.AssertedObject
+fixtureSecondObject (StoreFixture _first object _proposition) =
+ object
+
+fixtureProposition
+ :: StoreFixture
+ -> Identity.CheckedPropositionContent
+fixtureProposition (StoreFixture _first _second proposition) =
+ proposition
+
+makeStoreFixture
+ :: IO (Identity.TheoryId, StoreFixture)
+makeStoreFixture = do
+ foundation <- expectRight Foundation.checkedFoundation
+ let theory = Identity.theoryId foundation
+ first = intrinsicObject theory Core.Empty
+ second = intrinsicObject theory Core.PairSet
+ closure <- expectRight
+ (Identity.validateObjectClosure theory [first, second])
+ proposition <- expectRight
+ (Identity.validatePropositionContent
+ closure
+ (Core.CEq
+ Core.TySet
+ (Core.CGlobal (Identity.assertedObjectId first))
+ (Core.CGlobal (Identity.assertedObjectId first))))
+ pure
+ ( theory
+ , StoreFixture first second proposition
+ )
+
+intrinsicObject
+ :: Identity.TheoryId
+ -> Core.CoreIntrinsicTag
+ -> Identity.AssertedObject
+intrinsicObject theory tag =
+ Identity.assertedObject identity content
+ where
+ coreType = Core.coreIntrinsicType tag
+ content =
+ Identity.IntrinsicObjectContent
+ theory tag coreType
+ identity =
+ Identity.intrinsicObjectId
+ theory tag coreType
+
+withStoreFixture
+ :: String
+ -> ( FilePath
+ -> Identity.TheoryId
+ -> StoreFixture
+ -> IO a
+ )
+ -> IO a
+withStoreFixture template action =
+ Temp.withSystemTempDirectory template \root -> do
+ (theory, fixture) <- makeStoreFixture
+ action
+ (root Posix.</> "store.sqlite")
+ theory
+ fixture
+
+expectOpen
+ :: FilePath
+ -> Identity.TheoryId
+ -> IO (Store.StoreStartup, Store.Store)
+expectOpen path theory = do
+ result <- Store.openStore path theory
+ case result of
+ Left failure ->
+ assertFailure (show failure) >> fail "unreachable"
+ Right opened ->
+ pure opened
+
+expectRight :: Show failure => Either failure value -> IO value
+expectRight = \case
+ Left failure ->
+ assertFailure (show failure) >> fail "unreachable"
+ Right value ->
+ pure value
+
+expectRightIO
+ :: Show failure
+ => IO (Either failure value)
+ -> IO value
+expectRightIO action =
+ expectRight =<< action
+
+assertFreshRemoved :: IORef.IORef (Maybe FilePath) -> Assertion
+assertFreshRemoved pathReference = do
+ selected <- IORef.readIORef pathReference
+ case selected of
+ Nothing ->
+ assertFailure "fresh store path was not allocated"
+ Just path -> do
+ assertBool "fresh database was removed"
+ . not
+ =<< Directory.doesPathExist path
+ assertBool "fresh database directory was removed"
+ . not
+ =<< Directory.doesPathExist
+ (Posix.takeDirectory path)
+
+withEnvironment
+ :: String
+ -> String
+ -> IO value
+ -> IO value
+withEnvironment name value action =
+ Exception.bracket
+ (Environment.lookupEnv name)
+ restore
+ \_previous -> do
+ Environment.setEnv name value
+ action
+ where
+ restore = \case
+ Nothing ->
+ Environment.unsetEnv name
+ Just previous ->
+ Environment.setEnv name previous
diff --git a/source/Felix/Test/Unit/Token.hs b/source/Felix/Test/Unit/Token.hs
new file mode 100644
index 0000000..00c6755
--- /dev/null
+++ b/source/Felix/Test/Unit/Token.hs
@@ -0,0 +1,285 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.Token (unitTests) where
+
+import Base
+import Felix.Report.Location
+import Felix.Syntax.Adapt
+import Felix.Syntax.Abstract (Associativity(..))
+import Felix.Syntax.Interface
+import Felix.Syntax.Pragma
+import Felix.Syntax.Token
+
+import Data.Text qualified as Text
+import Test.Tasty
+import Test.Tasty.HUnit
+import Text.Megaparsec (errorBundlePretty)
+
+unitTests :: TestTree
+unitTests = testGroup "Lexer"
+ [ testCase "nested math inside text returns to text" nestedMathInsideText
+ , testCase "nested text and math returns to the enclosing frame" deeperAlternation
+ , testCase "braces inside text do not close the text frame" textBraceNesting
+ , testCase "cases is tokenized as an environment only inside math" casesOnlyInsideMath
+ , testCase "imports retain source locations and POSIX spellings" locatedImports
+ , testCase "commented environment starts are ignored"
+ ignoresCommentedEnvironmentStart
+ , testCase "empty inputs construct empty lexical syntax"
+ constructsEmptyLexicalSyntax
+ , testCase "extracts exact source fixity pragmas"
+ extractsSourceFixityPragmas
+ , testCase "rejects malformed reserved pragma lines"
+ rejectsMalformedPragmas
+ ]
+
+nestedMathInsideText :: Assertion
+nestedMathInsideText = do
+ tokens <- tokensInProof "$\\text{if $x \\in A$ then}$"
+ tokens `shouldBe`
+ [ BeginEnv "proof"
+ , BeginEnv "math"
+ , BeginEnv "text"
+ , Word "if"
+ , BeginEnv "math"
+ , Variable "x"
+ , Command "in"
+ , Variable "A"
+ , EndEnv "math"
+ , Word "then"
+ , EndEnv "text"
+ , EndEnv "math"
+ , EndEnv "proof"
+ ]
+
+deeperAlternation :: Assertion
+deeperAlternation = do
+ tokens <- tokensInProof "$\\text{a $ \\text{b $c$ d} e$ f}$"
+ tokens `shouldBe`
+ [ BeginEnv "proof"
+ , BeginEnv "math"
+ , BeginEnv "text"
+ , Word "a"
+ , BeginEnv "math"
+ , BeginEnv "text"
+ , Word "b"
+ , BeginEnv "math"
+ , Variable "c"
+ , EndEnv "math"
+ , Word "d"
+ , EndEnv "text"
+ , Variable "e"
+ , EndEnv "math"
+ , Word "f"
+ , EndEnv "text"
+ , EndEnv "math"
+ , EndEnv "proof"
+ ]
+
+textBraceNesting :: Assertion
+textBraceNesting = do
+ tokens <- tokensInProof "$\\text{a {b}}$"
+ tokens `shouldBe`
+ [ BeginEnv "proof"
+ , BeginEnv "math"
+ , BeginEnv "text"
+ , Word "a"
+ , InvisibleBraceL
+ , Word "b"
+ , InvisibleBraceR
+ , EndEnv "text"
+ , EndEnv "math"
+ , EndEnv "proof"
+ ]
+
+casesOnlyInsideMath :: Assertion
+casesOnlyInsideMath = do
+ tokensInsideMath <- tokensInProof "$\\begin{cases}x\\end{cases}$"
+ tokensInsideMath `shouldBe`
+ [ BeginEnv "proof"
+ , BeginEnv "math"
+ , BeginEnv "cases"
+ , Variable "x"
+ , EndEnv "cases"
+ , EndEnv "math"
+ , EndEnv "proof"
+ ]
+
+ tokensOutsideMath <- tokensInProof "\\begin{cases}x\\end{cases}"
+ assertBool
+ "cases should not be tokenized as an environment outside math"
+ (BeginEnv "cases" `notElem` tokensOutsideMath && EndEnv "cases" `notElem` tokensOutsideMath)
+
+locatedImports :: Assertion
+locatedImports = do
+ let raw = Text.unlines
+ [ "% heading"
+ , "\\import{set/base.tex}"
+ , "\\import{set\\special.tex}"
+ , "\\begin{axiom}"
+ , " $x = x$."
+ , "\\end{axiom}"
+ ]
+ case gatherImports (FileId maxBound) "import-unit" raw of
+ Left err ->
+ assertFailure (errorBundlePretty err)
+ Right imports@[firstImport, secondImport] -> do
+ assertEqual
+ "import paths"
+ ["set/base.tex", "set\\special.tex"]
+ (unLocated <$> imports)
+ assertEqual "first import line" 2 (locLine (startPos firstImport))
+ assertEqual "second import line" 3 (locLine (startPos secondImport))
+ Right imports ->
+ assertFailure ("expected two imports, got " <> show imports)
+
+ignoresCommentedEnvironmentStart :: Assertion
+ignoresCommentedEnvironmentStart = do
+ let raw = Text.unlines
+ [ "% \\begin{signature}"
+ , "ordinary text"
+ , "\\begin{struct}"
+ , " an ordered set $X$ is a onesorted structure."
+ , "\\end{struct}"
+ ]
+ (_, chunks) <-
+ either
+ (assertFailure . errorBundlePretty)
+ pure
+ (runLexer (FileId maxBound) "commented-environment" raw)
+ case chunks of
+ [Located{unLocated = BeginEnv "struct"} : _] -> pure ()
+ _ -> assertFailure ("expected the real structure environment, got " <> show chunks)
+
+constructsEmptyLexicalSyntax :: Assertion
+constructsEmptyLexicalSyntax =
+ forM_
+ [ ("empty", "")
+ , ("comment-only", "% heading\n% body")
+ ]
+ \(description, raw) -> do
+ let input = Text.pack raw
+ imports <-
+ either
+ (assertFailure . errorBundlePretty)
+ pure
+ (gatherImports
+ (FileId maxBound)
+ description
+ input)
+ assertEqual (description <> " imports") [] imports
+ (lexedImports, chunks) <-
+ either
+ (assertFailure . errorBundlePretty)
+ pure
+ (runLexer
+ (FileId maxBound)
+ description
+ input)
+ assertEqual (description <> " lexer imports") [] lexedImports
+ assertEqual (description <> " chunks") [] chunks
+ scanned <-
+ either
+ (assertFailure . show)
+ pure
+ (concat <$> traverse scanChunk chunks)
+ assertEqual (description <> " scanned declarations") [] scanned
+ delta <-
+ either
+ (assertFailure . show)
+ pure
+ (canonicalSyntaxDelta [])
+ assertBool
+ (description <> " syntax declarations")
+ (null (canonicalSyntaxDeltaEntries delta))
+
+extractsSourceFixityPragmas :: Assertion
+extractsSourceFixityPragmas = do
+ let input =
+ " %! infixl 0\n"
+ <> "\t%! infixr 07\r\n"
+ <> "%! infix 3"
+ ordinaryComments =
+ Text.unlines
+ [ "% ! infixl 1"
+ , "text %! infixr 2"
+ , "% ordinary"
+ ]
+ pragmas <-
+ either
+ (assertFailure . Text.unpack . renderSyntaxPragmaError)
+ pure
+ (extractSyntaxPragmas
+ (FileId maxBound)
+ "pragma-unit"
+ input)
+ assertEqual
+ "normalized pragmas"
+ [ (LeftAssoc, 0, 1, 3)
+ , (RightAssoc, 7, 2, 2)
+ , (NonAssoc, 3, 3, 1)
+ ]
+ [ ( syntaxPragmaAssociativity pragma
+ , sourceMixfixLevelValue (syntaxPragmaLevel pragma)
+ , locLine (syntaxPragmaLocation pragma)
+ , locColumn (syntaxPragmaLocation pragma)
+ )
+ | pragma <- pragmas
+ ]
+ assertEqual
+ "ordinary comments"
+ (Right [])
+ (extractSyntaxPragmas
+ (FileId maxBound)
+ "pragma-unit"
+ ordinaryComments)
+
+rejectsMalformedPragmas :: Assertion
+rejectsMalformedPragmas =
+ forM_
+ [ ("%!infixl 1\n", SyntaxPragmaMissingSpaceAfterPrefix)
+ , ("%!\n", SyntaxPragmaMissingKeyword)
+ , ("%! Infixl 1\n", SyntaxPragmaUnknownKeyword "Infixl")
+ , ("%! infixl\n", SyntaxPragmaMissingLevel)
+ , ("%! infixl -1\n", SyntaxPragmaInvalidLevel)
+ , ("%! infixl ١\n", SyntaxPragmaInvalidLevel)
+ , ("%! infixl 8\n", SyntaxPragmaLevelOutOfRange)
+ , ("%! infixl 1 extra\n", SyntaxPragmaTrailingContent)
+ , ("%! infixl 1\r", SyntaxPragmaLoneCarriageReturn)
+ ]
+ \(input, expectedProblem) ->
+ case extractSyntaxPragmas
+ (FileId maxBound)
+ "pragma-unit"
+ input of
+ Left (InvalidSyntaxPragma location actualProblem) -> do
+ assertEqual
+ ("problem for " <> show input)
+ expectedProblem
+ actualProblem
+ assertEqual "error line" 1 (locLine location)
+ assertEqual "error column" 1 (locColumn location)
+ Left err ->
+ assertFailure
+ ("unexpected pragma error: "
+ <> Text.unpack (renderSyntaxPragmaError err))
+ Right pragmas ->
+ assertFailure
+ ("expected malformed pragma rejection, got "
+ <> show pragmas)
+
+tokensInProof :: Text -> IO [Token]
+tokensInProof raw =
+ case runLexer (FileId maxBound) "lexer-unit" wrapped of
+ Left err ->
+ assertFailure (errorBundlePretty err)
+ Right (_imports, chunks) ->
+ pure (concatMap (map unLocated) chunks)
+ where
+ wrapped = Text.unlines
+ [ "\\begin{proof}"
+ , raw
+ , "\\end{proof}"
+ ]
+
+shouldBe :: (Eq a, Show a, HasCallStack) => a -> a -> Assertion
+shouldBe = flip (assertEqual "")
diff --git a/source/Felix/Verification.hs b/source/Felix/Verification.hs
index 572f3ce..716beea 100644
--- a/source/Felix/Verification.hs
+++ b/source/Felix/Verification.hs
@@ -48,11 +48,11 @@ module Felix.Verification
import Base
-import Checking.Declaration qualified as Declaration
-import Checking.Foundation qualified as Foundation
-import Checking.Identity qualified as Identity
-import Checking.Module qualified as Typed
-import Checking.Semantic qualified as Semantic
+import Felix.Checking.Declaration qualified as Declaration
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Checking.Identity qualified as Identity
+import Felix.Checking.Module qualified as Typed
+import Felix.Checking.Semantic qualified as Semantic
import Felix.Module (localDeclarationOrdinal)
import Felix.Parse (ParseWorkspaceError(..), ParsedSourceWorkspace)
import Felix.Parse qualified as Felix
@@ -61,10 +61,10 @@ import Felix.Provers
import Felix.Source
import Felix.Source.Graph (ResolvedSourceGraph)
import Felix.Store qualified as Store
-import Render.Html.Export qualified as HtmlExport
-import Report.Location
-import Syntax.Abstract qualified as Raw
-import Syntax.Interface qualified as Syntax
+import Felix.Render.Html.Export qualified as HtmlExport
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Interface qualified as Syntax
import Control.Exception qualified as Exception
import Control.Monad (unless)
diff --git a/source/Felix/Workspace.hs b/source/Felix/Workspace.hs
index 6a13d42..b9034c5 100644
--- a/source/Felix/Workspace.hs
+++ b/source/Felix/Workspace.hs
@@ -29,11 +29,11 @@ import Felix.Prelude qualified as Prelude
import Felix.Source
import Felix.Source.Graph (ResolvedSourceGraph)
import Felix.Source.Graph qualified as SourceGraph
-import Report.Location
-import Syntax.Abstract qualified as Raw
-import Syntax.Adapt (ScannedLexicalItem, scanChunk)
-import Syntax.Lexicon (builtins)
-import Syntax.Token
+import Felix.Report.Location
+import Felix.Syntax.Abstract qualified as Raw
+import Felix.Syntax.Adapt (ScannedLexicalItem, scanChunk)
+import Felix.Syntax.Lexicon (builtins)
+import Felix.Syntax.Token
import Control.Exception qualified as Exception
import Data.Bifunctor (first)