diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2024-10-09 18:31:30 -0600 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2024-10-09 18:31:59 -0600 |
commit | 25a00d9dae44dd1fce00ba554db252ffafcd3d86 (patch) | |
tree | e0f0bb96775a3096355a4f4b9a083f017627b034 /src/Language/Fiddle/Compiler.hs | |
parent | 9832f887e1772e1c0f546371584be323ae440fb8 (diff) | |
download | fiddle-25a00d9dae44dd1fce00ba554db252ffafcd3d86.tar.gz fiddle-25a00d9dae44dd1fce00ba554db252ffafcd3d86.tar.bz2 fiddle-25a00d9dae44dd1fce00ba554db252ffafcd3d86.zip |
CompilationPhases are categories.
Diffstat (limited to 'src/Language/Fiddle/Compiler.hs')
-rw-r--r-- | src/Language/Fiddle/Compiler.hs | 224 |
1 files changed, 163 insertions, 61 deletions
diff --git a/src/Language/Fiddle/Compiler.hs b/src/Language/Fiddle/Compiler.hs index 812d4f6..571c7b0 100644 --- a/src/Language/Fiddle/Compiler.hs +++ b/src/Language/Fiddle/Compiler.hs @@ -1,80 +1,145 @@ {-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RankNTypes #-} module Language.Fiddle.Compiler where import Control.Arrow +import Control.Category import Control.Monad (when) import Control.Monad.Identity (Identity) import Control.Monad.RWS import Control.Monad.Trans.Maybe import Control.Monad.Writer +import Data.Aeson (FromJSON, ToJSON (toJSON), encode) +import qualified Data.ByteString.Lazy.Char8 as BL import Data.Default +import Data.Kind (Type) import Data.Typeable +import GHC.Generics (Generic) import Language.Fiddle.Ast import Language.Fiddle.Types import Options.Applicative import System.IO (hPutStrLn, stderr) -import qualified Data.ByteString.Lazy.Char8 as BL import Text.Parsec (sourceColumn, sourceLine, sourceName) -import Data.Aeson (FromJSON, ToJSON (toJSON), encode) -import GHC.Generics (Generic) - +import Prelude hiding (id, (.)) + +-- | Represents the severity level of a diagnostic message. It indicates +-- how critical the diagnostic is. +-- +-- - 'Error': Indicates a serious problem that causes the compilation to fail, +-- meaning the resulting AST will not be passed to the backend. +-- - 'Warning': Represents a less severe issue, suggesting that something may +-- need attention but does not halt the compilation. +-- - 'Info': Used for informational messages that do not indicate a problem. data Level = Error | Warning | Info deriving (Eq, Ord, Show, Read, Enum, ToJSON, FromJSON, Generic) -data Diagnostic = Diagnostic { - diagnosticLevel :: Level, - diagnosticMesasge :: String, - diagnosticLocation :: SourceSpan +-- | A general representation of a compilation artifact. This can be any +-- typeable value that is generated or collected during the compilation +-- process. The 'Artifact' type is used to carry various outputs or +-- intermediate results produced by the compiler. +data Artifact where + Artifact :: forall t. (Typeable t) => t -> Artifact + +-- | Represents a diagnostic message that may be emitted during compilation. +-- A 'Diagnostic' contains the severity level, a descriptive message, and +-- the location in the source code where the issue was found. +data Diagnostic = Diagnostic + { -- | The severity of the diagnostic. + diagnosticLevel :: Level, + -- | The message describing the issue. + diagnosticMessage :: String, + -- | The location in the source code. + diagnosticLocation :: SourceSpan } deriving (Generic, ToJSON, FromJSON, Typeable) +-- | Emits a diagnostic with 'Error' severity, indicating a critical issue +-- that will cause the compilation to fail. The diagnostic includes a message +-- and the location where the error occurred. emitDiagnosticError :: String -> Commented SourceSpan -> Compile a () emitDiagnosticError str a = tell [Diagnostic Error str (unCommented a)] +-- | Emits a diagnostic with 'Warning' severity, indicating a potential issue +-- that does not prevent compilation but may require attention. Includes a +-- message and location information. emitDiagnosticWarning :: String -> Commented SourceSpan -> Compile a () emitDiagnosticWarning str a = tell [Diagnostic Warning str (unCommented a)] +-- | Emits a diagnostic with 'Info' severity, used to provide informational +-- messages during compilation. Includes a message and the relevant source +-- location. emitDiagnosticInfo :: String -> Commented SourceSpan -> Compile a () emitDiagnosticInfo str a = tell [Diagnostic Info str (unCommented a)] -data Artifact where - Artifact :: forall t. (Typeable t) => t -> Artifact +-- | Emits a compilation artifact, which is a general output or data produced +-- during the compilation process. Artifacts are collected for later use, such +-- as generating output files or debugging information. +emitArtifact :: Artifact -> Compile s () +emitArtifact art = Compile $ tell ([], [art]) +-- | The primary monad for managing the compilation process. 'Compile' handles +-- emitting diagnostics and collecting artifacts while allowing the state to be +-- modified. The 'MaybeT' layer is used to represent potential compilation +-- failure. newtype Compile s a = Compile (MaybeT (RWS () ([Diagnostic], [Artifact]) s) a) deriving newtype (Functor, Applicative, Monad) +-- | Signals a compilation failure, terminating the current compilation process. +-- No further diagnostics will be emitted. This should be used sparingly and +-- only when it is not possible to continue compilation. compilationFailure :: Compile s a compilationFailure = Compile $ MaybeT (return Nothing) +-- | Provides a 'MonadWriter' instance for the 'Compile' monad, allowing +-- diagnostics to be collected and manipulated during the compilation process. +-- The diagnostics are treated as the output to be written. instance MonadWriter [Diagnostic] (Compile s) where + -- \| Add diagnostics to the compilation output. tell t = Compile $ tell (t, []) + + -- \| Listen for diagnostics emitted during a computation, returning the result + -- along with the collected diagnostics. listen (Compile fn) = Compile $ do (a, (ds, _)) <- listen fn return (a, ds) + + -- \| Modify the collected diagnostics using the given function. pass (Compile mfun) = Compile $ pass (second first <$> mfun) +-- | Provides a 'MonadState' instance for the 'Compile' monad, allowing +-- manipulation of the compiler's state during the compilation process. instance MonadState s (Compile s) where + -- \| Get the current state. get = Compile get + + -- \| Set a new state. put s = Compile $ put s + + -- \| Apply a state transformation function. state fn = Compile $ state fn +-- | Run a compilation routine under a temporary state. After the routine +-- completes, the original state is restored, and the result along with the +-- modified temporary state is returned. subCompile :: s' -> Compile s' a -> Compile s (s', a) subCompile s' (Compile mtrws) = Compile $ do let (a, s, w) = runRWS (runMaybeT mtrws) () s' tell w MaybeT $ return $ fmap (s,) a -emitArtifact :: Artifact -> Compile s () -emitArtifact art = Compile $ tell ([], [art]) - +-- | Save the current state and run a provided compilation routine with a +-- duplicated state. Once the routine finishes, the original state is restored. pushState :: Compile s a -> Compile s a pushState cp = do s <- get snd <$> subCompile s cp +-- | Runs a compilation routine with the given initial state and returns the +-- diagnostics, artifacts, and result. If there are any 'Error' diagnostics, +-- the result will be 'Nothing'. compile :: Compile s a -> s -> ([Diagnostic], [Artifact], Maybe a) compile (Compile fn) initState = do let (a, _, (w, as)) = runRWS (runMaybeT fn) () initState @@ -82,84 +147,103 @@ compile (Compile fn) initState = do where hasError = any (\(Diagnostic e _ _) -> e == Error) +-- | Runs a compilation routine with a default initial state, using the 'Default' +-- type class to provide the initial state. compile_ :: (Default s) => Compile s a -> ([Diagnostic], [Artifact], Maybe a) compile_ c = compile c def +-- | Converts a 'Maybe' value into the 'Compile' monad. If the value is 'Nothing', +-- the compilation fails. hoistMaybe :: Maybe a -> Compile s a hoistMaybe = Compile . MaybeT . return -newtype DiagnosticFormat = DiagnosticFormat ([Diagnostic] -> String) +-- | Represents a format for displaying diagnostics, providing a function +-- to convert a list of diagnostics into a string. +newtype DiagnosticFormat = DiagnosticFormat + { diagnosticsToString :: [Diagnostic] -> String + } -jsonFormat :: DiagnosticFormat -jsonFormat = DiagnosticFormat $ BL.unpack . encode . toJSON +-- | Diagnostic format that outputs diagnostics as a JSON string. +jsonDiagnosticFormat :: DiagnosticFormat +jsonDiagnosticFormat = DiagnosticFormat $ BL.unpack . encode . toJSON +-- | Diagnostic format that outputs diagnostics in a colored format using ANSI +-- escape codes. The colors indicate the diagnostic level (Error, Warning, Info). coloredFormat :: DiagnosticFormat -coloredFormat = DiagnosticFormat $ \diags -> unlines $ map (\(Diagnostic level message (SourceSpan pos1 pos2)) -> - execWriter $ do - case level of - Error -> tell "\x1b[01;31mError " - Warning -> tell "\x1b[01;33mWarn " - Info -> tell "\x1b[01;37mInfo " - - tell "\x1b[0m" - tell (sourceName pos1) - tell "(" - tellPos pos1 - when (pos2 /= pos1) $ do - tell "-" - tellPos pos2 - tell "): " - tell (unwords $ words message) - ) diags +coloredFormat = DiagnosticFormat $ \diags -> + unlines $ + map + ( \(Diagnostic level message (SourceSpan pos1 pos2)) -> + execWriter $ do + case level of + Error -> tell "\x1b[01;31mError " + Warning -> tell "\x1b[01;33mWarn " + Info -> tell "\x1b[01;37mInfo " + + tell "\x1b[0m" + tell (sourceName pos1) + tell "(" + tellPos pos1 + when (pos2 /= pos1) $ do + tell "-" + tellPos pos2 + tell "): " + tell (unwords $ words message) + ) + diags where + -- \| Helper function to format a 'SourcePos' as a line:column string. tellPos pos = do tell (show $ sourceLine pos) tell ":" tell (show $ sourceColumn pos) -diagnosticsToString :: DiagnosticFormat -> [Diagnostic] -> String -diagnosticsToString (DiagnosticFormat f) = f - +-- | Prints a list of diagnostics to 'stderr' using the specified +-- diagnostic format. printDiagnostics :: DiagnosticFormat -> [Diagnostic] -> IO () printDiagnostics fmt d = hPutStrLn stderr (diagnosticsToString fmt d) +-- | Prints a single diagnostic to 'stderr' using the 'coloredFormat' +-- to visually indicate the diagnostic level (Error, Warning, Info). printDiagnostic :: Diagnostic -> IO () printDiagnostic d = hPutStrLn stderr (diagnosticsToString coloredFormat [d]) +-- | Converts a 'Maybe' value into the 'Compile' monad, emitting an +-- 'Error' diagnostic and causing compilation to fail if the value is 'Nothing'. +-- If the value is 'Just a', it is returned. fromMayberOrFail :: SourceSpan -> String -> Maybe a -> Compile s a fromMayberOrFail sourceSpan err Nothing = do tell [Diagnostic Error err sourceSpan] compilationFailure fromMayberOrFail _ _ (Just a) = return a +-- | Creates a pure compilation phase. The provided function is used to +-- transform the 'FiddleUnit' from one stage to another within the compilation +-- pipeline, without requiring any additional state or side effects. pureCompilationPhase :: - (CompilationStage stageFrom) => ( FiddleUnit stageFrom Identity - (StageAnnotation stageFrom) -> + (Commented SourceSpan) -> Compile () ( FiddleUnit stageTo Identity - (StageAnnotation stageTo) + (Commented SourceSpan) ) ) -> CompilationPhase stageFrom stageTo pureCompilationPhase fn = CompilationPhase (pure ()) (\_ _ -> return ([], Just ())) (\_ _ -> fn) +-- | Emits a list of compilation artifacts, adding them to the compilation +-- output. Artifacts are user-defined data that can be generated during +-- the compilation process. emitArtifacts :: [Artifact] -> Compile s () emitArtifacts arts = Compile $ tell ([], arts) --- data IOActionExtraData = IOActionExtraData --- { parseFile :: FilePath -> IO (TreeType FiddleUnit Parsed), --- stage3Compile :: TreeType FiddleUnit Parsed -> --- TreeType FiddleUnit Checked --- } - -- | 'CompilationPhase' represents a phase in the compilation process. -- It consists of an IO action that performs necessary side effects or state -- preparations before the next stage, and a function that transforms the @@ -187,17 +271,21 @@ data CompilationPhase stageFrom stageTo where FiddleUnit stageFrom Identity - (StageAnnotation stageFrom) -> + (Commented SourceSpan) -> Compile () ( FiddleUnit stageTo Identity - (StageAnnotation stageTo) + (Commented SourceSpan) ) } -> CompilationPhase stageFrom stageTo +instance Category CompilationPhase where + id = pureCompilationPhase return + (.) = flip thenPhase + -- | 'thenPhase' composes two 'CompilationPhase' stages into a single pipeline -- phase. It combines their IO actions and applies each stage in sequence. thenPhase :: @@ -224,23 +312,20 @@ thenPhase compile2 f2 s2 secondStage ) --- | Infix operator for 'thenPhase' to chain compilation phases. -(>>>) :: CompilationPhase stage1 stage2 -> CompilationPhase stage2 stage3 -> CompilationPhase stage1 stage3 -(>>>) = thenPhase - -- | 'execCompilationPipeline' executes a full compilation pipeline starting -- from the 'Parsed' phase. It performs the IO action of the first phase and -- then invokes the compilation function for the remaining stages. It returns -- a tuple containing diagnostics and an optional final 'FiddleUnit'. execCompilationPipelineWithCmdline :: + (StageAnnotation Parsed ~ Commented SourceSpan) => CompilationPhase Parsed s' -> Parser - ( FiddleUnit Parsed Identity (StageAnnotation Parsed) -> + ( FiddleUnit Parsed Identity (Commented SourceSpan) -> IO ( [Diagnostic], [Artifact], Maybe - ( FiddleUnit s' Identity (StageAnnotation s') + ( FiddleUnit s' Identity (Commented SourceSpan) ) ) ) @@ -250,26 +335,43 @@ execCompilationPipelineWithCmdline ( \opts ast -> do (diags, ms) <- ioAct opts ast case ms of - Just s -> return $ (\(a, b, c) -> (diags ++ a, b, c)) $ compile_ $ rest opts s ast + Just s -> + return $ + (\(a, b, c) -> (diags ++ a, b, c)) $ + compile_ $ + rest opts s ast Nothing -> return (diags, [], Nothing) ) flagsParser +-- | Extracts all embedded diagnostics from the abstract syntax tree (AST) +-- and emits them as compilation warnings or errors. This function inspects +-- each subelement in the AST for diagnostic messages. If any subelement +-- contains errors (represented as a 'Left' value), the compilation fails. +-- Otherwise, the AST is returned with all deferred diagnostics resolved. squeezeDiagnostics :: (Alter t) => t (Either [Diagnostic]) a -> Compile () (t Identity a) squeezeDiagnostics ast = do + -- Iterate over the AST, emitting any diagnostics found. _ <- alter ( \case - (Left l) -> tell l >> return (Left l) - r -> return r + (Left l) -> tell l >> return (Left l) -- Emit diagnostics if present. + r -> return r -- Otherwise, leave the subelement unchanged. ) return ast + -- Attempt to transform the AST into a non-deferred form. + -- If any subelement contained errors, the transformation will fail. case squeeze ast of - (Left _) -> compilationFailure - (Right a) -> return a - + (Left _) -> compilationFailure -- Fail compilation if there are unresolved diagnostics. + (Right a) -> return a -- Return the resolved AST if no errors are found. + +-- | Checks the result of a computation and either returns the result or +-- emits any associated diagnostics and fails the compilation. If the result +-- is a 'Right' value, the contained value is returned. If it is a 'Left' +-- value containing diagnostics, they are emitted and the compilation is +-- terminated. resolveOrFail :: Either [Diagnostic] a -> Compile s a -resolveOrFail (Right a) = return a -resolveOrFail (Left l) = tell l >> compilationFailure +resolveOrFail (Right a) = return a -- Return the value if no diagnostics. +resolveOrFail (Left l) = tell l >> compilationFailure -- Emit diagnostics and fail if errors are present. |