packages feed

hpp 0.3.1.0 → 0.4.0

raw patch · 18 files changed

+1198/−1234 lines, 18 filesdep +bytestringdep +bytestring-triedep +ghc-primPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, bytestring-trie, ghc-prim, semigroups

API changes (from Hackage documentation)

- Hpp: (~>) :: Monad m => Streamer m a b r -> Streamer m b c r' -> Streamer m a c r'
- Hpp: before :: Monad m => Streamer m i o q -> Streamer m i o r -> Streamer m i o r
- Hpp: hppRegisterCleanup :: (HasHppState m, Monad m) => Cleanup -> m ()
- Hpp: infixl 9 ~>
- Hpp: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Hpp.HppStream m)
- Hpp: instance GHC.Base.Applicative m => Hpp.Types.HasError (Hpp.HppStream m)
- Hpp: instance GHC.Base.Functor m => GHC.Base.Functor (Hpp.HppStream m)
- Hpp: instance GHC.Base.Monad m => GHC.Base.Applicative (Hpp.HppStream m)
- Hpp: instance GHC.Base.Monad m => GHC.Base.Monad (Hpp.HppStream m)
- Hpp: instance GHC.Base.Monad m => Hpp.HasHppFileIO (Hpp.HppStream m)
- Hpp: instance GHC.Base.Monad m => Hpp.Types.HasEnv (Hpp.HppStream m)
- Hpp: instance GHC.Base.Monad m => Hpp.Types.HasHppState (Hpp.HppStream m)
- Hpp: sinkToFile :: MonadIO m => (Cleanup -> m ()) -> FilePath -> Streamer m String o ()
- Hpp: sinkToStdOut :: MonadIO m => Streamer m String o ()
- Hpp: source :: (Monad m, Foldable f) => f a -> Streamer m i a ()
- Hpp: streamHpp :: (Monad m, HasHppFileIO m) => FilePath -> Source m String ()
- Hpp: yield :: Monad m => o -> Streamer m i o ()
- Hpp.Parser: Parser :: (forall r. ParserR m r i o) -> Parser m i o
- Hpp.Parser: [runParser] :: Parser m i o -> forall r. ParserR m r i o
- Hpp.Parser: awaitP :: Monad m => Parser m i (Maybe i)
- Hpp.Parser: instance (GHC.Base.Monad m, Hpp.Types.HasError m) => Hpp.Types.HasError (Hpp.Parser.Parser m i)
- Hpp.Parser: instance (GHC.Base.Monad m, Hpp.Types.HasHppState m) => Hpp.Types.HasHppState (Hpp.Parser.Parser m i)
- Hpp.Parser: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Hpp.Parser.Parser m i)
- Hpp.Parser: instance GHC.Base.Functor m => GHC.Base.Functor (Hpp.Parser.Parser m i)
- Hpp.Parser: instance GHC.Base.Monad m => GHC.Base.Applicative (Hpp.Parser.Parser m i)
- Hpp.Parser: instance GHC.Base.Monad m => GHC.Base.Monad (Hpp.Parser.Parser m i)
- Hpp.Parser: instance GHC.Base.MonadPlus m => GHC.Base.Alternative (Hpp.Parser.Parser m i)
- Hpp.Parser: liftP :: Monad m => m o -> Parser m i o
- Hpp.Parser: newtype Parser m i o
- Hpp.Parser: onParserSource :: Monad m => Streamer m i i () -> Parser m i ()
- Hpp.Parser: zoomParse :: Monad m => (forall r. Source m a r -> Source m b (Source m a r)) -> Parser m b o -> Parser m a o
- Hpp.Parser: zoomParseChunks :: Monad m => Parser m i r -> Parser m [i] r
- Hpp.StreamIO: sinkTell :: Monad m => (a -> m ()) -> Streamer m a o ()
- Hpp.StreamIO: sinkToFile :: MonadIO m => (Cleanup -> m ()) -> FilePath -> Streamer m String o ()
- Hpp.StreamIO: sinkToStdOut :: MonadIO m => Streamer m String o ()
- Hpp.StreamIO: sink_ :: Monad m => Streamer m i o ()
- Hpp.StreamIO: sourceFile :: (MonadIO m, MonadIO m') => (Cleanup -> m' ()) -> FilePath -> m' (Source m String ())
- Hpp.Streamer: (~>) :: Monad m => Streamer m a b r -> Streamer m b c r' -> Streamer m a c r'
- Hpp.Streamer: Await :: (i -> f) -> f -> StreamStep r i o f
- Hpp.Streamer: Chunky :: (a -> Source m b (Chunky m a b)) -> Chunky m a b
- Hpp.Streamer: Done :: (Maybe r) -> StreamStep r i o f
- Hpp.Streamer: Streamer :: m (StreamStep r i o (Streamer m i o r)) -> Streamer m i o r
- Hpp.Streamer: Yield :: !o -> f -> StreamStep r i o f
- Hpp.Streamer: [runStream] :: Streamer m i o r -> m (StreamStep r i o (Streamer m i o r))
- Hpp.Streamer: awaits :: Monad m => (i -> Streamer m i o r) -> Streamer m i o r
- Hpp.Streamer: before :: Monad m => Streamer m i o q -> Streamer m i o r -> Streamer m i o r
- Hpp.Streamer: data StreamStep r i o f
- Hpp.Streamer: done :: Monad m => r -> Streamer m i o r
- Hpp.Streamer: encase :: Monad m => StreamStep r i o (Streamer m i o r) -> Streamer m i o r
- Hpp.Streamer: filtering :: Monad m => (a -> Bool) -> Streamer m a a r
- Hpp.Streamer: flattenTil :: Monad m => Source m [i] r -> Source m i (Source m [i] r)
- Hpp.Streamer: infixl 9 ~>
- Hpp.Streamer: instance (GHC.Base.Monad m, Hpp.Types.HasEnv m) => Hpp.Types.HasEnv (Hpp.Streamer.Streamer m i o)
- Hpp.Streamer: instance (GHC.Base.Monad m, Hpp.Types.HasError m) => Hpp.Types.HasError (Hpp.Streamer.Streamer m i o)
- Hpp.Streamer: instance (GHC.Base.Monad m, Hpp.Types.HasHppState m) => Hpp.Types.HasHppState (Hpp.Streamer.Streamer m i o)
- Hpp.Streamer: instance GHC.Base.Functor (Hpp.Streamer.StreamStep r i o)
- Hpp.Streamer: instance GHC.Base.Monad m => GHC.Base.Alternative (Hpp.Streamer.Streamer m r i)
- Hpp.Streamer: instance GHC.Base.Monad m => GHC.Base.Applicative (Hpp.Streamer.Streamer m i o)
- Hpp.Streamer: instance GHC.Base.Monad m => GHC.Base.Functor (Hpp.Streamer.Streamer m i o)
- Hpp.Streamer: liftS :: Functor m => m a -> Streamer m i o a
- Hpp.Streamer: mapStream :: Monad m => (a -> b) -> Streamer m i a r -> Streamer m i b r
- Hpp.Streamer: mapTil :: Monad m => (a -> b) -> Streamer m Void a r -> Streamer m Void b (Streamer m Void a r)
- Hpp.Streamer: mapping :: Monad m => (a -> b) -> Streamer m a b r
- Hpp.Streamer: mappingMaybe :: Monad m => (a -> Maybe b) -> Streamer m a b r
- Hpp.Streamer: metamorph :: Monad m => Chunky m a b -> Streamer m a b ()
- Hpp.Streamer: newtype Chunky m a b
- Hpp.Streamer: newtype Streamer m i o r
- Hpp.Streamer: nextOutput :: Monad m => Streamer m i o r -> m (Either (Maybe r) (o, Streamer m i o r))
- Hpp.Streamer: onDone :: Monad m => (Maybe r -> Maybe r') -> Streamer m i o r -> Streamer m i o r'
- Hpp.Streamer: processPrefix :: Monad m => Source m o r -> Streamer m o o r' -> Source m o r
- Hpp.Streamer: run :: Monad m => Source m Void r -> m (Maybe r)
- Hpp.Streamer: source :: (Monad m, Foldable f) => f a -> Streamer m i a ()
- Hpp.Streamer: type Source m o r = Streamer m Void o r
- Hpp.Streamer: yield :: Monad m => o -> Streamer m i o ()
- Hpp.Streamer: yields :: Monad m => o -> Streamer m i o r -> Streamer m i o r
- Hpp.Tokens: fixExponents :: [Token] -> [Token]
- Hpp.Tokens: instance GHC.Classes.Eq Hpp.Tokens.Token
- Hpp.Tokens: instance GHC.Classes.Ord Hpp.Tokens.Token
- Hpp.Tokens: instance GHC.Show.Show Hpp.Tokens.Token
- Hpp.Tokens: splits :: (Char -> Bool) -> String -> [String]
- Hpp.Tokens: tokWords :: String -> [Token]
- Hpp.Tokens: validIdentifierChar :: Char -> Bool
- Hpp.Types: Cleanup :: (IORef (IO ())) -> Cleanup
- Hpp.Types: GetState :: (HppState -> r) -> HppF t r
- Hpp.Types: SetState :: HppState -> r -> HppF t r
- Hpp.Types: ThrowError :: Error -> HppF t r
- Hpp.Types: [hppCleanups] :: HppState -> [Cleanup]
- Hpp.Types: instance GHC.Base.Applicative m => Hpp.Types.HasError (Hpp.Types.HppT t m)
- Hpp.Types: instance GHC.Base.Monad m => Hpp.Types.HasEnv (Hpp.Types.HppT t m)
- Hpp.Types: instance GHC.Base.Monad m => Hpp.Types.HasHppState (Hpp.Types.HppT t m)
- Hpp.Types: mkCleanup :: IO () -> IO (Cleanup, IO ())
- Hpp.Types: newtype Cleanup
- Hpp.Types: runCleanup :: Cleanup -> IO ()
+ Hpp: hppFileContents :: Config -> Env -> FilePath -> [String] -> IO (Either Error [String])
+ Hpp.Config: [replaceTrigraphsF] :: ConfigF f -> f Bool
+ Hpp.Config: replaceTrigraphs :: Config -> Bool
+ Hpp.Parser: await :: Monad m => ParserT m src i (Maybe i)
+ Hpp.Parser: evalParse :: Monad m => Parser m i o -> [i] -> m o
+ Hpp.Parser: insertInputSegment :: Monad m => src -> m () -> ParserT m (RopeM m src) i ()
+ Hpp.Parser: onChunks :: Monad m => ParserT m (RopeM m [i]) [i] r -> Parser m i r
+ Hpp.Parser: onElements :: Monad m => ParserT m (RopeM m [[i]]) i r -> Parser m [i] r
+ Hpp.Parser: onInputSegment :: Monad m => (src -> src) -> ParserT m (RopeM 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: runParser :: Monad m => Parser m i o -> RopeM m [i] -> m (o, RopeM m [i])
+ Hpp.Parser: type Parser m i = ParserT m (RopeM m [i]) i
+ Hpp.Parser: type ParserT m src i = StateT (Headspring m src i, src) m
+ Hpp.StringSig: CharMatch :: !s -> !s -> CharOrSub s
+ Hpp.StringSig: NoMatch :: CharOrSub s
+ Hpp.StringSig: SubMatch :: !s -> !s -> CharOrSub s
+ Hpp.StringSig: boolJust :: Bool -> Maybe ()
+ Hpp.StringSig: breakCharOrSub :: Stringy s => Char -> s -> s -> CharOrSub s
+ Hpp.StringSig: breakOn :: Stringy s => [(s, t)] -> s -> Maybe (t, s, s)
+ Hpp.StringSig: class (IsString s, Monoid s) => Stringy s
+ Hpp.StringSig: cons :: Stringy s => Char -> s -> s
+ Hpp.StringSig: copy :: Stringy s => s -> s
+ Hpp.StringSig: data CharOrSub s
+ Hpp.StringSig: infixr 5 :.
+ Hpp.StringSig: instance Hpp.StringSig.Stringy Data.ByteString.Internal.ByteString
+ Hpp.StringSig: instance Hpp.StringSig.Stringy GHC.Base.String
+ Hpp.StringSig: isEmpty :: Stringy s => s -> Bool
+ Hpp.StringSig: predicateJust :: (a -> Bool) -> a -> Maybe a
+ Hpp.StringSig: putStringy :: Stringy s => Handle -> s -> IO ()
+ Hpp.StringSig: readLines :: Stringy s => FilePath -> IO [s]
+ Hpp.StringSig: sIsPrefixOf :: Stringy s => s -> s -> Bool
+ Hpp.StringSig: sall :: Stringy s => (Char -> Bool) -> s -> Bool
+ Hpp.StringSig: sbreak :: Stringy s => (Char -> Maybe t) -> s -> Maybe (t, s, s)
+ Hpp.StringSig: sdrop :: Stringy s => Int -> s -> s
+ Hpp.StringSig: sdropWhile :: Stringy s => (Char -> Bool) -> s -> s
+ Hpp.StringSig: snoc :: Stringy s => s -> Char -> s
+ Hpp.StringSig: stringify :: Stringy s => s -> s
+ Hpp.StringSig: toChars :: Stringy s => s -> [Char]
+ Hpp.StringSig: trimSpaces :: Stringy s => s -> s
+ Hpp.StringSig: uncons :: Stringy s => s -> Maybe (Char, s)
+ Hpp.StringSig: unquote :: Stringy s => s -> s
+ Hpp.StringSig: unsnoc :: Stringy s => s -> Maybe (s, Char)
+ Hpp.Tokens: instance GHC.Base.Functor Hpp.Tokens.Token
+ Hpp.Tokens: instance GHC.Classes.Eq s => GHC.Classes.Eq (Hpp.Tokens.Token s)
+ Hpp.Tokens: instance GHC.Classes.Ord s => GHC.Classes.Ord (Hpp.Tokens.Token s)
+ Hpp.Tokens: instance GHC.Show.Show s => GHC.Show.Show (Hpp.Tokens.Token s)
+ Hpp.Types: (%=) :: (HasHppState m, Monad m) => Lens HppState a -> (a -> a) -> m ()
+ Hpp.Types: (.=) :: (HasHppState m, Monad m) => Lens HppState a -> a -> m ()
+ Hpp.Types: WriteOutput :: t -> r -> HppF t r
+ Hpp.Types: config :: Lens HppState Config
+ Hpp.Types: emptyHppState :: Config -> HppState
+ Hpp.Types: env :: Lens HppState Env
+ Hpp.Types: getL :: Lens s a -> s -> a
+ Hpp.Types: infix 4 %=
+ Hpp.Types: instance (GHC.Base.Monad m, Hpp.Types.HasError m) => Hpp.Types.HasError (Control.Monad.Trans.State.Strict.StateT s m)
+ Hpp.Types: instance (GHC.Base.Monad m, Hpp.Types.HasError m) => Hpp.Types.HasError (Hpp.Types.HppT t m)
+ Hpp.Types: instance (GHC.Base.Monad m, Hpp.Types.HasHppState m) => Hpp.Types.HasEnv (Hpp.Types.HppT t m)
+ Hpp.Types: instance (GHC.Base.Monad m, Hpp.Types.HasHppState m) => Hpp.Types.HasHppState (Control.Monad.Trans.State.Strict.StateT s m)
+ Hpp.Types: instance (GHC.Base.Monad m, Hpp.Types.HasHppState m) => Hpp.Types.HasHppState (Hpp.Types.HppT t m)
+ Hpp.Types: instance GHC.Base.Monad m => Hpp.Types.HasEnv (Control.Monad.Trans.State.Strict.StateT Hpp.Types.Env m)
+ Hpp.Types: instance GHC.Base.Monad m => Hpp.Types.HasEnv (Control.Monad.Trans.State.Strict.StateT Hpp.Types.HppState m)
+ Hpp.Types: instance GHC.Base.Monad m => Hpp.Types.HasHppState (Control.Monad.Trans.State.Strict.StateT Hpp.Types.HppState m)
+ Hpp.Types: lineNum :: Lens HppState LineNum
+ Hpp.Types: lookupMacro :: (HasEnv m, Monad m) => String -> m (Maybe Macro)
+ Hpp.Types: over :: Lens s a -> (a -> a) -> s -> s
+ Hpp.Types: setL :: Lens s a -> a -> s -> s
+ Hpp.Types: type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s
+ Hpp.Types: type String = ByteString
+ Hpp.Types: type TOKEN = Token ByteString
+ Hpp.Types: use :: (HasHppState m, Functor m) => Lens HppState a -> m a
+ Hpp.Types: writeOutput :: Monad m => t -> HppT t m ()
- Hpp: hppIO :: (MonadIO m) => Config -> Env -> Streamer (HppStream m) Void b r -> Streamer (HppStream m) b Void () -> m (Maybe ())
+ Hpp: hppIO :: Config -> Env -> ([String] -> IO ()) -> [String] -> IO ()
- Hpp: hppReadFile :: HasHppFileIO m => Int -> FilePath -> m (InputStream m)
+ Hpp: hppReadFile :: Monad m => Int -> FilePath -> HppT src m src
- Hpp: parseDefinition :: [Token] -> Maybe (String, Macro)
+ Hpp: parseDefinition :: [TOKEN] -> Maybe (String, Macro)
- Hpp: preprocess :: (Monad m, HppCaps m) => Source m String () -> Source m String ()
+ Hpp: preprocess :: (Monad m, HppCaps m) => ([String] -> src) -> [String] -> HppT [String] (Parser m [TOKEN]) ()
- Hpp: type HppCaps t = (HasError t, HasHppState t, HasHppFileIO t, HasEnv t)
+ Hpp: type HppCaps t = (HasError t, HasHppState t, HasEnv t)
- Hpp.Config: Config :: f FilePath -> f [FilePath] -> f Bool -> f Bool -> f Bool -> f DateString -> f TimeString -> ConfigF f
+ Hpp.Config: Config :: f FilePath -> f [FilePath] -> f Bool -> f Bool -> f Bool -> f Bool -> f DateString -> f TimeString -> ConfigF f
- Hpp.Env: deleteKey :: Eq a => a -> [(a, b)] -> [(a, b)]
+ Hpp.Env: deleteKey :: ByteString -> Trie a -> Trie a
- Hpp.Env: emptyEnv :: [a]
+ Hpp.Env: emptyEnv :: Trie a
- Hpp.Env: insertPair :: a -> [a] -> [a]
+ Hpp.Env: insertPair :: (ByteString, a) -> Trie a -> Trie a
- Hpp.Env: lookupKey :: Eq a => a -> [(a, b)] -> Maybe (b, [(a, b)])
+ Hpp.Env: lookupKey :: ByteString -> Trie a -> Maybe a
- Hpp.Expansion: expandLine :: (HasError m, Monad m, HasEnv m) => Config -> Int -> Parser m Token [Token]
+ Hpp.Expansion: expandLine :: (HasError m, Monad m, HasEnv m) => Config -> Int -> Parser m [TOKEN] [TOKEN]
- Hpp.Expr: parseExpr :: [Token] -> Maybe Expr
+ Hpp.Expr: parseExpr :: [Token String] -> Maybe Expr
- Hpp.Parser: awaitJust :: (Monad m, HasError m) => String -> Parser m i i
+ Hpp.Parser: awaitJust :: (Monad m, HasError m) => String -> ParserT m src i i
- Hpp.Parser: droppingWhile :: Monad m => (i -> Bool) -> Parser m i ()
+ Hpp.Parser: droppingWhile :: (Monad m) => (i -> Bool) -> ParserT m src i ()
- Hpp.Parser: parse :: Monad m => Parser m i o -> Source m i r -> m o
+ Hpp.Parser: parse :: Monad m => Parser m i o -> [i] -> m (o, RopeM m [i])
- Hpp.Parser: precede :: Monad m => Source m i r -> Parser m i ()
+ Hpp.Parser: precede :: Monad m => [i] -> ParserT m src i ()
- Hpp.Parser: replace :: Monad m => i -> Parser m i ()
+ Hpp.Parser: replace :: (Monad m) => i -> ParserT m src i ()
- Hpp.Parser: takingWhile :: Monad m => (i -> Bool) -> Parser m i [i]
+ Hpp.Parser: takingWhile :: (Monad m) => (i -> Bool) -> ParserT m src i [i]
- Hpp.String: breakOn :: String -> String -> (String, String)
+ Hpp.String: breakOn :: [(String, t)] -> String -> Maybe (t, String, String)
- Hpp.Tokens: Important :: String -> Token
+ Hpp.Tokens: Important :: s -> Token s
- Hpp.Tokens: Other :: String -> Token
+ Hpp.Tokens: Other :: s -> Token s
- Hpp.Tokens: data Token
+ Hpp.Tokens: data Token s
- Hpp.Tokens: detok :: Token -> String
+ Hpp.Tokens: detok :: Token s -> s
- Hpp.Tokens: detokenize :: [Token] -> String
+ Hpp.Tokens: detokenize :: Monoid s => [Token s] -> s
- Hpp.Tokens: importants :: [Token] -> [String]
+ Hpp.Tokens: importants :: [Token s] -> [s]
- Hpp.Tokens: isImportant :: Token -> Bool
+ Hpp.Tokens: isImportant :: Token s -> Bool
- Hpp.Tokens: newLine :: Token -> Bool
+ Hpp.Tokens: newLine :: (Eq s, IsString s) => Token s -> Bool
- Hpp.Tokens: notImportant :: Token -> Bool
+ Hpp.Tokens: notImportant :: Token s -> Bool
- Hpp.Tokens: skipLiteral :: ((String -> String) -> String -> r) -> String -> r
+ Hpp.Tokens: skipLiteral :: Stringy s => s -> (s, s)
- Hpp.Tokens: tokenize :: String -> [Token]
+ Hpp.Tokens: tokenize :: Stringy s => s -> [Token s]
- Hpp.Tokens: trimUnimportant :: [Token] -> [Token]
+ Hpp.Tokens: trimUnimportant :: [Token s] -> [Token s]
- Hpp.Types: HppState :: Config -> LineNum -> [Cleanup] -> Env -> HppState
+ Hpp.Types: HppState :: Config -> LineNum -> Env -> HppState
- Hpp.Types: Object :: [Token] -> Macro
+ Hpp.Types: Object :: [Token String] -> Macro
- Hpp.Types: Rescan :: Token -> Scan
+ Hpp.Types: Rescan :: (Token String) -> Scan
- Hpp.Types: Scan :: Token -> Scan
+ Hpp.Types: Scan :: (Token String) -> Scan
- Hpp.Types: type Env = [(String, Macro)]
+ Hpp.Types: type Env = Trie Macro

Files

CHANGELOG.md view
@@ -1,3 +1,12 @@+# 0.4.0++- Simplify the parsing machinery+- Don't remove C++-style single-line comments+- Don't error on unknown cpp directives+  Previously, a line beginning with "#-}" would cause an error+- Don't do trigraph replacement by default.+  Haskell allows "??" in operator names and you can be sure `lens` uses it!+ # 0.3.1  Address a change wherein GHC 8 will pass `-include` arguments without a space between "-include" and the file to be included.
+ README.md view
@@ -0,0 +1,32 @@+`hpp` is a Haskell pre-processor that is also a C90-compatible+pre-processor (with the addition of a `--cpp` flag). It is+packaged as both a library and an executable.++To use as a Haskell preprocessor for resolving `#ifdef` conditionals+and macro expansion, an invocation might look like,++```+hpp -DDEBUG Foo.hs+```++To use as a C preprocessor, an invocation might look like,++```+hpp -DDEBUG --cpp foo.c+```++To have GHC use `hpp` as the C pre-processor, add this line to the top+of a Haskell source file that makes use of the `CPP` `LANGUAGE`+pragma,++```+{-# OPTIONS_GHC -cpp -pgmPhpp #-}+```++Or add this line to your `.cabal` file:++```+ghc-options: -pgmPhpp+```++Note that you will need to ensure that the `hpp` executable is available in your build environment (e.g. you can add `hpp` as a `build-depends` in your `.cabal` file).
hpp.cabal view
@@ -1,51 +1,19 @@ name:                hpp-version:             0.3.1.0+version:             0.4.0 synopsis:            A Haskell pre-processor--description: @hpp@ is a Haskell pre-processor that is also a-             C89/C90-compatible pre-processor (with the addition of a-             @--cpp@ flag). It is packaged as both a library and-             an executable.--             .--             To use as a Haskell preprocessor for resolving @#ifdef@-             conditionals and simple macro expansion while still-             allowing multi-line string literals, an invocation might-             look like,-             .-             @-             hpp -DDEBUG Foo.hs-             @-             .-             To use as a C preprocessor, an invocation might look-             like,-             .-             @-             hpp -DDEBUG --cpp foo.c-             @-             .--             To have GHC use @hpp@ as the C pre-processor, add this-             line to the top of a Haskell source file that makes use-             of the @CPP@ @LANGUAGE@ pragma.-             .--             @-             &#123;-\# OPTIONS_GHC -cpp -pgmPhpp -optP\-\-cpp \#-&#125;-             @--+description:         See the README for usage examples license:             BSD3 license-file:        LICENSE author:              Anthony Cowley maintainer:          acowley@gmail.com-copyright:           (C) 2015 Anthony Cowley+copyright:           (C) 2015-2016 Anthony Cowley category:            Development build-type:          Simple-extra-source-files:  tests/mcpp-validation.sh, CHANGELOG.md+extra-source-files:  tests/mcpp-validation.sh, CHANGELOG.md, README.md cabal-version:       >=1.10 homepage:            https://github.com/acowley/hpp+tested-with:         GHC == 7.10.3, GHC == 8.0.1+ source-repository head   type:     git   location: http://github.com/acowley/hpp.git@@ -58,18 +26,19 @@                        Hpp.Expansion,                        Hpp.Expr,                        Hpp.Parser,-                       Hpp.Streamer,-                       Hpp.StreamIO,                        Hpp.String,+                       Hpp.StringSig,                        Hpp.Tokens,                        Hpp.Types   build-depends:       base >=4.8 && <4.10, directory, time >=1.5, filepath,-                       transformers >=0.4+                       transformers >=0.4, bytestring, bytestring-trie, ghc-prim++  if impl(ghc < 8)+    build-depends: semigroups >= 0.18 && < 0.19+   hs-source-dirs:      src   default-language:    Haskell2010-  ghc-options: -Wall -fno-full-laziness-  -- ghc-options: -Wall -fprof-auto -fno-full-laziness -ddump-simpl -ddump-to-file---  -ddump-simpl-stats+  ghc-options: -Wall  executable hpp   main-is:             src/Main.hs
src/Hpp.hs view
@@ -1,39 +1,37 @@-{-# LANGUAGE BangPatterns, ConstraintKinds, LambdaCase, RankNTypes,-             ScopedTypeVariables, GeneralizedNewtypeDeriving #-}+{-# LANGUAGE BangPatterns, ConstraintKinds, LambdaCase, OverloadedStrings,+             ScopedTypeVariables, TupleSections, ViewPatterns #-} -- | Front-end interface to the pre-processor.-module Hpp (parseDefinition, preprocess, yield, before, source,-            hppReadFile, hppIO, hppRegisterCleanup,-            streamHpp, sinkToFile, sinkToStdOut, (~>), HppCaps) where-import Control.Applicative (empty)+module Hpp (parseDefinition, preprocess,+            hppReadFile, hppIO, HppCaps, hppFileContents) where+import Control.Arrow (first) import Control.Monad (unless)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Except import Control.Monad.IO.Class (MonadIO, liftIO)-import Control.Monad.Trans.State.Strict (runStateT)+import Control.Monad.Trans.State.Strict (StateT, evalStateT) import Data.Char (isSpace)-import Data.Foldable (traverse_)-import Data.Functor.Constant-import Data.Functor.Identity-import Data.List (isPrefixOf, uncons)+import Data.IORef+import Data.Maybe (fromMaybe) import Data.Monoid ((<>))-import Data.Void (Void)+import Data.String (fromString) import Hpp.Config (Config, curFileNameF, curFileName, includePaths,-                   eraseCComments, spliceLongLines, inhibitLinemarkers)-import Hpp.Env (deleteKey, emptyEnv, insertPair, lookupKey)+                   eraseCComments, spliceLongLines, inhibitLinemarkers,+                   replaceTrigraphs)+import Hpp.Env (deleteKey, insertPair, lookupKey) import Hpp.Expansion (expandLine) import Hpp.Expr (evalExpr, parseExpr)-import Hpp.Parser (Parser(..), zoomParseChunks, replace, awaitP, awaitJust,-                   droppingWhile, liftP, parse, onParserSource, precede,-                   takingWhile)-import Hpp.StreamIO (sinkToFile, sinkToStdOut, sourceFile)-import Hpp.Streamer (Streamer(..), Chunky(..), metamorph, done, yields, mapping,-                     (~>), Source, before, liftS, source, encase, StreamStep(..),-                     yield, filtering, run)-import Hpp.String (stringify, trimSpaces, unquote, cons, breakOn)+import Hpp.Parser (Parser, ParserT, replace, await, awaitJust, droppingWhile,+                   precede, takingWhile, insertInputSegment, onElements,+                   evalParse, onInputSegment)+import Hpp.StringSig import Hpp.Tokens (Token(..), importants, isImportant, newLine, trimUnimportant,                    detokenize, notImportant, tokenize, skipLiteral) import Hpp.Types import System.Directory (doesFileExist) import System.FilePath ((</>)) import Text.Read (readMaybe)+import Prelude hiding (String)+import qualified Prelude as P  -- * Trigraphs @@ -52,113 +50,94 @@             , ('>', '}')             , ('-', '~') ] -trigraphReplacement :: String -> String-trigraphReplacement = aux . breakOn "??"-  where aux (s,[]) = s-        aux (h,t) =-          case uncons (drop 2 t) of-            Nothing -> h <> "??"-            Just (c,t') -> +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' -> h <> cons c' (trigraphReplacement t')-                Nothing -> h <> "?" <> trigraphReplacement (cons '?' (cons c t'))+                Just c' -> snoc pre c' <> trigraphReplacement t+                Nothing -> snoc pre '?' <> trigraphReplacement (cons '?' pos)  -- * Line Splicing --- | Yields full lines of input.--- lineStream :: Monad m => Streamer m String String ()--- lineStream = go id---   where go acc = awaitMaybe (yield (acc [])) (yieldLines acc)---         yieldLines acc s = case break (== '\n') s of---                              (h,t) -> case t of---                                         [] -> go (acc . (h++))---                                         ['\n'] -> encase $ Yield (acc h) (go id)---                                         (_:t') -> encase $---                                                   Yield (acc h)---                                                         (yieldLines id t')- -- | If a line ends with a backslash, it is prepended to the following -- the line.-lineSplicing :: Monad m => Streamer m String String ()-lineSplicing = metamorph (Chunky go)-  where go [] = yields [] (done $ Chunky go)-        go ln = case last ln of-                  '\\' -> done . Chunky $ go . (init ln ++)-                  _ -> yields ln (done $ Chunky go)+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 :: String -> Maybe (String, String)-breakBlockCommentStart = go id-  where go _ [] = Nothing-        go acc ('"' : ts) = skipLiteral (go . (acc .)) ts-        go acc ('/' : '*' : t) = Just (acc [], t)-        go acc (c:cs) = go (acc . (c:)) cs--breakBlockCommentEnd :: String -> Maybe String-breakBlockCommentEnd [] = Nothing-breakBlockCommentEnd (_:'"':cs) = skipLiteral (const breakBlockCommentEnd) cs-breakBlockCommentEnd ('*':'/':t) = Just (' ':t)-breakBlockCommentEnd (_:cs) = breakBlockCommentEnd cs+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) -dropOneLineBlockComments :: String -> String-dropOneLineBlockComments [] = []-dropOneLineBlockComments (c:'"':cs) =-  c : skipLiteral (\x y -> x [] ++ dropOneLineBlockComments y) cs-dropOneLineBlockComments ('/':'*':cs) = go cs-  where go [] = "/*"-        go ('*':'/':t)-          | all isSpace t = t-          | otherwise = ' ' : dropOneLineBlockComments t-        go (_:t) = go t-dropOneLineBlockComments (c:cs) = c : dropOneLineBlockComments cs+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 -dropLineComments :: String -> String-dropLineComments = fst . breakOn "//"+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 :: Monad m => Int -> Streamer m String String ()-removeMultilineComments !lineStart = metamorph (Chunky $ goStart lineStart)-  where goStart !curLine ln =+removeMultilineComments :: Stringy s => Int -> [s] -> [s]+removeMultilineComments !lineStart = goStart lineStart+  where goStart _ [] = []+        goStart !curLine (ln:lns) =           case breakBlockCommentStart ln of-            Nothing -> yields ln (done . Chunky $ goStart (curLine+1))-            Just (pre,_) -> done . Chunky $ goEnd (curLine+1) pre-        goEnd !curLine pre ln =+            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 -> done (Chunky $ goEnd (curLine+1) pre)+            Nothing -> goEnd (curLine+1) pre lns             Just pos-              | all isSpace (pre++pos) ->-                yields ("#line "++show (curLine+1))-                       (done . Chunky . goStart $ curLine + 1)-              | otherwise -> yields (pre++pos) $-                             yields ("#line "++show (curLine+1))-                                    (done . Chunky $ goStart (curLine+1))--              -- FIXME: The #line command interferes here, but the-              -- strategy above fails when multi-line comments end and-              -- begin on the same line.--              -- yields ("#line "++show curLine)-              --        (goStart (curLine+1) (pre++pos))-{-# INLINE removeMultilineComments #-}+              | 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 :: Monad m => Streamer m String String ()-commentRemoval =  mapping dropOneLineBlockComments-               ~> removeMultilineComments 1-               ~> mapping dropLineComments+commentRemoval :: Stringy s => [s] -> [s]+commentRemoval = removeMultilineComments 1 . map dropOneLineBlockComments --- * Token Splices+-- * TOKEN Splices  -- | Deal with the two-character '##' token pasting/splicing -- operator. We do so eliminating spaces around the @##@ -- operator.-prepTokenSplices :: [Token] -> [Token]-prepTokenSplices = dropSpaces [] . mergeTokens []+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+        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) =@@ -170,142 +149,134 @@ -- | @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 :: [String] -> [TOKEN] -> [([Scan],String)] -> [Scan] functionMacro params body = paste                           . subst body'                           -- . M.fromList-                          . zip params-  where subst toks gamma = go toks+                          . zip params'+  where params' = map copy params+        subst toks gamma = go toks           where go [] = []                 go (p@(Important "##"):t@(Important s):ts) =-                  case lookupKey s gamma of+                  case lookup s gamma of                     Nothing -> Rescan p : Rescan t : go ts-                    Just ((_,arg),_) ->+                    Just (_,arg) ->                       Rescan p : Rescan (Important arg) : go ts                 go (t@(Important s):p@(Important "##"):ts) =-                  case lookupKey s gamma of+                  case lookup s gamma of                     Nothing -> Rescan t : go (p:ts)-                    Just ((_,arg),_) -> Rescan (Important arg) : 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 lookupKey s gamma of+                go (t@(Important ('#':.s)) : ts) =+                  case lookup s gamma of                     Nothing -> Rescan t : go ts-                    Just ((_,arg),_) ->+                    Just (_,arg) ->                       Rescan (Important (stringify arg)) : go ts                 go (t@(Important s) : ts) =-                  case lookupKey s gamma of+                  case lookup s gamma of                     Nothing -> Rescan t : go ts-                    Just ((arg,_),_) -> arg ++ 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 ('#':t) : prepStringify ts'+            (Important t : ts') -> Important (cons '#' t) : prepStringify ts'             _ -> Important "#" : ts         prepStringify (t:ts) = t : prepStringify ts-                    -        body' = prepStringify . prepTokenSplices $++        body' = prepStringify . prepTOKENSplices $                 dropWhile (not . isImportant) body         paste [] = []         paste (Rescan (Important s) : Rescan (Important "##")               : Rescan (Important t) : ts) =-          paste (Rescan (Important (trimSpaces s ++ dropWhile isSpace t)) : ts)+          paste (Rescan (Important (trimSpaces s <> sdropWhile isSpace t)) : ts)         paste (t:ts) = t : paste ts  -- * Pre-Processor Capabilities -config :: Lens HppState Config-config f (HppState cfg ln cln e) = (\cfg' -> HppState cfg' ln cln e) <$> f cfg--lineNum :: Lens HppState LineNum-lineNum f (HppState cfg ln cln e) = (\ln' -> HppState cfg ln' cln e) <$> f ln--cleanups :: Lens HppState [Cleanup]-cleanups f (HppState cfg ln cln e) = (\cln' -> HppState cfg ln cln' e) <$> f cln--env :: Lens HppState Env-env f (HppState cfg ln cln e) = (\e' -> HppState cfg ln cln e') <$> f e- 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-              -> Source m o ()-              -> Source m o ()-streamNewFile fp s = Streamer $+              => 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)-     runStream $-       before s (liftS $ modifyState (setL lineNum oldLine . setL config oldCfg))+     insertInputSegment+       s (modifyState (setL lineNum oldLine . setL config oldCfg)) --- * Running an Hpp Action+-- * Finding @include@ files -includeCandidates :: [FilePath] -> String -> Maybe [FilePath]+includeCandidates :: [FilePath] -> P.String -> Maybe [FilePath] includeCandidates searchPath nm =   case nm of     '<':nm' -> Just $ sysSearch (init nm')     '"':nm' -> let nm'' = init nm'-               in Just $ nm'' : sysSearch nm''+                in Just $ nm'' : sysSearch nm''     _ -> Nothing   where sysSearch f = map (</> f) searchPath -searchForInclude :: [FilePath] -> String -> IO (Maybe FilePath)+searchForInclude :: [FilePath] -> P.String -> IO (Maybe FilePath) searchForInclude paths = maybe (return Nothing) aux . includeCandidates paths   where aux [] = return Nothing         aux (f:fs) = do exists <- doesFileExist f                         if exists then return (Just f) else aux fs -searchForNextInclude :: [FilePath] -> String -> IO (Maybe FilePath)+searchForNextInclude :: [FilePath] -> P.String -> IO (Maybe FilePath) searchForNextInclude paths = maybe (return Nothing) (aux False)                            . includeCandidates paths   where aux _ [] = return Nothing         aux n (f:fs) = do exists <- doesFileExist f                           if exists-                          then if n -                               then return (Just f) +                          then if n+                               then return (Just f)                                else aux True fs                           else aux n fs -runHpp :: forall m a. MonadIO m-       => (FilePath -> HppStream m (InputStream (HppStream m)))-       -> HppState-       -> HppStream m a-       -> m (Either (FilePath,Error) (a, HppState))-runHpp readInput !st (HppStream m) = runHppT m >>= go-  where go :: FreeF (HppF (Source (HppStream m) String ()))-                    a-                    (HppT (InputStream (HppStream m)) m a)-           -> m (Either (FilePath, Error) (a, HppState))-        go (PureF x) = return $ Right (x,st)+-- * Running an Hpp Action++runHpp :: forall m a src. (MonadIO m, HasHppState m)+       => (FilePath -> m src)+       -> (src -> m ())+       -> HppT src m a+       -> m (Either (FilePath,Error) a)+runHpp source sink m = runHppT m >>= go+  where go :: FreeF (HppF src) a (HppT src m a)+           -> m (Either (FilePath, Error) a)+        go (PureF x) = return $ Right x         go (FreeF s) = case s of           ReadFile ln file k ->-            liftIO (searchForInclude (includePaths cfg) file)-            >>= readAux ln file (HppStream . k)+            (includePaths <$> use config)+            >>= liftIO . flip searchForInclude file+            >>= readAux ln file k           ReadNext ln file k ->-            liftIO (searchForNextInclude (includePaths cfg) file)-            >>= readAux ln file (HppStream . k)-          GetState k -> runHpp readInput st (HppStream $ k st)-          SetState st' k -> runHpp readInput st' (HppStream k)-          ThrowError e -> return $ Left (curFile, e)-        curFile = curFileName cfg+            (includePaths <$> use config)+            >>= liftIO . flip searchForNextInclude file+            >>= readAux ln file k+          WriteOutput output k -> sink output >> runHppT k >>= go+         readAux ln file _ Nothing =-          return $ Left (curFile, IncludeDoesNotExist ln file)-        readAux _ln _file k (Just file') = runHpp readInput st (readInput file' >>= k)-        cfg = hppConfig st+          Left . (, IncludeDoesNotExist ln file) . curFileName <$> use config+        readAux _ln _file k (Just file') =+          source file' >>= runHppT . k >>= go+{-# SPECIALIZE runHpp ::+    (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) a) #-}  -- * Preprocessor  -- | Parse the definition of an object-like or function macro.-parseDefinition :: [Token] -> Maybe (String, Macro)+parseDefinition :: [TOKEN] -> Maybe (String, Macro) parseDefinition toks =   case dropWhile (not . isImportant) toks of-    (Important name:Important "(":rst) -> +    (Important name:Important "(":rst) ->       let params = takeWhile (/= ")") $ filter (/= ",") (importants rst)           body = trimUnimportant . tail $ dropWhile (/= Important ")") toks           macro = Function (length params) (functionMacro params body)@@ -316,192 +287,158 @@                   str@(_:t)                     | all (not . isImportant) str -> [Important ""]                     | otherwise -> trimUnimportant t-      in Just (name, Object rhs)+      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 = do ln <- takingWhile (not . newLine)+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-              ln <$ incLine--dropLine :: (Monad m, HasError m, HasHppState m) => Parser m Token ()-dropLine = do droppingWhile (not . newLine)-              eat <- awaitJust "dropLine" -- Eat the newline character-              case eat of-                Other "\n" -> return ()-                wat -> error $ "Expected dropped newline: "++show wat-              incLine---- * Nano-lens--type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s--setL :: Lens s a -> a -> s -> s-setL l x = runIdentity . l (const $ Identity x)--getL :: Lens s a -> s -> a-getL l = getConstant . l Constant--over :: Lens s a -> (a -> a) -> s -> s-over l f = runIdentity . l (Identity . f)---- * State Lenses--emptyHppState :: Config -> HppState-emptyHppState cfg = HppState cfg 1 [] emptyEnv--getL' :: (Monad m, HasHppState m) => Lens HppState a -> Parser m i a-getL' l = liftP (getL l <$> getState)--setL' :: (Monad m, HasHppState m) => Lens HppState a -> a -> m ()-setL' l x = getState >>= setState . setL l x+              return ln)+           <* (lineNum %= (+1)) -over' :: (Monad m, HasHppState m)-      => Lens HppState a -> (a -> a) -> Parser m i ()-over' l f = liftP $ do st <- getState-                       setState $ over l f st+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 :: (HppCaps m, Monad m) => Parser m Token [Token]+expandLineP :: (Monad m, HasHppState m, HasEnv m, HasError m)+            => Parser m [TOKEN] [TOKEN] expandLineP =-  do st <- liftP getState+  do st <- getState      let ln = hppLineNum st          cfg = hppConfig st      expandLine cfg ln -lookupEnv :: (Monad m, HasHppState m) => String -> Parser m i (Maybe Macro)-lookupEnv s = liftP $-              do st <- getState-                 case lookupKey s (getL env st) of-                   Nothing -> return Nothing-                   Just (m,env') -> Just m <$ setState (setL env env' st)---- | Register a 'Cleanup' in a threaded 'HppState'.-hppRegisterCleanup :: (HasHppState m, Monad m) => Cleanup -> m ()-hppRegisterCleanup c = modifyState $ over cleanups (c:)--type InputStream m = Source m String ()--class HasHppFileIO m where-  -- | Read a file as an 'Hpp' action-  hppReadFile :: Int -> FilePath -> m (InputStream m)--  -- | Read a file available on the search path after the path-  -- containing the current file.-  hppReadNext :: Int -> FilePath -> m (InputStream m)---- | Lets us fix 'HppT''s input type to a 'Source' whose context is--- the type we are defining.-newtype HppStream m a = HppStream ( HppT (InputStream (HppStream m)) m a )-  deriving (Functor, Applicative, Monad, MonadIO, HasHppState, HasError, HasEnv)--instance Monad m => HasHppFileIO (HppStream m) where-  hppReadFile n file = HppStream . HppT . return . FreeF $ ReadFile n file return-  hppReadNext n file = HppStream . HppT . return . FreeF $ ReadNext n file return+hppReadFile :: Monad m => Int -> FilePath -> HppT src m src+hppReadFile n file = HppT (pure (FreeF (ReadFile n file return))) -incLine :: (Monad m, HasHppState m) => Parser m i ()-incLine = over' lineNum (+1)+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) => Parser m [Token] ()-directive = zoomParseChunks (awaitJust "directive" >>= aux) >>=-            either onParserSource (maybe (return ()) precede)-  where aux :: Token -> Parser m Token (Either (Streamer m [Token] [Token] ())-                                               (Maybe (Source m [Token] ())))+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" -> Right Nothing <$ dropLine -- Ignored-          "define" -> fmap parseDefinition takeLine >>= \case-                        Nothing -> getL' lineNum-                                   >>= throwError . BadMacroDefinition-                        Just def -> Right Nothing <$ over' env (insertPair def)-          "undef" -> do droppingWhile (not . isImportant)-                        Important name <- awaitJust "undef"-                        dropLine-                        Right Nothing <$ over' env (deleteKey name)-          "include" -> fmap (Right . Just) $ includeAux hppReadFile-          "include_next" -> fmap (Right . Just) $ includeAux hppReadNext-          "line" -> do toks <- droppingSpaces >> fmap init expandLineP+          "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 n:optFile ->+                         Important (toChars -> n):optFile ->                            case readMaybe n of-                             Nothing -> getL' lineNum >>=+                             Nothing -> use lineNum >>=                                         throwError . flip BadLineArgument n                              Just ln' -> do                                unless (null optFile) $ do-                                 let fn = unquote . detokenize -                                        . dropWhile (not . isImportant) $ optFile-                                 over' config $ \cfg ->-                                   cfg { curFileNameF = pure fn }-                               Right Nothing <$ setL' lineNum ln'-                         _ -> getL' lineNum >>=-                              throwError . flip BadLineArgument (detokenize toks)-          "ifdef" -> do ln <- getL' lineNum-                        toks <- droppingSpaces >> takeLine-                        case takeWhile isImportant toks of-                          [Important t] ->-                            lookupEnv t >>= \case-                              Nothing -> return . Left $ dropBranchLine (ln+1)-                              Just _ -> return . Left $ takeBranch (ln+1)-                          _ -> throwError . UnknownCommand ln $-                               "ifdef "++detokenize toks-          "ifndef" -> do toks <- droppingSpaces >> takeLine-                         ln <- getL' lineNum-                         case takeWhile isImportant toks of-                           [Important t] -> lookupEnv t >>= \case-                                               Nothing -> return . Left $-                                                          takeBranch (ln+1)-                                               Just _ -> return . Left $-                                                         dropBranchLine (ln+1)-                           _ -> throwError . UnknownCommand ln $-                                "ifndef "++detokenize toks-          "else" -> Right Nothing <$ dropLine-          "if" -> ifAux-          "elif" -> ifAux-          "endif" -> Right Nothing <$ dropLine-          "error" -> do ln <- getL' lineNum-                        curFile <- liftP $ curFileName . hppConfig <$> getState-                        toks <- droppingSpaces >> takeLine-                        throwError $-                          UserError ln (detokenize toks++" ("++curFile++")")-          "warning" -> Right Nothing <$ dropLine -- warnings not yet supported-          t -> do ln <- getL' lineNum-                  toks <- takeLine-                  throwError $ UnknownCommand ln (detokenize (Important t:toks))+                                 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 <- init <$> expandLineP-             ln <- getL' lineNum-             let fileName' = detokenize $ trimUnimportant fileName-             src <- liftP $ readFun ln fileName'-             setL' lineNum (ln+1)-             return $ streamNewFile (unquote fileName') (src ~> prepareInput)-        ifAux :: Parser m Token (Either (Streamer m [Token] [Token] ()) b)-        ifAux = do droppingSpaces-                   toks <- takeLine-                   e <- getL' env-                   ln <- getL' lineNum-                   setL' lineNum (ln - 1) -- takeLine incremented the line count-                   ex <- liftP . parse expandLineP $-                         source (squashDefines e toks)-                   let res = evalExpr <$> parseExpr ex-                   setL' lineNum ln-                   if maybe False (/= 0) res-                     then return . Left $ takeBranch ln-                     else return . Left $ dropBranchLine ln+          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 :: Env -> [TOKEN] -> [TOKEN] squashDefines _ [] = [] squashDefines env' (Important "defined" : ts) = go ts   where go (t@(Other _) : ts') = t : go ts'@@ -509,182 +446,188 @@         go (Important t : ts') =           case lookupKey t env' of             Nothing -> Important "0" : squashDefines env' ts'-            Just (_,env'') -> Important "1" : 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 :: [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 => Parser m Token ()+droppingSpaces ::(Monad m) => ParserT m src TOKEN () droppingSpaces = droppingWhile notImportant --- | Take an entire conditional expression (e.g. @#if--- ... #endif@). All the lines of the taken branch are returned, in--- reverse order.-takeConditional :: Monad m-                => LineNum -                -> (Int -> Streamer m [Token] [Token] r)-                -> Streamer m [Token] [Token] r-takeConditional !n0 k = go (1::Int) n0-  where go 0 !n = k n-        go nesting !n = encase $ Await aux empty-          where aux ln = case getCmd ln of-                           Just cmd-                             | cmd == "endif" ->-                               encase $ Yield ln (go (nesting-1) (n+1))-                             | cmd `elem` ["if","ifdef","ifndef"] ->-                               encase $ Yield ln (go (nesting+1) (n+1))-                           _ -> encase $ Yield ln (go nesting (n+1))+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 :: Monad m => LineNum -> Streamer m [Token] [Token] ()-takeBranch = go-  where go !n = encase $ Await aux empty-          where aux ln = case getCmd ln of-                           Just cmd-                             | cmd `elem` ["if","ifdef","ifndef"] ->-                               encase $ Yield ln (takeConditional (n+1) go)-                             | cmd == "endif" -> yieldLineNum n (done ())-                             | cmd `elem` ["else","elif"] ->-                               dropAllBranches $ \numSkipped ->-                                 yieldLineNum (n+1+numSkipped) empty-                           _ -> encase $ Yield ln (go (n+1))+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 :: Monad m => LineNum -> Streamer m i [Token] r -> Streamer m i [Token] r-yieldLineNum !ln k = encase $ Yield [Important ("#line "++show ln), Other "\n"] k -dropAllBranches :: Monad m-                => (Int -> Streamer m [Token] [Token] r)-                -> Streamer m [Token] [Token] r-dropAllBranches k = dropBranch (aux 0)-  where aux !acc Nothing !numDropped = k (acc+numDropped)-        aux !acc _ !numDropped = dropBranch (aux (acc+numDropped))+yieldLineNum :: LineNum -> [TOKEN]+yieldLineNum !ln = [Important ("#line " <> fromString (show ln)), Other "\n"] -dropBranchLine :: Monad m => LineNum -> Streamer m [Token] [Token] ()-dropBranchLine !ln = dropBranch $ \el numSkipped ->-                       yieldLineNum (ln + numSkipped) (traverse_ yield el)+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 :: Monad m-           => (Maybe [Token] -> Int -> Streamer m [Token] [Token] r)-           -> Streamer m [Token] [Token] r-dropBranch k = go (1::Int) 0-  where go !nesting !n = encase . flip Await empty $ \ln ->-          case getCmd ln of-            Just cmd-              | cmd == "endif" -> if nesting == 1-                                  then k 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 k (Just ln) (n+1)-                                               else go nesting (n+1)-            _ -> go nesting (n+1)+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 :: (HppCaps m, Monad m) => Parser m [Token] (Maybe [Token])+macroExpansion :: (Monad m, HppCaps m)+               => HppT [String] (Parser m [TOKEN]) (Maybe [TOKEN]) macroExpansion = do-  awaitP >>= \case+  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-        [] -> incLine >> return (Just ln)-        Important "#":rst -> do replace (dropWhile notImportant rst)-                                directive-                                macroExpansion-        _ -> do replace ln-                zoomParseChunks (Just <$> expandLineP) <* incLine+        [] -> 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, HasHppFileIO t, HasEnv t)+type HppCaps t = (HasError t, HasHppState t, HasEnv t)  parseStreamHpp :: Monad m-               => Parser m i (Maybe o)-               -> Source m i ()-               -> Source m o ()-parseStreamHpp (Parser m) = go-  where go src = Streamer $-                 do (o,src') <- runStateT m src-                    case o of-                      Nothing -> return $ Done (Just ())-                      Just o' -> return $ Yield o' (go src')+               => 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 :: Monad m => Streamer m String [Token] ()-normalCPP = mapping trigraphReplacement-          ~> mapping dropOneLineBlockComments-          ~> removeMultilineComments 1-          ~> mapping dropLineComments-          ~> lineSplicing-          ~> mapping ((++[Other "\n"]) . tokenize)+normalCPP :: [String] -> [[TOKEN]]+normalCPP = map ((++ [Other "\n"]) . tokenize)+          . lineSplicing+          -- . map dropLineComments+          . removeMultilineComments 1+          . map (dropOneLineBlockComments . trigraphReplacement)+{-# INLINABLE normalCPP #-} --- | For Haskell we often want to ignore C-style comments and long--- line splicing.-haskellCPP :: Monad m => Streamer m String [Token] ()-haskellCPP = mapping ((++[Other "\n"]) . tokenize)+-- | 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 :: Monad m => Config -> Streamer m String [Token] ()-genericConfig cfg = mapping trigraphReplacement-                  ~> (if eraseCComments cfg then commentRemoval else idS)-                  ~> (if spliceLongLines cfg then lineSplicing else idS)-                  ~> mapping ((++[Other "\n"]) . tokenize)-  where idS :: Monad m => Streamer m i i r-        idS = encase $ Await (encase . flip Yield idS) empty+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)  -- * Front End -prepareInput :: (Monad m, HppCaps m)-             => Streamer m String [Token] ()-prepareInput = Streamer $+prepareInput :: (Monad m, HppCaps m) => m ([String] -> [[TOKEN]])+prepareInput =   do cfg <- getL config <$> getState      case () of-       _ | eraseCComments cfg && spliceLongLines cfg -           && not (inhibitLinemarkers cfg) -> runStream normalCPP-       _ | not (eraseCComments cfg || spliceLongLines cfg) ->-           runStream haskellCPP-       _ | otherwise -> runStream $ genericConfig cfg-{-# SPECIALIZE prepareInput :: Streamer (HppStream IO) String [Token] () #-}+       _ | 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)-           => Source m String ()-           -> Source m String ()-preprocess src = Streamer $+           => ([String] -> src) -> [String] -> HppT [String] (Parser m [TOKEN]) ()+preprocess _convertOutput src =   do cfg <- getL config <$> getState-     runStream $ if inhibitLinemarkers cfg-                 then go ~> filtering (not . isPrefixOf "#line")-                 else go-  where {-# INLINE go #-}-        go = parseStreamHpp macroExpansion (src ~> prepareInput)-           ~> mapping detokenize+     prep <- prepareInput+     let prepOutput = if inhibitLinemarkers cfg then aux else pure+     lift (precede (prep src))+     parseStreamHpp (fmap (prepOutput . detokenize) <$> macroExpansion)+  where aux xs | sIsPrefixOf "#line" xs = []+               | otherwise = [xs] --- | Preprocess the given file producing line by line output.-streamHpp :: (Monad m, HasHppFileIO m)-          => FilePath -> Source m String ()-streamHpp f = Streamer $-              hppReadFile 0 ('"':f++"\"") >>= runStream+-- | General hpp runner against input source file lines; can return an+-- 'Error' value if something goes wrong.+hppIO' :: Config -> Env -> ([String] -> IO ()) -> [String] -> IO (Maybe Error)+hppIO' cfg env' snk src =+  runExceptT'+    (evalStateT+       (evalParse+          ((>>= either (throwError . snd) return)+           (runHpp (liftIO . readLines)+                   (liftIO . snk)+                   (preprocess id src)))+          [])+       initialState) >>= return . either Just (const Nothing)+  where initialState = setL env env' $ emptyHppState cfg+        runExceptT' = runExceptT :: ExceptT Error m a -> m (Either Error a) --- | Monad morphism between Hpp and IO.-hppIO :: (MonadIO m) => Config -> Env-      -> Streamer (HppStream m) Void b r-      -> Streamer (HppStream m) b Void ()-      -> m (Maybe ())-hppIO cfg env' s snk = runHpp (sourceFile hppRegisterCleanup)-                              initialState-                              (run (s ~> snk))-                       >>= either (error .show) cleanup-  where cleanup (e, s') = e <$ (liftIO $ mapM_ runCleanup (getL cleanups s'))-        initialState = setL env env' $ emptyHppState cfg+-- | General hpp runner against input source file lines. Any errors+-- encountered are thrown with 'error'.+hppIO :: Config -> Env -> ([String] -> IO ()) -> [String] -> IO ()+hppIO cfg env' snk = fmap (maybe () (error . show)) . hppIO' cfg env' snk++-- | hpp runner that returns output lines.+hppFileContents :: Config -> Env ->  FilePath -> [String] -> IO (Either Error [String])+hppFileContents cfg env' fileName src = do+  r <- newIORef id+  let snk xs = modifyIORef r (. (xs++))+  hppIO' (cfg {curFileNameF = pure fileName}) env' snk src >>= \case+    Nothing -> Right . ($ []) <$> readIORef r+    Just e -> return (Left e)
src/Hpp/CmdLine.hs view
@@ -1,14 +1,17 @@-{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE OverloadedStrings, RankNTypes #-} -- | 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 Data.String (fromString) import Hpp import Hpp.Config import Hpp.Env (deleteKey, emptyEnv, insertPair)+import Hpp.StringSig (readLines, putStringy) import Hpp.Tokens import Hpp.Types (Env, Error(..)) import System.Directory (doesFileExist, makeAbsolute)+import System.IO (openFile, IOMode(..), hClose, stdout)  -- | Break a string on an equals sign. For example, the string @x=y@ -- is broken into @[x,"=",y]@.@@ -37,15 +40,15 @@             Just cfg' -> return (Right (env, acc [], cfg', out))             Nothing -> return (Left NoInputFile)         go env acc cfg out ("-D":name:"=":body:rst) =-          case parseDefinition (Important name : Other " " : tokenize body) of+          case parseDefinition (Important (fromString name) : Other " " : tokenize (fromString body)) of             Nothing -> return . Left $ BadMacroDefinition 0             Just def -> go (insertPair def env) acc cfg out rst         go env acc cfg out ("-D":name:rst) =-          case parseDefinition ([Important name, Other " ", Important "1"]) of+          case parseDefinition ([Important (fromString name), Other " ", Important "1"]) of             Nothing -> return . Left $ BadMacroDefinition 0             Just def -> go (insertPair def env) acc cfg out rst         go env acc cfg out ("-U":name:rst) =-          go (deleteKey name env) acc cfg out rst+          go (deleteKey (fromString name) env) acc cfg out rst         go env acc cfg out ("-I":dir:rst) =           let cfg' = cfg { includePathsF = fmap (++[dir]) (includePathsF cfg) }           in go env acc cfg' out rst@@ -57,21 +60,27 @@           in go env acc cfg' out rst         go env acc cfg out ("--cpp":rst) =           let cfg' = cfg { spliceLongLinesF = Just True-                         , eraseCCommentsF = Just True }+                         , eraseCCommentsF = Just True+                         , inhibitLinemarkersF = Just False+                         , replaceTrigraphsF = Just True }               defs = concatMap ("-D":)                        [ ["__STDC__"]                          -- __STDC_VERSION__ is only defined in C94 and later-                       , ["__STDC_VERSION__","=","199409L"]-                       , ["_POSIX_C_SOURCE","=","200112L"] ]+                       , ["__STDC_VERSION__","=","199409L"] ]+                       -- , ["_POSIX_C_SOURCE","=","200112L"] ]           in go env acc cfg' out (defs ++ rst)         go env acc cfg out ("--fline-splice":rst) =           go env acc (cfg { spliceLongLinesF = Just True }) out rst         go env acc cfg out ("--ferase-comments":rst) =           go env acc (cfg { eraseCCommentsF = Just True }) out rst+        go env acc cfg out ("--freplace-trigraphs":rst) =+          go env acc (cfg { replaceTrigraphsF = Just True }) out rst         go env acc cfg _ ("-o":file:rst) =           go env acc cfg (Just file) rst         go env acc cfg out ("-x":_lang:rst) =           go env acc cfg out rst -- We ignore source language specification+        go env acc cfg out ("-traditional":rst) =+          go env acc cfg out rst -- Ignore the "-traditional" flag         go env acc cfg Nothing (file:rst) =           case curFileNameF cfg of             Nothing -> go env acc (cfg { curFileNameF = Just file }) Nothing rst@@ -91,11 +100,16 @@        "Couldn't open input file: "++curFileName cfg      let fileName = curFileName cfg          cfg' = cfg { curFileNameF = pure fileName }-     snk <- case outPath of-              Nothing -> pure sinkToStdOut-              Just f -> fmap (\f' -> sinkToFile hppRegisterCleanup f')-                             (makeAbsolute f)-     _ <- hppIO cfg' env-            (preprocess (before (source lns) (streamHpp fileName)))-            snk-     return ()+     (snk, closeSnk) <- case outPath of+                          Nothing -> return (mapM_ (putStringy stdout) , return ())+                          Just f ->+                            do h <- makeAbsolute f >>=+                                    flip openFile WriteMode+                               return (\os -> mapM_ (putStringy h) os+                                      ,hClose h)+     _ <- (readLines fileName)+           >>= hppIO cfg' env snk . (map fromString lns ++)+     -- lns' <- (lines <$> readFile fileName)+     --          >>= hppFileContents cfg env fileName . (lns ++)+     -- snk lns'+     closeSnk
src/Hpp/Config.hs view
@@ -22,6 +22,7 @@                         , spliceLongLinesF    :: f Bool                         , eraseCCommentsF     :: f Bool                         , inhibitLinemarkersF :: f Bool+                        , replaceTrigraphsF   :: f Bool                         , prepDateF           :: f DateString                         , prepTimeF           :: f TimeString } @@ -35,10 +36,11 @@                       (Just spliceLines)                       (Just comments)                       (Just inhibitLines)+                      (Just trigraphs)                       (Just pdate)                       (Just ptime)) =   Just (Config (pure fileName) (pure paths) (pure spliceLines) (pure comments)-               (pure inhibitLines) (pure pdate) (pure ptime))+               (pure inhibitLines) (pure trigraphs) (pure pdate) (pure ptime)) realizeConfig _ = Nothing  -- | Extract the current file name from a configuration.@@ -61,6 +63,10 @@ inhibitLinemarkers :: Config -> Bool inhibitLinemarkers = runIdentity . inhibitLinemarkersF +-- | Determine if trigraph sequences should be replaced.+replaceTrigraphs :: Config -> Bool+replaceTrigraphs = runIdentity . replaceTrigraphsF+ -- | The date the pre-processor was run on. prepDate :: Config -> DateString prepDate = runIdentity . prepDateF@@ -72,7 +78,7 @@ -- | A default configuration with no current file name set. defaultConfigF :: ConfigF Maybe defaultConfigF = Config Nothing (Just [])-                        (Just False) (Just False) (Just False)+                        (Just True) (Just True) (Just True) (Just False)                         (Just (DateString "??? ?? ????"))                         (Just (TimeString "??:??:??")) 
src/Hpp/Env.hs view
@@ -1,6 +1,24 @@+{-# LANGUAGE TupleSections #-} -- | 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 @@ -20,7 +38,7 @@ lookupKey k m = fmap (\x -> (x, m)) (M.lookup k m) {-# INLINE lookupKey #-} -}-+{- -- | An empty binding environment. emptyEnv :: [a] emptyEnv = []@@ -48,3 +66,4 @@           | k == x = Just (v, h : acc [] ++ xs)           | otherwise = go (acc . (h:)) xs {-# INLINE lookupKey #-}+-}
src/Hpp/Expansion.hs view
@@ -1,25 +1,28 @@-{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}+{-# LANGUAGE LambdaCase, OverloadedStrings, RankNTypes, ScopedTypeVariables #-} -- | 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-import Control.Monad (forM)+import Control.Monad.Trans.Class (lift) import Data.Bool (bool) import Data.Foldable (foldl', traverse_) import Data.List (delete) import Data.Maybe (listToMaybe, mapMaybe)+import Data.Monoid ((<>))+import Data.String (fromString) import Hpp.Config (Config, curFileName,                    getDateString, getTimeString, prepDate, prepTime)-import Hpp.Env (lookupKey, deleteKey)-import Hpp.Streamer (source, mapTil)-import Hpp.Parser (Parser, awaitP, liftP, precede, zoomParse,-                   droppingWhile, awaitJust, replace, parse)-import Hpp.String (stringify)+import Hpp.Env (deleteKey)+import Hpp.Parser (Parser, ParserT, precede, replace, await, onIsomorphism,+                   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 (HasError(..), HasEnv(..), Scan(..), Error(..), Macro(..),+                  TOKEN, String, lookupMacro)+import Prelude hiding (String) --- | Extract the 'Token' payload from a 'Scan'.-unscan :: Scan -> Maybe Token+-- | Extract the 'TOKEN' payload from a 'Scan'.+unscan :: Scan -> Maybe TOKEN unscan (Scan t) = Just t unscan (Rescan t) = Just t unscan _ = Nothing@@ -33,35 +36,34 @@ -- | Expand all macros to the end of the current line or until all -- in-progress macro invocations are complete, whichever comes last. expandLine :: (HasError m, Monad m, HasEnv m)-           => Config -> Int -> Parser m Token [Token]-expandLine cfg lineNum = fmap (mapMaybe unscan) $-                         zoomParse (mapTil Scan)-                                   (expandLine' True cfg lineNum)+           => Config -> Int -> Parser m [TOKEN] [TOKEN]+expandLine cfg lineNum =+  mapMaybe unscan <$>+  onElements (onIsomorphism Scan unscan (expandLine' True cfg lineNum)) -expandLine' :: forall m. (HasError m, Monad m, HasEnv m)-            => Bool -> Config -> Int -> Parser m Scan [Scan]+expandLine' :: forall m src. (HasError m, Monad m, HasEnv m)+            => Bool -> Config -> Int -> ParserT m src Scan [Scan] expandLine' oneLine cfg lineNum = go id []-  where go :: ([Scan] -> [Scan]) -> [String] -> Parser m Scan [Scan]-        go acc mask = awaitP >>= maybe (return $ acc []) aux-          where aux :: Scan -> Parser m Scan [Scan]+  where go :: ([Scan] -> [Scan]) -> [String] -> ParserT m src Scan [Scan]+        go acc mask = await >>= maybe (return $ acc []) aux+          where aux :: Scan -> ParserT m src Scan [Scan]                 aux tok = case tok of                   Unmask name -> go (acc . (tok:)) (delete name mask)                   Mask name -> go (acc . (tok:)) (name : mask)                   Scan (Important t) -> do ts <- expandMacro cfg lineNum t tok                                            if ts == [tok]                                            then go (acc . (tok:)) mask-                                           else do precede $ source ts-                                                   go acc mask+                                           else precede ts >> go acc mask                   Rescan (Important t) ->-                    do oldEnv <- liftP $+                    do oldEnv <- lift $                                  do env <- getEnv                                     setEnv $ foldl' (flip deleteKey) env mask                                     return env                        ts <- expandMacro cfg lineNum t tok-                       liftP $ setEnv oldEnv+                       lift $ setEnv oldEnv                        if ts == [tok]                        then go (acc . (tok:)) mask-                       else precede (source ts) >> go acc mask+                       else precede ts >> go acc mask                   Scan (Other "\n")                     | oneLine -> return (acc [tok])                     | otherwise -> go (acc . (tok:)) mask@@ -73,10 +75,11 @@ -- | Parse a function application. Arguments are separated by commas, -- and the application runs until the balanced closing parenthesis. If -- this is not an application, 'Nothing' is returned.-appParse :: (Monad m, HasError m) => Parser m Scan (Maybe [[Scan]])+appParse :: (Monad m, HasError m)+         => ParserT m src Scan (Maybe [[Scan]]) appParse = droppingWhile isSpaceScan >> checkApp   where imp = maybe True notImportant . unscan-        checkApp = do tok <- droppingWhile imp >> awaitP+        checkApp = do tok <- droppingWhile imp >> await                       case tok >>= unscan of                         Just (Important "(") -> goApp                         _ -> traverse_ replace tok >> return Nothing@@ -90,7 +93,7 @@ -- | Emit the tokens of a single argument. Returns 'True' if this is -- the final argument in an application (indicated by an unbalanced -- closing parenthesis.-argParse :: (Monad m, HasError m) => Parser m Scan [Scan]+argParse :: (Monad m, HasError m) => ParserT m src Scan [Scan] argParse = go id   where go acc = do tok <- awaitJust "argParse"                     case unscan tok of@@ -104,7 +107,7 @@  -- | Kick this off after an opening parenthesis and it will yield -- every token up to the closing parenthesis.-parenthetical :: (Monad m, HasError m) => Parser m Scan [Scan]+parenthetical :: (Monad m, HasError m) => ParserT m src Scan [Scan] parenthetical = go id (1::Int)   where go acc 0 = return (acc [])         go acc n = do tok <- awaitJust "parenthetical"@@ -115,16 +118,17 @@  argError :: Int -> String -> Int -> [String] -> Error argError lineNum name arity args =-  TooFewArgumentsToMacro lineNum $ name++"<"++show arity++">"++show args+  TooFewArgumentsToMacro lineNum $+  toChars name <> "<" <> show arity <> ">" <> show args  -- | Returns 'Nothing' if this isn't an application; @Left args@ if we -- parsed arguments @args@, but there is an arity mismatch; or @Right -- tokens@ if the function application expanded successfully. expandFunction :: (Monad m, HasError m)                => String -> Int -> ([([Scan],String)] -> [Scan])-               -> (forall r'. [String] -> Parser m Scan r')-               -> Parser m Scan [Scan]-               -> Parser m Scan (Maybe [Scan])+               -> (forall r'. [String] -> ParserT m src Scan r')+               -> ([Scan] -> ParserT m src Scan [Scan])+               -> ParserT m src Scan (Maybe [Scan]) expandFunction name arity f mkErr expand =   do margs <- appParse      case margs of@@ -133,24 +137,24 @@          | length args /= arity -> mkErr $                                    map (detokenize . mapMaybe unscan) args          | otherwise ->-           do args' <- forM args $ \arg -> liftP (parse expand (source arg))+           do args' <- mapM expand args               let raw = map (detokenize . mapMaybe unscan) args               return . Just $ Mask name : f (zip args' raw) ++ [Unmask name] -lookupEnv :: (Monad m, HasEnv m)-          => String -> Parser m i (Maybe Macro)-lookupEnv s = liftP $ getEnv >>= traverse aux . lookupKey s-  where aux (m, env') = setEnv env' >> return m+-- lookupEnv :: (Monad m, HasEnv m)+--           => String -> ParserT m src Scan (Maybe Macro)+-- lookupEnv s = lift $ getEnv >>= traverse aux . lookupKey s+--   where aux (m, env') = setEnv env' >> return m  expandMacro :: (Monad m, HasError m, HasEnv m)-            => Config -> Int -> String -> Scan -> Parser m Scan [Scan]+            => Config -> Int -> String -> Scan -> ParserT m src Scan [Scan] expandMacro cfg lineNum name tok =   case name of-    "__LINE__" -> simple $ show lineNum-    "__FILE__" -> simple . stringify $ curFileName cfg-    "__DATE__" -> simple . stringify . getDateString $ prepDate cfg-    "__TIME__" -> simple . stringify . getTimeString $ prepTime cfg-    _ -> do mm <- lookupEnv name+    "__LINE__" -> simple . fromString $ show lineNum+    "__FILE__" -> simple . stringify . fromString $ curFileName cfg+    "__DATE__" -> simple . stringify . fromString . getDateString $ prepDate cfg+    "__TIME__" -> simple . stringify . fromString . getTimeString $ prepTime cfg+    _ -> do mm <- lift (lookupMacro name)             case mm of               Nothing -> return [tok]               Just m ->@@ -159,22 +163,26 @@                     return $ Mask name : map Rescan (spaced t') ++ [Unmask name]                   Function arity f ->                     let ex = expandLine' False cfg lineNum-                        err = liftP . throwError+                        err = lift . throwError                             . argError lineNum name arity-                    in do mts <- expandFunction name arity f err ex+                    in do mts <- expandFunction name arity f err+                                                (lift . evalParse ex)                           case mts of                             Nothing -> return [tok]                             Just ts -> return ts   where simple s = return [Rescan (Important s)]         -- Avoid accidentally merging tokens like @'-'@-        spaced xs = pre ++ pos-          where importantChar (Important [c]) = elem c oops+        spaced xs = pre <> pos+          where importantChar (Important t) =+                  case uncons t of+                    Nothing -> False+                    Just (c,t') -> elem c oops && isEmpty t'                 importantChar _ = False                 pre = bool xs (Other " ":xs)$                       (maybe False importantChar $ listToMaybe xs)                 pos = bool [] [Other " "] $                       (maybe False importantChar $ listToMaybe (reverse xs))-                oops = "-+*.><"+                oops = "-+*.><" :: [Char]  -- | Trim whitespace from both ends of a sequence of 'Scan' tokens. trimScan :: [Scan] -> [Scan]
src/Hpp/Expr.hs view
@@ -126,7 +126,7 @@ -- * Lexing  -- | String literals are split by tokenization. Fix them!-fixStringLits :: [Token] -> Maybe [String]+fixStringLits :: [Token String] -> Maybe [String] fixStringLits [] = Just [] fixStringLits (Important h@('"':_):xs) =   let (hs,ys) = break ((== '"') . last . detok) xs@@ -138,9 +138,9 @@ fixStringLits (Other _ : xs) = fixStringLits xs  onFirstImportant :: (String -> String)-                 -> ([Token] -> [Token])-                 -> [Token]-                 -> [Token]+                 -> ([Token String] -> [Token String])+                 -> [Token String]+                 -> [Token String] onFirstImportant f k = go   where go [] = k []         go (Important s : ts') = Important (f s) : k ts'@@ -148,7 +148,7 @@  -- | Re-combine positive and negative unary operators with the tokens -- to which they are attached.-fixUnaryOps :: [Token] -> [Token]+fixUnaryOps :: [Token String] -> [Token String] fixUnaryOps [] = [] fixUnaryOps (t0:ts0) =   case t0 of@@ -164,7 +164,7 @@         go _ (o@(Other _):ts) = o : go False ts  -- | Re-combine multiple-character operators.-fixBinaryOps :: [Token] -> [Token]+fixBinaryOps :: [Token String] -> [Token String] fixBinaryOps [] = [] fixBinaryOps (h@(Important t1) : ts@(Important t2 : ts')) =   case parseBinOp (t1++t2) of@@ -198,7 +198,7 @@ renderUnaryOp Not = "!" renderUnaryOp Defined = "defined " -lexExpr :: [Token] -> Maybe [String]+lexExpr :: [Token String] -> Maybe [String] lexExpr = fixStringLits . fixUnaryOps . fixBinaryOps  -- * Parsing Tokens@@ -367,7 +367,7 @@ rpnToExpr _ _ = Nothing  -- | Try to read an 'Expr' from a sequence of 'Token's.-parseExpr :: [Token] -> Maybe Expr+parseExpr :: [Token String] -> Maybe Expr parseExpr = lexExpr >=> shuntRPN [] [] >=> rpnToExpr []  -- | Pretty-print an 'Expr' to something semantically equivalent to the original@@ -378,7 +378,7 @@ renderExpr (ELit (LitStr e)) = '"':e++"\"" renderExpr (ELit (LitChar c)) = [c] renderExpr (ELit (LitID e)) = e-renderExpr (EBinOp o e1 e2) = concat [ "(", renderExpr e1," ",renderBinOp o +renderExpr (EBinOp o e1 e2) = concat [ "(", renderExpr e1," ",renderBinOp o                                      , " ", renderExpr e2, ")" ] renderExpr (EUnaryOp o e1) = renderUnaryOp o ++ renderExpr e1 @@ -423,6 +423,6 @@         go (EUnaryOp BitNot e2) = cppComplement $ go e2         go (EUnaryOp Not e2) = int . fromEnum $ go e2 == 0         go (EUnaryOp Defined e2) =-          case e2 of +          case e2 of             ELit (LitInt 1) -> 1             _ -> 0
src/Hpp/Parser.hs view
@@ -1,110 +1,115 @@-{-# LANGUAGE BangPatterns, LambdaCase, RankNTypes #-}+{-# LANGUAGE LambdaCase, Rank2Types, TupleSections, ScopedTypeVariables #-} -- | Parsers over streaming input.-module Hpp.Parser (Parser(..), parse,-                   awaitP, awaitJust, replace, droppingWhile, liftP,-                   onParserSource, precede, takingWhile,-                   zoomParse, zoomParseChunks) where-import Control.Applicative (Alternative(..))-import Control.Monad (MonadPlus(..))+module Hpp.Parser (Parser, ParserT, parse, evalParse, await, awaitJust, replace,+                   droppingWhile, precede, takingWhile, onChunks, onElements,+                   onInputSegment, insertInputSegment, onIsomorphism,+                   runParser) where+import Control.Arrow (second, (***)) import Control.Monad.Trans.State.Strict-import Hpp.Streamer (Source, Streamer(..), yield, before, processPrefix,-                     nextOutput, flattenTil, StreamStep(..))-import Hpp.Types (HasError(..), HasHppState(..), Error(UserError))+import Hpp.Types (HasError(..), Error(UserError)) import Control.Monad.Trans.Class (lift)-import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.List.NonEmpty (NonEmpty(..))+import Data.Maybe (mapMaybe)+import Data.Monoid ((<>))  -- * Parsers -type ParserR m r i = StateT (Source m i r) m+type RopeM m a = [Either (m ()) a] --- | A 'Parser' is a 'Streamer' whose monadic context is a bit of--- state carrying a source input stream.-newtype Parser m i o = Parser { runParser :: forall r. ParserR m r i o }+-- | A 'Parser' is a bit of state carrying a source of input.+type ParserT m src i = StateT (Headspring m src i, src) m --- | Run a 'Parser' with a given input stream.-parse :: Monad m => Parser m i o -> Source m i r -> m o-parse (Parser m) s = evalStateT m s-{-# INLINE parse #-}+type Parser m i = ParserT m (RopeM m [i]) i -instance Functor m => Functor (Parser m i) where-  fmap f (Parser p) = Parser (fmap f p)-  {-# INLINE fmap #-}+data Headspring m src i =+  Headspring { hsAwait :: src -> m (Maybe (i, src))+             , hsPrecede :: [i] -> src -> src } -instance Monad m => Applicative (Parser m i) where-  pure x = Parser (pure x)-  {-# INLINE pure #-}-  Parser f <*> Parser x = Parser (f <*> x)-  {-# INLINE (<*>) #-}+-- | Pop the head non-effect element from a list.+unconsM :: Applicative m => RopeM m a -> m (Maybe (a, RopeM m a))+unconsM [] = pure Nothing+unconsM (Left m : ms) = m *> unconsM ms+unconsM (Right x : ms) = pure (Just (x, ms)) -instance Monad m => Monad (Parser m i) where-  return = pure-  {-# INLINE return #-}-  Parser ma >>= fb = Parser $ ma >>= runParser . fb-  {-# INLINE (>>=) #-}+-- | 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 r = unconsM r >>= \case+  Nothing -> pure Nothing+  Just ([], rst) -> unconsMNonEmpty rst+  Just (x:xs, rst) -> return (Just (x :| xs, rst)) -instance MonadPlus m => Alternative (Parser m i) where-  empty = Parser empty-  {-# INLINE empty #-}-  Parser a <|> Parser b = Parser (a <|> b)-  {-# INLINE (<|>) #-}+unconsSpring :: Monad m => Headspring m (RopeM m [i]) i+unconsSpring = Headspring aw ropePrecede+  where aw r = unconsMNonEmpty r >>= \case+          Nothing -> return Nothing+          Just (x :| xs, r') -> return (Just (x, Right xs : r')) -instance (Monad m, HasError m) => HasError (Parser m i) where-  throwError = liftP . throwError-  {-# INLINE throwError #-}+flattenSpring :: Monad m => Headspring m  (RopeM m [[i]]) i+flattenSpring = Headspring aw pr+  where aw r = unconsMNonEmpty r >>= \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 -instance (Monad m, HasHppState m) => HasHppState (Parser m i) where-  getState = liftP getState-  setState = liftP . setState+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 -instance MonadIO m => MonadIO (Parser m i) where-  liftIO = liftP . liftIO+await :: Monad m => ParserT m src i (Maybe i)+await = do (hs, st) <- get+           lift (hsAwait hs st) >>= \case+             Nothing -> return Nothing+             Just (x,st') -> Just x <$ put (hs,st')+{-# INLINE await #-} --- * Operations on Parsers          +-- | Push a value back into a parser's source.+replace :: (Monad m) => i -> ParserT m src i ()+replace = precede . pure --- | Lift a monadic action into a Parser.-liftP :: Monad m => m o -> Parser m i o-liftP m = Parser (lift m)-{-# INLINE liftP #-}+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 --- | @onParserSource proc@ feeds the 'Parser' source through @proc@--- using 'processPrefix'. This means that when @proc@ finishes, the--- remaining source continues unmodified.-onParserSource :: Monad m => Streamer m i i () -> Parser m i ()-onParserSource s = Parser (modify' (flip processPrefix s))--- onParserSource s = Parser (get >>= lift . flip processPrefix s >>= put)-{-# INLINE onParserSource #-}+-- | 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)+{-# INLINE precede #-} --- | Waits for a value from upstream. Returns 'Nothing' if upstream is--- empty.-awaitP :: Monad m => Parser m i (Maybe i)-awaitP = Parser $ get >>= lift . nextOutput >>= \case-  Left _ -> put empty >> return Nothing-  Right !(!i, !src') -> put src' >> return (Just i)-{-# INLINABLE awaitP #-}+-- | 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)++evalParse :: Monad m => Parser m i o -> [i] -> m o+evalParse m xs = evalStateT m (unconsSpring, [Right xs])++-- * Operations on Parsers+ -- | 'awaitP' 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 -> Parser m i i-awaitJust s = awaitP >>= maybe (liftP $ throwError err) return+awaitJust :: (Monad m, HasError m) => String -> ParserT m src i i+awaitJust s = await >>= maybe (lift $ throwError err) return   where err = UserError 0 ("awaitJust: " ++ s)---- | Push a value back into a parser's source.-replace :: Monad m => i -> Parser m i ()-replace x = Parser $ modify' (before (yield x))-{-# INLINE replace #-}---- | Push a stream of values back into a parser's source.-precede :: Monad m => Source m i r -> Parser m i ()-precede m = Parser (modify' (before m))-{-# INLINE precede #-}+{-# INLINE awaitJust #-}  -- | Discard all values until one fails to satisfy a predicate. At -- that point, the failing value is 'replace'd, and the -- 'droppingWhile' stream stops.-droppingWhile :: Monad m => (i -> Bool) -> Parser m i ()+droppingWhile :: (Monad m) => (i -> Bool) -> ParserT m src i () droppingWhile p = go-  where go = awaitP >>= \case+  where go = await >>= \case                Nothing -> return ()                Just x -> if p x then go else replace x {-# INLINE droppingWhile #-}@@ -112,42 +117,52 @@ -- | Echo all values until one fails to satisfy a predicate. At that -- point, the failing value is 'replace'd, and the 'takingWhile' -- stream stops.-takingWhile :: Monad m => (i -> Bool) -> Parser m i [i]+takingWhile :: (Monad m) => (i -> Bool) -> ParserT m src i [i] takingWhile p = go id-  where go acc = awaitP >>= \case+  where go acc = await >>= \case                    Nothing -> return (acc [])                    Just x                      | p x -> go (acc . (x:))                      | otherwise -> replace x >> return (acc []) {-# INLINE takingWhile #-} --- * Zooming+insertInputSegment :: Monad m => src -> m () -> ParserT m (RopeM m src) i ()+insertInputSegment xs k = modify' (second ([Right xs, Left k]++)) --- | This is rather like a Lens zoom, but quite fragile. The idea is--- that we run a 'Parser' on a transformation of the original--- source. The transformation of the source is responsible for--- yielding transformed values, and ending /on demand/ with the rest--- of the original source. We additionally scoop up any leftover--- transformed values and prepend them onto the remaining source after--- inverting the original transformation.-zoomParse :: Monad m-          => (forall r. Source m a r -> Source m b (Source m a r))-          -> Parser m b o-          -> Parser m a o-zoomParse f (Parser p) = Parser $ do-  src <- get-  (r, src') <- lift $ runStateT p (f src)-  lift (runStream src') >>= \case-    Await k _ -> lift (runStream (k undefined)) >>= \case-                   Done (Just src'') -> r <$ put src''-                   _ -> error "zoomParse blew it"-    Done (Just src'') -> r <$ put src''-    Done Nothing -> r <$ put empty-    Yield _ _ -> error "zoomParse blew it by yielding"-{-# INLINABLE zoomParse #-}+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+{-# INLINABLE onInputSegment #-} --- | Turn a 'Parser' on individual values into a 'Parser' on chunks.-zoomParseChunks :: Monad m => Parser m i r -> Parser m [i] r-zoomParseChunks = zoomParse flattenTil-{-# INLINE zoomParseChunks #-}+-- * 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')+{-# INLINE onElements #-}++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+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')+{-# INLINE onIsomorphism #-}
− src/Hpp/StreamIO.hs
@@ -1,70 +0,0 @@-{-# LANGUAGE LambdaCase #-}--- | IO on streams.-module Hpp.StreamIO where-import Control.Monad.IO.Class (MonadIO, liftIO)-import Hpp.Streamer-import Hpp.Types-import System.Directory (getTemporaryDirectory, renameFile, removeFile)-import System.IO (IOMode(ReadMode), hClose, hPutStr, openTempFile, openFile,-                  hGetLine, hIsEOF, hIsClosed, hSetBuffering, BufferMode(..))---- | @sourceFile registerCleanup filePath@ produces a 'Source' of--- lines from file @filePath@ after registering an action that closes--- the file using the provided @registerCleanup@ function.-sourceFile :: (MonadIO m, MonadIO m')-           => (Cleanup -> m' ()) -> FilePath -> m' (Source m String ())-sourceFile register fp =-  do h <- liftIO $ do h <- openFile fp ReadMode-                      hSetBuffering h (BlockBuffering Nothing)-                      return h-     (cleanup,neutralize) <- liftIO $ mkCleanup (hClose h)-     let {-# INLINABLE go #-}-         go :: MonadIO m => Source m String ()-         go = Streamer $-              do closed <- liftIO $ hIsClosed h-                 if closed-                 then return $ Done (Just ())-                 else do eof <- liftIO $ hIsEOF h-                         if eof-                         then Done (Just ()) <$ (liftIO (neutralize >> hClose h))-                         else liftIO (fmap (flip Yield go) (hGetLine h))-                         -- else liftIO (hGetLine h) >>= return . flip Yield go -- . (++"\n")-     register cleanup >> return go-{-# INLINE sourceFile #-}---- | Incrementally writes 'String's to a temporary file. When all--- input is exhausted, the temporary file is renamed to the supplied--- 'FilePath'.-sinkToFile :: MonadIO m-           => (Cleanup -> m ()) -> FilePath -> Streamer m String o ()-sinkToFile register fp = Streamer$-  do (tmp,h) <- liftIO $ getTemporaryDirectory >>= flip openTempFile "hpp.tmp"-     (cleanup, neutralize) <- liftIO $ mkCleanup (hClose h >> removeFile tmp)-     let dunzo = Streamer . liftIO $ do neutralize-                                        hClose h-                                        renameFile tmp fp-                                        return (Done (Just ()))-         go = encase $ Await (\s -> Streamer $-                                    liftIO (hPutStr h s) >> runStream go)-                             dunzo-                             -     register cleanup-     runStream go-{-# INLINE sinkToFile #-}---- | Sink a stream with a function evaluated only for its--- side-effects.-sinkTell :: Monad m => (a -> m ()) -> Streamer m a o ()-sinkTell tell = go-  where go = awaits (\i -> Streamer (tell i >> runStream go))-{-# INLINE sinkTell #-}---- | Sink a stream to 'System.IO.stdout'-sinkToStdOut :: MonadIO m => Streamer m String o ()-sinkToStdOut = sinkTell (liftIO . putStr)-{-# INLINE sinkToStdOut #-}---- | @sink_ = forever await@ Simply discards all inputs. This may be--- used to exhaust a stream solely for its effects.-sink_ :: Monad m => Streamer m i o ()-sink_ = awaits (const sink_)
− src/Hpp/Streamer.hs
@@ -1,356 +0,0 @@-{-# LANGUAGE LambdaCase #-}--- | Streaming input and output.-module Hpp.Streamer (Streamer(..), StreamStep(..), Source, encase,-                     done, yield, yields, awaits, source, liftS,-                     nextOutput, run,-                     before, (~>), processPrefix, mapping, filtering, mapStream,-                     mappingMaybe, onDone, mapTil, flattenTil,-                     Chunky(..), metamorph) where-import Control.Applicative (Alternative(..))-import Control.Monad ((<=<))-import Data.Foldable (toList)-import Data.Void-import Hpp.Types (HasError(..), HasHppState(..), HasEnv(..))---- * Streams of Steps---- | Basic pipe.-data StreamStep r i o f = Await (i -> f) f-                        | Yield !o f-                        | Done (Maybe r)--instance Functor (StreamStep r i o) where-  fmap f (Await g d) = Await (f . g) (f d)-  fmap f (Yield o n) = Yield o (f n)-  fmap _ (Done r) = Done r-  {-# INLINE fmap #-}---- | A stream of steps in a computational context.-newtype Streamer m i o r =-  Streamer { runStream :: m (StreamStep r i o (Streamer m i o r)) }---- | A stream of steps that never awaits anything from upstream.-type Source m o r = Streamer m Void o r---- | Package a step into a 'Streamer'-encase :: Monad m => StreamStep r i o (Streamer m i o r) -> Streamer m i o r-encase = Streamer . return-{-# INLINE encase #-}--instance Monad m => Functor (Streamer m i o) where-  fmap f (Streamer ma) = Streamer . flip fmap ma $ \case-    Await g d -> Await (fmap f . g) (fmap f d)-    Yield o n -> Yield o (fmap f n)-    Done r -> Done (fmap f r)-  {-# INLINE fmap #-}--instance Monad m => Applicative (Streamer m i o) where-  pure = Streamer . return . Done . Just-  {-# INLINE pure #-}-  Streamer ma <*> g = Streamer $ ma >>= \case-    Await f d -> return $ Await ((<*> g) . f) (d <*> g)-    Yield o n -> return $ Yield o (n <*> g)-    Done r -> maybe (runStream empty) (runStream . flip fmap g) r-  {-# INLINE (<*>) #-}--instance Monad m => Alternative (Streamer m r i) where-  empty = Streamer . return $ Done Nothing-  {-# INLINE empty #-}-  Streamer ma <|> b = Streamer . flip fmap ma $ \case-    Await g d -> Await ((<|> b) . g) (b <|> d)-    Yield o n -> Yield o (n <|> b)-    Done r -> Done r-  {-# INLINE (<|>) #-}--{---- This instance is quite often not really wanted due to associativity--- issues.--instance Monad m => Monad (Streamer m r i) where-  return = pure-  {-# INLINE return #-}-  Streamer ma >>= fb = Streamer $ ma >>= \case-    Await f d -> return $ Await (\i -> f i >>= fb) (d >>= fb)-    Yield o n -> return $ Yield o (n >>= fb)-    Done r -> maybe (runStream empty) (runStream . fb) r-  {-# INLINE (>>=) #-}--instance Monad m => MonadPlus (Streamer m r i) where-  mzero = empty-  mplus = (<|>)--}--instance (Monad m, HasError m) => HasError (Streamer m i o) where-  throwError = liftS . throwError-  {-# INLINE throwError #-}--instance (Monad m, HasHppState m) => HasHppState (Streamer m i o) where-  getState = liftS getState-  {-# INLINE getState #-}-  setState = liftS . setState-  {-# INLINE setState #-}--instance (Monad m, HasEnv m) => HasEnv (Streamer m i o) where-  getEnv = liftS getEnv-  {-# INLINE getEnv #-}-  setEnv = liftS . setEnv-  {-# INLINE setEnv #-}---- * Builders---- | Yield a value downstream, then finish.-yield :: Monad m => o -> Streamer m i o ()-yield o = encase $ Yield o (done ())-{-# INLINE yield #-}---- | Yield a value then continue with another 'Streamer'.-yields :: Monad m => o -> Streamer m i o r -> Streamer m i o r-yields = (encase .) . Yield-{-# INLINE yields #-}---- | Package a function that returns a 'Streamer' into a 'Streamer'.-awaits :: Monad m => (i -> Streamer m i o r) -> Streamer m i o r-awaits f = encase $ Await f empty-{-# INLINE awaits #-}---- | The end of a stream.-done :: Monad m => r -> Streamer m i o r-done = pure-{-# INLINE done #-}---- | Feed values downstream.-source :: (Monad m, Foldable f) => f a -> Streamer m i a ()-source = go . toList-  where go [] = done ()-        go (x:xs) = encase $ Yield x (go xs)-{-# INLINE source #-}---- | Lift a monadic value into a 'Streamer'-liftS :: Functor m => m a -> Streamer m i o a-liftS = Streamer . fmap (Done . Just)-{-# INLINE liftS #-}---- * Runners---- | A source whose outputs have all been sunk may be run for its--- effects and return value.-run :: Monad m => Source m Void r -> m (Maybe r)-run (Streamer s) = s >>= go-  where go (Done r) = return r-        go (Await _ _) = error "Source is awaiting in exhaustStreamer"-        go (Yield _ _) = error "A capped sink is yielding in exhaustStreamer"---- | Compute the next step of a 'Streamer'.-nextOutput :: Monad m-           => Streamer m i o r -> m (Either (Maybe r) (o, Streamer m i o r))-nextOutput s = runStream s >>= go-  where go (Await _ n) = runStream n >>= go-        go (Yield o n) = return (Right (o, n))-        go (Done r) = return (Left r)---- * Combinators---- | Map a function over the values yielded by a stream.-mapStream :: Monad m => (a -> b) -> Streamer m i a r -> Streamer m i b r-mapStream f = go-  where go (Streamer s) = Streamer $ s >>= \case-          Done r -> pure $ Done r-          Await g e -> pure $ Await (go . g) (go e)-          Yield o n -> pure $ Yield (f o) (go n)-{-# INLINE[1] mapStream #-}--{-# RULES "hpp: mapStream/mapStream"-  forall f g s. mapStream f (mapStream g s) = mapStream (f . g) s #-}---- | @upstream ~> downstream@ composes two streams such that values--- flow from upstream to downstream.-(~>) :: Monad m => Streamer m a b r -> Streamer m b c r' -> Streamer m a c r'-src0 ~> Streamer mb = Streamer $ mb >>= goSnk src0-  where goSnk _ (Done r) = return $ Done r-        goSnk src (Yield o n) = return-                              $ Yield o (Streamer $ runStream n >>= goSnk src)-        goSnk src (Await f e) = runStream src >>= goSrc f e-        goSrc _ e (Done _) = runStream e >>= goSnk empty-        goSrc k _ (Yield i n) =  runStream (k i) >>= goSnk n-        goSrc k e (Await f' e') =-          return $ Await (\i -> Streamer $ runStream (f' i) >>= goSrc k e)-                         (e' ~> e)-{-# INLINE[1] (~>) #-}-infixl 9 ~>--_feedStreamer :: Monad m => Streamer m i o r -> [i] -> m ([i], [o], Maybe r)-_feedStreamer s xs0 = runStream s >>= aux xs0 id-  where aux [] acc (Await _ d) = runStream d >>= aux [] acc-        aux (x:xs) acc (Await f _) = runStream (f x) >>= aux xs acc-        aux xs acc (Yield o n) = runStream n >>= aux xs (acc . (o:))-        aux xs acc (Done r) = return (xs, acc [], r)---- | @x `before` y@ runs @x@ to completion, discards its 'Done' value,--- then becomes @y@.-before :: Monad m => Streamer m i o q -> Streamer m i o r -> Streamer m i o r-before (Streamer ma) mb = Streamer $ ma >>= go-  where go (Await f d) = return $ Await (\i -> Streamer $ runStream (f i) >>= go)-                                        (Streamer $ runStream d >>= go)-        go (Yield o n) = return $ Yield o (Streamer $ runStream n >>= go)-        go (Done _) = runStream mb---- | Apply a function to the ending value of a stream.-onDone :: Monad m-       => (Maybe r -> Maybe r')-       -> Streamer m i o r-       -> Streamer m i o r'-onDone f (Streamer m) = Streamer $ m >>= go-  where go (Done r) = return $ Done (f r)-        go (Yield o n) = return $ Yield o (Streamer $ runStream n >>= go)-        go (Await f' e) = return $ Await (\i -> Streamer $ runStream (f' i) >>= go)-                                         (Streamer $ runStream e >>= go)-{-# INLINE[1] onDone #-}--{-# RULES "hpp: onDone/onDone"-  forall g f s. onDone g (onDone f s) = onDone (g . f) s #-}---- | Apply a function to each value in a stream.-mapping :: Monad m => (a -> b) -> Streamer m a b r-mapping f = go-  where go = awaits (\i -> yields (f i) go)-        {-# INLINABLE go #-}-{-# INLINE[1] mapping #-}--{-# RULES "hpp: mapping/mapping"-  forall f g. mapping f ~> mapping g = mapping (g . f) #-}---- | Discard all values that do not satisfy a predicate.-filtering :: Monad m => (a -> Bool) -> Streamer m a a r-filtering p = go-  where go = encase $ Await aux empty-        aux x = if p x then encase $ Yield x go else go-{-# INLINE[1] filtering #-}---- | A combined filter and map.-mappingMaybe :: Monad m => (a -> Maybe b) -> Streamer m a b r-mappingMaybe f = go-  where go = awaits (\i -> maybe go (flip yields go) $ f i)-{-# INLINE[1] mappingMaybe #-}--predicateMap :: (a -> Bool) -> (a -> b) -> a -> Maybe b-predicateMap p f = \x -> if p x then Just (f x) else Nothing-{-# INLINE[1] predicateMap #-}--maybeNot :: (a -> Bool) -> Maybe a -> Maybe a-maybeNot p = \x -> case x of-                     Nothing -> Nothing-                     Just x' -> if p x' then x else Nothing-{-# INLINE[1] maybeNot #-}--{-# RULES "hpp: mapping ~> filtering"-  forall f p. mapping f ~> filtering p = mappingMaybe (maybeNot p . Just . f)-  ; "hpp: filtering ~> mapping"-  forall p f. filtering p ~> mapping f = mappingMaybe (predicateMap p f)-  ; "hpp: mappingMaybe ~> mappingMaybe"-  forall f g. mappingMaybe f ~> mappingMaybe g = mappingMaybe (f <=< g)-  #-}---- | @processPrefix src snk@ is like '~>' except that when @snk@--- finishes, the composite 'Streamer' becomes the remaining @src@.-processPrefix :: Monad m => Source m o r -> Streamer m o o r' -> Source m o r-processPrefix src0 snk = Streamer $ runStream snk >>= goSnk src0-  where goSnk src (Done _) = runStream src-        goSnk src (Yield o n) =-          return $ Yield o (Streamer $ runStream n >>= goSnk src)-        goSnk src (Await f _) = runStream src >>= goSrc f-        goSrc _ d@(Done _) = return d-        goSrc k (Yield i n) = runStream (k i) >>= goSnk n-        goSrc k (Await f e) =-          return $ Await (\i -> Streamer $ runStream (f i) >>= goSrc k)-                         (Streamer $ runStream e >>= goSrc k)---- * Zoom support---- | This is a left fold over a 'Streamer' with a final step to deal--- with leftovers represented by whatever state the fold function--- maintains.-common :: Monad m-       => (s -> Maybe (Streamer m i o ()))-       -> ((s -> Streamer m i o r -> Streamer m i' o' (Streamer m i o r))-            -> s -> Streamer m i o r -> Streamer m i' o' (Streamer m i o r))-       -> s-       -> Streamer m i o r -> Streamer m i' o' (Streamer m i o r)-common fin nxt = go-  where go acc src = encase $ Await (const (fin' acc src)) (nxt go acc src)-        fin' acc src = done . maybe src (`before` src) $ fin acc-{-# INLINABLE common #-}---- | Flatten out chunks of inputs into individual values. The returned--- 'Source' smuggles the remaining original 'Source' in an 'Await'--- constructor, while the flattened source continues on with the--- \"empty\" part of the 'Await' step. The upshot is that the value--- may be used a regular 'Source', but it can also be swapped back--- into the original 'Source'.-flattenTil :: Monad m-           => Source m [i] r -> Source m i (Source m [i] r)-flattenTil = common fin go []-  where fin acc = if null acc then Nothing else Just (yield acc)-        go k [] src = Streamer $ nextOutput src >>= \case-          Left r -> return $ Done (Just (encase (Done r)))-          -- Right (o,n) -> runStream $ go k o n-          Right (o,n) -> runStream $ go k o n-        go k (x:xs) src = encase $ Yield x (k xs src)-{-# INLINABLE flattenTil #-}---- | See 'flattenTil' for an explanation.-mapTil :: Monad m-       => (a -> b)-       -> Streamer m Void a r-       -> Streamer m Void b (Streamer m Void a r)-mapTil f = common (const Nothing) go ()-  where go k () src = Streamer $ nextOutput src >>= \case-          Left r -> return $ Done (Just (encase (Done r)))-          Right (o, n) -> return $ Yield (f o) (k () n)-{-# INLINABLE mapTil #-}---- * Chunky metamorphisms---- | A function that produces an output stream that finishes with--- another such function. Think of the input to this function as--- coming from upstream, while the closure of the streamed output may--- be used to thread state.-newtype Chunky m a b = Chunky (a -> Source m b (Chunky m a b))---- | Apply a function to a 'Chunky''s output.-chunkMap :: Monad m => (b -> c) -> Chunky m a b -> Chunky m a c-chunkMap g (Chunky f) = Chunky (onDone (fmap (chunkMap g)) . mapStream g . f)-{-# INLINE[1] chunkMap #-}--{-# RULES "hpp: chunkMap/chunkMap"-  forall f g c. chunkMap f (chunkMap g c) = chunkMap (f . g) c #-}---- | Apply a function to a 'Chunky's input.-chunkConmap :: Monad m => (a -> b) -> Chunky m b c -> Chunky m a c-chunkConmap g (Chunky f) = Chunky (onDone (fmap (chunkConmap g)) . f . g)-{-# INLINE[1] chunkConmap #-}--{-# RULES "hpp: chunkConmap/chunkConmap"-  forall f g c. chunkConmap f (chunkConmap g c) = chunkConmap (f . g) c #-}---- | This is something like a composition of an unfold with a fold. We--- fold the upstream values into some state carried by a 'Chunky',--- then unfold that state in the 'Chunky''s output stream.-metamorph :: Monad m-          => Chunky m a b-          -> Streamer m a b ()-metamorph (Chunky f) = go-  where go = awaits (Streamer . aux . f)-        aux s = runStream s >>= \case-          Done (Just k) -> runStream $ metamorph k-          Done Nothing -> return $ Done Nothing-          Await _ _ -> error "Sources shouldn't await"-          Yield o n -> return $ Yield o (Streamer $ aux n)-{- INLINABLE metamorph #-}--- inlining metamorph trips a bug in GHC-7.10.2--{-# RULES-    "hpp: metamorph/mapping"-    forall c f. metamorph c ~> mapping f = metamorph (chunkMap f c)-  ; "hpp: mapping/metamorph"-    forall c f. mapping f ~> metamorph c = metamorph (chunkConmap f c)-  #-}
src/Hpp/String.hs view
@@ -2,7 +2,7 @@ -- | HELPERS for working with 'String's module Hpp.String (stringify, unquote, trimSpaces, breakOn, cons) where import Data.Char (isSpace)-import Data.List (isPrefixOf)+import Data.List (isPrefixOf, find)  -- | Stringification puts double quotes around a string and -- backslashes before existing double quote characters and backslash@@ -37,16 +37,19 @@  -- | Similar to the function of the same name in the @text@ package. ----- @breakOn needle haystack@ finds the first instance of @needle@ in--- @haystack@. The first component of the result is the prefix of--- @haystack@ before @needle@ is matched. The second is the remainder of--- @haystack@, starting with the match.-breakOn :: String -> String -> (String, String)-breakOn needle haystack = go 0 haystack-  where go _ [] = (haystack, [])-        go !i xs@(_:xs')-          | needle `isPrefixOf` xs = (take i haystack, xs)-          | otherwise = go (i+1) xs'+-- @breakOn needles haystack@ finds the first instance of an element+-- of @needles@ in @haystack@. The first component of the result is+-- the needle tag, the second component is the prefix of @haystack@+-- before the matched needle, the third component is the remainder of+-- the @haystack@ /after/ the needle..+breakOn :: [(String,t)] -> String -> Maybe (t, String, String)+breakOn needles haystack = go 0 haystack+  where go _ [] = Nothing+        go !i xs@(_:xs') =+          case find (flip isPrefixOf xs . fst) needles of+            Nothing -> go (i+1) xs'+            Just (n,tag) -> Just (tag, take i haystack, drop (length n) xs)+{-# INLINE breakOn #-}  -- | Used to make switching to the @text@ package easier. cons :: a -> [a] -> [a]
+ src/Hpp/StringSig.hs view
@@ -0,0 +1,213 @@+{-# LANGUAGE BangPatterns, CPP, FlexibleInstances, OverloadedStrings,+             PatternSynonyms, TypeSynonymInstances, ViewPatterns #-}+module Hpp.StringSig where+import Data.Char+import qualified Data.List as L+import Data.Maybe (isJust)+import Data.String (IsString)+import qualified Hpp.String as S+import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString.Lazy.Char8 as BL+import System.IO (Handle, hPutStr)++data CharOrSub s = CharMatch !s !s | SubMatch !s !s | NoMatch++class (IsString s, Monoid s) => Stringy s where+  -- | Stringification puts double quotes around a string and+  -- backslashes before existing double quote characters and backslash+  -- characters.+  stringify :: s -> s++  -- | Remove double quote characters from the ends of a string.+  unquote :: s -> s++  -- | Trim trailing spaces from a 'String'+  trimSpaces :: s -> s++  -- | Similar to the function of the same name in the @text@ package.+  --+  -- @breakOn needles haystack@ finds the first instance of an element+  -- of @needles@ in @haystack@. The first component of the result is+  -- the needle tag, the second component is the prefix of @haystack@+  -- before the matched needle, the third component is the remainder of+  -- the @haystack@ /after/ the needle..+  breakOn :: [(s,t)] -> s -> Maybe (t, s, s)++  -- | A special case of 'breakOn' in which we are looking for either+  -- a special character or a particular substring.+  breakCharOrSub :: Char -> s -> s -> CharOrSub s+  cons :: Char -> s -> s+  uncons :: s -> Maybe (Char, s)+  snoc :: s -> Char -> s+  unsnoc :: s -> Maybe (s, Char)+  sdrop :: Int -> s -> s+  sbreak :: (Char -> Maybe t) -> s -> Maybe (t,s,s)+  sall :: (Char -> Bool) -> s -> Bool+  sIsPrefixOf :: s -> s -> Bool+  isEmpty :: s -> Bool+  readLines :: FilePath -> IO [s]+  putStringy :: Handle -> s -> IO ()+  toChars :: s -> [Char]+  -- | An opportunity to copy a string to its own storage to help with GC+  copy :: s -> s++instance Stringy String where+  stringify = S.stringify+  {-# INLINE stringify #-}+  unquote = S.unquote+  {-# INLINE unquote  #-}+  trimSpaces = S.trimSpaces+  {-# INLINE trimSpaces #-}+  breakOn = S.breakOn+  {-# INLINE breakOn #-}+  breakCharOrSub c sub str =+    case S.breakOn [([c], True), (sub, False)] str of+      Nothing -> NoMatch+      Just (True, pre, pos) -> CharMatch pre pos+      Just (False, pre, pos) -> SubMatch pre pos+  {-# INLINE breakCharOrSub #-}+  cons = S.cons+  {-# INLINE cons #-}+  uncons = L.uncons+  {-# INLINE uncons #-}+  snoc s c = s ++ [c]+  {-# INLINE snoc #-}+  unsnoc [] = Nothing+  unsnoc s = Just (init s, last s)+  {-# INLINE unsnoc #-}+  sdrop = drop+  {-# INLINE sdrop #-}+  sbreak _ [] =  Nothing+  sbreak p (x:xs') =+    case p x of+      Nothing -> let res = sbreak p xs' in fmap (_2 (x:)) res+      Just t -> Just (t, [], xs')+    where _2 f (a,b,c) = (a, f b, c)+  {-# INLINE sbreak #-}+  sall = all+  {-# INLINE sall #-}+  sIsPrefixOf = L.isPrefixOf+  isEmpty = null+  {-# INLINE isEmpty #-}+  readLines = fmap lines . readFile+  putStringy = hPutStr+  toChars = id+  copy = id++instance Stringy B.ByteString where+  stringify s = B.cons '"' (B.snoc (B.concatMap aux (strip s)) '"')+    where aux '\\' = "\\\\"+          aux '"' = "\\\""+          aux c = B.singleton c+          strip = trimSpaces . B.dropWhile isSpace+  {-# INLINE stringify #-}+  unquote s = let s' = case B.uncons s of+                         Nothing -> s+                         Just (c, rst) -> if c == '"' then rst else s+              in case B.unsnoc s' of+                   Nothing -> s'+                   Just (ini, c) -> if c == '"' then ini else s'+  {-# INLINE unquote #-}+  trimSpaces s = let go !i = if isSpace (B.index s i)+                             then go (i-1)+                             else B.length s - i - 1+                 in B.drop (go (B.length s - 1)) s+  {-# INLINE trimSpaces #-}+  breakOn [!(!n1,!t1)] haystack =+    case B.breakSubstring n1 haystack of+      (pre,pos) | B.null pos -> Nothing+                | otherwise -> Just (t1, pre, B.drop (B.length n1) pos)++  breakOn [!(!n1, !t1), !(n2, !t2)] haystack = go2 0 haystack+    where go2 !i !h+            | B.null h = Nothing+            | B.isPrefixOf n1 h = let !h' = B.drop (B.length n1) h+                                      !pre = B.take i haystack+                                  in Just (t1, pre, h')+            | B.isPrefixOf n2 h = let !h' = B.drop (B.length n2) h+                                      !pre = B.take i haystack+                                  in Just (t2, pre, h')+            | otherwise = go2 (i+1) (B.tail h)+  breakOn [!(!n1, !t1), !(n2, !t2), !(!n3, !t3)] haystack = go3 0 haystack+    where go3 !i !h+            | B.null h = Nothing+            | B.isPrefixOf n1 h = let h' = B.drop (B.length n1) h+                                  in Just (t1, B.take i haystack, h')+            | B.isPrefixOf n2 h = let h' = B.drop (B.length n2) h+                                  in Just (t2, B.take i haystack, h')+            | B.isPrefixOf n3 h = let h' = B.drop (B.length n3) h+                                  in Just (t3, B.take i haystack, h')+            | otherwise = go3 (i+1) (B.tail h)+  breakOn needles haystack = go 0 haystack+    where go !i !h+            | B.null h = Nothing+            | otherwise =+              case L.find (flip B.isPrefixOf h . fst) needles of+                Nothing -> go (i+1) (B.tail h)+                Just (n,tag) -> let h' = B.drop (B.length n ) h+                                in Just (tag, B.take i haystack, h')+  {-# INLINE breakOn #-}+  breakCharOrSub c sub str =+    case B.elemIndex c str of+      Nothing -> case B.breakSubstring sub str of+                   (pre,pos)+                     | B.null pos -> NoMatch+                     | otherwise -> SubMatch pre (B.drop (B.length sub) pos)+      Just i ->+        case B.breakSubstring sub str of+          (pre,pos)+            | B.null pos -> CharMatch (B.take i str) (B.drop (i+1) str)+            | B.length pre < i -> SubMatch pre (B.drop (B.length sub) pos)+            | otherwise -> CharMatch (B.take i str) (B.drop (i+1) str)++  {-# INLINE breakCharOrSub #-}+  cons = B.cons+  uncons = B.uncons+  snoc = B.snoc+  unsnoc = B.unsnoc+  sdrop = B.drop . fromIntegral+  sbreak f s = case B.break (isJust . f) s of+                 (h,t) -> case B.uncons t of+                            Nothing -> Nothing+                            Just (c,t') -> fmap (\r -> (r,h,t')) (f c)+  {-# INLINE sbreak #-}+  sall = B.all+  sIsPrefixOf = B.isPrefixOf+  isEmpty = B.null+  readLines = fmap (map BL.toStrict . BL.lines) . BL.readFile+  {-# INLINE readLines #-}+  putStringy = B.hPutStr+  toChars = B.unpack+  copy = B.copy++boolJust :: Bool -> Maybe ()+boolJust True = Just ()+boolJust False = Nothing+{-# INLINE boolJust #-}++predicateJust :: (a -> Bool) -> a -> Maybe a+predicateJust f c = if f c then Just c else Nothing+{-# INLINE predicateJust #-}++sdropWhile :: Stringy s => (Char -> Bool) -> s -> s+sdropWhile f s = case sbreak (boolJust . f) s of+                   Nothing -> s+                   Just (_, _, s') -> s'+{-# INLINE sdropWhile #-}++#if __GLASGOW_HASKELL__ >= 800+pattern (:.) :: Stringy s => Char -> s -> s+#else+pattern (:.) :: () => Stringy s => Char -> s -> s+#endif+pattern x :. xs <- (uncons -> Just (x,xs)) where+  x:.xs = cons x xs+infixr 5 :.++#if __GLASGOW_HASKELL__ >= 800+pattern Nil :: Stringy s => s+#else+pattern Nil :: () => Stringy s => s+#endif+pattern Nil <- (isEmpty -> True) where+  Nil = mempty
src/Hpp/Tokens.hs view
@@ -1,86 +1,150 @@+{-# LANGUAGE BangPatterns, OverloadedStrings, ViewPatterns #-} -- | Tokenization breaks a 'String' into pieces of whitespace, -- constants, symbols, and identifiers.-module Hpp.Tokens where-import Data.Char (isAlphaNum, isDigit, isSpace)+module Hpp.Tokens (Token(..), detok, isImportant, notImportant, importants,+                   trimUnimportant, detokenize, tokenize, newLine,+                   skipLiteral) where+import Control.Arrow (first, second)+import Data.Char (isAlphaNum, isDigit, isSpace, isOctDigit, isHexDigit, digitToInt)+import Data.Foldable (foldl')+import Data.Monoid ((<>))+import Data.String (IsString, fromString)+import Hpp.StringSig  -- | Tokenization is 'words' except the white space is tagged rather -- than discarded.-data Token = Important String-           -- ^ Identifiers, symbols, and constants-           | Other String-           -- ^ White space, etc.-             deriving (Eq,Ord,Show)+data Token s = Important s+             -- ^ Identifiers, symbols, and constants+             | Other s+             -- ^ White space, etc.+               deriving (Eq,Ord,Show) +instance Functor Token where+  fmap f (Important s) = Important (f s)+  fmap f (Other s) = Other (f s)+  {-# INLINE fmap #-}+ -- | Extract the contents of a 'Token'.-detok :: Token -> String+detok :: Token s -> s detok (Important s) = s detok (Other s) = s {-# INLINE detok #-}  -- | 'True' if the given 'Token' is 'Important'; 'False' otherwise.-isImportant :: Token -> Bool+isImportant :: Token s -> Bool isImportant (Important _) = True isImportant _ = False  -- | 'True' if the given 'Token' is /not/ 'Important'; 'False' -- otherwise.-notImportant :: Token -> Bool+notImportant :: Token s -> Bool notImportant (Other _) = True notImportant _ = False  -- | Return the contents of only 'Important' (non-space) tokens.-importants :: [Token] -> [String]+importants :: [Token s] -> [s] importants = map detok . filter isImportant  -- | Trim 'Other' 'Token's from both ends of a list of 'Token's.-trimUnimportant :: [Token] -> [Token]+trimUnimportant :: [Token s] -> [Token s] trimUnimportant = aux id . dropWhile (not . isImportant)   where aux _ [] = []         aux acc (t@(Important _) : ts) = acc (t : aux id ts)         aux acc (t@(Other _) : ts) = aux (acc . (t:)) ts  -- | Is a 'Token' a newline character?-newLine :: Token -> Bool-newLine (Other "\n") = True+newLine :: (Eq s, IsString s) => Token s -> Bool+newLine (Other s) = s == "\n" newLine _ = False +maybeImp :: Stringy s => s -> [Token s]+maybeImp s = if isEmpty s then [] else [Important s]++digitsFromBase :: Stringy s => Int -> s -> s+digitsFromBase base = fromString . show . foldl' aux 0 . map digitToInt . toChars+  where aux acc d = base * acc + d++escapeChar :: Stringy s => Char -> Maybe s+escapeChar = fmap fromString . flip lookup lut+  where lut = map (second (show :: Int -> String))+                  [ ('a', 0x07), ('b', 0x08), ('f', 0x0C), ('n', 0x0A)+                  , ('r', 0x0D), ('t', 0x09), ('v', 0x0B), ('\\', 0x5C)+                  , ('\'', 0x27), ('"', 0x22), ('?', 0x3F) ]++data TokChar = TokSpace Char | TokQuote | TokDQuote -- | Break a 'String' into space and non-whitespace runs.-tokWords :: String -> [Token]-tokWords [] = []-tokWords (c:cs)-  | isSpace c = let (spaces,rst) = break (not . isSpace) cs-                in Other (c : spaces) : tokWords rst-  | c == '\'' && isCharLit = goCharLit-  | c == '"' = flip skipLiteral cs $ \str rst ->-               Important (str []) : tokWords rst-  | otherwise = let (chars,rst) = break (not . validIdentifierChar) cs-                in Important (c:chars) : tokWords rst-     where (isCharLit, goCharLit) =-             case cs of-               (c':'\'':cs') -> (True, Important ['\'',c','\''] : tokWords cs')-               _ -> (False, [])+tokWords :: Stringy s => s -> [Token s]+tokWords s =+  case sbreak aux s of+     -- No word breaks+     Nothing -> [Important s] --- | If you encounter a string literal, call this helper with a--- double-barreled continuation and the rest of your input. The--- continuation will expect the remainder of the string literal as the--- first argument, and the remaining input as the second argument.-skipLiteral :: ((String -> String) -> String -> r) -> String -> r-skipLiteral k = go ('"':)-  where go acc ('\\':'\\':cs) = go (acc . ("\\\\"++)) cs-        go acc ('\\':'"':cs) = go (acc . ("\\\""++)) cs-        go acc ('"':cs) = k (acc . ('"':)) cs-        go acc (c:cs) = go (acc . (c :)) cs-        go acc [] = k acc []-{-# INLINE skipLiteral #-}+     -- Word delimited by space+     Just (TokSpace c, pre, pos) ->+       case sbreak (predicateJust (not . isSpace)) pos of+         Nothing -> maybeImp pre ++ [Other (cons c pos)]+         Just (c', spaces, pos') ->+           maybeImp pre +++           Other (cons c spaces) : tokWords (cons c' pos') +     -- Possible character literal+     Just (TokQuote, pre, pos) ->+       let pre' = snoc pre '\''+       in case pos of+            '\\' :. cs ->+              case sbreak (boolJust . (== '\'')) cs of+                Nothing -> [Important (pre' <> pos)]+                Just (_,esc,pos') ->+                  let esc' = if sall isOctDigit esc+                             then Important (digitsFromBase 8 esc)+                             else case esc of+                                    'x' :. hs+                                      | sall isHexDigit hs ->+                                      Important (digitsFromBase 16 hs)+                                    (escapeChar -> Just e) :. Nil -> Important e+                                    _ -> Important ("'\\" <> snoc esc '\'')+                  in  maybeImp pre ++ esc' : tokWords pos'+            c:.('\'':.cs) -> maybeImp pre+                                ++ Important (fromString ['\'', c, '\''])+                                : tokWords cs+            _:._ -> let oops = snoc pre '\''+                    in case tokWords pos of+                         (Important t:ts) -> Important (oops<>t) : ts+                         ts -> Important oops : ts+            _ -> [Important (snoc pre '\'')]++     -- String literal+     Just (TokDQuote, pre, pos) ->+       let (lit,pos') = skipLiteral pos+       in (if isEmpty pre then [] else [Important pre])+          ++ Important (cons '"' lit) : tokWords pos'+  where aux c | isSpace c = Just (TokSpace c)+              | c == '\'' = Just TokQuote+              | c == '"' = Just TokDQuote+              | otherwise = Nothing+        {-# INLINE aux #-}+{-# INLINABLE tokWords #-}++data LitStringChar = DBackSlash | EscapedDQuote | DQuote+skipLiteral :: Stringy s => s -> (s,s)+skipLiteral s =+  case breakOn [("\\\\", DBackSlash), ("\\\"", EscapedDQuote), ("\"", DQuote)] s of+    Nothing -> (s, mempty) -- Unmatched double quote?!+    Just (DBackSlash, pre, pos) -> first ((pre <> "\\\\") <>) (skipLiteral pos)+    Just (EscapedDQuote, pre, pos) -> first ((pre <> "\\\"") <>) (skipLiteral pos)+    Just (DQuote, pre, pos) -> (snoc pre '"', pos)+{-# INLINABLE skipLiteral #-}+ -- | @splits isDelimiter str@ tokenizes @str@ using @isDelimiter@ as a -- delimiter predicate. Leading whitespace is also stripped from -- tokens.-splits :: (Char -> Bool) -> String -> [String]-splits isDelim = filter (not . null) . go . dropWhile isSpace-  where go s = case break isDelim s of-                 (h,[]) -> [dropWhile isSpace h]-                 (h,d:t) -> dropWhile isSpace h : [d] : go t+splits :: Stringy s => (Char -> Bool) -> s -> [s]+splits isDelim = filter (not . isEmpty) . go . sdropWhile isSpace+  where go s = case sbreak (\c -> if isDelim c then Just c else Nothing) s of+                  Nothing -> [s]+                  Just (d, pre, pos) ->+                    pre : fromString [d] : go (sdropWhile isSpace pos)+{-# INLINE splits #-}  -- | Predicate on space characters based on something approximating -- valid identifier syntax. This is used to break apart non-space@@ -90,26 +154,36 @@  -- | Something like @12E+FOO@ is a single pre-processor token, so -- @FOO@ should not be macro expanded.-fixExponents :: [Token] -> [Token]+fixExponents :: Stringy s => [Token s] -> [Token s] fixExponents [] = []-fixExponents (Important (t1@(d1:_)):Important [c]:Important (d2:t2):ts)-  | elem c "-+" && isDigit d1 && elem (last t1) "eE" && isAlphaNum d2 =-    Important (t1++c:d2:t2) : fixExponents ts+fixExponents (t1'@(Important t1) : ts@(Important t2 : Important t3 : ts')) =+  case (,,,) <$> uncons t1 <*> unsnoc t1 <*> uncons t2 <*> uncons t3 of+    Just !(!(!d1,_), !(_,!e), !(!c,!cs), !(!d2,_))+      | elem c ("-+" :: [Char]) &&+        isEmpty cs && isDigit d1 && isAlphaNum d2 &&+        elem e ("eE" :: [Char]) -> let t = t1 <> t2 <> t3+                                   in t `seq` Important t : fixExponents ts'+    _ -> t1' : fixExponents ts fixExponents (t:ts) = t : fixExponents ts+{-# INLINABLE fixExponents #-}  -- | Break an input 'String' into a sequence of 'Tokens'. Warning: -- This may not exactly correspond to your target language's -- definition of a valid identifier!-tokenize :: String -> [Token]-tokenize = fixExponents . concatMap seps . tokWords+tokenize :: Stringy s => s -> [Token s]+tokenize = fixExponents . foldMap seps . tokWords   where seps t@(Other _) = [t]-        seps t@(Important ('"':_)) = [t]-        seps t@(Important ('\'':_)) = [t]-        seps (Important s) = map Important $-                             splits (not . validIdentifierChar) s+        seps t@(Important s) =+          case uncons s of+            Nothing -> []+            Just (c,_)+              | c == '"' -> [t]+              | c == '\'' -> [t]+              | otherwise -> map Important (splits (not . validIdentifierChar) s)+{-# INLINABLE tokenize #-}  -- | Collapse a sequence of 'Tokens' back into a 'String'. @detokenize -- . tokenize == id@.-detokenize :: [Token] -> String-detokenize = concatMap detok+detokenize :: Monoid s => [Token s] -> s+detokenize = foldMap detok {-# INLINE detokenize #-}
src/Hpp/Types.hs view
@@ -1,37 +1,50 @@-{-# LANGUAGE FlexibleInstances, LambdaCase #-}+{-# LANGUAGE FlexibleInstances, LambdaCase, Rank2Types #-} -- | The core types involved used by the pre-processor. module Hpp.Types where-import Control.Monad (ap, join)+import Control.Monad (ap) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Trans.Class (MonadTrans, lift) import Control.Monad.Trans.Except (ExceptT, throwE)-import Data.IORef (IORef, newIORef, readIORef, writeIORef)+import Control.Monad.Trans.State.Strict (StateT, get, put)+import Data.ByteString.Char8 (ByteString)+import Data.Functor.Constant+import Data.Functor.Identity -- import qualified Data.Map as M+import qualified Data.Trie as T import Hpp.Config+import Hpp.Env (emptyEnv, lookupKey)+import Hpp.StringSig (toChars) import Hpp.Tokens+import Prelude hiding (String)+import qualified Prelude as P  -- | Line numbers are represented as 'Int's type LineNum = Int  -- | A macro binding environment.-type Env = [(String, Macro)]+-- type Env = [(String, Macro)] -- type Env = M.Map String Macro+type Env = T.Trie Macro +-- * Changing the underlying string type+type String = ByteString+type TOKEN = Token ByteString+ -- * Errors  -- | Error conditions we may encounter. data Error = UnterminatedBranch            | BadMacroDefinition LineNum            | BadIfPredicate-           | BadLineArgument LineNum String+           | BadLineArgument LineNum P.String            | IncludeDoesNotExist LineNum FilePath            | FailedInclude LineNum FilePath-           | UserError LineNum String-           | UnknownCommand LineNum String-           | TooFewArgumentsToMacro LineNum String-           | BadMacroArguments LineNum String+           | UserError LineNum P.String+           | UnknownCommand LineNum P.String+           | TooFewArgumentsToMacro LineNum P.String+           | BadMacroArguments LineNum P.String            | NoInputFile-           | BadCommandLine String+           | BadCommandLine P.String            | RanOutOfInput              deriving (Eq, Ord, Show) @@ -41,6 +54,7 @@  instance Monad m => HasError (ExceptT Error m) where   throwError = throwE+  {-# INLINE throwError #-}  instance (Monad m, HasHppState m) => HasHppState (ExceptT e m) where   getState = lift getState@@ -48,27 +62,13 @@   setState = lift . setState   {-# INLINE setState #-} --- * Resource cleanup---- | A cleanup action that is run at most once. To be used as an--- abstract type with only 'runCleanup' and 'mkCleanup' as interface.-newtype Cleanup  = Cleanup (IORef (IO ()))---- | Runs an action and replaces it with a nop-runCleanup :: Cleanup -> IO ()-runCleanup (Cleanup r) = join (readIORef r) >> writeIORef r (return ())+instance (Monad m, HasError m) => HasError (StateT s m) where+  throwError = lift . throwError+  {-# INLINE throwError #-} --- | @mkCleanup cleanup@ returns two things: a 'Cleanup' value, and an--- action to neutralize that 'Cleanup'. In this way, the 'Cleanup'--- value can be registered with a resource manager so that, in the--- event of an error, the cleanup action is run, while the neutralizer--- may be used to ensure that the registered 'Cleanup' action has no--- effect if it is run. Typically one would neutralize a registered--- cleanup action before performing a manual cleanup that subsumes the--- registered cleanup.-mkCleanup :: IO () -> IO (Cleanup, IO ())-mkCleanup m = do r <- newIORef m-                 return $ (Cleanup r, writeIORef r (return ()))+instance (Monad m, HasError m) => HasError (HppT t m) where+  throwError = lift . throwError+  {-# INLINE throwError #-}  -- * Free Monad Transformers @@ -85,23 +85,18 @@ -- | Dynamic state of the preprocessor engine. data HppState = HppState { hppConfig :: Config                          , hppLineNum :: LineNum-                         , hppCleanups :: [Cleanup]                          , hppEnv :: Env }  -- | A free monad construction to strictly delimit what capabilities -- we need to perform pre-processing. data HppF t r = ReadFile Int FilePath (t -> r)               | ReadNext Int FilePath (t -> r)-              | GetState (HppState -> r)-              | SetState HppState r-              | ThrowError Error+              | WriteOutput t r  instance Functor (HppF t) where   fmap f (ReadFile ln file k) = ReadFile ln file (f . k)   fmap f (ReadNext ln file k) = ReadNext ln file (f . k)-  fmap f (GetState k) = GetState (f . k)-  fmap f (SetState cfg k) = SetState cfg (f k)-  fmap _ (ThrowError e) = ThrowError e+  fmap f (WriteOutput o k) = WriteOutput o (f k)   {-# INLINE fmap #-}  -- * Hpp Monad Transformer@@ -109,6 +104,10 @@ -- | 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)) } +writeOutput :: Monad m => t -> HppT t m ()+writeOutput = HppT . return . FreeF . flip WriteOutput (return ())+{-# INLINE writeOutput #-}+ instance Functor m => Functor (HppT t m) where   fmap f (HppT x) = HppT $ fmap f' x     where f' (PureF y) = PureF (f y)@@ -127,7 +126,6 @@   HppT ma >>= fb = HppT $ ma >>= \case                      PureF x -> runHppT $ fb x                      FreeF x -> return . FreeF $ fmap (>>= fb) x-  {-# INLINE (>>=) #-}  instance MonadTrans (HppT t) where   lift = HppT . fmap PureF@@ -142,27 +140,47 @@   getState :: m HppState   setState :: HppState -> m () -instance Monad m => HasHppState (HppT t m) where-  getState = HppT . pure . FreeF $ GetState pure+instance {-# OVERLAPS #-} Monad m => HasHppState (StateT HppState m) where+  getState = get   {-# INLINE getState #-}-  setState s = HppT . pure . FreeF $ SetState s (pure ())+  setState = put   {-# INLINE setState #-} +instance (Monad m, HasHppState m) => HasHppState (StateT s m) where+  getState = lift getState+  {-# INLINE getState #-}+  setState = lift . setState+  {-# INLINE setState #-}++instance (Monad m, HasHppState m) => HasHppState (HppT t m) where+  getState = lift getState+  {-# INLINE getState #-}+  setState = lift . setState+  {-# INLINE setState #-}+ -- | An interpreter capability of threading a binding environment. class HasEnv m where   getEnv :: m Env   setEnv :: Env -> m () -instance Monad m => HasEnv (HppT t m) where-  getEnv = fmap hppEnv getState+instance (Monad m, HasHppState m) => HasEnv (HppT t m) where+  getEnv = fmap hppEnv (lift getState)   {-# INLINE getEnv #-}-  setEnv e = getState >>= setState . (\s -> s { hppEnv = e })+  setEnv e = lift getState >>= lift . setState . (\s -> s { hppEnv = e })   {-# INLINE setEnv #-} -instance Applicative m => HasError (HppT t m) where-  throwError = HppT . pure . FreeF . ThrowError-  {-# INLINE throwError #-}+instance Monad m => HasEnv (StateT HppState m) where+  getEnv = hppEnv <$> get+  {-# INLINE getEnv #-}+  setEnv = (env .=)+  {-# INLINE setEnv #-} +instance Monad m => HasEnv (StateT Env m) where+  getEnv = get+  {-# INLINE getEnv #-}+  setEnv = put+  {-# INLINE setEnv #-}+ instance (HasEnv m, Monad m) => HasEnv (ExceptT e m) where   getEnv = lift getEnv   {-# INLINE getEnv #-}@@ -172,10 +190,10 @@ -- * Expansion  -- | Macro expansion involves treating tokens differently if they--- appear in the original source for or as the result of a previous--- macro expansion. This distinction is used to prevent divergence by+-- appear in the original source or as the result of a previous macro+-- expansion. This distinction is used to prevent divergence by -- masking out definitions that could be used recursively.--- +-- -- Things are made somewhat more complicated than one might expect due -- to the fact that the scope of this masking is /not/ structurally -- recursive. A object-like macro can expand into a fragment of a@@ -184,21 +202,75 @@ -- be expanded. data Scan = Unmask String           | Mask String-          | Scan Token-          | Rescan Token+          | Scan (Token String)+          | Rescan (Token String)             deriving (Eq, Show)  -- * Macros  -- | There are object-like macros and function-like macros.-data Macro = Object [Token]+data Macro = Object [Token String]            -- ^ An object-like macro is replaced with its definition-           | Function Int ([([Scan],String)] -> [Scan])+           | Function Int ([([Scan], String)] -> [Scan])            -- ^ A function-like macro of some arity taks            -- macro-expanded and raw versions of its arguments, then            -- substitutes them into a body producing a new set of            -- tokens.  instance Show Macro where-  show (Object ts) = "Object "++ detokenize ts-  show (Function n _) = "Fun<"++show n++">" +  show (Object ts) = "Object "++ toChars (detokenize ts)+  show (Function n _) = "Fun<"++show n++">"++-- | Looks up a 'Macro' in the current environment. If the 'Macro' is+-- found, the environment is juggled so that subsequent lookups of the+-- same 'Macro' may evaluate more quickly.+lookupMacro :: (HasEnv m, Monad m) => String -> m (Maybe Macro)+lookupMacro s = lookupKey s <$> getEnv+{-# INLINE lookupMacro #-}++-- * Nano-lens++type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s++setL :: Lens s a -> a -> s -> s+setL l x = runIdentity . l (const $ Identity x)+{-# INLINE setL #-}++getL :: Lens s a -> s -> a+getL l = getConstant . l Constant+{-# INLINE getL #-}++over :: Lens s a -> (a -> a) -> s -> s+over l f = runIdentity . l (Identity . f)+{-# INLINE over #-}++-- * State Lenses++emptyHppState :: Config -> HppState+emptyHppState cfg = HppState cfg 1 emptyEnv++config :: Lens HppState Config+config f (HppState cfg ln e) = (\cfg' -> HppState cfg' ln e) <$> f cfg+{-# INLINE config #-}++lineNum :: Lens HppState LineNum+lineNum f (HppState cfg ln e) = (\ln' -> HppState cfg ln' e) <$> f ln+{-# INLINE lineNum #-}++env :: Lens HppState Env+env f (HppState cfg ln e) = (\e' -> HppState cfg ln e') <$> f e+{-# INLINE env #-}++use :: (HasHppState m, Functor m) => Lens HppState a -> m a+use l = getL l <$> getState+{-# INLINE use #-}++(.=) :: (HasHppState m, Monad m) => Lens HppState a -> a -> m ()+l .= x = getState >>= setState . setL l x+infix 4 .=+{-# INLINE (.=) #-}++(%=) :: (HasHppState m, Monad m) => Lens HppState a -> (a -> a) -> m ()+l %= f = getState >>= setState . over l f+infix 4 %=+{-# INLINE (%=) #-}
src/Main.hs view
@@ -25,12 +25,17 @@   , "  Like -include, except that output is discarded. Only"   , "  macro definitions are kept."   , "--cpp"-  , "  C98 compatibility. Implies: --fline-splice --ferase-comments"+  , "  C98 compatibility."+  , "  Implies: --fline-splice --ferase-comments --freplace-trigraphs"   , "  -D __STDC__  -D __STDC_VERSION__=199409L -D _POSIX_C_SOURCE=200112L"+  , "-P"+  , "  Inhibit #line markers (when this option is given after --cpp)"   , "--fline-splice"   , "  Enable continued line splicing."   , "--ferase-comments"-  , "  Remove all C-style comments before processing." ]+  , "  Remove all C-style comments before processing."+  , "--freplace-trigraphs"+  , "  Replace trigraph sequences before processing." ]  main :: IO () main = do getArgs >>= \case@@ -46,6 +51,6 @@  For the mcpp validation suite -../tool/cpp_test HPP "../../dist/build/hpp/hpp -I/usr/local/include -I/usr/include -fline-splice -ferase-comments -D __x86_64__ -D __GNUC__=4 -D __STDC__ -D __STDC_VERSION__=199409L -D _POSIX_C_SOURCE -D __DARWIN_ONLY_UNIX_CONFORMANCE %s.c | gcc -o %s -x c -" "rm %s" < n_i_.lst +../tool/cpp_test HPP "../../dist/build/hpp/hpp -I/usr/local/include -I/usr/include -fline-splice -ferase-comments -D __x86_64__ -D __GNUC__=4 -D __STDC__ -D __STDC_VERSION__=199409L -D _POSIX_C_SOURCE -D __DARWIN_ONLY_UNIX_CONFORMANCE %s.c | gcc -o %s -x c -" "rm %s" < n_i_.lst  -}
tests/mcpp-validation.sh view
@@ -5,7 +5,7 @@ GCC=gcc  # Note: head -n -1 would be faster than "sed '$ d'", but doesn't work-# with BSD or OS X head. +# with BSD or OS X head.  # Get the include paths GCC will use by default. GCCDIR=($(echo | ${GCC} -E -v - 2>&1 | sed -n '/#include <...> search starts/,/End of search list/ p' | tail -n +2 | sed '$ d' | grep -v '(framework directory)' | sed 's/^[[:space:]]*//'))@@ -32,7 +32,11 @@ fi  echo 'Building hpp'-(cd .. && cabal build)+if [ -z "$TRAVIS" ]; then+  (cd .. && stack build hpp:hpp)+else+  (cd .. && cabal build)+fi if ! [ $? -eq 0 ]; then   echo 'Building hpp failed'   exit 1@@ -48,7 +52,11 @@ # Note that we define __i386__ as the architecture as the MCPP # validation test n_12.c assumes a long is 32 bits. -(cd mcpp-2.7.2/test-c &&  ../tool/cpp_test HPP "../../../dist/build/hpp/hpp -I/usr/include ${GCCDIR} --cpp -D__i386__ -D__DARWIN_ONLY_UNIX_CONFORMANCE %s.c | gcc -o %s -w -x c -" "rm %s" < n_i_.lst)+if [ -z "$TRAVIS" ]; then+  (cd mcpp-2.7.2/test-c &&  ../tool/cpp_test HPP "$(cd ../../.. && stack exec which -- hpp) -I/usr/include ${GCCDIR} --cpp -D__i386__ -D__DARWIN_ONLY_UNIX_CONFORMANCE %s.c | gcc -o %s -w -x c -" "rm %s" < n_i_.lst)+else+  (cd mcpp-2.7.2/test-c &&  ../tool/cpp_test HPP "$(find ../../../dist -type f -executable -name hpp) -I/usr/include ${GCCDIR} --cpp -D__i386__ -D__DARWIN_ONLY_UNIX_CONFORMANCE %s.c | gcc -o %s -w -x c -" "rm %s" < n_i_.lst)+fi  if ! [ $? -eq 0 ]; then   echo 'The test runner exited with an error.'