packages feed

hpp 0.5.2 → 0.6.0

raw patch · 17 files changed

+788/−726 lines, 17 filesdep +unordered-containersdep −bytestring-triedep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: unordered-containers

Dependencies removed: bytestring-trie

Dependency ranges changed: base

API changes (from Hackage documentation)

- Hpp.Expansion: expandLine :: (HasError m, Monad m, HasEnv m) => Config -> Int -> Parser m [TOKEN] [TOKEN]
- Hpp.Parser: onChunks :: Monad m => ParserT m (RopeM m [i]) [i] r -> Parser m i r
- Hpp.Parser: parse :: Monad m => Parser m i o -> [i] -> m (o, RopeM m [i])
- Hpp.Parser: runParser :: Monad m => Parser m i o -> RopeM m [i] -> m (o, RopeM m [i])
- Hpp.RunHpp: parseDefinition :: [TOKEN] -> Maybe (String, Macro)
- Hpp.RunHpp: type HppCaps t = (HasError t, HasHppState t, HasEnv t)
- Hpp.Types: writeOutput :: Monad m => t -> HppT t m ()
+ Hpp.Conditional: dropBranch :: (HasError m, HasHppState m, Monad m) => Parser m [TOKEN] ()
+ Hpp.Conditional: takeBranch :: LineNum -> [[TOKEN]] -> [[TOKEN]]
+ Hpp.Config: instance GHC.Show.Show (Hpp.Config.ConfigF Data.Functor.Identity.Identity)
+ Hpp.Directive: directive :: forall m. (Monad m, HasError m, HasHppState m, HasEnv m) => HppT [String] (Parser m [TOKEN]) Bool
+ Hpp.Directive: macroExpansion :: (Monad m, HasHppState m, HasError m, HasEnv m) => HppT [String] (Parser m [TOKEN]) (Maybe [TOKEN])
+ Hpp.Expansion: expandLineState :: (Monad m, HasHppState m, HasEnv m, HasError m) => Parser m [TOKEN] [TOKEN]
+ Hpp.Macro: parseDefinition :: [TOKEN] -> Maybe (String, Macro)
+ Hpp.Parser: instance GHC.Base.Functor (Hpp.Parser.InputItem m)
+ Hpp.Preprocessing: cCommentAndTrigraph :: Stringy s => [s] -> [s]
+ Hpp.Preprocessing: cCommentRemoval :: Stringy s => [s] -> [s]
+ Hpp.Preprocessing: lineSplicing :: Stringy s => [s] -> [s]
+ Hpp.Preprocessing: prepareInput :: (Monad m, HasHppState m) => m ([String] -> [[TOKEN]])
+ Hpp.Preprocessing: trigraphReplacement :: Stringy s => s -> s
+ Hpp.String: stripAngleBrackets :: String -> String
+ Hpp.StringSig: stripR :: ByteString -> ByteString
+ Hpp.Types: [hppCurDir] :: HppState -> FilePath
+ Hpp.Types: dir :: Lens HppState FilePath
+ Hpp.Types: hppReadFile :: Monad m => Int -> FilePath -> HppT src m src
+ Hpp.Types: hppReadNext :: Monad m => Int -> FilePath -> HppT src m src
+ Hpp.Types: hppWriteOutput :: Monad m => t -> HppT t m ()
+ Hpp.Types: instance GHC.Show.Show Hpp.Types.HppState
+ Hpp.Types: type Hpp t = FreeF (HppF t)
- Hpp.CmdLine: runWithArgs :: [String] -> IO ()
+ Hpp.CmdLine: runWithArgs :: [String] -> IO (Maybe Error)
- Hpp.Env: deleteKey :: ByteString -> Trie a -> Trie a
+ Hpp.Env: deleteKey :: ByteString -> HashMap ByteString a -> HashMap ByteString a
- Hpp.Env: emptyEnv :: Trie a
+ Hpp.Env: emptyEnv :: HashMap ByteString a
- Hpp.Env: insertPair :: (ByteString, a) -> Trie a -> Trie a
+ Hpp.Env: insertPair :: (ByteString, a) -> HashMap ByteString a -> HashMap ByteString a
- Hpp.Env: lookupKey :: ByteString -> Trie a -> Maybe a
+ Hpp.Env: lookupKey :: ByteString -> HashMap ByteString a -> Maybe a
- Hpp.Parser: insertInputSegment :: Monad m => src -> m () -> ParserT m (RopeM m src) i ()
+ Hpp.Parser: insertInputSegment :: Monad m => src -> m () -> ParserT m (Input m src) i ()
- Hpp.Parser: onElements :: Monad m => ParserT m (RopeM m [[i]]) i r -> Parser m [i] r
+ Hpp.Parser: onElements :: Monad m => ParserT m (Input m [[i]]) i r -> Parser m [i] r
- Hpp.Parser: onInputSegment :: Monad m => (src -> src) -> ParserT m (RopeM m src) i ()
+ Hpp.Parser: onInputSegment :: Monad m => (src -> src) -> ParserT m (Input m src) i ()
- Hpp.Parser: onIsomorphism :: forall m a b src r. Monad m => (a -> b) -> (b -> Maybe a) -> ParserT m ([b], src) b r -> ParserT m src a r
+ Hpp.Parser: onIsomorphism :: Monad m => (a -> b) -> (b -> Maybe a) -> ParserT m ([b], src) b r -> ParserT m src a r
- Hpp.Parser: type Parser m i = ParserT m (RopeM m [i]) i
+ Hpp.Parser: type Parser m i = ParserT m (Input m [i]) i
- Hpp.Parser: type ParserT m src i = StateT (Headspring m src i, src) m
+ Hpp.Parser: type ParserT m src i = StateT (Source m src i) m
- Hpp.RunHpp: preprocess :: (Monad m, HppCaps m) => [String] -> HppT [String] (Parser m [TOKEN]) ()
+ Hpp.RunHpp: preprocess :: (Monad m, HasHppState m, HasError m, HasEnv m) => [String] -> HppT [String] (Parser m [TOKEN]) ()
- Hpp.RunHpp: runHpp :: forall m a src. (MonadIO m) => Config -> (FilePath -> m src) -> (src -> m ()) -> HppT src m a -> m (Either (FilePath, Error) (HppResult a))
+ Hpp.RunHpp: runHpp :: forall m a src. (MonadIO m, HasHppState m) => (FilePath -> m src) -> (src -> m ()) -> HppT src m a -> m (Either (FilePath, Error) (HppResult a))
- Hpp.StringSig: class (IsString s, Monoid s) => Stringy s
+ Hpp.StringSig: class (IsString s, Monoid s, Semigroup s) => Stringy s
- Hpp.Types: HppState :: Config -> LineNum -> Env -> HppState
+ Hpp.Types: HppState :: Config -> FilePath -> LineNum -> Env -> HppState
- Hpp.Types: HppT :: m (FreeF (HppF t) a (HppT t m a)) -> HppT t m a
+ Hpp.Types: HppT :: m (Hpp t a (HppT t m a)) -> HppT t m a
- Hpp.Types: [runHppT] :: HppT t m a -> m (FreeF (HppF t) a (HppT t m a))
+ Hpp.Types: [runHppT] :: HppT t m a -> m (Hpp t a (HppT t m a))
- Hpp.Types: type Env = Trie Macro
+ Hpp.Types: type Env = HashMap ByteString Macro

Files

hpp.cabal view
@@ -1,5 +1,5 @@ name:                hpp-version:             0.5.2+version:             0.6.0 synopsis:            A Haskell pre-processor description:         See the README for usage examples license:             BSD3@@ -21,18 +21,23 @@ library   exposed-modules:     Hpp,                        Hpp.CmdLine,+                       Hpp.Conditional,                        Hpp.Config,+                       Hpp.Directive,                        Hpp.Env,                        Hpp.Expansion,                        Hpp.Expr,+                       Hpp.Macro,                        Hpp.Parser,+                       Hpp.Preprocessing,                        Hpp.RunHpp,                        Hpp.String,                        Hpp.StringSig,                        Hpp.Tokens,                        Hpp.Types-  build-depends:       base >=4.8 && <4.12, directory, time >=1.5, filepath,-                       transformers >=0.4, bytestring, bytestring-trie, ghc-prim+  build-depends:       base >=4.8 && <4.13, directory, time >=1.5, filepath,+                       transformers >=0.4, bytestring, unordered-containers,+                       ghc-prim    if impl(ghc < 8)     build-depends: semigroups >= 0.18 && < 0.19
src/Hpp.hs view
@@ -18,8 +18,10 @@ import Data.Maybe (fromMaybe) import qualified Hpp.Config as C import qualified Hpp.Env as E+import qualified Hpp.Macro as M import qualified Hpp.RunHpp as R import qualified Hpp.Types as T+import Hpp.Types (setL, config, env, lineNum) import Hpp.Parser (evalParse, Parser) import Hpp.StringSig (readLines) import Hpp.Tokens (tokenize)@@ -71,8 +73,7 @@ streamHpp st snk (HppT h) =   do (a, st') <- S.runStateT                      (evalParse-                        (R.runHpp (T.hppConfig st)-                                  (liftIO . readLines)+                        (R.runHpp (liftIO . readLines)                                   (lift . lift . lift . snk)                                   h)                         [])@@ -116,7 +117,7 @@  -- | Create a 'T.HppState' with the given 'C.Config' and 'T.Env'. initHppState :: C.Config -> T.Env -> T.HppState-initHppState c e = T.HppState c 1 e+initHppState c e = setL lineNum 1 . setL env e . setL config c $ emptyHppState  -- | @addDefinition name expression@ adds a binding of @name@ to -- @expression@ in the preprocessor’s internal state.@@ -128,4 +129,4 @@ -- with 'E.insertPair' for manual construction of a 'T.Env' binding -- environment. parseDefinition :: ByteString -> ByteString -> Maybe (ByteString, T.Macro)-parseDefinition name val = R.parseDefinition (tokenize name ++ tokenize val)+parseDefinition name val = M.parseDefinition (tokenize name ++ tokenize val)
src/Hpp/CmdLine.hs view
@@ -2,7 +2,7 @@ -- | A front-end to run Hpp with textual arguments as from a command -- line invocation. module Hpp.CmdLine (runWithArgs) where-import Control.Monad (unless, (>=>))+import Control.Monad (unless) import Control.Monad.Trans.Except (runExceptT) import Data.String (fromString) import Hpp@@ -89,7 +89,7 @@           return . Left $ BadCommandLine "Multiple output files given"  -- | Run Hpp with the given commandline arguments.-runWithArgs :: [String] -> IO ()+runWithArgs :: [String] -> IO (Maybe Error) runWithArgs args =   do cfgNow <- defaultConfigFNow      let args' = concatMap splitSwitches args@@ -108,16 +108,12 @@                                     flip openFile WriteMode                                return ( \os -> mapM_ (putStringy h) os                                       , hClose h )-     let errorExcept = runExceptT >=> either (error . show) pure-     _ <- readLines fileName-            >>= errorExcept-                . streamHpp (initHppState cfg' env) snk-                . preprocess-                . (map fromString lns ++)-     -- (lns', _) <- readLines fileName-     --              >>= errorExcept-     --                  . runHpp (initHppState cfg' env)-     --                  . preprocess-     --                  . (map fromString lns ++)-     -- snk (hppOutput lns')+     result <- readLines fileName+           >>= runExceptT+               . streamHpp (initHppState cfg' env) snk+               . preprocess+               . (map fromString lns ++)      closeSnk+     case result of+       Left err -> return $ Just err+       _ -> return Nothing
+ src/Hpp/Conditional.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}+-- | Parsing functionality for pre-processor conditionals.+module Hpp.Conditional (dropBranch, takeBranch) where+#if __GLASGOW_HASKELL__ < 804+import Data.Semigroup ((<>))+#endif+import Data.String (fromString)+import Hpp.Parser (replace, awaitJust, Parser)+import Hpp.Tokens (notImportant, Token(..))+import Hpp.Types (lineNum, use, HasHppState, HasError, LineNum, TOKEN, String)+import Prelude hiding (String)++yieldLineNum :: LineNum -> [TOKEN]+yieldLineNum !ln = [Important ("#line " <> fromString (show ln)), Other "\n"]++getCmd :: [TOKEN] -> Maybe String+getCmd = aux . dropWhile notImportant+  where aux (Important "#" : ts) = case dropWhile notImportant ts of+                                     (Important cmd:_) -> Just cmd+                                     _ -> Nothing+        aux _ = Nothing++dropBranchFun :: [[TOKEN]] -> (Int, [[TOKEN]])+dropBranchFun = go (1::Int) 0+  where go _ !n [] = (n,[])+        go !nesting !n (ln:lns) =+          case getCmd ln of+            Just cmd+              | cmd == "endif" -> if nesting == 1+                                  then (n, ln:lns)+                                  else go (nesting-1) (n+1) lns+              | cmd `elem` ["if","ifdef","ifndef"] ->+                go (nesting+1) (n+1) lns+              | cmd `elem` ["else","elif"] -> if nesting == 1+                                              then (n, ln : lns)+                                              else go nesting (n+1) lns+            _ -> go nesting (n+1) lns++-- | Take everything up to the end of this branch, drop all remaining+-- branches (if any).+takeBranch :: LineNum -> [[TOKEN]] -> [[TOKEN]]+takeBranch = go (1::Int)+  where go _ _ [] = [] -- error: unterminated conditional+        go 0 !n lns = yieldLineNum n : lns+        go !nesting !n (ln:lns) =+          case getCmd ln of+            Just cmd+              | cmd `elem` ["if","ifdef","ifndef"] ->+                ln : go (nesting+1) (n+1) lns+              | cmd == "endif" -> ln : go (nesting - 1) (n + 1) lns+              | nesting == 1 && cmd `elem` ["else","elif"] ->+                let (numSkipped, lns') = dropBranchFun lns+                in go 1 (n+1+numSkipped) lns'+            _ -> ln : go nesting (n+1) lns++-- | Drop the rest of a conditional expression incrementing the given+-- 'LineNum' by the number of lines skipped.+dropBranch :: (HasError m, HasHppState m, Monad m) => Parser m [TOKEN] ()+dropBranch = do ln <- use lineNum+                (el, numSkipped) <- dropBranchAux+                let ln' = ln + numSkipped+                replace (yieldLineNum ln')+                mapM_ replace el++-- | Skip to the end of a conditional branch. Returns the 'Just' the+-- token that ends this branch if it is an @else@ or @elif@, or+-- 'Nothing' otherwise, and the number of lines skipped.+dropBranchAux :: (HasError m, Monad m) => Parser m [TOKEN] (Maybe [TOKEN], Int)+dropBranchAux = go (1::Int) 0+  where go !nesting !n =+          do ln <- awaitJust "dropBranch"+             case getCmd ln of+               Just cmd+                 | cmd == "endif" -> if nesting == 1+                                     then return (Nothing, n+1)+                                     else go (nesting-1) (n+1)+                 | cmd `elem` ["if","ifdef","ifndef"] ->+                   go (nesting+1) (n+1)+                 | cmd `elem` ["else", "elif"] -> if nesting == 1+                                                  then return (Just ln, n+1)+                                                  else go nesting (n+1)+               _ -> go nesting (n+1)
src/Hpp/Config.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE StandaloneDeriving, FlexibleInstances #-} -- | Preprocessor Configuration module Hpp.Config where import Data.Functor.Identity@@ -47,6 +48,8 @@  -- | A fully-populated configuration for the pre-processor. type Config = ConfigF Identity++deriving instance Show (ConfigF Identity)  -- | Ensure that required configuration fields are supplied. realizeConfig :: ConfigF Maybe -> Maybe Config
+ src/Hpp/Directive.hs view
@@ -0,0 +1,201 @@+{-# LANGUAGE LambdaCase, OverloadedStrings, ScopedTypeVariables,+             ViewPatterns #-}+-- | Implement the logic of CPP directives (commands prefixed with an+-- octothorpe).+module Hpp.Directive (directive, macroExpansion) where+import Control.Monad (unless)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Except+import Control.Monad.Trans.State.Strict (StateT)+import Hpp.Conditional (dropBranch, takeBranch)+import Hpp.Config (curFileName, curFileNameF)+import Hpp.Env (lookupKey, deleteKey, insertPair)+import Hpp.Expansion (expandLineState)+import Hpp.Expr (evalExpr, parseExpr)+import Hpp.Macro (parseDefinition)+import Hpp.Preprocessing (prepareInput)+import Hpp.StringSig (unquote, toChars)+import Hpp.Tokens (newLine, notImportant, trimUnimportant, detokenize, isImportant, Token(..))+import Hpp.Types+import Hpp.Parser (replace, await, insertInputSegment, takingWhile, droppingWhile, onInputSegment, evalParse, onElements, awaitJust, ParserT, Parser)+import Text.Read (readMaybe)+import Prelude hiding (String)++-- | Returns everything up to the next newline. The newline character+-- itself is consumed.+takeLine :: (Monad m, HasError m, HasHppState m) => Parser m [TOKEN] [TOKEN]+takeLine = (onElements $ do+              ln <- takingWhile (not . newLine)+              eat <- awaitJust "takeLine" -- Eat the newline character+              case eat of+                Other "\n" -> return ()+                wat -> error $ "Expected newline: "++show wat++" after "++show ln+              return ln)+           <* (lineNum %= (+1))++dropLine :: (Monad m, HasError m, HasHppState m) => Parser m [TOKEN] ()+dropLine = do onElements $ do+                droppingWhile (not . newLine)+                eat <- awaitJust "dropLine" -- Eat the newline character+                case eat of+                  Other "\n" -> return ()+                  wat -> error $ "Expected dropped newline: "++show wat+              lineNum %= (+1)++droppingSpaces ::(Monad m) => ParserT m src TOKEN ()+droppingSpaces = droppingWhile notImportant++-- | Run a Stream with a configuration for a new file.+streamNewFile :: (Monad m, HasHppState m)+              => FilePath -> [[TOKEN]] -> Parser m [TOKEN] ()+streamNewFile fp s =+  do (oldCfg,oldLine) <- do st <- getState+                            let cfg = hppConfig st+                                cfg' = cfg { curFileNameF = pure fp }+                                ln = hppLineNum st+                            -- NOTE: We should *NOT* use a the config lens here+                            --       because it will mutate the directory which+                            --       we *don't* want in this instance.+                            setState (st {hppConfig = cfg', hppLineNum = 1})+                            return (cfg, ln)+     insertInputSegment+       s (getState >>= setState . setL lineNum oldLine . setL config oldCfg)++-- | Handle preprocessor directives (commands prefixed with an octothorpe).+directive :: forall m. (Monad m, HasError m, HasHppState m, HasEnv m)+          => HppT [String] (Parser m [TOKEN]) Bool+directive = lift (onElements (awaitJust "directive")) >>= aux+  where aux :: TOKEN -> HppT [String] (Parser m [TOKEN]) Bool+        aux (Important cmd) = case cmd of+          "pragma" -> True <$ lift dropLine -- Ignored+          "define" -> True <$+                      (lift $ fmap parseDefinition takeLine >>= \case+                        Nothing -> use lineNum >>=+                                   throwError . BadMacroDefinition+                        Just def -> env %= insertPair def)+          "undef" -> do name <- lift . onElements $ do+                          droppingWhile (not . isImportant)+                          Important name <- awaitJust "undef"+                          return name+                        lift dropLine+                        env %= deleteKey name+                        return True+          "include" -> True <$ includeAux hppReadFile+          "include_next" -> True <$ includeAux hppReadNext+          "line" -> do lift (onElements droppingSpaces)+                       toks <- lift (init <$> expandLineState)+                       case toks of+                         Important (toChars -> n):optFile ->+                           case readMaybe n of+                             Nothing -> use lineNum >>=+                                        throwError . flip BadLineArgument n+                             Just ln' -> do+                               unless (null optFile) $ do+                                 let fn = toChars . unquote . detokenize+                                        . dropWhile (not . isImportant)+                                        $ optFile+                                 config %= (\cfg -> cfg { curFileNameF = pure fn })+                               lineNum .= ln'+                               return True+                         _ -> use lineNum >>=+                              throwError+                              . flip BadLineArgument (toChars (detokenize toks))+          "ifdef" ->+            do toks <- lift (onElements droppingSpaces >> takeLine)+               ln <- use lineNum+               case takeWhile isImportant toks of+                 [Important t] ->+                   lookupMacro t >>= \case+                     Nothing ->+                       lift dropBranch+                     Just _ ->+                       lift (onInputSegment (takeBranch ln)) -- (takeBranch ln >>= precede)+                 _ -> throwError . UnknownCommand ln $+                      "ifdef "++ toChars (detokenize toks)+               return True+          "ifndef" ->+            do toks <- lift (onElements droppingSpaces >> takeLine)+               ln <- use lineNum+               case takeWhile isImportant toks of+                 [Important t] ->+                   lookupMacro t >>= \case+                      Nothing -> lift (onInputSegment (takeBranch ln)) -- takeBranch ln >>= precede)+                      Just _ -> lift dropBranch+                 _ -> throwError . UnknownCommand ln $+                      "ifndef "++ toChars (detokenize toks)+               return True+          "else" -> True <$ lift dropLine+          "if" -> True <$ ifAux+          "elif" -> True <$ ifAux+          "endif" -> True <$ lift dropLine+          "error" -> do toks <- lift (onElements droppingSpaces >> takeLine)+                        ln <- subtract 1 <$> use lineNum+                        curFile <- curFileName <$> use config+                        let tokStr = toChars (detokenize toks)+                        throwError $ UserError ln (tokStr++" ("++curFile++")")+          "warning" -> True <$ lift dropLine -- warnings not yet supported+          t -> do toks <- lift takeLine+                  ln <- subtract 1 <$> use lineNum+                  throwError $ UnknownCommand ln+                    (toChars (detokenize (Important t:toks)))+        aux _ = error "Impossible unimportant directive"+        includeAux :: (LineNum -> FilePath -> HppT src (Parser m [TOKEN]) [String])+                   -> HppT src (Parser m [TOKEN]) ()+        includeAux readFun =+          do fileName <- lift (toChars . detokenize . trimUnimportant . init+                               <$> expandLineState)+             ln <- use lineNum+             src <- prepareInput <*> readFun ln fileName+             lineNum .= ln+1+             lift (streamNewFile (unquote fileName) src)+        {- SPECIALIZE includeAux ::+            (LineNum -> FilePath -> HppT [String] (Parser (StateT HppState (ExceptT Error IO)) [TOKEN]) [String])+            -> HppT [String] (Parser (StateT HppState (ExceptT Error IO)) [TOKEN]) () #-}+        ifAux =+          do toks <- lift (onElements droppingSpaces >> takeLine)+             e <- use env+             ln <- use lineNum+             lineNum .= ln - 1 -- takeLine incremented the line count+             ex <- lift (lift (evalParse expandLineState [squashDefines e toks]))+             let res = evalExpr <$> parseExpr (map (fmap toChars) ex)+             lineNum .= ln+             if maybe False (/= 0) res+               then lift (onInputSegment (takeBranch ln)) -- (takeBranch ln >>= precede)+               else lift dropBranch+{-# SPECIALIZE directive ::+    HppT [String] (Parser (StateT HppState (ExceptT Error IO)) [TOKEN]) Bool #-}++-- | We want to expand macros in expressions that must be evaluated+-- for conditionals, but we want to take special care when dealing+-- with the meta @defined@ operator of the expression language that is+-- a predicate on the evaluation environment.+squashDefines :: Env -> [TOKEN] -> [TOKEN]+squashDefines _ [] = []+squashDefines env' (Important "defined" : ts) = go ts+  where go (t@(Other _) : ts') = t : go ts'+        go (t@(Important "(") : ts') = t : go ts'+        go (Important t : ts') =+          case lookupKey t env' of+            Nothing -> Important "0" : squashDefines env' ts'+            -- Just (_,env'') -> Important "1" : squashDefines env'' ts'+            Just _ -> Important "1" : squashDefines env' ts'+        go [] = []+squashDefines env' (t : ts) = t : squashDefines env' ts++-- | Expands an input line producing a stream of output lines.+macroExpansion :: (Monad m, HasHppState m, HasError m, HasEnv m)+               => HppT [String] (Parser m [TOKEN]) (Maybe [TOKEN])+macroExpansion = do+  lift await >>= \case+    Nothing -> return Nothing+    Just ln ->+      -- when (not (all isSpace (detokenize ln)))+      --      (trace ("macro expand: "++detokenize ln) (return ())) >>+      case dropWhile notImportant ln of+        [] -> Just ln <$ (lineNum %= (+1))+        Important "#":rst -> do lift (replace (dropWhile notImportant rst))+                                processed <- directive+                                if processed+                                then macroExpansion+                                else Just ln <$ lift takeLine+        _ -> lift (replace ln >> (Just <$> expandLineState)) <* (lineNum %= (+1))
src/Hpp/Env.hs view
@@ -2,68 +2,17 @@ -- | A name binding context, or environment. module Hpp.Env where import Data.ByteString (ByteString)-import qualified Data.Trie as T--emptyEnv :: T.Trie a-emptyEnv = T.empty--insertPair :: (ByteString, a) -> T.Trie a -> T.Trie a-insertPair = uncurry T.insert--deleteKey :: ByteString -> T.Trie a -> T.Trie a-deleteKey = T.delete---- lookupKey :: L.ByteString -> T.Trie a -> Maybe (a, T.Trie a)--- lookupKey k t = (,t) <$> T.lookup (L.toStrict k) t-lookupKey :: ByteString -> T.Trie a -> Maybe a-lookupKey = T.lookup---{--import qualified Data.Map as M--emptyEnv :: M.Map String a-emptyEnv = M.empty-{-# INLINE emptyEnv #-}--insertPair :: (String, a) -> M.Map String a -> M.Map String a-insertPair = uncurry M.insert-{-# INLINE insertPair #-}--deleteKey :: String -> M.Map String a -> M.Map String a-deleteKey = M.delete-{-# INLINE deleteKey #-}+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HashMap -lookupKey :: String -> M.Map String a -> Maybe (a, M.Map String a)-lookupKey k m = fmap (\x -> (x, m)) (M.lookup k m)-{-# INLINE lookupKey #-}--}-{---- | An empty binding environment.-emptyEnv :: [a]-emptyEnv = []-{-# INLINE emptyEnv #-}+emptyEnv :: HashMap ByteString a+emptyEnv = HashMap.empty --- | Add a @(key,value)@ pair to an environment.-insertPair :: a -> [a] -> [a]-insertPair = (:)-{-# INLINE insertPair #-}+insertPair :: (ByteString, a) -> HashMap ByteString a -> HashMap ByteString a+insertPair = uncurry HashMap.insert --- | Delete an entry from an association list.-deleteKey :: Eq a => a -> [(a,b)] -> [(a,b)]-deleteKey k = go-  where go [] = []-        go (h@(x,_) : xs) = if x == k then xs else h : go xs-{-# INLINE deleteKey #-}+deleteKey :: ByteString -> HashMap ByteString a -> HashMap ByteString a+deleteKey = HashMap.delete --- | Looks up a value in an association list. If the key is found, the--- value is returned along with an updated association list with that--- key at the front.-lookupKey :: Eq a => a -> [(a,b)] -> Maybe (b, [(a,b)])-lookupKey k = go id-  where go _ [] = Nothing-        go acc (h@(x,v) : xs)-          | k == x = Just (v, h : acc [] ++ xs)-          | otherwise = go (acc . (h:)) xs-{-# INLINE lookupKey #-}--}+lookupKey :: ByteString -> HashMap ByteString a -> Maybe a+lookupKey = HashMap.lookup
src/Hpp/Expansion.hs view
@@ -2,7 +2,7 @@ -- | Line expansion is the core input token processing -- logic. Object-like macros are substituted, and function-like macro -- applications are expanded.-module Hpp.Expansion (expandLine) where+module Hpp.Expansion (expandLineState) where import Control.Monad.Trans.Class (lift) import Data.Bool (bool) import Data.Foldable (foldl', traverse_)@@ -17,7 +17,7 @@                    onElements, droppingWhile, awaitJust, evalParse) import Hpp.StringSig (stringify, uncons, isEmpty, toChars) import Hpp.Tokens (Token(..), notImportant, isImportant, detokenize)-import Hpp.Types (HasError(..), HasEnv(..), Scan(..), Error(..), Macro(..),+import Hpp.Types (hppConfig, hppLineNum, getState, HasHppState, HasError(..), HasEnv(..), Scan(..), Error(..), Macro(..),                   TOKEN, String, lookupMacro) import Prelude hiding (String) @@ -32,6 +32,15 @@  isImportantScan :: Scan -> Bool isImportantScan = maybe False isImportant . unscan++expandLineState :: (Monad m, HasHppState m, HasEnv m, HasError m)+                => Parser m [TOKEN] [TOKEN]+expandLineState =+  do st <- getState+     let ln = hppLineNum st+         cfg = hppConfig st+     expandLine cfg ln+  -- | Expand all macros to the end of the current line or until all -- in-progress macro invocations are complete, whichever comes last.
+ src/Hpp/Macro.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE CPP, OverloadedStrings, ViewPatterns #-}+module Hpp.Macro (parseDefinition) where+import Data.Char (isSpace)+#if __GLASGOW_HASKELL__ < 804+import Data.Semigroup ((<>))+#endif+import Hpp.StringSig+import Hpp.Tokens (trimUnimportant, importants, Token(..), isImportant)+import Hpp.Types (Macro(..), String, TOKEN, Scan(..))+import Prelude hiding (String)++-- * TOKEN Splices++-- | Deal with the two-character '##' token pasting/splicing+-- operator. We do so eliminating spaces around the @##@+-- operator.+prepTOKENSplices :: [TOKEN] -> [TOKEN]+prepTOKENSplices = map (fmap copy) . dropSpaces [] . mergeTOKENs []+  where -- Merges ## tokens, and reverses the input list+        mergeTOKENs acc [] = acc+        mergeTOKENs acc (Important "#" : Important "#" : ts) =+          mergeTOKENs (Important "##" : acc) (dropWhile (not . isImportant) ts)+        mergeTOKENs acc (t:ts) = mergeTOKENs (t : acc) ts+        -- Drop trailing spaces and re-reverse the list+        dropSpaces acc [] = acc+        dropSpaces acc (t@(Important "##") : ts) =+          dropSpaces (t : acc) (dropWhile (not . isImportant) ts)+        dropSpaces acc (t:ts) = dropSpaces (t : acc) ts++-- | Parse the definition of an object-like or function macro.+parseDefinition :: [TOKEN] -> Maybe (String, Macro)+parseDefinition toks =+  case dropWhile (not . isImportant) toks of+    (Important name:Important "(":rst) ->+      let params = takeWhile (/= ")") $ filter (/= ",") (importants rst)+          body = trimUnimportant . tail $ dropWhile (/= Important ")") toks+          macro = Function (length params) (functionMacro params body)+      in Just (name, macro)+    (Important name:_) ->+      let rhs = case dropWhile (/= Important name) toks of+                  [] -> [Important ""]+                  str@(_:t)+                    | all (not . isImportant) str -> [Important ""]+                    | otherwise -> trimUnimportant t+      in Just (copy name, Object (map (fmap copy) rhs))+    _ -> Nothing++-- * Function-like macros as Haskell functions++-- | Drop spaces following @'#'@ characters.+prepStringify :: [TOKEN] -> [TOKEN]+prepStringify [] = []+prepStringify (Important "#" : ts) =+  case dropWhile (not . isImportant) ts of+    (Important t : ts') -> Important (cons '#' t) : prepStringify ts'+    _ -> Important "#" : ts+prepStringify (t:ts) = t : prepStringify ts++-- | Concatenate tokens separated by @'##'@.+paste :: [Scan] -> [Scan]+paste [] = []+paste (Rescan (Important s) : Rescan (Important "##") : Rescan (Important t) : ts) =+  paste (Rescan (Important (trimSpaces s <> sdropWhile isSpace t)) : ts)+paste (t:ts) = t : paste ts++-- | @functionMacro parameters body arguments@ substitutes @arguments@+-- for @parameters@ in @body@ and performs stringification for uses of+-- the @#@ operator and token concatenation for the @##@ operator.+functionMacro :: [String] -> [TOKEN] -> [([Scan],String)] -> [Scan]+functionMacro params body = paste+                          . subst body'+                          -- . M.fromList+                          . zip params'+  where params' = map copy params+        subst toks gamma = go toks+          where go [] = []+                go (p@(Important "##"):t@(Important s):ts) =+                  case lookup s gamma of+                    Nothing -> Rescan p : Rescan t : go ts+                    Just (_,arg) ->+                      Rescan p : Rescan (Important arg) : go ts+                go (t@(Important s):p@(Important "##"):ts) =+                  case lookup s gamma of+                    Nothing -> Rescan t : go (p:ts)+                    Just (_,arg) -> Rescan (Important arg) : go (p:ts)+                go (t@(Important "##"):ts) = Rescan t : go ts+                go (t@(Important (uncons -> Just ('#',s))) : ts) =+                  case lookup s gamma of+                    Nothing -> Rescan t : go ts+                    Just (_,arg) ->+                      Rescan (Important (stringify arg)) : go ts+                go (t@(Important s) : ts) =+                  case lookup s gamma of+                    Nothing -> Rescan t : go ts+                    Just (arg,_) -> arg ++ go ts+                go (t:ts) = Rescan t : go ts+        body' = prepStringify . prepTOKENSplices $+                dropWhile (not . isImportant) body
src/Hpp/Parser.hs view
@@ -1,107 +1,94 @@-{-# LANGUAGE LambdaCase, Rank2Types, TupleSections, ScopedTypeVariables #-}+{-# LANGUAGE DeriveFunctor, LambdaCase, TupleSections #-} -- | Parsers over streaming input.-module Hpp.Parser (Parser, ParserT, parse, evalParse, await, awaitJust, replace,-                   droppingWhile, precede, takingWhile, onChunks, onElements,-                   onInputSegment, insertInputSegment, onIsomorphism,-                   runParser) where-import Control.Arrow (second, (***))+module Hpp.Parser (Parser, ParserT, evalParse, await, awaitJust, replace,+                   droppingWhile, precede, takingWhile, onElements,+                   onInputSegment, insertInputSegment, onIsomorphism) where+import Control.Arrow ((***)) import Control.Monad.Trans.State.Strict import Hpp.Types (HasError(..), Error(UserError)) import Control.Monad.Trans.Class (lift) import Data.List.NonEmpty (NonEmpty(..)) import Data.Maybe (mapMaybe)-import Data.Monoid ((<>))  -- * Parsers --- | Our input is a list of values each of which is either an action or a value.-type RopeM m a = [Either (m ()) a]+-- | A single pre-processor input is either an action or a value+data InputItem m a = Action (m ()) | Value a deriving Functor +-- | Our input is a list of values each of which is either an action+-- or a value.+type Input m a = [InputItem m a]++-- | Functions for working with input sources.+data Source m src i =  Source { srcSrc :: src+                              , _srcAwait :: src -> m (Maybe (i, src))+                              , _srcPrecede :: [i] -> src -> src }+ -- | A 'ParserT' is a bit of state that carries a source of input.-type ParserT m src i = StateT (Headspring m src i, src) m+type ParserT m src i = StateT (Source m src i) m  -- | A 'Parser' is a bit of state that carries a source of input -- consisting of a list of values which are either actions in an -- underlying monad or sequences of inputs. Thus we have chunks of -- input values with interspersed effects.-type Parser m i = ParserT m (RopeM m [i]) i--data Headspring m src i =-  Headspring { hsAwait :: src -> m (Maybe (i, src))-             , hsPrecede :: [i] -> src -> src }+type Parser m i = ParserT m (Input m [i]) i  -- | Pop the head non-effect element from a list.-unconsM :: Applicative m => RopeM m a -> m (Maybe (a, RopeM m a))+unconsM :: Applicative m => Input m a -> m (Maybe (a, Input m a)) unconsM [] = pure Nothing-unconsM (Left m : ms) = m *> unconsM ms-unconsM (Right x : ms) = pure (Just (x, ms))+unconsM (Action m : ms) = m *> unconsM ms+unconsM (Value x : ms) = pure (Just (x, ms))  -- | Pop the first non-null, non-effect element from a list.-unconsMNonEmpty :: Monad m => RopeM m [a] -> m (Maybe (NonEmpty a, RopeM m [a]))+unconsMNonEmpty :: Monad m => Input m [a] -> m (Maybe (NonEmpty a, Input m [a])) unconsMNonEmpty r = unconsM r >>= \case   Nothing -> pure Nothing   Just ([], rst) -> unconsMNonEmpty rst   Just (x:xs, rst) -> return (Just (x :| xs, rst)) -unconsSpring :: Monad m => Headspring m (RopeM m [i]) i-unconsSpring = Headspring aw ropePrecede+unconsSource :: Monad m => Input m [i] -> Source m (Input m [i]) i+unconsSource src = Source src aw ropePrecede   where aw r = unconsMNonEmpty r >>= \case           Nothing -> return Nothing-          Just (x :| xs, r') -> return (Just (x, Right xs : r'))+          Just (x :| xs, r') -> return (Just (x, Value xs : r')) -flattenSpring :: Monad m => Headspring m  (RopeM m [[i]]) i-flattenSpring = Headspring aw pr-  where aw r = unconsMNonEmpty r >>= \case+flattenSource :: Monad m => Source m (Input m [[i]]) [i] -> Source m (Input m [[i]]) i+flattenSource (Source src0 aw pr) = Source src0 aw' pr'+  where aw' src = aw src >>= \case           Nothing -> return Nothing-          Just ([] :| ys, r') -> aw (Right ys : r')-          Just ((x:xs) :| ys, r') -> return (Just (x, Right (xs:ys) : r'))-        pr xs [] = [Right [xs]]-        pr xs (Right (ys:zs) : ms) = Right ((xs++ys) : zs) : ms-        pr xs (Right [] : ms) = Right [xs] : ms-        pr xs ms@(Left _ : _) = Right [xs] : ms--chunkSpring :: (Monoid src, Applicative m) => Headspring m (RopeM m src) src-chunkSpring = Headspring unconsM pr-  where pr xs [] = [Right (mconcat xs)]-        pr xs (Right ys : ms) = Right (mconcat xs <> ys) : ms-        pr xs ms@(Left _ : _) = Right (mconcat xs) : ms+          Just ([], src') -> aw' src'+          Just (x:xs, src') -> return (Just (x, pr' xs src'))+        pr' xs src = pr [xs] src  await :: Monad m => ParserT m src i (Maybe i)-await = do (hs, st) <- get-           lift (hsAwait hs st) >>= \case+await = do Source src aw pr <- get+           lift (aw src) >>= \case              Nothing -> return Nothing-             Just (x,st') -> Just x <$ put (hs,st')+             Just (x, src') -> Just x <$ put (Source src' aw pr) {-# INLINE await #-}  -- | Push a value back into a parser's source. replace :: (Monad m) => i -> ParserT m src i () replace = precede . pure -ropePrecede :: [i] -> RopeM m [i] -> RopeM m [i]-ropePrecede xs [] = [Right xs]-ropePrecede xs ms@(Left _ : _) = Right xs : ms-ropePrecede xs (Right ys : ms) = Right (xs++ys) : ms+ropePrecede :: [i] -> Input m [i] -> Input m [i]+ropePrecede xs [] = [Value xs]+ropePrecede xs ms@(Action _ : _) = Value xs : ms+ropePrecede xs (Value ys : ms) = Value (xs++ys) : ms  -- | Push a stream of values back into a parser's source. precede :: Monad m => [i] -> ParserT m src i ()-precede xs = do (hs,st) <- get-                put (hs, hsPrecede hs xs st)+precede xs = do Source src aw pr <- get+                put (Source (pr xs src) aw pr) {-# INLINE precede #-} --- | Run a 'Parser' with a given input stream.-parse :: Monad m => Parser m i o -> [i] -> m (o, RopeM m [i])-parse m xs = second snd <$> runStateT m (unconsSpring, [Right xs])-{-# INLINE parse #-}--runParser :: Monad m => Parser m i o -> RopeM m [i] -> m (o, RopeM m [i])-runParser m xs = second snd <$> runStateT m (unconsSpring, xs)-+-- | Evaluate a 'Parser' with a given input stream. evalParse :: Monad m => Parser m i o -> [i] -> m o-evalParse m xs = evalStateT m (unconsSpring, [Right xs])+evalParse m xs = evalStateT m (unconsSource [Value xs])  -- * Operations on Parsers --- | 'awaitP' that throws an error with the given message if no more+-- | 'await' that throws an error with the given message if no more -- input is available. This may be used to locate where in a -- processing pipeline input was unexpectedly exhausted. awaitJust :: (Monad m, HasError m) => String -> ParserT m src i i@@ -131,43 +118,45 @@                      | otherwise -> replace x >> return (acc []) {-# INLINE takingWhile #-} -insertInputSegment :: Monad m => src -> m () -> ParserT m (RopeM m src) i ()-insertInputSegment xs k = modify' (second ([Right xs, Left k]++))+insertInputSegment :: Monad m => src -> m () -> ParserT m (Input m src) i ()+insertInputSegment xs k =+  modify' (\s -> s { srcSrc = [Value xs, Action k] ++ srcSrc s}) -onInputSegment :: Monad m => (src -> src) -> ParserT m (RopeM m src) i ()-onInputSegment f = do (hs,st) <- get-                      case st of-                        [] -> return ()-                        (Right xs : ys) -> put (hs, Right (f xs) : ys)-                        (Left m : xs) -> lift m >> put (hs,xs) >> onInputSegment f+onInputSegment :: Monad m => (src -> src) -> ParserT m (Input m src) i ()+onInputSegment f =+  do Source src aw pr <- get+     case src of+       [] -> return ()+       (Value xs : ys) -> put (Source (Value (f xs) : ys) aw pr)+       (Action m : xs) -> lift m >> put (Source xs aw pr) >> onInputSegment f {-# INLINABLE onInputSegment #-}  -- * Parser Transformations -onChunks :: Monad m => ParserT m (RopeM m [i]) [i] r -> Parser m i r-onChunks m = do (hs,st) <- get-                (r, (_,st')) <- lift (runStateT m (chunkSpring, st))-                r <$ put (hs,st')--onElements :: Monad m => ParserT m (RopeM m [[i]]) i r -> Parser m [i] r-onElements m = do (hs,st) <- get-                  (r, (_,st')) <- lift (runStateT m (flattenSpring, st))-                  let onHead _ [] = []-                      onHead f (x:xs) = f x : xs-                  r <$ put (hs, onHead (fmap (dropWhile null)) st')+-- | A parser on lists of things can embed a parser on things. For+-- example, if we have a parser on lists of words, we can embed a+-- parser on individual words.+onElements :: Monad m => ParserT m (Input m [[i]]) i r -> Parser m [i] r+onElements m = do s@(Source _ aw pr) <- get+                  (r, Source src' _ _) <- lift (runStateT m (flattenSource s))+                  r <$ put (Source (onHead (fmap (dropWhile null)) src') aw pr)+  where onHead _ [] = []+        onHead f (x:xs) = f x : xs {-# INLINE onElements #-} -onIsomorphism :: forall m a b src r. Monad m-              => (a -> b) -> (b -> Maybe a)+-- | Given a function with type @a -> b@, and a partial inverse, @b ->+-- Maybe a@, we can embed a parser on values of type @b@ in a parser+-- on values of type @a@.+onIsomorphism :: Monad m+              => (a -> b)+              -> (b -> Maybe a)               -> ParserT m ([b],src) b r               -> ParserT m src a r onIsomorphism fwd bwd m =-  do (hs,st) <- get-     let aw :: ([b],src) -> m (Maybe (b, ([b], src)))-         aw ([], src) = fmap (fmap (fwd *** ([],))) (hsAwait hs src)-         aw ((b:bs), src) = return (Just (b, (bs,src)))-         pr xs (bs,src) = (xs++bs, src)-         mappedSpring = Headspring aw pr-     (r, (_, (bs, st'))) <- lift (runStateT m (mappedSpring, ([], st)))-     r <$ put (hs, hsPrecede hs (mapMaybe bwd bs) st')+  do Source src aw pr <- get+     let aw' ([], src') = fmap (fmap (fwd *** ([],))) (aw src')+         aw' ((b:bs), src') = return (Just (b, (bs,src')))+         pr' xs (bs, src') = (xs++bs, src')+     (r, Source (bs, src') _ _) <- lift (runStateT m (Source ([],src) aw' pr'))+     r <$ put (Source (pr (mapMaybe bwd bs) src') aw pr) {-# INLINE onIsomorphism #-}
+ src/Hpp/Preprocessing.hs view
@@ -0,0 +1,157 @@+{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}+-- | The simplest pre-processing steps are represented as distinct+-- passes over input lines.+module Hpp.Preprocessing+  (+    trigraphReplacement+  , lineSplicing+  , cCommentRemoval+  , cCommentAndTrigraph+  , prepareInput+  ) where+import Control.Arrow (first)+import Data.Char (isSpace)+#if __GLASGOW_HASKELL__ < 804+import Data.Semigroup ((<>))+#endif+import Data.String (fromString)+import Hpp.Config+import Hpp.StringSig+import Hpp.Tokens (tokenize, Token(..), skipLiteral)+import Hpp.Types (TOKEN, String, HasHppState, getState, config, getL)+import Prelude hiding (String)++-- * Trigraphs++-- | The first component of each pair represents the end of a known+-- trigraph sequence (each trigraph begins with two consecutive+-- question marks (@\"??\"@). The second component is the+-- single-character equivalent that we substitute in for the trigraph.+trigraphs :: [(Char, Char)]+trigraphs = [ ('=', '#')+            , ('/', '\\')+            , ('\'', '^')+            , ('(', '[')+            , (')', ']')+            , ('!', '|')+            , ('<', '{')+            , ('>', '}')+            , ('-', '~') ]++trigraphReplacement :: Stringy s => s -> s+trigraphReplacement s = aux (breakOn [("??", ())] s)+  where aux Nothing = s+        aux (Just (_, pre, pos)) =+          case uncons pos of+            Nothing -> pre <> "??"+            Just (c,t) ->+              case lookup c trigraphs of+                Just c' -> snoc pre c' <> trigraphReplacement t+                Nothing -> snoc pre '?' <> trigraphReplacement (cons '?' pos)++-- * Line Splicing++-- | If a line ends with a backslash, it is prepended to the following+-- the line.+lineSplicing :: Stringy s => [s] -> [s]+lineSplicing = go id+  where go acc [] = [acc mempty]+        go acc (ln:lns) = case unsnoc ln of+                            Nothing -> acc mempty : go id lns+                            Just (ini, '\\') -> go (acc . (ini<>)) lns+                            Just _ -> acc ln : go id lns+{-# INLINE lineSplicing #-}++-- * C Comments++breakBlockCommentStart :: Stringy s => s -> Maybe (s, s)+breakBlockCommentStart s =+  case breakCharOrSub '"' "/*" s of+    NoMatch -> Nothing+    CharMatch pre pos -> let (lit, rest) = skipLiteral pos+                         in first ((pre <> lit) <>) <$>+                            breakBlockCommentStart rest+    SubMatch pre pos -> Just (pre, pos)++breakBlockCommentEnd :: Stringy s => s -> Maybe s+breakBlockCommentEnd s =+  case breakCharOrSub '"' "*/" s of+    NoMatch -> Nothing+    CharMatch _ pos -> let (_, rest) = skipLiteral pos+                       in breakBlockCommentEnd rest+    SubMatch _ pos -> Just pos++dropOneLineBlockComments :: Stringy s => s -> s+dropOneLineBlockComments s =+  case breakCharOrSub '"' "/*"s of+    NoMatch -> s+    CharMatch pre pos ->+      let (lit,rest) = skipLiteral pos+      in snoc pre '"' <> lit <> dropOneLineBlockComments rest+    SubMatch pre pos ->+      case breakOn [("*/", ())] pos of+        Nothing -> pre <> "/*"+        Just (_,_,pos') -> snoc pre ' ' <> dropOneLineBlockComments pos'++removeMultilineComments :: Stringy s => Int -> [s] -> [s]+removeMultilineComments !lineStart = goStart lineStart+  where goStart _ [] = []+        goStart !curLine (ln:lns) =+          case breakBlockCommentStart ln of+            Nothing -> ln : goStart (curLine+1) lns+            Just (pre,_) -> goEnd (curLine+1) pre lns+        goEnd _ _ [] = error "Unmatched /*"+        goEnd !curLine pre (ln:lns) =+          case breakBlockCommentEnd ln of+            Nothing -> goEnd (curLine+1) pre lns+            Just pos+              | sall isSpace (pre<>pos) ->+                ("#line "<> fromString (show (curLine+1))) : goStart (curLine + 1) lns+              | otherwise -> (pre<>pos)+                             : ("#line "<> fromString (show (curLine+1)))+                             : goStart (curLine+1) lns++-- | Remove C-style comments bracketed by @/*@ and @*/@.+cCommentRemoval :: Stringy s => [s] -> [s]+cCommentRemoval = removeMultilineComments 1 . map dropOneLineBlockComments++-- | Remove C-style comments bracked by @/*@ and @*/@ and perform+-- trigraph replacement.+cCommentAndTrigraph :: Stringy s => [s] -> [s]+cCommentAndTrigraph = removeMultilineComments 1+                    . map (dropOneLineBlockComments . trigraphReplacement)++prepareInput :: (Monad m, HasHppState m) => m ([String] -> [[TOKEN]])+prepareInput =+  do cfg <- getL config <$> getState+     case () of+       _ | eraseCComments cfg && spliceLongLines cfg+           && not (inhibitLinemarkers cfg) -> pure normalCPP+       _ | (eraseCComments cfg && spliceLongLines cfg+            && (not (replaceTrigraphs cfg))) ->+           pure haskellCPP+       _ | otherwise -> pure (genericConfig cfg)++-- * HPP configurations++-- | Standard CPP settings for processing C files.+normalCPP :: [String] -> [[TOKEN]]+normalCPP = map ((++ [Other "\n"]) . tokenize)+          . lineSplicing+          . cCommentAndTrigraph+{-# INLINABLE normalCPP #-}++-- | For Haskell we do not want trigraph replacement.+haskellCPP :: [String] -> [[TOKEN]]+haskellCPP = map ((++[Other "\n"]) . tokenize)+           . lineSplicing+           . cCommentRemoval+{-# INLINABLE haskellCPP #-}++-- | If we don't have a predefined processor, we build one based on a+-- 'Config' value.+genericConfig :: Config -> [String] -> [[TOKEN]]+genericConfig cfg = map ((++ [Other "\n"]) . tokenize)+                  . (if spliceLongLines cfg then lineSplicing else id)+                  . (if eraseCComments cfg then cCommentRemoval else id)+                  . (if replaceTrigraphs cfg then map trigraphReplacement else id)
src/Hpp/RunHpp.hs view
@@ -1,236 +1,50 @@ {-# LANGUAGE BangPatterns, ConstraintKinds, LambdaCase, OverloadedStrings,              ScopedTypeVariables, TupleSections, ViewPatterns #-} -- | Mid-level interface to the pre-processor.-module Hpp.RunHpp (parseDefinition, preprocess, runHpp, expandHpp,-                   hppIOSink, HppCaps, hppIO, HppResult(..)) where-import Control.Arrow (first)+module Hpp.RunHpp (preprocess, runHpp, expandHpp,+                   hppIOSink, hppIO, HppResult(..)) where import Control.Exception (throwIO)-import Control.Monad (unless, (>=>))+import Control.Monad ((>=>)) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Except import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Trans.State.Strict (StateT, evalStateT, State)-import Data.Char (isSpace) import Data.IORef-import Data.Maybe (fromMaybe)-import Data.Monoid ((<>))-import Data.String (fromString)-import Hpp.Config (Config, curFileNameF, curFileName, includePaths,-                   eraseCComments, spliceLongLines, inhibitLinemarkers,-                   replaceTrigraphs)-import Hpp.Env (deleteKey, insertPair, lookupKey)-import Hpp.Expansion (expandLine)-import Hpp.Expr (evalExpr, parseExpr)-import Hpp.Parser (Parser, ParserT, replace, await, awaitJust, droppingWhile,-                   precede, takingWhile, insertInputSegment, onElements,-                   evalParse, onInputSegment)+import Hpp.Config (Config, curFileNameF, curFileName, includePaths, inhibitLinemarkers)+import Hpp.Directive (macroExpansion)+import Hpp.Parser (Parser, precede, evalParse)+import Hpp.Preprocessing import Hpp.StringSig-import Hpp.Tokens (Token(..), importants, isImportant, newLine, trimUnimportant,-                   detokenize, notImportant, tokenize, skipLiteral)+import Hpp.String (stripAngleBrackets)+import Hpp.Tokens (detokenize) import Hpp.Types import System.Directory (doesFileExist) import System.FilePath ((</>))-import Text.Read (readMaybe) import Prelude hiding (String) import qualified Prelude as P --- * Trigraphs---- | The first component of each pair represents the end of a known--- trigraph sequence (each trigraph begins with two consecutive--- question marks (@\"??\"@). The second component is the--- single-character equivalent that we substitute in for the trigraph.-trigraphs :: [(Char, Char)]-trigraphs = [ ('=', '#')-            , ('/', '\\')-            , ('\'', '^')-            , ('(', '[')-            , (')', ']')-            , ('!', '|')-            , ('<', '{')-            , ('>', '}')-            , ('-', '~') ]--trigraphReplacement :: Stringy s => s -> s-trigraphReplacement s = aux (breakOn [("??", ())] s)-  where aux Nothing = s-        aux (Just (_, pre, pos)) =-          case uncons pos of-            Nothing -> pre <> "??"-            Just (c,t) ->-              case lookup c trigraphs of-                Just c' -> snoc pre c' <> trigraphReplacement t-                Nothing -> snoc pre '?' <> trigraphReplacement (cons '?' pos)---- * Line Splicing---- | If a line ends with a backslash, it is prepended to the following--- the line.-lineSplicing :: Stringy s => [s] -> [s]-lineSplicing = go id-  where go acc [] = [acc mempty]-        go acc (ln:lns) = case unsnoc ln of-                            Nothing -> acc mempty : go id lns-                            Just (ini, '\\') -> go (acc . (ini<>)) lns-                            Just _ -> acc ln : go id lns-{-# INLINE lineSplicing #-}---- * C Comments--breakBlockCommentStart :: Stringy s => s -> Maybe (s, s)-breakBlockCommentStart s =-  case breakCharOrSub '"' "/*" s of-    NoMatch -> Nothing-    CharMatch pre pos -> let (lit, rest) = skipLiteral pos-                         in first ((pre <> lit) <>) <$>-                            breakBlockCommentStart rest-    SubMatch pre pos -> Just (pre, pos)--breakBlockCommentEnd :: Stringy s => s -> Maybe s-breakBlockCommentEnd s =-  case breakCharOrSub '"' "*/" s of-    NoMatch -> Nothing-    CharMatch _ pos -> let (_, rest) = skipLiteral pos-                       in breakBlockCommentEnd rest-    SubMatch _ pos -> Just pos--dropOneLineBlockComments :: Stringy s => s -> s-dropOneLineBlockComments s =-  case breakCharOrSub '"' "/*"s of-    NoMatch -> s-    CharMatch pre pos ->-      let (lit,rest) = skipLiteral pos-      in snoc pre '"' <> lit <> dropOneLineBlockComments rest-    SubMatch pre pos ->-      case breakOn [("*/", ())] pos of-        Nothing -> pre <> "/*"-        Just (_,_,pos') -> snoc pre ' ' <> dropOneLineBlockComments pos'--removeMultilineComments :: Stringy s => Int -> [s] -> [s]-removeMultilineComments !lineStart = goStart lineStart-  where goStart _ [] = []-        goStart !curLine (ln:lns) =-          case breakBlockCommentStart ln of-            Nothing -> ln : goStart (curLine+1) lns-            Just (pre,_) -> goEnd (curLine+1) pre lns-        goEnd _ _ [] = error "Unmatched /*"-        goEnd !curLine pre (ln:lns) =-          case breakBlockCommentEnd ln of-            Nothing -> goEnd (curLine+1) pre lns-            Just pos-              | sall isSpace (pre<>pos) ->-                ("#line "<> fromString (show (curLine+1))) : goStart (curLine + 1) lns-              | otherwise -> (pre<>pos)-                             : ("#line "<> fromString (show (curLine+1)))-                             : goStart (curLine+1) lns--commentRemoval :: Stringy s => [s] -> [s]-commentRemoval = removeMultilineComments 1 . map dropOneLineBlockComments---- * TOKEN Splices---- | Deal with the two-character '##' token pasting/splicing--- operator. We do so eliminating spaces around the @##@--- operator.-prepTOKENSplices :: [TOKEN] -> [TOKEN]-prepTOKENSplices = map (fmap copy) . dropSpaces [] . mergeTOKENs []-  where -- Merges ## tokens, and reverses the input list-        mergeTOKENs acc [] = acc-        mergeTOKENs acc (Important "#" : Important "#" : ts) =-          mergeTOKENs (Important "##" : acc) (dropWhile (not . isImportant) ts)-        mergeTOKENs acc (t:ts) = mergeTOKENs (t : acc) ts-        -- Drop trailing spaces and re-reverse the list-        dropSpaces acc [] = acc-        dropSpaces acc (t@(Important "##") : ts) =-          dropSpaces (t : acc) (dropWhile (not . isImportant) ts)-        dropSpaces acc (t:ts) = dropSpaces (t : acc) ts---- * Function-like macros as Haskell functions---- | @functionMacro parameters body arguments@ substitutes @arguments@--- for @parameters@ in @body@ and performs stringification for uses of--- the @#@ operator and token concatenation for the @##@ operator.-functionMacro :: [String] -> [TOKEN] -> [([Scan],String)] -> [Scan]-functionMacro params body = paste-                          . subst body'-                          -- . M.fromList-                          . zip params'-  where params' = map copy params-        subst toks gamma = go toks-          where go [] = []-                go (p@(Important "##"):t@(Important s):ts) =-                  case lookup s gamma of-                    Nothing -> Rescan p : Rescan t : go ts-                    Just (_,arg) ->-                      Rescan p : Rescan (Important arg) : go ts-                go (t@(Important s):p@(Important "##"):ts) =-                  case lookup s gamma of-                    Nothing -> Rescan t : go (p:ts)-                    Just (_,arg) -> Rescan (Important arg) : go (p:ts)-                go (t@(Important "##"):ts) = Rescan t : go ts-                go (t@(Important ('#':.s)) : ts) =-                  case lookup s gamma of-                    Nothing -> Rescan t : go ts-                    Just (_,arg) ->-                      Rescan (Important (stringify arg)) : go ts-                go (t@(Important s) : ts) =-                  case lookup s gamma of-                    Nothing -> Rescan t : go ts-                    Just (arg,_) -> arg ++ go ts-                go (t:ts) = Rescan t : go ts-        prepStringify [] = []-        prepStringify (Important "#" : ts) =-          case dropWhile (not . isImportant) ts of-            (Important t : ts') -> Important (cons '#' t) : prepStringify ts'-            _ -> Important "#" : ts-        prepStringify (t:ts) = t : prepStringify ts--        body' = prepStringify . prepTOKENSplices $-                dropWhile (not . isImportant) body-        paste [] = []-        paste (Rescan (Important s) : Rescan (Important "##")-              : Rescan (Important t) : ts) =-          paste (Rescan (Important (trimSpaces s <> sdropWhile isSpace t)) : ts)-        paste (t:ts) = t : paste ts---- * Pre-Processor Capabilities--modifyState :: (Monad m, HasHppState m) => (HppState -> HppState) -> m ()-modifyState f = getState >>= setState . f---- | Run a Stream with a configuration for a new file.-streamNewFile :: (Monad m, HasHppState m)-              => FilePath -> [[TOKEN]] -> Parser m [TOKEN] ()-streamNewFile fp s =-  do (oldCfg,oldLine) <- do st <- getState-                            let cfg = hppConfig st-                                cfg' = cfg { curFileNameF = pure fp }-                                ln = hppLineNum st-                            setState (st {hppConfig = cfg', hppLineNum = 1})-                            return (cfg, ln)-     insertInputSegment-       s (modifyState (setL lineNum oldLine . setL config oldCfg))- -- * Finding @include@ files -includeCandidates :: [FilePath] -> P.String -> Maybe [FilePath]-includeCandidates searchPath nm =+includeCandidates :: FilePath -> [FilePath] -> P.String -> Maybe [FilePath]+includeCandidates curDir searchPath nm =   case nm of-    '<':nm' -> Just $ sysSearch (init nm')+    '<':nm' -> Just $ sysSearch   (init nm')     '"':nm' -> let nm'' = init nm'-                in Just $ nm'' : sysSearch nm''+               in Just $ nm'' : localSearch nm''     _ -> Nothing-  where sysSearch f = map (</> f) searchPath+  where sysSearch   f = map (</> f) searchPath+        localSearch f = map (</> f) $ curDir : searchPath -searchForInclude :: [FilePath] -> P.String -> IO (Maybe FilePath)-searchForInclude paths = maybe (return Nothing) aux . includeCandidates paths+searchForInclude :: FilePath -> [FilePath] -> P.String -> IO (Maybe FilePath)+searchForInclude curDir paths =+  maybe (return Nothing) aux . includeCandidates curDir paths   where aux [] = return Nothing         aux (f:fs) = do exists <- doesFileExist f                         if exists then return (Just f) else aux fs -searchForNextInclude :: [FilePath] -> P.String -> IO (Maybe FilePath)-searchForNextInclude paths = maybe (return Nothing) (aux False)-                           . includeCandidates paths+searchForNextInclude :: FilePath -> [FilePath] -> P.String -> IO (Maybe FilePath)+searchForNextInclude curDir paths =+  maybe (return Nothing) (aux False) . includeCandidates curDir paths   where aux _ [] = return Nothing         aux n (f:fs) = do exists <- doesFileExist f                           if exists@@ -247,33 +61,38 @@ -- | Interpret the IO components of the preprocessor. This -- implementation relies on IO for the purpose of checking search -- paths for included files.-runHpp :: forall m a src. (MonadIO m)-       => Config-       -> (FilePath -> m src)+runHpp :: forall m a src. (MonadIO m, HasHppState m)+       => (FilePath -> m src)        -> (src -> m ())        -> HppT src m a        -> m (Either (FilePath,Error) (HppResult a))-runHpp cfg source sink m = runHppT m >>= go []+runHpp source sink m = runHppT m >>= go []   where go :: [FilePath]            -> FreeF (HppF src) a (HppT src m a)            -> m (Either (FilePath, Error) (HppResult a))         go files (PureF x) = return $ Right (HppResult files x)         go files (FreeF s) = case s of-          ReadFile ln file k ->-            liftIO (searchForInclude (includePaths cfg) file)-            >>= readAux (file:files) ln file k-          ReadNext ln file k ->-            liftIO (searchForNextInclude (includePaths cfg) file)-            >>= readAux (file:files) ln file k+          ReadFile ln file k -> do+            cfg    <- use config+            curDir <- use dir+            let ipaths = includePaths cfg+            mFound <- liftIO $ searchForInclude curDir ipaths file+            readAux (file:files) ln file k mFound+          ReadNext ln file k -> do+            cfg    <- use config+            curDir <- use dir+            let ipaths = includePaths cfg+            mFound <- liftIO $ searchForNextInclude curDir ipaths file+            readAux (file:files) ln file k mFound           WriteOutput output k -> sink output >> runHppT k >>= go files          readAux _files ln file _ Nothing =-          pure (Left (curFileName cfg, IncludeDoesNotExist ln file))+          Left . (, IncludeDoesNotExist ln (stripAngleBrackets file))+               . curFileName <$> use config         readAux files _ln _file k (Just file') =           source file' >>= runHppT . k >>= go files {-# SPECIALIZE runHpp ::-    Config- -> (FilePath -> Parser (StateT HppState (ExceptT Error IO)) [TOKEN] [String])+    (FilePath -> Parser (StateT HppState (ExceptT Error IO)) [TOKEN] [String])  -> ([String] -> Parser (StateT HppState (ExceptT Error IO)) [TOKEN] ())  -> HppT [String] (Parser (StateT HppState (ExceptT Error IO)) [TOKEN]) a  -> Parser (StateT HppState (ExceptT Error IO)) [TOKEN] (Either (FilePath,Error) (HppResult a)) #-}@@ -308,332 +127,17 @@                    (ExceptT Error (State ([String] -> [String]))))            [TOKEN] (Either (FilePath,Error) (HppResult a)) #-} --- * Preprocessor---- | Parse the definition of an object-like or function macro.-parseDefinition :: [TOKEN] -> Maybe (String, Macro)-parseDefinition toks =-  case dropWhile (not . isImportant) toks of-    (Important name:Important "(":rst) ->-      let params = takeWhile (/= ")") $ filter (/= ",") (importants rst)-          body = trimUnimportant . tail $ dropWhile (/= Important ")") toks-          macro = Function (length params) (functionMacro params body)-      in Just (name, macro)-    (Important name:_) ->-      let rhs = case dropWhile (/= Important name) toks of-                  [] -> [Important ""]-                  str@(_:t)-                    | all (not . isImportant) str -> [Important ""]-                    | otherwise -> trimUnimportant t-      in Just (copy name, Object (map (fmap copy) rhs))-    _ -> Nothing---- | Returns everything up to the next newline. The newline character--- itself is consumed.-takeLine :: (Monad m, HasError m, HasHppState m) => Parser m [TOKEN] [TOKEN]-takeLine = (onElements $ do-              ln <- takingWhile (not . newLine)-              eat <- awaitJust "takeLine" -- Eat the newline character-              case eat of-                Other "\n" -> return ()-                wat -> error $ "Expected newline: "++show wat++" after "++show ln-              return ln)-           <* (lineNum %= (+1))--dropLine :: (Monad m, HasError m, HasHppState m) => Parser m [TOKEN] ()-dropLine = do onElements $ do-                droppingWhile (not . newLine)-                eat <- awaitJust "dropLine" -- Eat the newline character-                case eat of-                  Other "\n" -> return ()-                  wat -> error $ "Expected dropped newline: "++show wat-              lineNum %= (+1)---- * State Zooming--expandLineP :: (Monad m, HasHppState m, HasEnv m, HasError m)-            => Parser m [TOKEN] [TOKEN]-expandLineP =-  do st <- getState-     let ln = hppLineNum st-         cfg = hppConfig st-     expandLine cfg ln---- | @hppReadFile lineNumber fileName@ introduces an @#include--- <fileName>@ as if it occurred at the given line number.-hppReadFile :: Monad m => Int -> FilePath -> HppT src m src-hppReadFile n file = HppT (pure (FreeF (ReadFile n file return)))---- | @hppReadNext lineNumber fileName@ introduces an @#include_next--- <fileName>@ as if it occurred at the given line number.-hppReadNext :: Monad m => Int -> FilePath -> HppT src m src-hppReadNext n file = HppT (pure (FreeF (ReadNext n file return)))---- * Directive Processing---- | Handle preprocessor directives (commands prefixed with an octothorpe).-directive :: forall m. (Monad m, HppCaps m)-          => HppT [String] (Parser m [TOKEN]) Bool-directive = lift (onElements (awaitJust "directive")) >>= aux-  where aux :: TOKEN -> HppT [String] (Parser m [TOKEN]) Bool-        aux (Important cmd) = case cmd of-          "pragma" -> True <$ lift dropLine -- Ignored-          "define" -> True <$-                      (lift $ fmap parseDefinition takeLine >>= \case-                        Nothing -> use lineNum >>=-                                   throwError . BadMacroDefinition-                        Just def -> env %= insertPair def)-          "undef" -> do name <- lift . onElements $ do-                          droppingWhile (not . isImportant)-                          Important name <- awaitJust "undef"-                          return name-                        lift dropLine-                        env %= deleteKey name-                        return True-          "include" -> True <$ includeAux hppReadFile-          "include_next" -> True <$ includeAux hppReadNext-          "line" -> do lift (onElements droppingSpaces)-                       toks <- lift (init <$> expandLineP)-                       case toks of-                         Important (toChars -> n):optFile ->-                           case readMaybe n of-                             Nothing -> use lineNum >>=-                                        throwError . flip BadLineArgument n-                             Just ln' -> do-                               unless (null optFile) $ do-                                 let fn = toChars . unquote . detokenize-                                        . dropWhile (not . isImportant)-                                        $ optFile-                                 config %= (\cfg -> cfg { curFileNameF = pure fn })-                               lineNum .= ln'-                               return True-                         _ -> use lineNum >>=-                              throwError-                              . flip BadLineArgument (toChars (detokenize toks))-          "ifdef" ->-            do toks <- lift (onElements droppingSpaces >> takeLine)-               ln <- use lineNum-               case takeWhile isImportant toks of-                 [Important t] ->-                   lookupMacro t >>= \case-                     Nothing ->-                       lift (dropBranchLine ln >>= replace . fst)-                     Just _ ->-                       lift (onInputSegment (takeBranchFun ln)) -- (takeBranch ln >>= precede)-                 _ -> throwError . UnknownCommand ln $-                      "ifdef "++ toChars (detokenize toks)-               return True-          "ifndef" ->-            do toks <- lift (onElements droppingSpaces >> takeLine)-               ln <- use lineNum-               case takeWhile isImportant toks of-                 [Important t] ->-                   lookupMacro t >>= \case-                      Nothing -> lift (onInputSegment (takeBranchFun ln)) -- takeBranch ln >>= precede)-                      Just _ -> lift (dropBranchLine ln >>= replace . fst)-                 _ -> throwError . UnknownCommand ln $-                      "ifndef "++ toChars (detokenize toks)-               return True-          "else" -> True <$ lift dropLine-          "if" -> True <$ ifAux-          "elif" -> True <$ ifAux-          "endif" -> True <$ lift dropLine-          "error" -> do toks <- lift (onElements droppingSpaces >> takeLine)-                        ln <- subtract 1 <$> use lineNum-                        curFile <- curFileName <$> use config-                        let tokStr = toChars (detokenize toks)-                        throwError $ UserError ln (tokStr++" ("++curFile++")")-          "warning" -> True <$ lift dropLine -- warnings not yet supported-          -- t -> do toks <- lift takeLine-          --         ln <- subtract 1 <$> use lineNum-          --         throwError $ UnknownCommand ln (detokenize (Important t:toks))-          _ -> return False -- Ignore unknown command-        aux _ = error "Impossible unimportant directive"-        includeAux :: (LineNum -> FilePath -> HppT src (Parser m [TOKEN]) [String])-                   -> HppT src (Parser m [TOKEN]) ()-        includeAux readFun =-          do fileName <- lift (toChars . detokenize . trimUnimportant . init-                               <$> expandLineP)-             ln <- use lineNum-             src <- prepareInput <*> readFun ln fileName-             lineNum .= ln+1-             lift (streamNewFile (unquote fileName) src)-        {- SPECIALIZE includeAux ::-            (LineNum -> FilePath -> HppT [String] (Parser (StateT HppState (ExceptT Error IO)) [TOKEN]) [String])-            -> HppT [String] (Parser (StateT HppState (ExceptT Error IO)) [TOKEN]) () #-}-        ifAux =-          do toks <- lift (onElements droppingSpaces >> takeLine)-             e <- use env-             ln <- use lineNum-             lineNum .= ln - 1 -- takeLine incremented the line count-             ex <- lift (lift (evalParse expandLineP [squashDefines e toks]))-             let res = evalExpr <$> parseExpr (map (fmap toChars) ex)-             lineNum .= ln-             if maybe False (/= 0) res-               then lift (onInputSegment (takeBranchFun ln)) -- (takeBranch ln >>= precede)-               else lift (dropBranchLine ln >>= replace . fst)--{-# SPECIALIZE directive ::-    HppT [String] (Parser (StateT HppState (ExceptT Error IO)) [TOKEN]) Bool #-}---- | We want to expand macros in expressions that must be evaluated--- for conditionals, but we want to take special care when dealing--- with the meta @defined@ operator of the expression language that is--- a predicate on the evaluation environment.-squashDefines :: Env -> [TOKEN] -> [TOKEN]-squashDefines _ [] = []-squashDefines env' (Important "defined" : ts) = go ts-  where go (t@(Other _) : ts') = t : go ts'-        go (t@(Important "(") : ts') = t : go ts'-        go (Important t : ts') =-          case lookupKey t env' of-            Nothing -> Important "0" : squashDefines env' ts'-            -- Just (_,env'') -> Important "1" : squashDefines env'' ts'-            Just _ -> Important "1" : squashDefines env' ts'-        go [] = []-squashDefines env' (t : ts) = t : squashDefines env' ts--getCmd :: [TOKEN] -> Maybe String-getCmd = aux . dropWhile notImportant-  where aux (Important "#" : ts) = case dropWhile notImportant ts of-                                     (Important cmd:_) -> Just cmd-                                     _ -> Nothing-        aux _ = Nothing--droppingSpaces ::(Monad m) => ParserT m src TOKEN ()-droppingSpaces = droppingWhile notImportant--dropBranchFun :: [[TOKEN]] -> (Int, [[TOKEN]])-dropBranchFun = go (1::Int) 0-  where go _ !n [] = (n,[])-        go !nesting !n (ln:lns) =-          case getCmd ln of-            Just cmd-              | cmd == "endif" -> if nesting == 1-                                  then (n, ln:lns)-                                  else go (nesting-1) (n+1) lns-              | cmd `elem` ["if","ifdef","ifndef"] ->-                go (nesting+1) (n+1) lns-              | cmd `elem` ["else","elif"] -> if nesting == 1-                                              then (n, ln : lns)-                                              else go nesting (n+1) lns-            _ -> go nesting (n+1) lns---- | Take everything up to the end of this branch, drop all remaining--- branches (if any).-takeBranchFun :: LineNum -> [[TOKEN]] -> [[TOKEN]]-takeBranchFun = go (1::Int)-  where go _ _ [] = [] -- error: unterminated conditional-        go 0 !n lns = yieldLineNum n : lns-        go !nesting !n (ln:lns) =-          case getCmd ln of-            Just cmd-              | cmd `elem` ["if","ifdef","ifndef"] ->-                ln : go (nesting+1) (n+1) lns-              | cmd == "endif" -> ln : go (nesting - 1) (n + 1) lns-              | nesting == 1 && cmd `elem` ["else","elif"] ->-                let (numSkipped, lns') = dropBranchFun lns-                in go 1 (n+1+numSkipped) lns'-            _ -> ln : go nesting (n+1) lns---yieldLineNum :: LineNum -> [TOKEN]-yieldLineNum !ln = [Important ("#line " <> fromString (show ln)), Other "\n"]--dropBranchLine :: (HasError m, Monad m)-               => LineNum -> Parser m [TOKEN] ([TOKEN], LineNum)-dropBranchLine !ln = do (el, numSkipped) <- dropBranch-                        let ln' = ln + numSkipped-                        return (yieldLineNum ln' ++ fromMaybe [] el, ln')---- | Skip to the end of a conditional branch. Returns the 'Just' the--- token that ends this branch if it is an @else@ or @elif@, or--- 'Nothing' otherwise, and the number of lines skipped.-dropBranch :: (HasError m, Monad m) => Parser m [TOKEN] (Maybe [TOKEN], Int)-dropBranch = go (1::Int) 0-  where go !nesting !n =-          do ln <- awaitJust "dropBranch"-             case getCmd ln of-               Just cmd-                 | cmd == "endif" -> if nesting == 1-                                     then return (Nothing, n+1)-                                     else go (nesting-1) (n+1)-                 | cmd `elem` ["if","ifdef","ifndef"] ->-                   go (nesting+1) (n+1)-                 | cmd `elem` ["else", "elif"] -> if nesting == 1-                                                  then return (Just ln, n+1)-                                                  else go nesting (n+1)-               _ -> go nesting (n+1)---- | Expands an input line producing a stream of output lines.-macroExpansion :: (Monad m, HppCaps m)-               => HppT [String] (Parser m [TOKEN]) (Maybe [TOKEN])-macroExpansion = do-  lift await >>= \case-    Nothing -> return Nothing-    Just ln ->-      -- when (not (all isSpace (detokenize ln)))-      --      (trace ("macro expand: "++detokenize ln) (return ())) >>-      case dropWhile notImportant ln of-        [] -> Just ln <$ (lineNum %= (+1))-        Important "#":rst -> do lift (replace (dropWhile notImportant rst))-                                processed <- directive-                                if processed-                                then macroExpansion-                                else Just ln <$ lift takeLine-        _ -> lift (replace ln >> (Just <$> expandLineP)) <* (lineNum %= (+1))---- | The dynamic capabilities offered by HPP-type HppCaps t = (HasError t, HasHppState t, HasEnv t)- parseStreamHpp :: Monad m                => HppT t (Parser m i) (Maybe t) -> HppT t (Parser m i) () parseStreamHpp m = go   where go = m >>= \case           Nothing -> return ()-          Just o -> writeOutput o >> go---- * HPP configurations---- | Standard CPP settings for processing C files.-normalCPP :: [String] -> [[TOKEN]]-normalCPP = map ((++ [Other "\n"]) . tokenize)-          . lineSplicing-          -- . map dropLineComments-          . removeMultilineComments 1-          . map (dropOneLineBlockComments . trigraphReplacement)-{-# INLINABLE normalCPP #-}---- | For Haskell we do not want trigraph replacement.-haskellCPP :: [String] -> [[TOKEN]]-haskellCPP = map ((++[Other "\n"]) . tokenize)-           . lineSplicing-           . commentRemoval-{-# INLINABLE haskellCPP #-}---- | If we don't have a predefined processor, we build one based on a--- 'Config' value.-genericConfig :: Config -> [String] -> [[TOKEN]]-genericConfig cfg = map ((++ [Other "\n"]) . tokenize)-                  . (if spliceLongLines cfg then lineSplicing else id)-                  . (if eraseCComments cfg then commentRemoval else id)-                  . (if replaceTrigraphs cfg then map trigraphReplacement else id)+          Just o -> hppWriteOutput o >> go  -- * Front End -prepareInput :: (Monad m, HppCaps m) => m ([String] -> [[TOKEN]])-prepareInput =-  do cfg <- getL config <$> getState-     case () of-       _ | eraseCComments cfg && spliceLongLines cfg-           && not (inhibitLinemarkers cfg) -> pure normalCPP-       _ | (eraseCComments cfg && spliceLongLines cfg-            && (not (replaceTrigraphs cfg))) ->-           pure haskellCPP-       _ | otherwise -> pure (genericConfig cfg)- -- | Run a stream of lines through the preprocessor.-preprocess :: (Monad m, HppCaps m)+preprocess :: (Monad m, HasHppState m, HasError m, HasEnv m)            => [String] -> HppT [String] (Parser m [TOKEN]) () preprocess src =   do cfg <- getL config <$> getState@@ -651,7 +155,7 @@ -- `ExceptT`, `StateT`, and `Parser` (note that `Parser` is actually -- just another `StateT`). --- | A concreate choice of types to satisfy `HppCaps` as required by+-- | A concreate choice of types to satisfy the constraints of -- `preprocess`. dischargeHppCaps :: Monad m                  => Config -> Env@@ -673,7 +177,7 @@ hppIOSink' cfg env' snk src =   fmap (fmap hppFilesRead)   . dischargeHppCaps cfg env' $-  runHpp cfg (liftIO . readLines) (liftIO . snk) (preprocess src)+  runHpp (liftIO . readLines) (liftIO . snk) (preprocess src)  -- | General hpp runner against input source file lines. Output lines -- are fed to the caller-supplied sink function. Any errors
src/Hpp/String.hs view
@@ -1,6 +1,14 @@ {-# LANGUAGE BangPatterns #-} -- | HELPERS for working with 'String's-module Hpp.String (stringify, unquote, trimSpaces, breakOn, cons) where+module Hpp.String (+  stringify+  , unquote+  , stripAngleBrackets+  , trimSpaces+  , breakOn+  , cons+  )+  where import Data.Char (isSpace) import Data.List (isPrefixOf, find) @@ -16,11 +24,22 @@  -- | Remove double quote characters from the ends of a string. unquote :: String -> String-unquote ('"':xs) = go xs-  where go ['"'] = []+unquote = stripEnds '"' '"'++-- | Remove angle brackets from the ends of a string.+stripAngleBrackets :: String -> String+stripAngleBrackets = stripEnds '<' '>'++stripEnds :: Char -> Char -> String -> String+stripEnds start end s =+  case s of+    (start':rest)+      | start == start' -> go rest+    _ -> s+  where go (c:[])+          | c == end = []         go [] = []         go (c:cs) = c : go cs-unquote xs = xs  -- | Trim trailing spaces from a 'String' trimSpaces :: String -> String
src/Hpp/StringSig.hs view
@@ -1,11 +1,17 @@ {-# LANGUAGE BangPatterns, CPP, FlexibleInstances, OverloadedStrings,              PatternSynonyms, TypeSynonymInstances, ViewPatterns #-}+-- | Defines a signature, 'Stringy', for string-like types that we may+-- want to use. module Hpp.StringSig where import Data.Char import qualified Data.List as L import Data.Maybe (isJust)+#if __GLASGOW_HASKELL__ < 804+import Data.Semigroup (Semigroup)+#endif import Data.String (IsString) import qualified Hpp.String as S+import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as BL import System.IO (Handle, hPutStr)@@ -13,7 +19,7 @@ data CharOrSub s = CharMatch !s !s | SubMatch !s !s | NoMatch  -- | A collection of operations relating to sequences of characters.-class (IsString s, Monoid s) => Stringy s where+class (IsString s, Monoid s, Semigroup s) => Stringy s where   -- | Stringification puts double quotes around a string and   -- backslashes before existing double quote characters and backslash   -- characters.@@ -175,7 +181,7 @@   sall = B.all   sIsPrefixOf = B.isPrefixOf   isEmpty = B.null-  readLines = fmap (map BL.toStrict . BL.lines) . BL.readFile+  readLines = fmap (map stripR . map BL.toStrict . BL.lines) . BL.readFile   {-# INLINE readLines #-}   putStringy = B.hPutStr   toChars = B.unpack@@ -195,6 +201,12 @@                    Nothing -> s                    Just (_, _, s') -> s' {-# INLINE sdropWhile #-}++stripR :: ByteString -> ByteString+stripR bs+  | not (B.null bs) && B.last bs == '\r' = B.init bs+  | otherwise = bs+{-# INLINE stripR #-}  #if __GLASGOW_HASKELL__ >= 800 pattern (:.) :: Stringy s => Char -> s -> s
src/Hpp/Tokens.hs view
@@ -95,7 +95,16 @@             '\\' :. cs ->               case sbreak (boolJust . (== '\'')) cs of                 Nothing -> [Important (pre' <> pos)]-                Just (_,esc,pos') ->++                Just (_,esc,pos')+                  | isEmpty esc ->+                    case sbreak (boolJust . (== '\'')) pos' of+                      Just (_,esc', pos'')+                        | isEmpty esc' ->+                          Important pre : Important ("'\\\''") : tokWords pos''+                          -- Important (fromJust $ escapeChar '\'') : tokWords pos''+                      _ -> [Important (pre' <> pos)]+                  | otherwise ->                   let esc' = if sall isOctDigit esc                              then Important (digitsFromBase 8 esc)                              else case esc of@@ -127,6 +136,9 @@ {-# INLINABLE tokWords #-}  data LitStringChar = DBackSlash | EscapedDQuote | DQuote++-- | Skip over a string or character literal returning the literal and+-- the remaining the input. skipLiteral :: Stringy s => s -> (s,s) skipLiteral s =   case breakOn [("\\\\", DBackSlash), ("\\\"", EscapedDQuote), ("\"", DQuote)] s of
src/Hpp/Types.hs view
@@ -11,19 +11,20 @@ import Data.Functor.Constant import Data.Functor.Identity -- import qualified Data.Map as M-import qualified Data.Trie as T+import Data.HashMap.Strict (HashMap) import Hpp.Config import Hpp.Env (emptyEnv, lookupKey) import Hpp.StringSig (toChars) import Hpp.Tokens import Prelude hiding (String) import qualified Prelude as P+import System.FilePath (takeDirectory)  -- | Line numbers are represented as 'Int's type LineNum = Int  -- | A macro binding environment.-type Env = T.Trie Macro+type Env = HashMap ByteString Macro  -- * Changing the underlying string type type String = ByteString@@ -86,11 +87,14 @@ -- | Dynamic state of the preprocessor engine. data HppState = HppState { hppConfig :: Config                            -- ^ Initial configuration+                         , hppCurDir :: FilePath+                           -- ^ Directory of input file                          , hppLineNum :: LineNum                            -- ^ Current line number of input file                          , hppEnv :: Env                            -- ^ Preprocessor binding environment                          }+  deriving Show  -- | A free monad construction to strictly delimit what capabilities -- we need to perform pre-processing.@@ -104,15 +108,30 @@   fmap f (WriteOutput o k) = WriteOutput o (f k)   {-# INLINE fmap #-} +-- | 'Hpp' is a monad with 'HppF' as its base functor.+type Hpp t = FreeF (HppF t)+ -- * Hpp Monad Transformer  -- | A free monad transformer specialized to HppF as the base functor.-newtype HppT t m a = HppT { runHppT :: m (FreeF (HppF t) a (HppT t m a)) }+newtype HppT t m a = HppT { runHppT :: m (Hpp t a (HppT t m a)) } -writeOutput :: Monad m => t -> HppT t m ()-writeOutput = HppT . return . FreeF . flip WriteOutput (return ())-{-# INLINE writeOutput #-}+-- | @hppReadFile lineNumber fileName@ introduces an @#include+-- <fileName>@ at the given line number.+hppReadFile :: Monad m => Int -> FilePath -> HppT src m src+hppReadFile n file = HppT (pure (FreeF (ReadFile n file return)))+{-# INLINE hppReadFile #-} +-- | @hppReadNext lineNumber fileName@ introduces an @#include_next+-- <fileName>@ at the given line number.+hppReadNext :: Monad m => Int -> FilePath -> HppT src m src+hppReadNext n file = HppT (pure (FreeF (ReadNext n file return)))+{-# INLINE hppReadNext #-}++hppWriteOutput :: Monad m => t -> HppT t m ()+hppWriteOutput = HppT . return . FreeF . flip WriteOutput (return ())+{-# INLINE hppWriteOutput #-}+ instance Functor m => Functor (HppT t m) where   fmap f (HppT x) = HppT $ fmap f' x     where f' (PureF y) = PureF (f y)@@ -252,18 +271,24 @@ -- * State Lenses  emptyHppState :: Config -> HppState-emptyHppState cfg = HppState cfg 1 emptyEnv+emptyHppState cfg = HppState cfg (takeDirectory (curFileName cfg)) 1 emptyEnv  config :: Lens HppState Config-config f (HppState cfg ln e) = (\cfg' -> HppState cfg' ln e) <$> f cfg+config f (HppState cfg _dir ln e) =+  (\cfg' -> HppState cfg' (takeDirectory (curFileName cfg')) ln e) <$> f cfg {-# INLINE config #-} +dir :: Lens HppState FilePath+dir f (HppState cfg dirOld ln e) =+  (\dirNew -> HppState cfg dirNew ln e) <$> f dirOld+{-# INLINE dir #-}+ lineNum :: Lens HppState LineNum-lineNum f (HppState cfg ln e) = (\ln' -> HppState cfg ln' e) <$> f ln+lineNum f (HppState cfg dir0 ln e) = (\ln' -> HppState cfg dir0 ln' e) <$> f ln {-# INLINE lineNum #-}  env :: Lens HppState Env-env f (HppState cfg ln e) = (\e' -> HppState cfg ln e') <$> f e+env f (HppState cfg dir0 ln e) = (\e' -> HppState cfg dir0 ln e') <$> f e {-# INLINE env #-}  use :: (HasHppState m, Functor m) => Lens HppState a -> m a
src/Main.hs view
@@ -40,7 +40,7 @@ main :: IO () main = do getArgs >>= \case             [] -> usage-            args -> runWithArgs args+            args -> runWithArgs args >> return ()   {-