alex-tools 0.4 → 0.5
raw patch · 3 files changed
+321/−8 lines, 3 filesdep +bytestringdep ~basedep ~template-haskelldep ~textPVP ok
version bump matches the API change (PVP)
Dependencies added: bytestring
Dependency ranges changed: base, template-haskell, text
API changes (from Hackage documentation)
+ AlexTools: instance Language.Haskell.TH.Syntax.Lift AlexTools.SourcePos
+ AlexTools: instance Language.Haskell.TH.Syntax.Lift AlexTools.SourceRange
+ AlexToolsBin: (<->) :: (HasRange a, HasRange b) => a -> b -> SourceRange
+ AlexToolsBin: A :: (Input -> Input -> Int -> s -> (s, a)) -> Action s a
+ AlexToolsBin: Input :: {-# UNPACK #-} !SourcePos -> {-# UNPACK #-} !ByteString -> {-# UNPACK #-} !SourcePos -> {-# UNPACK #-} !Word8 -> Input
+ AlexToolsBin: Lexeme :: !ByteString -> !t -> !SourceRange -> Lexeme t
+ AlexToolsBin: LexerConfig :: s -> (s -> Int) -> (s -> SourcePos -> [Lexeme t]) -> LexerConfig s t
+ AlexToolsBin: SourcePos :: !Int -> !Text -> SourcePos
+ AlexToolsBin: SourceRange :: !SourcePos -> !SourcePos -> SourceRange
+ AlexToolsBin: [inputBytes] :: Input -> {-# UNPACK #-} !ByteString
+ AlexToolsBin: [inputPos] :: Input -> {-# UNPACK #-} !SourcePos
+ AlexToolsBin: [inputPrevByte] :: Input -> {-# UNPACK #-} !Word8
+ AlexToolsBin: [inputPrev] :: Input -> {-# UNPACK #-} !SourcePos
+ AlexToolsBin: [lexemeBytes] :: Lexeme t -> !ByteString
+ AlexToolsBin: [lexemeRange] :: Lexeme t -> !SourceRange
+ AlexToolsBin: [lexemeToken] :: Lexeme t -> !t
+ AlexToolsBin: [lexerEOF] :: LexerConfig s t -> s -> SourcePos -> [Lexeme t]
+ AlexToolsBin: [lexerInitialState] :: LexerConfig s t -> s
+ AlexToolsBin: [lexerStateMode] :: LexerConfig s t -> s -> Int
+ AlexToolsBin: [runA] :: Action s a -> Input -> Input -> Int -> s -> (s, a)
+ AlexToolsBin: [sourceFile] :: SourcePos -> !Text
+ AlexToolsBin: [sourceFrom] :: SourceRange -> !SourcePos
+ AlexToolsBin: [sourceIndex] :: SourcePos -> !Int
+ AlexToolsBin: [sourceTo] :: SourceRange -> !SourcePos
+ AlexToolsBin: alexGetByte :: AlexInput -> Maybe (Word8, AlexInput)
+ AlexToolsBin: alexInputPrevChar :: AlexInput -> Char
+ AlexToolsBin: beforeStartPos :: Text -> SourcePos
+ AlexToolsBin: class HasRange t
+ AlexToolsBin: data Input
+ AlexToolsBin: data Lexeme t
+ AlexToolsBin: data LexerConfig s t
+ AlexToolsBin: data SourcePos
+ AlexToolsBin: data SourceRange
+ AlexToolsBin: data Word8
+ AlexToolsBin: endInput :: Action s Input
+ AlexToolsBin: getLexerState :: Action s s
+ AlexToolsBin: initialInput :: Text -> ByteString -> Input
+ AlexToolsBin: inputFile :: Input -> Text
+ AlexToolsBin: instance (AlexToolsBin.HasRange a, AlexToolsBin.HasRange b) => AlexToolsBin.HasRange (Data.Either.Either a b)
+ AlexToolsBin: instance AlexToolsBin.HasRange (AlexToolsBin.Lexeme t)
+ AlexToolsBin: instance AlexToolsBin.HasRange AlexToolsBin.SourcePos
+ AlexToolsBin: instance AlexToolsBin.HasRange AlexToolsBin.SourceRange
+ AlexToolsBin: instance Control.DeepSeq.NFData AlexToolsBin.SourcePos
+ AlexToolsBin: instance Control.DeepSeq.NFData AlexToolsBin.SourceRange
+ AlexToolsBin: instance Control.DeepSeq.NFData t => Control.DeepSeq.NFData (AlexToolsBin.Lexeme t)
+ AlexToolsBin: instance GHC.Base.Applicative (AlexToolsBin.Action s)
+ AlexToolsBin: instance GHC.Base.Functor (AlexToolsBin.Action s)
+ AlexToolsBin: instance GHC.Base.Monad (AlexToolsBin.Action s)
+ AlexToolsBin: instance GHC.Classes.Eq AlexToolsBin.SourcePos
+ AlexToolsBin: instance GHC.Classes.Eq AlexToolsBin.SourceRange
+ AlexToolsBin: instance GHC.Classes.Eq t => GHC.Classes.Eq (AlexToolsBin.Lexeme t)
+ AlexToolsBin: instance GHC.Show.Show AlexToolsBin.SourcePos
+ AlexToolsBin: instance GHC.Show.Show AlexToolsBin.SourceRange
+ AlexToolsBin: instance GHC.Show.Show t => GHC.Show.Show (AlexToolsBin.Lexeme t)
+ AlexToolsBin: lexeme :: t -> Action s [Lexeme t]
+ AlexToolsBin: makeLexer :: ExpQ
+ AlexToolsBin: matchBytes :: Action s ByteString
+ AlexToolsBin: matchLength :: Action s Int
+ AlexToolsBin: matchRange :: Action s SourceRange
+ AlexToolsBin: moveSourcePos :: SourcePos -> SourcePos
+ AlexToolsBin: newtype Action s a
+ AlexToolsBin: prettySourcePos :: SourcePos -> String
+ AlexToolsBin: prettySourcePosLong :: SourcePos -> String
+ AlexToolsBin: prettySourceRange :: SourceRange -> String
+ AlexToolsBin: prettySourceRangeLong :: SourceRange -> String
+ AlexToolsBin: prevPos :: SourcePos -> SourcePos
+ AlexToolsBin: range :: HasRange t => t -> SourceRange
+ AlexToolsBin: setLexerState :: s -> Action s ()
+ AlexToolsBin: simpleLexer :: LexerConfig () t
+ AlexToolsBin: startInput :: Action s Input
+ AlexToolsBin: startPos :: Text -> SourcePos
+ AlexToolsBin: type AlexInput = Input
- AlexTools: data Word8 :: *
+ AlexTools: data Word8
Files
- alex-tools.cabal +5/−4
- src/AlexTools.hs +5/−4
- src/AlexToolsBin.hs +311/−0
alex-tools.cabal view
@@ -1,5 +1,5 @@ name: alex-tools-version: 0.4+version: 0.5 synopsis: A set of functions for a common use case of Alex. description: This captures a common patter for using Alex. license: ISC@@ -17,12 +17,13 @@ location: https://github.com/GaloisInc/alex-tools library- exposed-modules: AlexTools+ exposed-modules: AlexTools, AlexToolsBin other-extensions: TemplateHaskell- build-depends: base >=4.7 && <4.12,+ build-depends: base >=4.7 && <4.14, text >=1.2 && <1.3,+ bytestring >= 0.9 && <0.11, deepseq >=1.3 && <1.5,- template-haskell >=2.9.0 && <2.14+ template-haskell >=2.9.0 && <2.16 hs-source-dirs: src ghc-options: -Wall default-language: Haskell2010
src/AlexTools.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TemplateHaskell, CPP #-}+{-# LANGUAGE TemplateHaskell, CPP, DeriveLift #-} module AlexTools ( -- * Lexer Basics initialInput, Input(..), inputFile@@ -45,6 +45,7 @@ import qualified Data.Text as Text import Control.Monad(liftM,ap,replicateM) import Language.Haskell.TH+import Language.Haskell.TH.Syntax #if !MIN_VERSION_base(4,8,0) import Control.Applicative #endif@@ -63,7 +64,7 @@ , sourceLine :: !Int , sourceColumn :: !Int , sourceFile :: !Text- } deriving (Show, Eq)+ } deriving (Show, Eq, Lift) -- | Pretty print the source position without the file name. prettySourcePos :: SourcePos -> String@@ -80,7 +81,7 @@ instance NFData SourcePos where- rnf (SourcePos w x y z) = rnf (w,x,y,z)+ rnf (SourcePos {}) = () -- | Update a 'SourcePos' for a particular matched character moveSourcePos :: Char -> SourcePos -> SourcePos@@ -103,7 +104,7 @@ data SourceRange = SourceRange { sourceFrom :: !SourcePos , sourceTo :: !SourcePos- } deriving (Show, Eq)+ } deriving (Show, Eq, Lift) -- | Pretty print the range, without the file name prettySourceRange :: SourceRange -> String
+ src/AlexToolsBin.hs view
@@ -0,0 +1,311 @@+{-# LANGUAGE TemplateHaskell, CPP #-}+module AlexToolsBin+ ( -- * Lexer Basics+ initialInput, Input(..), inputFile+ , Lexeme(..)+ , SourcePos(..), startPos, beforeStartPos, prevPos+ , SourceRange(..)+ , prettySourcePos, prettySourceRange+ , prettySourcePosLong, prettySourceRangeLong+ , HasRange(..)+ , (<->)+ , moveSourcePos++ -- * Writing Lexer Actions+ , Action(..)++ -- ** Lexemes+ , lexeme+ , matchLength+ , matchRange+ , matchBytes++ -- ** Manipulating the lexer's state+ , getLexerState+ , setLexerState++ -- ** Access to the lexer's input+ , startInput+ , endInput++ -- * Interface with Alex+ , AlexInput+ , alexInputPrevChar+ , alexGetByte+ , makeLexer+ , LexerConfig(..)+ , simpleLexer+ , Word8++ ) where++import Control.DeepSeq+import Data.Word(Word8)+import Data.ByteString(ByteString)+import qualified Data.ByteString as BS+import Data.Text(Text)+import qualified Data.Text as Text+import Control.Monad(liftM,ap,replicateM)+import Language.Haskell.TH+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif++data Lexeme t = Lexeme+ { lexemeBytes :: !ByteString+ , lexemeToken :: !t+ , lexemeRange :: !SourceRange+ } deriving (Show, Eq)++instance NFData t => NFData (Lexeme t) where+ rnf (Lexeme x y z) = rnf (x,y,z)++data SourcePos = SourcePos+ { sourceIndex :: !Int+ , sourceFile :: !Text+ } deriving (Show, Eq)++-- | Pretty print the source position without the file name.+prettySourcePos :: SourcePos -> String+prettySourcePos x = show (sourceIndex x)++-- | Pretty print the source position, including the file name.+prettySourcePosLong :: SourcePos -> String+prettySourcePosLong x = Text.unpack (sourceFile x) ++ ":" ++ show (sourceIndex x)++instance NFData SourcePos where+ rnf (SourcePos {}) = ()++-- | Update a 'SourcePos' for a particular matched character+moveSourcePos :: SourcePos -> SourcePos+moveSourcePos p = p { sourceIndex = sourceIndex p + 1 }++-- | A range in the source code.+data SourceRange = SourceRange+ { sourceFrom :: !SourcePos+ , sourceTo :: !SourcePos+ } deriving (Show, Eq)++-- | Pretty print the range, without the file name+prettySourceRange :: SourceRange -> String+prettySourceRange x = prettySourcePos (sourceFrom x) ++ "--" +++ prettySourcePos (sourceTo x)++-- | Pretty print the range, including the file name.+prettySourceRangeLong :: SourceRange -> String+prettySourceRangeLong x+ | sourceFile pfrom == sourceFile pto =+ Text.unpack (sourceFile pfrom) ++ ":" +++ prettySourcePos pfrom ++ "--" ++ prettySourcePos pto+ | otherwise = prettySourcePosLong pfrom ++ "--" +++ prettySourcePosLong pto+ where+ pfrom = sourceFrom x+ pto = sourceTo x++++instance NFData SourceRange where+ rnf (SourceRange {}) = ()++class HasRange t where+ range :: t -> SourceRange++instance HasRange SourcePos where+ range p = SourceRange { sourceFrom = p, sourceTo = p }++instance HasRange SourceRange where+ range = id++instance HasRange (Lexeme t) where+ range = lexemeRange++instance (HasRange a, HasRange b) => HasRange (Either a b) where+ range (Left x) = range x+ range (Right x) = range x++(<->) :: (HasRange a, HasRange b) => a -> b -> SourceRange+x <-> y = SourceRange { sourceFrom = sourceFrom (range x)+ , sourceTo = sourceTo (range y)+ }++--------------------------------------------------------------------------------++-- | An action to be taken when a regular expression matchers.+newtype Action s a = A { runA :: Input -> Input -> Int -> s -> (s, a) }++instance Functor (Action s) where+ fmap = liftM++instance Applicative (Action s) where+ pure a = A (\_ _ _ s -> (s,a))+ (<*>) = ap++instance Monad (Action s) where+ return = pure+ A m >>= f = A (\i1 i2 l s -> let (s1,a) = m i1 i2 l s+ A m1 = f a+ in m1 i1 i2 l s1)++-- | Acces the input just before the regular expression started matching.+startInput :: Action s Input+startInput = A (\i1 _ _ s -> (s,i1))++-- | Acces the input just after the regular expression that matched.+endInput :: Action s Input+endInput = A (\_ i2 _ s -> (s,i2))++-- | The number of characters in the matching input.+matchLength :: Action s Int+matchLength = A (\_ _ l s -> (s,l))++-- | Acces the curent state of the lexer.+getLexerState :: Action s s+getLexerState = A (\_ _ _ s -> (s,s))++-- | Change the state of the lexer.+setLexerState :: s -> Action s ()+setLexerState s = A (\_ _ _ _ -> (s,()))++-- | Get the range for the matching input.+matchRange :: Action s SourceRange+matchRange =+ do i1 <- startInput+ i2 <- endInput+ return (inputPos i1 <-> inputPrev i2)++-- | Get the text associated with the matched input.+matchBytes :: Action s ByteString+matchBytes =+ do i1 <- startInput+ n <- matchLength+ return (BS.take n (inputBytes i1))++-- | Use the token and the current match to construct a lexeme.+lexeme :: t -> Action s [Lexeme t]+lexeme tok =+ do r <- matchRange+ txt <- matchBytes+ let l = Lexeme { lexemeRange = r+ , lexemeToken = tok+ , lexemeBytes = txt+ }+ l `seq` return [ l ]++-- | Information about the lexer's input.+data Input = Input+ { inputPos :: {-# UNPACK #-} !SourcePos+ -- ^ Current input position.++ , inputBytes :: {-# UNPACK #-} !ByteString+ -- ^ The text that needs to be lexed.++ , inputPrev :: {-# UNPACK #-} !SourcePos+ -- ^ Location of the last consumed character.++ , inputPrevByte :: {-# UNPACK #-} !Word8+ -- ^ The last consumed character.+ }++-- | Prepare the text for lexing.+initialInput :: Text {- ^ Where the text came from -} ->+ ByteString {- ^ The text to lex -} -> Input+initialInput file str = Input+ { inputPos = startPos file+ , inputPrev = beforeStartPos file+ , inputPrevByte = 0+ , inputBytes = str+ }++startPos :: Text {- ^ Name of file/thing containing this -} -> SourcePos+startPos file = SourcePos { sourceIndex = 0, sourceFile = file }++beforeStartPos :: Text -> SourcePos+beforeStartPos file = SourcePos { sourceIndex = -1, sourceFile = file }++{- | Move one position back. Assumes that newlines use a single bytes.++This function is intended to be used when starting the lexing somewhere+in the middle of the input, for example, if we are implementing a quasi+quoter, and the previous part of the input is not in our language.+-}+prevPos :: SourcePos -> SourcePos+prevPos p+ | sourceIndex p < 0 = p+ | otherwise = p { sourceIndex = sourceIndex p - 1 }++-- | The file/thing for the current position.+inputFile :: Input -> Text+inputFile = sourceFile . inputPos++--------------------------------------------------------------------------------+-- | Lexer configuration.+data LexerConfig s t = LexerConfig+ { lexerInitialState :: s+ -- ^ State that the lexer starts in++ , lexerStateMode :: s -> Int+ -- ^ Determine the current lexer mode from the lexer's state.++ , lexerEOF :: s -> SourcePos -> [Lexeme t]+ -- ^ Emit some lexemes at the end of the input.+ }++-- | A lexer that uses no lexer-modes, and does not emit anything at the+-- end of the file.+simpleLexer :: LexerConfig () t+simpleLexer = LexerConfig+ { lexerInitialState = ()+ , lexerStateMode = \_ -> 0+ , lexerEOF = \_ _ -> []+ }+++-- | Generate a function to use an Alex lexer.+-- The expression is of type @LexerConfig s t -> Input -> [Lexeme t]@+makeLexer :: ExpQ+makeLexer =+ do let local = do n <- newName "x"+ return (varP n, varE n)++ ([xP,yP,zP], [xE,yE,zE]) <- unzip <$> replicateM 3 local++ let -- Defined by Alex+ alexEOF = conP (mkName "AlexEOF") [ ]+ alexError = conP (mkName "AlexError") [ wildP ]+ alexSkip = conP (mkName "AlexSkip") [ xP, wildP ]+ alexToken = conP (mkName "AlexToken") [ xP, yP, zP ]+ alexScanUser = varE (mkName "alexScanUser")++ let p ~> e = match p (normalB e) []+ body go mode inp cfg =+ caseE [| $alexScanUser $mode $inp (lexerStateMode $cfg $mode) |]+ [ alexEOF ~> [| lexerEOF $cfg $mode (inputPrev $inp) |]+ , alexError ~>+ [| error "internal error in lexer (AlexToolsBin.hs)" |]+ , alexSkip ~> [| $go $mode $xE |]+ , alexToken ~> [| case runA $zE $inp $xE $yE $mode of+ (mode', ts) -> ts ++ $go mode' $xE |]+ ]++ [e| \cfg -> let go mode inp = $(body [|go|] [|mode|] [|inp|] [|cfg|])+ in go (lexerInitialState cfg) |]++type AlexInput = Input++{-# INLINE alexInputPrevChar #-}+alexInputPrevChar :: AlexInput -> Char+alexInputPrevChar = toEnum . fromEnum . inputPrevByte++{-# INLINE alexGetByte #-}+alexGetByte :: AlexInput -> Maybe (Word8,AlexInput)+alexGetByte inp =+ do (b,bs) <- BS.uncons (inputBytes inp)+ let inp1 = Input { inputPrev = inputPos inp+ , inputPrevByte = b+ , inputPos = moveSourcePos (inputPos inp)+ , inputBytes = bs+ }+ inp1 `seq` pure (b,inp1)++