scanf (empty) → 0.1.0.0
raw patch · 8 files changed
+393/−0 lines, 8 filesdep +basedep +scanfdep +template-haskellsetup-changed
Dependencies added: base, scanf, template-haskell
Files
- LICENSE +19/−0
- README.md +42/−0
- Setup.hs +2/−0
- scanf.cabal +46/−0
- src/Text/Scanf.hs +24/−0
- src/Text/Scanf/Internal.hs +147/−0
- src/Text/Scanf/TH.hs +55/−0
- test/example.hs +58/−0
+ LICENSE view
@@ -0,0 +1,19 @@+Copyright Li-yao Xia (c) 2018++Permission is hereby granted, free of charge, to any person obtaining a copy of+this software and associated documentation files (the “Software”), to deal in+the Software without restriction, including without limitation the rights to+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies+of the Software, and to permit persons to whom the Software is furnished to do+so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,42 @@+# Haskell scanf [](https://travis-ci.org/Lysxia/scanf)++A lightweight library for one-off parsing and printing.++```haskell+scanf [fmt|%d lazy %s and %d strict %s|]+ "3 lazy functions and 2 strict fields"+ = Just (3 :+ "functions" :+ 2 :+ "fields" :+ ())+```++Quasiquotes are optional. Format strings can also be defined with pure+Haskell combinators.++```haskell+printf (fmt_ (int . " lazy " % string . " and " % int . " strict " % string)+ (8 :+ "dogs" :+ 9 :+ "cats" :+ ())+ = "8 lazy dogs and 9 strict cats"+```++With quasiquotes, the following conversion strings are supported:++- `%d`: signed integer (`Int`)+- `%l`: signed integer (`Integer`, unbounded)+- `%f`: floating point (`Double`)+- `%s`: string of non-space characters (`String`)+- `%c`: single character (`Char`)+- `%%`: parse/print a literal percent character++N.B.: in `scanf`, spaces in the format string match any number of whitespace+character until the next nonspace character.++---++Though it also offers a `printf` function, *scanf* is thus named as+there are only few other preexisting Haskell implementations,+whereas many Haskell *printf* libraries can already be found on Hackage.++Similar to:++- [xformat](https://hackage.haskell.org/package/xformat) (scanf and printf)+- [fmt](https://hackage.haskell.org/package/fmt) (printf)+- [formatting](https://hackage.haskell.org/package/formatting) (printf)
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ scanf.cabal view
@@ -0,0 +1,46 @@+name: scanf+version: 0.1.0.0+synopsis: Easy and type-safe format strings for parsing and printing+description:++ A lightweight library for one-off parsing and printing.+ .+ See README.++homepage: https://github.com/Lysxia/scanf#readme+license: MIT+license-file: LICENSE+author: Li-yao Xia+maintainer: lysxia@gmail.com+copyright: 2018 Li-yao Xia+category: Text+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10+tested-with: GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.1++library+ hs-source-dirs: src+ exposed-modules:+ Text.Scanf+ Text.Scanf.Internal+ Text.Scanf.TH+ build-depends:+ template-haskell,+ base >= 4.7 && < 5+ ghc-options: -Wall+ default-language: Haskell2010++test-suite example-test+ hs-source-dirs: test+ main-is: example.hs+ build-depends:+ scanf,+ base+ ghc-options: -Wall+ default-language: Haskell2010+ type: exitcode-stdio-1.0++source-repository head+ type: git+ location: https://github.com/Lysxia/scanf
+ src/Text/Scanf.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE TypeOperators #-}++-- | Dead simple and type-safe @'scanf'@/@'printf'@.++module Text.Scanf+ ( -- * Main definitions+ scanf+ , printf+ , (:+)(..)+ , fmt+ , fmt_+ , Format++ -- * Format constructors+ , (%)+ , integer+ , int+ , double+ , string+ , char+ ) where++import Text.Scanf.Internal+import Text.Scanf.TH
+ src/Text/Scanf/Internal.hs view
@@ -0,0 +1,147 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeOperators #-}++module Text.Scanf.Internal where++import Data.Char (isSpace)+import Data.List (stripPrefix)++-- | A pretty pair type to build lists with values of different types. +-- Remember to close lists with @()@.+--+-- @+-- 3 ':+' "14" ':+' () :: 'Int' ':+' 'String' ':+' ()+-- @+data a :+ b = a :+ b+ deriving (Eq, Ord, Show)++infixr 1 :+++-- | Typed @'scanf'@/@'printf'@ format strings.+-- They can be built using the 'Text.Scanf.fmt' quasiquote or+-- with the 'fmt_' function and format combinators.+data Format t where+ Empty :: Format ()+ Constant :: String -> Format t -> Format t+ Whitespace :: String -> Format t -> Format t+ Readable :: (Read a, Show a) => Format t -> Format (a :+ t)+ String :: Format t -> Format (String :+ t) -- whitespace delimited+ Char :: Format t -> Format (Char :+ t)++deriving instance Show (Format t)++emptyFmt :: Format ()+emptyFmt = Empty++-- | Construct a format string. This is an alternative to 'Text.Scanf.fmt'+-- that doesn't rely on Template Haskell.+--+-- The components of a format string are composed using @('.')@ (function+-- composition) and @('%')@ (wrapper for constant strings).+--+-- @+-- 'fmt_' ('int' '.' \" lazy \" '%' 'string' '.' \" and \" '%' 'int' '.' \" strict \" '%' 'string')+-- :: 'Format' ('Int' ':+' 'String' ':+' 'Int' ':+' 'String' ':+' ())+-- @+fmt_ :: (Format () -> Format t) -> Format t+fmt_ f = f emptyFmt++-- | Append a constant string to a format string component.+--+-- N.B.: in 'scanf', spaces in the format string match any number of whitespace+-- character until the next nonspace character.+(%) :: String -> (Format t -> Format q) -> Format t -> Format q+(%) s f = constant s . f++infixr 9 %++-- | Append a constant string to a format string.+constant :: String -> Format t -> Format t+constant "" f = f+constant s@(c : _) f | isSpace c =+ let (s0, s') = span isSpace s+ in whitespace s0 (constant s' f)+constant s f =+ let (s0, s') = break isSpace s+ in constant' s0 (constant s' f)++-- | Append a constant string with no whitespace to a format string.+constant' :: String -> Format t -> Format t+constant' s (Constant s' f) = Constant (s ++ s') f+constant' "" f = f+constant' s f = Constant s f++-- | Append a constant whitespace string to a format string.+whitespace :: String -> Format t -> Format t+whitespace s (Whitespace s' f) = Whitespace (s ++ s') f+whitespace s f = Whitespace s f++readable :: (Read a, Show a) => Format t -> Format (a :+ t)+readable = Readable++-- | Format an 'Integer'.+integer :: Format t -> Format (Integer :+ t)+integer = Readable++-- | Format an 'Int'.+int :: Format t -> Format (Int :+ t)+int = Readable++-- | Format a 'Double'.+double :: Format t -> Format (Double :+ t)+double = Readable++-- | Format a 'String'.+string :: Format t -> Format (String :+ t)+string = String++-- | Format a 'Char'.+char :: Format t -> Format (Char :+ t)+char = Char++readmap :: (a -> b) -> ReadS a -> ReadS b+readmap f = (fmap . fmap) (\(r, s) -> (f r, s))++readsFormat :: Format t -> ReadS t+readsFormat (Constant z f) s = do+ Just s' <- pure (stripPrefix z s)+ readsFormat f s'+readsFormat (Readable f) s = do+ (a, s') <- readsPrec 0 s+ readmap (a :+) (readsFormat f) s'+readsFormat (String f) s = do+ let (s0, s') = break isSpace s+ readmap (s0 :+) (readsFormat f) s'+readsFormat (Char f) s = do+ c : s' <- pure s+ readmap (c :+) (readsFormat f) s'+readsFormat (Whitespace _ f) s = do+ let s' = dropWhile isSpace s+ readsFormat f s'+readsFormat Empty s = pure ((), s)++-- | Parse a string according to a format string.+--+-- @+-- 'scanf' ['Text.Scanf.fmt'|Hello %s|] \"Hello world!\" :: 'Maybe' ('String' ':+' ())+-- = (\"world!\" ':+' ())+-- @+scanf :: Format t -> String -> Maybe t+scanf f s = do+ (r, _) : _ <- pure (readsFormat f s)+ pure r++-- | Print a string according to a format string.+--+-- @+-- 'printf' ['Text.Scanf.fmt'|Hello %s|] (\"everyone!\" ':+' ())+-- = \"Hello everyone!\" +-- @+printf :: Format t -> t -> String+printf (Constant z f) t = z ++ printf f t+printf (Readable f) (a :+ t) = show a ++ printf f t+printf (String f) (s :+ t) = s ++ printf f t+printf (Char f) (c :+ t) = c : printf f t+printf (Whitespace s f) t = s ++ printf f t+printf Empty _ = ""
+ src/Text/Scanf/TH.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE TemplateHaskell #-}++module Text.Scanf.TH where++import Data.Char (isSpace)++import Language.Haskell.TH+import Language.Haskell.TH.Syntax+import Language.Haskell.TH.Quote++import Text.Scanf.Internal++formatString :: String -> Q Exp+formatString "" = [|Empty|]+formatString ('%' : s1) =+ case s1 of+ 'c' : s' -> [|char $(formatString s')|]+ 'd' : s' -> [|int $(formatString s')|]+ 'f' : s' -> [|double $(formatString s')|]+ 'l' : s' -> [|integer $(formatString s')|]+ 's' : s' -> [|string $(formatString s')|]+ '%' : s' -> [|constant "%" $(formatString s')|]+ _ -> error "Invalid format string"+formatString s@(c : _) | isSpace c =+ let (s0, s') = span isSpace s+ in [|whitespace $(lift s0) $(formatString s')|]+formatString s =+ let (s0, s') = break (\c -> isSpace c || c == '%') s+ in [|constant $(lift s0) $(formatString s')|]++-- | Parse a typed 'Format' string.+--+-- The following conversion strings are supported:+--+-- - @%d@: signed integer ('Int')+-- - @%l@: signed integer ('Integer', unbounded)+-- - @%f@: floating point ('Double')+-- - @%s@: string of non-space characters ('String')+-- - @%c@: single character ('Char')+-- - @%%@: parse/print a literal percent character+--+-- N.B.: in 'scanf', spaces in the format string match any number of whitespace+-- character until the next nonspace character.+--+-- @+-- ['fmt'|%d lazy %s and %d strict %s|]+-- :: 'Format' ('Int' ':+' 'String' ':+' 'Int' ':+' 'String' ':+' ())+-- @+fmt :: QuasiQuoter+fmt = QuasiQuoter+ { quoteExp = formatString+ , quotePat = undefined+ , quoteType = undefined+ , quoteDec = undefined+ }
+ test/example.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE QuasiQuotes #-}++import Text.Scanf++main :: IO ()+main = do+ scanf [fmt|%c|] "34"+ =? Just ('3' :+ ())++ scanf [fmt|%d|] "10"+ =? Just (10 :+ ())++ scanf [fmt|%f|] "4.3"+ =? Just (4.3 :+ ())++ scanf [fmt| %l|]+ " 1000000000000000000000000000000000"+ =? Just (1000000000000000000000000000000000 :+ ())++ scanf [fmt|%s %s|] " a b"+ =? Just ("" :+ "a" :+ ())++ scanf [fmt|%%|] "%"+ =? Just ()++ scanf [fmt|a a|] "a a b"+ =? Just ()++ let s0 = "10-11-12 Fri 4.3C"+ f0 = [fmt|%d-%d-%l %s %f %c|]+ f0' = fmt_ (int . "-" % int . "-" % integer . " " % string . " " % double . " " % char)+ r0 = Just+ ((10 :: Int) :+ (11 :: Int) :+ (12 :: Integer)+ :+ "Fri" :+ (4.3 :: Double) :+ 'C' :+ ())+ scanf f0 s0 =? r0+ scanf f0' s0 =? r0++ let s1 = "3 lazy functions and 4 strict fields"+ f1 = [fmt|%d lazy %s and %d strict %s|]+ f1' = fmt_ (int . " lazy " % string . " and " % int . " strict " % string)+ r1 = Just ((3 :: Int) :+ "functions" :+ (4 :: Int) :+ "fields" :+ ())+ p1 = (2 :: Int) :+ "dogs" :+ (3 :: Int) :+ "cats" :+ ()+ q1 = "2 lazy dogs and 3 strict cats"+ scanf f1 s1 =? r1+ scanf f1' s1 =? r1++ printf f1 p1 =? q1+ printf f1' p1 =? q1++assertEq, (=?) :: (Eq a, Show a) => a -> a -> IO ()+assertEq a b = do+ if a == b then+ return ()+ else+ fail $ show a ++ " /= " ++ show b++(=?) = assertEq+