scanf 0.1.0.0 → 0.2.0.0
raw patch · 7 files changed
+192/−21 lines, 7 filesdep +containersdep ~template-haskell
Dependencies added: containers
Dependency ranges changed: template-haskell
Files
- CHANGELOG.md +11/−0
- README.md +10/−7
- scanf.cabal +5/−4
- src/Text/Scanf.hs +2/−0
- src/Text/Scanf/Internal.hs +18/−1
- src/Text/Scanf/TH.hs +88/−9
- test/example.hs +58/−0
+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# Changelog for scanf++See [latest version of this changelog](https://github.com/Lysxia/scanf/blob/master/CHANGELOG.md).++## 0.2.0.0 - 2026-11-07++- Add scansets: `%[abc]` matches a string with only the characters `a`, `b`, `c`.++## 0.1.0.0 - 2018-03-18++- Create scanf
README.md view
@@ -1,5 +1,7 @@-# Haskell scanf [](https://travis-ci.org/Lysxia/scanf)+# Haskell scanf +[](https://github.com/Lysxia/workflows/actions?query=branch%3Amaster) [](https://hackage.haskell.org/package/scanf)+ A lightweight library for one-off parsing and printing. ```haskell@@ -19,12 +21,13 @@ 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+- `%d` : signed integer (`Int`)+- `%l` : signed integer (`Integer`, unbounded)+- `%f` : floating point (`Double`)+- `%s` : string of non-space characters (`String`)+- `%[...]`: [Scanset](https://www.man7.org/linux/man-pages/man3/sscanf.3.html) (`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.
scanf.cabal view
@@ -1,5 +1,5 @@ name: scanf-version: 0.1.0.0+version: 0.2.0.0 synopsis: Easy and type-safe format strings for parsing and printing description: @@ -15,9 +15,9 @@ copyright: 2018 Li-yao Xia category: Text build-type: Simple-extra-source-files: README.md+extra-source-files: README.md, CHANGELOG.md cabal-version: >=1.10-tested-with: GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.1+tested-with: GHC == 8.0.2, GHC == 8.4.4, GHC == 9.12.2 library hs-source-dirs: src@@ -26,7 +26,8 @@ Text.Scanf.Internal Text.Scanf.TH build-depends:- template-haskell,+ template-haskell < 3,+ containers >= 0.6.6 && < 1, base >= 4.7 && < 5 ghc-options: -Wall default-language: Haskell2010
src/Text/Scanf.hs view
@@ -5,6 +5,7 @@ module Text.Scanf ( -- * Main definitions scanf+ , scanf' , printf , (:+)(..) , fmt@@ -17,6 +18,7 @@ , int , double , string+ , scanset , char ) where
src/Text/Scanf/Internal.hs view
@@ -6,6 +6,7 @@ import Data.Char (isSpace) import Data.List (stripPrefix)+import Data.Set (Set) -- | A pretty pair type to build lists with values of different types. -- Remember to close lists with @()@.@@ -26,7 +27,8 @@ 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+ String :: Format t -> Format (String :+ t) -- whitespace delimited+ Scanset :: Set Char -> Bool -> Format t -> Format (String :+ t) -- custom delimiter Char :: Format t -> Format (Char :+ t) deriving instance Show (Format t)@@ -96,6 +98,11 @@ string :: Format t -> Format (String :+ t) string = String +-- | Format a scanset 'String', given a set of characters to include if second argument+-- | is @True@, excluding them otherwise.+scanset :: Set Char -> Bool -> Format t -> Format (String :+ t)+scanset = Scanset+ -- | Format a 'Char'. char :: Format t -> Format (Char :+ t) char = Char@@ -113,6 +120,9 @@ readsFormat (String f) s = do let (s0, s') = break isSpace s readmap (s0 :+) (readsFormat f) s'+readsFormat (Scanset set inc f) s = do+ let (s0, s') = break (if inc then not . flip elem set else flip elem set) s+ readmap (s0 :+) (readsFormat f) s' readsFormat (Char f) s = do c : s' <- pure s readmap (c :+) (readsFormat f) s'@@ -132,6 +142,12 @@ (r, _) : _ <- pure (readsFormat f s) pure r +-- | Same as scanf, but returns the unparsed string as well.+scanf' :: Format t -> String -> Maybe (t, String)+scanf' f s = do+ x : _ <- pure (readsFormat f s)+ pure x+ -- | Print a string according to a format string. -- -- @@@ -142,6 +158,7 @@ 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 (Scanset _ _ 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
@@ -10,23 +10,102 @@ import Text.Scanf.Internal +import Data.Set (Set, empty, insert, singleton)++import Numeric (showHex)+ formatString :: String -> Q Exp-formatString "" = [|Empty|]-formatString ('%' : s1) =+formatString ('\\' : s1) = formatString' $ escape s1+formatString s1 = formatString' s1++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')|]+ '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' : s' -> [|string $(formatString s')|]+ '[' : s' -> mkScanset s' '%' : s' -> [|constant "%" $(formatString s')|] _ -> error "Invalid format string"-formatString s@(c : _) | isSpace c =+ where+ mkScanset :: String -> Q Exp+ mkScanset s' = [|scanset set inc $(formatString s'')|]+ where+ set :: Set Char+ inc :: Bool+ s'' :: String++ ((set, s''), inc) = parseScanset s'++ parseScanset :: String -> ((Set Char, String), Bool)+ parseScanset [] = error "Unterminated scanset"+ parseScanset ['^', ']'] = error "Unterminated scanset"+ parseScanset ('^' : ']' : t'') = (parseScanset' (singleton ']') t'', False)+ parseScanset ('^' : t'') = (parseScanset' empty t'', False)+ parseScanset (']' : t'') = (parseScanset' (singleton ']') t'', True)+ parseScanset t'' = (parseScanset' empty t'', True)++ parseScanset' :: Set Char -> String -> (Set Char, String)+ parseScanset' s ('\\' : t'') = parseScanset'' s $ escape t''+ parseScanset' s t'' = parseScanset'' s t''++ parseScanset'' :: Set Char -> String -> (Set Char, String)+ parseScanset'' _ [] = error "Unterminated scanset"+ parseScanset'' s (']' : t'') = (s, t'')+ parseScanset'' s (c1 : '-' : ']' : t'') = (insert '-' $ insert c1 s , t'')+ parseScanset'' s (c1 : '-' : c2 : t'') =+ case c2 of+ '\\' -> parseScanset' (foldr insert s [c1..c2']) t'''+ _ -> parseScanset' (foldr insert s [c1..c2]) t''+ where+ (c2',t''') = escape' t''+ parseScanset'' s (c : t'') = parseScanset' (insert c s) t''++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+formatString' s =+ let (s0, s') = break (\c -> isSpace c || c == '%' || c == '\\') s in [|constant $(lift s0) $(formatString s')|]++-- Escapes the first character(s) in the input string.+escape :: String -> String+escape s = c : s'+ where (c,s') = escape' s++escape' :: String -> (Char,String)+escape' "" = error "Can't escape an empty string"+escape' (c : s) =+ case c of+ '0' -> (toEnum 0x00 , s)+ 'a' -> (toEnum 0x07 , s)+ 'b' -> (toEnum 0x08 , s)+ 'e' -> (toEnum 0x1b , s)+ 'f' -> (toEnum 0x0c , s)+ 'n' -> ('\n' , s)+ 'r' -> (toEnum 0x0d , s)+ 't' -> ('\t' , s)+ 'v' -> (toEnum 0x0b , s)+ 'x' -> escapeHex s+ '\'' -> ('\'' , s)+ '"' -> ('\"' , s)+ '?' -> (toEnum 0x3f , s)+ '\\' -> ('\\' , s)+ _ -> error $ "Unknown escape sequence " ++ "(0x" ++ showHex (fromEnum c) "): '" ++ [c] ++ "'"+ where+ -- Replaces two hex digits with a character of that value.+ escapeHex :: String -> (Char,String)+ escapeHex (c1 : c0 : t) = ((toEnum $ 16 * fromHexDigit c1 + fromHexDigit c0) , t)+ where+ fromHexDigit :: Char -> Int+ fromHexDigit hc | '0' <= hc && hc <= '9' = fromEnum hc - fromEnum '0'+ | 'A' <= hc && hc <= 'F' = 10 + fromEnum hc - fromEnum 'A'+ | 'a' <= hc && hc <= 'f' = 10 + fromEnum hc - fromEnum 'a'+ | otherwise = error $ "Invalid hex digit in \\x sequence: " ++ [hc]+ escapeHex _ = error "Missing 2-digit hexadecimal number following \\x." -- | Parse a typed 'Format' string. --
test/example.hs view
@@ -20,11 +20,69 @@ scanf [fmt|%s %s|] " a b" =? Just ("" :+ "a" :+ ()) + scanf [fmt|%[ ab]|] " a b"+ =? Just (" a b" :+ ())++ scanf [fmt|%[^ ab]|] "c a b"+ =? Just ("c" :+ ())++ scanf [fmt|%[]]|] "]]]]]]]a]]]]]"+ =? Just ("]]]]]]]" :+ ())++ scanf [fmt|%[^]]|] "aaaaaaa]aaaaa"+ =? Just ("aaaaaaa" :+ ())++ scanf [fmt|%[a-d]|] "aabaabcbaabcdcbaabcdedcba"+ =? Just ("aabaabcbaabcdcbaabcd" :+ ())++ scanf [fmt|%[^\n]|] "aabaabcbaabc\ndcbaabcdedcba"+ =? Just ("aabaabcbaabc" :+ ())+ scanf [fmt|%%|] "%" =? Just () scanf [fmt|a a|] "a a b" =? Just ()++ scanf [fmt|\n\n\n%[\x08\x09]|] "\n\n\n\x08\x09\x09"+ =? Just ("\x08\x09\x09" :+ ())++ scanf [fmt|\n\n\n%[\x08\x09]|] "\n\n\n\x08\x09\x09"+ =? Just ("\x08\x09\x09" :+ ())++ scanf [fmt|%[^\\\'\a]|] "abcdef\\\'"+ =? Just ("abcdef" :+ ())++ scanf [fmt|\a\b\t\x6c\x13%[^\b]|] "\a\b\tl\x13 1234567890\x12\b"+ =? Just (" 1234567890\x12" :+ ())++ scanf [fmt|\a\b\t\x6c\x13 %[^\b]|] "\a\b\tl\x13 \n\n\t\x09\x020 1234567890\x12\b"+ =? Just ("1234567890\x12" :+ ())++ scanf [fmt|This is an alphanumeric string: %[a-z0-9]|] "This is an alphanumeric string: abcdefghijklmnopqrstuv0123456789"+ =? Just ("abcdefghijklmnopqrstuv0123456789" :+ ())++ scanf [fmt|test: %[\x00-\x10]|] "test: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11"+ =? Just ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" :+ ())++ scanf [fmt|test: %[\\-\\]|] "test: \\\\\\\\\\\\\a"+ =? Just ("\\\\\\\\\\\\" :+ ())++ scanf [fmt|test: %[\t-\n]|] "test: \t\n\t\n\t\n\t"+ =? Just ("" :+ ())++ scanf [fmt|test:%[ ]%[\t-\n]:test2|] "test: \t\n\t\n\t\n\t:test2"+ =? Just (" " :+ "\t\n\t\n\t\n\t" :+ ())++ scanf [fmt|%[%b-a]|] "%%%ababbaa"+ =? Just ("%%%" :+ ())++ scanf [fmt|%[^%b-a]|] "ababbaa%%%"+ =? Just ("ababbaa" :+ ())++ scanf [fmt|%[\x2C\x2b]|] "\x2C\x2c\x2B\x2b"+ =? Just ("\x2c\x2C\x2b\x2B" :+ ())+ let s0 = "10-11-12 Fri 4.3C" f0 = [fmt|%d-%d-%l %s %f %c|]