journal: factor out rawnumberp
This commit is contained in:
parent
bcf7a1add5
commit
dafdaec1ca
@ -23,7 +23,6 @@ import Prelude.Compat hiding (readFile)
|
||||
import Control.Monad.Compat
|
||||
import Control.Monad.Except (ExceptT(..), runExceptT, throwError) --, catchError)
|
||||
import Control.Monad.State.Strict
|
||||
import Data.Char (isNumber)
|
||||
import Data.Data
|
||||
import Data.Default
|
||||
import Data.Functor.Identity
|
||||
@ -527,53 +526,65 @@ fixedlotpricep =
|
||||
--
|
||||
numberp :: TextParser m (Quantity, Int, Maybe Char, Maybe DigitGroupStyle)
|
||||
numberp = do
|
||||
-- a number is an optional sign followed by a sequence of digits possibly
|
||||
-- interspersed with periods, commas, or both
|
||||
-- ptrace "numberp"
|
||||
sign <- signp
|
||||
parts <- some $ choice' [some digitChar, some $ oneOf ['.', ',']]
|
||||
dbg8 "numberp parsed" (sign,parts) `seq` return ()
|
||||
-- a number is an optional sign followed by a sequence of digits possibly
|
||||
-- interspersed with periods, commas, or both
|
||||
-- ptrace "numberp"
|
||||
sign <- signp
|
||||
raw <- rawnumberp
|
||||
dbg8 "numberp parsed" raw `seq` return ()
|
||||
return $ dbg8 "numberp quantity,precision,mdecimalpoint,mgrps" (fromRawNumber (sign == "-") raw)
|
||||
<?> "numberp"
|
||||
|
||||
-- check the number is well-formed and identify the decimal point and digit
|
||||
-- group separator characters used, if any
|
||||
let (numparts, puncparts) = partition numeric parts
|
||||
(ok, mdecimalpoint, mseparator) =
|
||||
case (numparts, puncparts) of
|
||||
([],_) -> (False, Nothing, Nothing) -- no digits, not ok
|
||||
(_,[]) -> (True, Nothing, Nothing) -- digits with no punctuation, ok
|
||||
(_,[[d]]) -> (True, Just d, Nothing) -- just a single punctuation of length 1, assume it's a decimal point
|
||||
(_,[_]) -> (False, Nothing, Nothing) -- a single punctuation of some other length, not ok
|
||||
(_,_:_:_) -> -- two or more punctuations
|
||||
let (s:ss, d) = (init puncparts, last puncparts) -- the leftmost is a separator and the rightmost may be a decimal point
|
||||
in if any ((/=1).length) puncparts -- adjacent punctuation chars, not ok
|
||||
|| any (s/=) ss -- separator chars vary, not ok
|
||||
|| head parts == s -- number begins with a separator char, not ok
|
||||
then (False, Nothing, Nothing)
|
||||
else if s == d
|
||||
then (True, Nothing, Just $ head s) -- just one kind of punctuation - must be separators
|
||||
else (True, Just $ head d, Just $ head s) -- separator(s) and a decimal point
|
||||
unless ok $ fail $ "number seems ill-formed: "++concat parts
|
||||
fromRawNumber :: Bool -> (Maybe Char, [String], Maybe (Char, String)) -> (Quantity, Int, Maybe Char, Maybe DigitGroupStyle)
|
||||
fromRawNumber negated raw = (quantity, precision, mdecimalpoint, mgrps) where
|
||||
-- unpack with a hint if useful
|
||||
(mseparator, intparts, mdecimalpoint, frac) =
|
||||
case raw of
|
||||
-- just a single punctuation between two digits groups, assume it's a decimal point
|
||||
(Just s, [firstGroup, lastGroup], Nothing)
|
||||
-> (Nothing, [firstGroup], Just s, lastGroup)
|
||||
|
||||
-- get the digit group sizes and digit group style if any
|
||||
let (intparts',fracparts') = span ((/= mdecimalpoint) . Just . head) parts
|
||||
(intparts, fracpart) = (filter numeric intparts', filter numeric fracparts')
|
||||
groupsizes = reverse $ case map length intparts of
|
||||
(firstSep, digitGroups, Nothing) -> (firstSep, digitGroups, Nothing, [])
|
||||
(firstSep, digitGroups, Just (d, frac)) -> (firstSep, digitGroups, Just d, frac)
|
||||
|
||||
-- get the digit group sizes and digit group style if any
|
||||
groupsizes = reverse $ case map length intparts of
|
||||
(a:b:cs) | a < b -> b:cs
|
||||
gs -> gs
|
||||
mgrps = (`DigitGroups` groupsizes) <$> mseparator
|
||||
mgrps = (`DigitGroups` groupsizes) <$> mseparator
|
||||
|
||||
-- put the parts back together without digit group separators, get the precision and parse the value
|
||||
let int = concat $ "":intparts
|
||||
frac = concat $ "":fracpart
|
||||
precision = length frac
|
||||
int' = if null int then "0" else int
|
||||
frac' = if null frac then "0" else frac
|
||||
quantity = read $ sign++int'++"."++frac' -- this read should never fail
|
||||
-- put the parts back together without digit group separators, get the precision and parse the value
|
||||
repr = (if negated then "-" else "") ++ "0" ++ concat intparts ++ (if null frac then "" else "." ++ frac)
|
||||
quantity = read repr
|
||||
precision = length frac
|
||||
|
||||
rawnumberp :: TextParser m (Maybe Char, [String], Maybe (Char, String))
|
||||
rawnumberp = do
|
||||
let sepChars = ['.', ','] -- all allowed punctuation characters
|
||||
|
||||
(firstSep, groups) <- option (Nothing, []) $ do
|
||||
leadingDigits <- some digitChar
|
||||
option (Nothing, [leadingDigits]) . try $ do
|
||||
firstSep <- oneOf sepChars
|
||||
groups <- some digitChar `sepBy1` char firstSep
|
||||
return (Just firstSep, leadingDigits : groups)
|
||||
|
||||
let remSepChars = maybe sepChars (`delete` sepChars) firstSep
|
||||
modifier
|
||||
| null groups = fmap Just -- if no digits so far, we require at least some decimals
|
||||
| otherwise = optional
|
||||
|
||||
extraGroup <- modifier $ do
|
||||
lastSep <- oneOf remSepChars
|
||||
digits <- modifier $ some digitChar -- decimal separator allowed to be without digits if had some before
|
||||
return (lastSep, fromMaybe [] digits)
|
||||
|
||||
-- make sure we didn't leading part of mistyped number
|
||||
notFollowedBy $ oneOf sepChars
|
||||
|
||||
return $ dbg8 "rawnumberp" (firstSep, groups, extraGroup)
|
||||
<?> "rawnumberp"
|
||||
|
||||
return $ dbg8 "numberp quantity,precision,mdecimalpoint,mgrps" (quantity,precision,mdecimalpoint,mgrps)
|
||||
<?> "numberp"
|
||||
where
|
||||
numeric = isNumber . headDef '_'
|
||||
|
||||
-- test_numberp = do
|
||||
-- let s `is` n = assertParseEqual (parseWithState mempty numberp s) n
|
||||
|
||||
Loading…
Reference in New Issue
Block a user