packages feed

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 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 [![Build Status](https://travis-ci.org/Lysxia/scanf.svg?branch=master)](https://travis-ci.org/Lysxia/scanf)+# Haskell scanf +[![Build Status](https://github.com/Lysxia/scanf/workflows/Haskell-CI/badge.svg)](https://github.com/Lysxia/workflows/actions?query=branch%3Amaster) [![Hackage](https://img.shields.io/hackage/v/scanf.svg)](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|]