diff options
| author | adelon <22380201+adelon@users.noreply.github.com> | 2026-07-27 16:23:12 +0200 |
|---|---|---|
| committer | adelon <22380201+adelon@users.noreply.github.com> | 2026-07-27 18:19:29 +0200 |
| commit | 4a65f1430fdf3e8d75c36b5d74e07deb07b17f9e (patch) | |
| tree | b29bc2e8b6d1b6c6decb14c93df811a9f505d02d /source/Checking | |
| parent | 829beab59eb793dda5dee8c40d57b29973fbbc5f (diff) | |
Make structure registration transactional
Diffstat (limited to 'source/Checking')
| -rw-r--r-- | source/Checking/Dependencies.hs | 124 | ||||
| -rw-r--r-- | source/Checking/Facts.hs | 269 | ||||
| -rw-r--r-- | source/Checking/Structure.hs | 134 |
3 files changed, 527 insertions, 0 deletions
diff --git a/source/Checking/Dependencies.hs b/source/Checking/Dependencies.hs new file mode 100644 index 0000000..cf5c0f3 --- /dev/null +++ b/source/Checking/Dependencies.hs @@ -0,0 +1,124 @@ +{-# LANGUAGE NoImplicitPrelude #-} + +module Checking.Dependencies + ( DependencyRegistry + , DependencyRegistrationError(..) + , fromRootSymbols + , registerDependencies + , lookupDependencies + , dependencyClosure + , dependencyPath + ) where + +import Base +import Syntax.Internal + +import Data.Map.Strict qualified as Map +import Data.Set qualified as Set + + +newtype DependencyRegistry = DependencyRegistry + { dependencyRows :: Map Symbol (Set Symbol) + } + deriving (Show, Eq) + +data DependencyRegistrationError + = DependencyOwnerAlreadyRegistered !Symbol + | SelfDependency !Symbol + | UnknownDependency !Symbol + deriving (Show, Eq) + +fromRootSymbols :: Set Symbol -> DependencyRegistry +fromRootSymbols roots = + DependencyRegistry + (Map.fromSet (const mempty) roots) + +registerDependencies + :: Symbol + -> Set Symbol + -> DependencyRegistry + -> Either DependencyRegistrationError DependencyRegistry +registerDependencies owner dependencies registry + | Map.member owner rows = + Left (DependencyOwnerAlreadyRegistered owner) + | owner `Set.member` dependencies = + Left (SelfDependency owner) + | Just unknown <- Set.lookupMin (dependencies `Set.difference` Map.keysSet rows) = + Left (UnknownDependency unknown) + | otherwise = + Right + (DependencyRegistry + (Map.insert owner dependencies rows)) + where + rows = dependencyRows registry + +lookupDependencies + :: Symbol + -> DependencyRegistry + -> Maybe (Set Symbol) +lookupDependencies symbol = + Map.lookup symbol . dependencyRows + +dependencyClosure + :: DependencyRegistry + -> Set Symbol + -> Either Symbol (Set Symbol) +dependencyClosure registry = + go mempty . Set.toList + where + go seen [] = + Right seen + go seen (symbol:rest) + | symbol `Set.member` seen = + go seen rest + | otherwise = + case lookupDependencies symbol registry of + Nothing -> + Left symbol + Just direct -> + go + (Set.insert symbol seen) + (Set.toList direct <> rest) + +dependencyPath + :: DependencyRegistry + -> Set Symbol + -> Symbol + -> Either Symbol (Maybe [Symbol]) +dependencyPath registry seeds target = + firstPath (Set.toList seeds) + where + firstPath [] = + Right Nothing + firstPath (seed:rest) = do + path <- go mempty seed + case path of + Just _ -> + pure path + Nothing -> + firstPath rest + + go seen symbol + | symbol == target = + Right (Just [symbol]) + | symbol `Set.member` seen = + Right Nothing + | otherwise = + case lookupDependencies symbol registry of + Nothing -> + Left symbol + Just direct -> + prependFirst + symbol + (Set.toList direct) + (Set.insert symbol seen) + + prependFirst _prefix [] _seen = + Right Nothing + prependFirst prefix (dependency:rest) seen = do + path <- go seen dependency + case path of + Just found -> + Right (Just (prefix : found)) + Nothing -> + prependFirst prefix rest seen diff --git a/source/Checking/Facts.hs b/source/Checking/Facts.hs new file mode 100644 index 0000000..410c05d --- /dev/null +++ b/source/Checking/Facts.hs @@ -0,0 +1,269 @@ +{-# LANGUAGE NoImplicitPrelude #-} + +module Checking.Facts + ( PreparedSemanticFact + , prepareSemanticFact + , preparedSemanticStatement + , preparedSemanticDependencies + , FactOrigin + , factOrigin + , factOriginLocation + , factOriginBlock + , StagedFact + , stageFact + , stagedFactAliases + , stagedFactSemantic + , FactRegistry + , emptyFactRegistry + , registerStagedFacts + , lookupPreparedFact + , lookupFactOrigin + , registeredFacts + , restrictFactRegistry + , partitionFactRegistry + , factRegistryInvariant + ) where + +import Base +import Report.Location +import Syntax.Internal + +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 + + +-- | A checked fact before aliases and diagnostic provenance are attached. +-- This is the complete long-lived semantic row. +data PreparedSemanticFact = PreparedSemanticFact + { preparedSemanticStatement :: !Formula + , preparedSemanticDependencies :: !(Set Symbol) + } + deriving (Show, Eq) + +prepareSemanticFact :: Formula -> PreparedSemanticFact +prepareSemanticFact statement = + PreparedSemanticFact + { preparedSemanticStatement = statement + , preparedSemanticDependencies = mentionedSymbols statement + } + +data FactOrigin = FactOrigin + { factOriginLocation :: !Location + , factOriginBlock :: !Marker + } + deriving (Show, Eq) + +factOrigin :: Location -> Marker -> FactOrigin +factOrigin = FactOrigin + +-- | Registration data for one semantic fact. +data StagedFact = StagedFact + { stagedFactAliases :: !(NonEmpty Marker) + , stagedFactOrigin :: !FactOrigin + , stagedFactSemantic :: !PreparedSemanticFact + } + deriving (Show, Eq) + +stageFact + :: NonEmpty Marker + -> FactOrigin + -> PreparedSemanticFact + -> StagedFact +stageFact = StagedFact + +newtype FactHandle = FactHandle + { unFactHandle :: Int + } + deriving (Show, Eq, Ord) + +data FactRegistration = FactRegistration + { factRegistrationAliases :: !(NonEmpty Marker) + , factRegistrationOrigin :: !FactOrigin + } + deriving (Show, Eq) + +-- | Semantic rows and their registration sidecars share only a private handle. +data FactRegistry = FactRegistry + { factOrder :: ![FactHandle] + , factRows :: !(Map FactHandle PreparedSemanticFact) + , factRegistrations :: !(Map FactHandle FactRegistration) + , factAliases :: !(Map Marker FactHandle) + , nextFactHandle :: !Int + } + deriving (Show, Eq) + +emptyFactRegistry :: FactRegistry +emptyFactRegistry = + FactRegistry + { factOrder = [] + , factRows = mempty + , factRegistrations = mempty + , factAliases = mempty + , nextFactHandle = 0 + } + +registerStagedFacts + :: NonEmpty StagedFact + -> FactRegistry + -> Either Marker FactRegistry +registerStagedFacts staged registry = + case firstDuplicateAlias (Map.keysSet (factAliases registry)) aliases of + Just duplicate -> + Left duplicate + Nothing -> + Right + registry + { factOrder = handles <> factOrder registry + , factRows = + Map.union + (Map.fromList + [ (handle, stagedFactSemantic fact) + | (handle, fact) <- registrations + ]) + (factRows registry) + , factRegistrations = + Map.union + (Map.fromList + [ ( handle + , FactRegistration + { factRegistrationAliases = + stagedFactAliases fact + , factRegistrationOrigin = + stagedFactOrigin fact + } + ) + | (handle, fact) <- registrations + ]) + (factRegistrations registry) + , factAliases = + Map.union + (Map.fromList + [ (alias, handle) + | (handle, fact) <- registrations + , alias <- + NonEmpty.toList (stagedFactAliases fact) + ]) + (factAliases registry) + , nextFactHandle = + nextFactHandle registry + length stagedList + } + where + stagedList = NonEmpty.toList staged + handles = + FactHandle <$> + [ nextFactHandle registry + .. nextFactHandle registry + length stagedList - 1 + ] + registrations = zip handles stagedList + aliases = + concatMap + (NonEmpty.toList . stagedFactAliases) + stagedList + +lookupPreparedFact :: Marker -> FactRegistry -> Maybe PreparedSemanticFact +lookupPreparedFact marker registry = do + handle <- Map.lookup marker (factAliases registry) + Map.lookup handle (factRows registry) + +lookupFactOrigin :: Marker -> FactRegistry -> Maybe FactOrigin +lookupFactOrigin marker registry = do + handle <- Map.lookup marker (factAliases registry) + factRegistrationOrigin + <$> Map.lookup handle (factRegistrations registry) + +-- | Facts in checker premise order, paired with their primary aliases. +registeredFacts :: FactRegistry -> [(Marker, PreparedSemanticFact)] +registeredFacts registry = + [ (primaryAlias registration, semantic) + | handle <- factOrder registry + , Just registration <- [Map.lookup handle (factRegistrations registry)] + , Just semantic <- [Map.lookup handle (factRows registry)] + ] + where + primaryAlias = + NonEmpty.head . factRegistrationAliases + +restrictFactRegistry + :: NonEmpty Marker + -> FactRegistry + -> Either Marker FactRegistry +restrictFactRegistry markers registry = do + handles <- traverse resolveHandle markers + pure (registryForHandles (orderedUnique (NonEmpty.toList handles)) registry) + where + resolveHandle marker = + maybe (Left marker) Right (Map.lookup marker (factAliases registry)) + +partitionFactRegistry + :: NonEmpty Marker + -> FactRegistry + -> Either Marker (FactRegistry, FactRegistry) +partitionFactRegistry markers registry = do + selected <- restrictFactRegistry markers registry + let selectedHandles = Set.fromList (factOrder selected) + selectedInRegistryOrder = + List.filter (`Set.member` selectedHandles) (factOrder registry) + unselectedHandles = + List.filter (`Set.notMember` selectedHandles) (factOrder registry) + pure + ( registryForHandles selectedInRegistryOrder registry + , registryForHandles unselectedHandles registry + ) + +factRegistryInvariant :: FactRegistry -> Bool +factRegistryInvariant registry = + length order == Set.size orderSet + && orderSet == Map.keysSet (factRows registry) + && orderSet == Map.keysSet (factRegistrations registry) + && aliasCount == Map.size expectedAliases + && expectedAliases == factAliases registry + && all ((< nextFactHandle registry) . unFactHandle) order + where + order = factOrder registry + orderSet = Set.fromList order + aliasPairs = + [ (alias, handle) + | (handle, registration) <- + Map.toList (factRegistrations registry) + , alias <- + NonEmpty.toList (factRegistrationAliases registration) + ] + aliasCount = length aliasPairs + expectedAliases = Map.fromList aliasPairs + +registryForHandles :: [FactHandle] -> FactRegistry -> FactRegistry +registryForHandles handles registry = + registry + { factOrder = handles + , factRows = Map.restrictKeys (factRows registry) handleSet + , factRegistrations = + Map.restrictKeys (factRegistrations registry) handleSet + , factAliases = + Map.filter (`Set.member` handleSet) (factAliases registry) + } + where + handleSet = Set.fromList handles + +firstDuplicateAlias :: Set Marker -> [Marker] -> Maybe Marker +firstDuplicateAlias existing = + go mempty + where + go _seen [] = + Nothing + go seen (marker:rest) + | marker `Set.member` existing || marker `Set.member` seen = + Just marker + | otherwise = + go (Set.insert marker seen) rest + +orderedUnique :: Ord a => [a] -> [a] +orderedUnique = + reverse . snd . foldl' step (mempty, []) + where + step (seen, acc) value + | value `Set.member` seen = + (seen, acc) + | otherwise = + (Set.insert value seen, value : acc) diff --git a/source/Checking/Structure.hs b/source/Checking/Structure.hs new file mode 100644 index 0000000..a4ca5db --- /dev/null +++ b/source/Checking/Structure.hs @@ -0,0 +1,134 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE RecordWildCards #-} + +module Checking.Structure + ( CheckedStructDefn + , StructurePreparationError(..) + , prepareCheckedStructDefn + , checkedStructPhrase + , checkedStructAncestors + , checkedStructInternalSymbols + , checkedStructAllSymbols + , checkedStructSymbol + , checkedStructDependencies + , checkedStructSemanticFacts + , checkedStructFacts + , checkedStructMarkers + ) where + +import Base +import Checking.Facts +import Report.Location +import Syntax.Internal +import Syntax.Lexicon + +import Data.List.NonEmpty qualified as NonEmpty +import Data.Set qualified as Set + + +-- | A structure declaration whose formulas have been fully prepared. +data CheckedStructDefn = CheckedStructDefn + { checkedStructPhrase :: !StructPhrase + , checkedStructAncestors :: !(Set StructPhrase) + , checkedStructInternalSymbols :: !(Set StructSymbol) + , checkedStructAllSymbols :: !(Set StructSymbol) + , checkedStructSymbol :: !Symbol + , checkedStructDependencies :: !(Set Symbol) + , checkedStructFacts :: !(NonEmpty StagedFact) + } + deriving (Show, Eq) + +checkedStructSemanticFacts + :: CheckedStructDefn + -> NonEmpty PreparedSemanticFact +checkedStructSemanticFacts = + fmap stagedFactSemantic . checkedStructFacts + +checkedStructMarkers :: CheckedStructDefn -> NonEmpty Marker +checkedStructMarkers = + (>>= stagedFactAliases) . checkedStructFacts + +data StructurePreparationError + = SelfReferentialStructure !Symbol + deriving (Show, Eq) + +prepareCheckedStructDefn + :: Location + -> Marker + -> StructDefn + -> Set StructPhrase + -> Set StructSymbol + -> Either StructurePreparationError CheckedStructDefn +prepareCheckedStructDefn location marker StructDefn{..} ancestors inheritedSymbols + | prospectiveSymbol `Set.member` assumptionDependencies = + Left (SelfReferentialStructure prospectiveSymbol) + | otherwise = + Right + CheckedStructDefn + { checkedStructPhrase = structPhrase + , checkedStructAncestors = ancestors + , checkedStructInternalSymbols = structDefnFixes + , checkedStructAllSymbols = structDefnFixes <> inheritedSymbols + , checkedStructSymbol = prospectiveSymbol + , checkedStructDependencies = + parentSymbols <> assumptionDependencies + , checkedStructFacts = stagedFacts + } + where + prospectiveSymbol = + SymbolPredicate (PredicateNounStruct structPhrase) + parentSymbols = + Set.map + (SymbolPredicate . PredicateNounStruct) + structParents + assumptionDependencies = + Set.unions + [ preparedSemanticDependencies (prepareSemanticFact formula) + | (_assumptionMarker, formula) <- structDefnAssumes + ] + isStruct phrase = + TermSymbol + Nowhere + (SymbolPredicate (PredicateNounStruct phrase)) + [TermVar structDefnLabel] + parentPremises + | structParents == Set.singleton _Onesorted = + [] + | otherwise = + isStruct <$> Set.toList structParents + intro = + makeConjunction + (parentPremises <> (snd <$> structDefnAssumes)) + `Implies` isStruct structPhrase + inherit = + isStruct structPhrase + `Implies` makeConjunction + [ isStruct parent + | parent <- Set.toList structParents + ] + generated = + (marker, intro) + :| ( (inheritMarker, inherit) + : [ (assumptionMarker, isStruct structPhrase `Implies` formula) + | (assumptionMarker, formula) <- structDefnAssumes + ] + ) + inheritMarker = + case marker of + Marker text -> + Marker (text <> "inherit") + origin = + factOrigin location marker + semanticFacts = + fmap + (prepareSemanticFact . forallClosure mempty . snd) + generated + stagedFacts = + NonEmpty.zipWith + (\(factMarker, _formula) semantic -> + stageFact + (factMarker :| []) + origin + semantic) + generated + semanticFacts |
