summaryrefslogtreecommitdiff
path: root/source/Felix/Prelude.hs
diff options
context:
space:
mode:
Diffstat (limited to 'source/Felix/Prelude.hs')
-rw-r--r--source/Felix/Prelude.hs200
1 files changed, 180 insertions, 20 deletions
diff --git a/source/Felix/Prelude.hs b/source/Felix/Prelude.hs
index d159b21..28ad961 100644
--- a/source/Felix/Prelude.hs
+++ b/source/Felix/Prelude.hs
@@ -1,7 +1,7 @@
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE NoImplicitPrelude #-}
--- | The reserved in-memory prelude source used during migration.
+-- | The reserved packaged final-prelude source and its authority-free parser.
module Felix.Prelude
( ReservedPreludeSourceInput
, reservedPreludeSourceInput
@@ -9,11 +9,15 @@ module Felix.Prelude
, emptyBootstrapSourceInput
, reservedPreludeSourceOwner
, reservedPreludeSourceLabel
+ , reservedPreludeSourceCanonicalPath
, reservedPreludeSourceBytes
, reservedPreludeSourceText
+ , rejectOrdinaryPreludeSourceGraph
, preludeDiagnosticLabel
, PreludeLoadError(..)
+ , renderPreludeLoadError
, PreludeParseError(..)
+ , renderPreludeParseError
, ReservedParsedPrelude
, reservedParsedPreludeInput
, reservedParsedPreludeModule
@@ -24,6 +28,8 @@ import Base
import Felix.Module
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
@@ -41,6 +47,7 @@ import Paths_felix qualified as Paths
data ReservedPreludeSourceInput = ReservedPreludeSourceInput
!ModuleName
!FilePath
+ !(Maybe CanonicalPath)
!ByteString
!Text
@@ -51,38 +58,76 @@ reservedPreludeSourceInput bytes =
ReservedPreludeSourceInput
preludeModuleName
preludeDiagnosticLabel
+ Nothing
bytes
<$> Text.decodeUtf8' bytes
data PreludeLoadError
= PreludeSourceReadFailed !FilePath !Text
+ | PreludeSourceCanonicalizationFailed !FilePath !SourceError
| PreludeSourceUtf8Failed !UnicodeException
- deriving stock (Show)
+
+instance Show PreludeLoadError where
+ show = Text.unpack . renderPreludeLoadError
+
+renderPreludeLoadError :: PreludeLoadError -> Text
+renderPreludeLoadError = \case
+ PreludeSourceReadFailed path failure ->
+ Text.pack path
+ <> ": unable to read packaged final prelude: "
+ <> failure
+ PreludeSourceCanonicalizationFailed path failure ->
+ Text.pack path
+ <> ": unable to resolve packaged final prelude: "
+ <> renderSourceError failure
+ PreludeSourceUtf8Failed failure ->
+ preludeErrorPrefix
+ <> "malformed UTF-8: "
+ <> Text.pack (displayException failure)
loadReservedPreludeSourceInput
:: IO (Either PreludeLoadError ReservedPreludeSourceInput)
loadReservedPreludeSourceInput = do
path <- Paths.getDataFileName "data/felix-prelude.tex"
- loaded <- try (ByteString.readFile path)
- :: IO (Either IOException ByteString)
- pure case loaded of
+ canonicalized <- canonicalizeExistingSourcePath path
+ case canonicalized of
Left failure ->
- Left
- (PreludeSourceReadFailed
- path
- (Text.pack (displayException failure)))
- Right bytes ->
- case reservedPreludeSourceInput bytes of
+ pure
+ (Left
+ (PreludeSourceCanonicalizationFailed path failure))
+ Right canonical -> do
+ loaded <- try
+ (ByteString.readFile
+ (canonicalPathFilePath canonical))
+ :: IO (Either IOException ByteString)
+ pure case loaded of
Left failure ->
- Left (PreludeSourceUtf8Failed failure)
- Right input ->
- Right input
+ Left
+ (PreludeSourceReadFailed
+ path
+ (Text.pack (displayException failure)))
+ Right bytes ->
+ case Text.decodeUtf8' bytes of
+ Left failure ->
+ Left (PreludeSourceUtf8Failed failure)
+ Right sourceText ->
+ Right
+ (ReservedPreludeSourceInput
+ preludeModuleName
+ preludeDiagnosticLabel
+ (Just canonical)
+ bytes
+ sourceText)
+-- | Empty final-prelude input used only by focused compiler fixtures.
+--
+-- Production verification always calls 'loadReservedPreludeSourceInput'.
emptyBootstrapSourceInput :: ReservedPreludeSourceInput
emptyBootstrapSourceInput =
ReservedPreludeSourceInput
preludeModuleName
preludeDiagnosticLabel
+ Nothing
ByteString.empty
Text.empty
@@ -90,28 +135,58 @@ reservedPreludeSourceOwner
:: ReservedPreludeSourceInput
-> ModuleName
reservedPreludeSourceOwner
- (ReservedPreludeSourceInput owner _label _bytes _text) =
+ (ReservedPreludeSourceInput owner _label _canonical _bytes _text) =
owner
reservedPreludeSourceLabel
:: ReservedPreludeSourceInput
-> FilePath
reservedPreludeSourceLabel
- (ReservedPreludeSourceInput _owner label _bytes _text) =
+ (ReservedPreludeSourceInput _owner label _canonical _bytes _text) =
label
+reservedPreludeSourceCanonicalPath
+ :: ReservedPreludeSourceInput
+ -> Maybe CanonicalPath
+reservedPreludeSourceCanonicalPath
+ (ReservedPreludeSourceInput _owner _label canonical _bytes _text) =
+ canonical
+
reservedPreludeSourceBytes
:: ReservedPreludeSourceInput
-> ByteString
reservedPreludeSourceBytes
- (ReservedPreludeSourceInput _owner _label bytes _text) =
+ (ReservedPreludeSourceInput _owner _label _canonical bytes _text) =
bytes
reservedPreludeSourceText :: ReservedPreludeSourceInput -> Text
reservedPreludeSourceText
- (ReservedPreludeSourceInput _owner _label _bytes sourceText) =
+ (ReservedPreludeSourceInput
+ _owner _label _canonical _bytes sourceText) =
sourceText
+-- | Reject an ordinary source graph containing the physical packaged
+-- prelude. Synthetic reserved inputs deliberately have no physical path.
+rejectOrdinaryPreludeSourceGraph
+ :: ReservedPreludeSourceInput
+ -> ResolvedSourceGraph
+ -> Either SourceError ()
+rejectOrdinaryPreludeSourceGraph sourceInput graph =
+ case reservedPreludeSourceCanonicalPath sourceInput of
+ Nothing ->
+ Right ()
+ Just reservedPath ->
+ case listToMaybe
+ [ source
+ | node <- sourceGraphNodes graph
+ , let source = sourceNodeResolved node
+ , resolvedSourceCanonicalPath source == reservedPath
+ ] of
+ Nothing ->
+ Right ()
+ Just source ->
+ Left (PackagedPreludeSelectedAsOrdinarySource source)
+
preludeDiagnosticLabel :: FilePath
preludeDiagnosticLabel =
"<felix-prelude>"
@@ -130,7 +205,92 @@ data PreludeParseError
| PreludeFreshInputFailed !FreshModuleInputError
| PreludeParsedModuleKeyFailed !Parsed.ParsedModuleKeyError
| PreludeParseInvariantFailed !Text
- deriving stock (Show)
+
+instance Show PreludeParseError where
+ show = Text.unpack . renderPreludeParseError
+
+renderPreludeParseError :: PreludeParseError -> Text
+renderPreludeParseError = \case
+ PreludeLocationRegistrationFailed FileIdSpaceExhausted ->
+ preludeErrorPrefix
+ <> "could not register source locations: file identifier space exhausted"
+ PreludeSyntaxPragmaFailed failure ->
+ renderSyntaxPragmaError failure
+ PreludeImportNotSupported imports ->
+ preludeErrorPrefix
+ <> "imports are not supported"
+ <> case imports of
+ [] -> ""
+ _ ->
+ ": "
+ <> Text.intercalate
+ ", "
+ (Text.pack . show <$> imports)
+ PreludeLexicalScanFailed failure ->
+ Text.pack (show failure)
+ PreludeSyntaxDeclarationFailed failure ->
+ Text.pack (show failure)
+ PreludeLexiconCollision collision ->
+ Text.pack (show collision)
+ PreludeSyntaxDeltaFailed collision ->
+ preludeErrorPrefix
+ <> "syntax delta has conflicting entries for pattern "
+ <> Text.pack (show (canonicalCollisionPattern collision))
+ <> ": "
+ <> Text.pack
+ (show (toList (canonicalCollisionEntries collision)))
+ PreludeSyntaxInterfaceFailed failure ->
+ preludeErrorPrefix <> renderSyntaxInterfaceError failure
+ PreludeSyntaxMaterializationFailed failure ->
+ preludeErrorPrefix <> renderSyntaxMaterializationError failure
+ PreludeParseFailed failure ->
+ preludeErrorPrefix <> Text.pack (show failure)
+ PreludeFreshInputFailed failure ->
+ preludeErrorPrefix <> renderFreshModuleInputError failure
+ PreludeParsedModuleKeyFailed failure ->
+ preludeErrorPrefix <> renderParsedModuleKeyError failure
+ PreludeParseInvariantFailed failure ->
+ preludeErrorPrefix <> "parse invariant failed: " <> failure
+
+preludeErrorPrefix :: Text
+preludeErrorPrefix =
+ Text.pack preludeDiagnosticLabel <> ": "
+
+renderSyntaxInterfaceError :: SyntaxInterfaceError -> Text
+renderSyntaxInterfaceError = \case
+ DuplicateDirectSyntaxInterface duplicate ->
+ "duplicate direct syntax input " <> Text.pack (show duplicate)
+ UnexpectedBaseSyntaxInterface actual expected ->
+ "base syntax interface mismatch: expected "
+ <> Text.pack (show expected)
+ <> ", got "
+ <> Text.pack (show actual)
+ SyntaxInterfaceIdMismatch asserted computed ->
+ "syntax interface identity mismatch: asserted "
+ <> Text.pack (show asserted)
+ <> ", computed "
+ <> Text.pack (show computed)
+
+renderFreshModuleInputError :: FreshModuleInputError -> Text
+renderFreshModuleInputError = \case
+ FreshModuleTextDoesNotMatchBytes ->
+ "decoded source text does not match its source bytes"
+ FreshPhysicalOwnerMismatch expected actual ->
+ "physical source owner mismatch: expected "
+ <> Text.pack (show expected)
+ <> ", got "
+ <> Text.pack (show actual)
+ FreshPhysicalLocationPathMismatch expected actual ->
+ "physical source location mismatch: expected "
+ <> Text.pack (show expected)
+ <> ", got "
+ <> Text.pack (show actual)
+
+renderParsedModuleKeyError :: Parsed.ParsedModuleKeyError -> Text
+renderParsedModuleKeyError = \case
+ Parsed.DuplicateParsedDirectSyntaxInput duplicate ->
+ "parsed module has duplicate direct syntax input "
+ <> Text.pack (show duplicate)
data ReservedParsedPrelude = ReservedParsedPrelude
!FreshModuleInput
@@ -148,7 +308,7 @@ reservedParsedPreludeModule
reservedParsedPreludeModule (ReservedParsedPrelude _input parsed) =
parsed
--- | Parse a reserved migration input through the ordinary fresh-source path.
+-- | Parse reserved final-prelude input through the ordinary fresh-source path.
parseReservedPreludeSource
:: ReservedPreludeSourceInput
-> IO (Either PreludeParseError ReservedParsedPrelude)