summaryrefslogtreecommitdiff
path: root/src/Language/Fiddle/Compiler/Backend/C.hs
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-10-16 00:03:09 -0600
committerJosh Rahm <joshuarahm@gmail.com>2024-10-16 00:03:09 -0600
commitc31a34382d6fe1307a0c6fe1710c42f27fe833ca (patch)
treef74810d73aeda78e85f63f7c023769791c6afea2 /src/Language/Fiddle/Compiler/Backend/C.hs
parent5924b745fbaf52000981c298ec8f18b3c0c4a1be (diff)
downloadfiddle-c31a34382d6fe1307a0c6fe1710c42f27fe833ca.tar.gz
fiddle-c31a34382d6fe1307a0c6fe1710c42f27fe833ca.tar.bz2
fiddle-c31a34382d6fe1307a0c6fe1710c42f27fe833ca.zip
Add framework for more easily editing files.
This introduces the FilesM monad, which allows for monadic and fragmented writing to files in a filesystem. This provides an abstraction over writing to different "fragments" of files so implementation, headers and declarations can all be written using just one pass of the compiler.
Diffstat (limited to 'src/Language/Fiddle/Compiler/Backend/C.hs')
-rw-r--r--src/Language/Fiddle/Compiler/Backend/C.hs234
1 files changed, 68 insertions, 166 deletions
diff --git a/src/Language/Fiddle/Compiler/Backend/C.hs b/src/Language/Fiddle/Compiler/Backend/C.hs
index 9dbbec6..dd79bac 100644
--- a/src/Language/Fiddle/Compiler/Backend/C.hs
+++ b/src/Language/Fiddle/Compiler/Backend/C.hs
@@ -9,7 +9,7 @@ module Language.Fiddle.Compiler.Backend.C (cBackend) where
import Control.Arrow
import Control.Monad (unless)
import Control.Monad.RWS
-import Control.Monad.State (State)
+import Control.Monad.State
import Control.Monad.Trans.Writer (Writer, execWriter)
import Data.Char (isSpace)
import Data.Data (Typeable, cast)
@@ -26,6 +26,9 @@ import Data.Text (Text)
import qualified Data.Text as Text
import Language.Fiddle.Ast
import Language.Fiddle.Compiler.Backend
+import qualified Language.Fiddle.Compiler.Backend.Internal.FragTree as FragTree
+import Language.Fiddle.Compiler.Backend.Internal.FormattedWriter
+import Language.Fiddle.Compiler.Backend.Internal.Writer
import Language.Fiddle.Internal.UnitInterface
import Language.Fiddle.Internal.UnitNumbers
import Language.Fiddle.Types
@@ -41,147 +44,47 @@ data CBackendFlags = CBackendFlags
type StructName = Text
-newtype Fragment = Fragment Int
- deriving (Eq, Ord)
-
-- | Header fragment. The top. Starts which include guards and has include
-- statements.
-hF :: Fragment
-hF = Fragment 0
+hF :: FileFragment
+hF = ("HEADER", FragTree.above FragTree.center)
-- | Structures fragment. The text fragment where structures are defined.
-sF :: Fragment
-sF = Fragment 25
+sF :: FileFragment
+sF = ("HEADER", FragTree.below (snd hF))
-- | Implementation fragment. This is where function implementations go.
-iF :: Fragment
-iF = Fragment 75
+iF :: FileFragment
+iF = ("HEADER", FragTree.above (snd fF))
-- | Assert fragment. This is where static asserts go.
-aF :: Fragment
-aF = Fragment 50
+aF :: FileFragment
+aF = ("HEADER", FragTree.below (snd sF))
-- | Footer fragment. This is wehre the file include endif goes.
-fF :: Fragment
-fF = Fragment 100
-
-tellLn :: (MonadWriter Text m) => Text -> m ()
-tellLn s = tell s >> tell "\n"
+fF :: FileFragment
+fF = ("HEADER", FragTree.below FragTree.center)
type A = Commented SourceSpan
type I = Identity
-data St = St
-
--- | Current local state information while traversing the tree.
-data Fmt = Fmt
- { indentLevel :: Int,
- pendingLine :: Text
- }
-
-newtype M a = M {unM :: RWS () () (St, Files) a}
- deriving newtype (Functor, Applicative, Monad, MonadState (St, Files))
-
-newtype FormattedWriter a = FormattedWriter (RWS () Text Fmt a)
- deriving newtype (Functor, Applicative, Monad, MonadState Fmt)
-
-indented :: FileM a -> FileM a
-indented fn = do
- textM (modify (\(Fmt id p) -> Fmt (id + 1) p))
- fn <* textM (modify (\(Fmt id p) -> Fmt (id - 1) p))
-
-execFormattedWriter :: FormattedWriter a -> Text
-execFormattedWriter ((>> flush) -> FormattedWriter rws) = snd $ execRWS rws () (Fmt 0 "")
-
-flush :: FormattedWriter ()
-flush = do
- p <- gets pendingLine
- modify $ \s -> s {pendingLine = ""}
- tell p
-
-newtype FileFragments = FileFragments (Map Fragment (FormattedWriter ()))
-
-instance Semigroup FileFragments where
- (FileFragments m1) <> (FileFragments m2) = FileFragments (Map.unionWith (>>) m1 m2)
-
-instance Monoid FileFragments where
- mempty = FileFragments mempty
+type M a = FilesM () FormattedWriter CFileState a
newtype CFileState = CFileState
{ includedFiles :: Set String
}
-newtype FileM a = FileM {unFileM :: RWS Fragment FileFragments CFileState a}
- deriving newtype (Functor, Applicative, Monad, MonadWriter FileFragments, MonadReader Fragment, MonadState CFileState)
-
-execFileM :: FileM a -> Text
-execFileM fm =
- let (_, FileFragments mp) = execRWS (unFileM fm) hF (CFileState mempty)
- in ( execFormattedWriter
- . sequence_
- . Map.elems
- )
- mp
-
-requireInclude :: String -> FileM ()
+requireInclude :: String -> M ()
requireInclude file = do
b <- (Set.member file) <$> gets includedFiles
unless b $ do
- under hF $
+ checkout hF $
text $
Text.pack $
printf "#include <%s>\n" file
modify $ \s -> s {includedFiles = Set.insert file (includedFiles s)}
--- | Writes text to the current fragment context
-text :: Text -> FileM ()
-text t = flip tellF_ t =<< ask
-
--- | Writes text to the current fragment context
-textM :: FormattedWriter () -> FileM ()
-textM t = flip tellFM_ t =<< ask
-
--- | Executes a file monad within a different fragment.
-under :: Fragment -> FileM () -> FileM ()
-under fr = local (const fr)
-
-tellF_ :: Fragment -> Text -> FileM ()
-tellF_ fp txt = tell $ FileFragments $ Map.singleton fp (tell txt)
-
-tellFM_ :: Fragment -> FormattedWriter () -> FileM ()
-tellFM_ fp txtM = tell $ FileFragments $ Map.singleton fp txtM
-
-newtype Files = Files
- { filepaths :: Map FilePath (FileM ())
- }
-
-withFile :: FilePath -> FileM () -> M ()
-withFile fp fn = do
- modify
- ( second $ \(Files {filepaths = fps}) ->
- Files
- { filepaths = Map.alter (Just . (>> fn) . fromMaybe (return ())) fp fps
- }
- )
-
-instance MonadWriter Text FormattedWriter where
- tell txt = FormattedWriter $ do
- indent <- (`Text.replicate` " ") <$> gets indentLevel
- let lines = Text.splitOn "\n" txt
- forM_ (init lines) $ \line -> do
- pending <- gets pendingLine
- modify $ \s -> s {pendingLine = ""}
- tell indent
- tell pending
- tell line
- tell "\n"
- modify $ \s -> s {pendingLine = pendingLine s <> last lines}
-
- listen (FormattedWriter fn) = FormattedWriter $ listen fn
-
- pass (FormattedWriter fn) = FormattedWriter $ pass fn
-
cBackend :: Backend
cBackend =
Backend
@@ -211,10 +114,6 @@ cBackend =
backendTranspile = transpile
}
-toTranspileResult :: Files -> TranspileResult
-toTranspileResult Files {filepaths = fps} =
- TranspileResult $ fmap execFileM fps
-
transpile ::
CBackendFlags ->
() ->
@@ -226,28 +125,37 @@ transpile
cSourceOut = sourceFile
}
()
- fiddleUnit = toTranspileResult . snd . fst $ execRWS (unM run) () (St, Files mempty)
+ fiddleUnit = toTranspileResult $ fst $ runFilesM execFormattedWriter () (CFileState mempty) hF run
where
- run :: M ()
+ toTranspileResult :: Map FilePath Text -> TranspileResult
+ toTranspileResult mp =
+ TranspileResult $
+ Map.mapKeys
+ ( \case
+ "SOURCE" | Right sourceFile' <- sourceFile -> sourceFile'
+ "HEADER" -> headerFile
+ k -> k
+ )
+ mp
+
run = do
- withFile headerFile $ do
+ checkout hF $
textM $ do
tell $ "#ifndef " <> headerGuard <> "\n"
tell $ "#define " <> headerGuard <> "\n\n"
tell "#include <stdint.h>\n"
- -- Pad out the implementation
- under iF $ text "\n"
+ -- Pad out the implementation
+ checkout iF $ text "\n"
walk (transpileWalk sourceFile headerFile) fiddleUnit ()
- withFile headerFile $ do
- under hF $
- textM $ do
- tell "\nstatic_assert(true); // https://github.com/clangd/clangd/issues/1167\n"
+ checkout hF $
+ textM $ do
+ tell "\nstatic_assert(true); // https://github.com/clangd/clangd/issues/1167\n"
- under fF $
- text headerFinal
+ checkout fF $
+ text headerFinal
headerFinal = "\n#endif /* " <> headerGuard <> " */\n"
@@ -269,20 +177,10 @@ instance IsText Text where
qualifiedPathToIdentifier :: (Foldable f, IsText t) => f t -> Text
qualifiedPathToIdentifier = Text.intercalate "_" . map toText . toList
-ensureNL :: FormattedWriter ()
-ensureNL = do
- p <- gets pendingLine
- if Text.null p
- then return ()
- else do
- modify $ \s -> s {pendingLine = ""}
- tell p
- tell "\n"
-
-pad :: FileM () -> FileM ()
+pad :: M () -> M ()
pad f = text "\n" *> f <* text "\n"
-writeStaticAssert :: Text -> String -> N Bytes -> FileM ()
+writeStaticAssert :: Text -> String -> N Bytes -> M ()
writeStaticAssert structName regname off = do
requireInclude "stddef.h"
text $
@@ -309,7 +207,7 @@ selectByModifier mod (getter, setter) =
(ModifierKeyword Wo _) -> [setter]
(ModifierKeyword Pr _) -> []
-writeRegGet :: StructName -> QRegMetadata True -> FileM ()
+writeRegGet :: StructName -> QRegMetadata True -> M ()
writeRegGet
structType
( QRegMetadata
@@ -347,7 +245,7 @@ writeRegGet
tell $ Text.pack $ printf " out[%d] = o->%s[%d];\n" i fieldName i
tell "}\n\n"
-writeRegSet :: StructName -> QRegMetadata True -> FileM ()
+writeRegSet :: StructName -> QRegMetadata True -> M ()
writeRegSet
structType
( QRegMetadata
@@ -401,10 +299,10 @@ pattern DefinedBitsP bitsName bitsFullPath offset <-
}
)
-writeRegisterBody :: StructName -> QRegMetadata True -> RegisterBody Checked I A -> FileM ()
+writeRegisterBody :: StructName -> QRegMetadata True -> RegisterBody Checked I A -> M ()
writeRegisterBody structName regmeta = walk_ registerWalk
where
- registerWalk :: forall t. (Walk t, Typeable t) => t I A -> FileM ()
+ registerWalk :: forall t. (Walk t, Typeable t) => t I A -> M ()
registerWalk t = case () of
()
| (Just (DefinedBitsP bitsName fullPath offset)) <- castTS t ->
@@ -433,7 +331,7 @@ writeImplementation ::
QRegMetadata True ->
Modifier f a ->
Maybe (RegisterBody Checked I A) ->
- FileM ()
+ M ()
-- | Register is just padding, don't emit anything
writeImplementation _ (regIsPadding -> True) _ _ = return ()
@@ -444,7 +342,7 @@ writeImplementation structName qMeta mod bod = do
mapM_ (writeRegisterBody structName qMeta) bod
-structBody :: StructName -> ObjTypeBody Checked I A -> FileM ()
+structBody :: StructName -> ObjTypeBody Checked I A -> M ()
structBody structName (ObjTypeBody _ decls _) = do
forM_ decls $ \(Directed _ decl _) ->
case decl of
@@ -462,10 +360,10 @@ structBody structName (ObjTypeBody _ decls _) = do
tell (sizeToField i sz)
tell ";\n"
- under aF $
+ checkout aF $
writeStaticAssert structName i off
- under iF $
+ checkout iF $
writeImplementation structName regMetadata mod bod
TypeSubStructure
{ subStructureBody = Identity bod,
@@ -491,7 +389,7 @@ structBody structName (ObjTypeBody _ decls _) = do
8 -> "volatile uint64_t " <> f
n -> "volatile uint8_t " <> f <> "[" <> Text.pack (show n) <> "]"
-union :: Text -> FileM () -> FileM ()
+union :: Text -> M () -> M ()
union identifier fn = do
text "#pragma pack(push, 1)\n"
text $ "union " <> identifier <> " "
@@ -499,7 +397,7 @@ union identifier fn = do
text ";\n"
text "#pragma pack(pop)\n"
-struct :: Text -> FileM () -> FileM ()
+struct :: Text -> M () -> M ()
struct identifier fn = do
text "#pragma pack(push, 1)\n"
text $ "struct " <> identifier <> " "
@@ -507,8 +405,11 @@ struct identifier fn = do
text ";\n"
text "#pragma pack(pop)\n"
-body :: FileM a -> FileM a
-body f = text "{\n" *> indented f <* textM (ensureNL >> tell "}")
+body :: M a -> M a
+body f = text "{\n" *> withIndent f <* textM (ensureNL >> tell "}")
+
+withIndent :: M a -> M a
+withIndent = block incIndent decIndent
identifierFor :: (ExportableDecl d) => d -> Text
identifierFor = qualifiedPathToIdentifier . metadataFullyQualifiedPath . getMetadata
@@ -536,7 +437,10 @@ emitDocComments (Commented comments _) = do
then Text.tail t
else t
-transpileWalk :: Either ImplementationInHeader FilePath -> FilePath -> (forall t. (Walk t, Typeable t) => t I A -> () -> M (WalkContinuation ()))
+transpileWalk ::
+ Either ImplementationInHeader FilePath ->
+ FilePath ->
+ (forall t. (Walk t, Typeable t) => t I A -> () -> M (WalkContinuation ()))
transpileWalk _ headerFile t _ = case () of
()
| Just
@@ -551,21 +455,19 @@ transpileWalk _ headerFile t _ = case () of
Union {} -> union
Struct {} -> struct
- withFile headerFile $ do
- under sF $ do
- pad $ do
- textM $ emitDocComments a
- let structName = identifierFor (unwrap metadata)
- structureType structName $ do
- structBody structName objTypeBody
+ checkout sF $ do
+ pad $ do
+ textM $ emitDocComments a
+ let structName = identifierFor (unwrap metadata)
+ structureType structName $ do
+ structBody structName objTypeBody
return Stop
() | Just (getExportedObjectDecl -> Just e) <- castTS t -> do
let qname = qualifiedPathToIdentifier (metadataFullyQualifiedPath (getMetadata e))
- withFile headerFile $
- under fF $ do
- text "#define "
- text qname
- text $ Text.pack $ printf " ((%s*)0x%08x)\n" (toLiteralTypeName (exportedObjectDeclType e)) (exportedObjectDeclLocation e)
+ checkout fF $ do
+ text "#define "
+ text qname
+ text $ Text.pack $ printf " ((%s*)0x%08x)\n" (toLiteralTypeName (exportedObjectDeclType e)) (exportedObjectDeclLocation e)
return Stop
_ -> return (Continue ())