packages feed

parser-regex-0.1.0.0: src/Regex/Internal/Num.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Regex.Internal.Num
  ( mkNaturalDec
  , mkWordDecN
  , mkWordRangeDec
  , mkNaturalHex
  , mkWordHexN
  , mkWordRangeHex
  , mkSignedInteger
  , mkSignedIntRange
  ) where

#include "MachDeps.h"

import Control.Applicative
import Control.Monad
import Data.Primitive.PrimArray
import Data.Bits
import Numeric.Natural

import GHC.Num.Natural as Nat

import Regex.Internal.Regex (RE)
import qualified Regex.Internal.Regex as R

mkNaturalDec
  :: (Word -> Word -> RE c Word)  -- Decimal digit range
  -> RE c Natural
mkNaturalDec d =
      0 <$ d 0 0
  <|> liftA2 finishDec (d 1 9) (R.foldlMany' stepDec state0 (d 0 9))
  where
    state0 = NatParseState 0 1 WNil
    -- Start with len=1, it's reserved for the leading digit
{-# INLINE mkNaturalDec #-}

mkNaturalHex
  :: (Word -> Word -> RE c Word)  -- Hexadecimal digit range
  -> RE c Natural
mkNaturalHex d =
      0 <$ d 0 0
  <|> liftA2 finishHex (d 1 15) (R.foldlMany' stepHex state0 (d 0 15))
  where
    state0 = NatParseState 0 1 WNil
    -- Start with len=1, it's reserved for the leading digit
{-# INLINE mkNaturalHex #-}

mkSignedInteger :: RE c minus -> RE c plus -> RE c Natural -> RE c Integer
mkSignedInteger minus plus rnat = signed <*> rnat
  where
    signed = negate . fromIntegral <$ minus
         <|> fromIntegral <$ plus
         <|> pure fromIntegral

mkWordDecN
  :: (Word -> Word -> RE c Word)  -- Decimal digit range
  -> Int
  -> RE c Word
mkWordDecN d n0
  | n0 <= 0 = empty
  | maxBoundWordDecLen <= n0 =
      replicateM_ (n0 - maxBoundWordDecLen) d00 *>
      (    d00 *> go (maxBoundWordDecLen - 1)
       <|> mkWordRangeDec d (pow10 safeWordDecLen, maxBound) )
  | otherwise = go n0
  where
    go 1 = d09
    go n = R.liftA2' (\x y -> x * 10 + y) (go (n-1)) d09
    d00 = d 0 0
    d09 = d 0 9
{-# INLINE mkWordDecN #-}

mkWordHexN
  :: (Word -> Word -> RE c Word)  -- Hexadecimal digit range
  -> Int
  -> RE c Word
mkWordHexN d n0
  | n0 <= 0 = empty
  | maxBoundWordHexLen < n0 =
      replicateM_ (n0 - maxBoundWordHexLen) d00 *> go maxBoundWordHexLen
  | otherwise = go n0
  where
    go 1 = d0f
    go n = R.liftA2' (\x y -> x * 16 + y) (go (n-1)) d0f
    d00 = d 0 0
    d0f = d 0 15
{-# INLINE mkWordHexN #-}

mkWordRangeDec
  :: (Word -> Word -> RE c Word)  -- Decimal digit range
  -> (Word, Word)  -- Low high
  -> RE c Word
mkWordRangeDec d (l,h) = mkWordRangeBase 10 quotRemPow10 pow10 len10 d l h
  where
    quotRemPow10 i x = x `quotRem` pow10 i
{-# INLINE mkWordRangeDec #-}

mkWordRangeHex
  :: (Word -> Word -> RE c Word)  -- Hexadecimal digit range
  -> (Word, Word)  -- Low high
  -> RE c Word
mkWordRangeHex d (l,h) = mkWordRangeBase 16 quotRemPow16 pow16 len16 d l h
  where
    quotRemPow16 i x = (x `unsafeShiftR` (4*i), x .&. (pow16 i - 1))
{-# INLINE mkWordRangeHex #-}

mkSignedIntRange
  :: RE c minus
  -> RE c plus
  -> ((Word, Word) -> RE c Word)  -- Word range
  -> (Int, Int)  -- Low high
  -> RE c Int
mkSignedIntRange minus plus wordRangeDec (low,high) = case (negR, nonNegR) of
  (Nothing, Nothing) -> empty
  (Nothing, Just r2) -> r2
  (Just r1, Nothing) -> r1
  (Just r1, Just r2) -> r1 <|> r2
  where
    negR
      | low > 0 = Nothing
      | otherwise = Just $
        minus *>
        R.fmap' (negate . fromIntegral)
                (wordRangeDec (absw (min 0 high), absw low))
    nonNegR
      | high < 0 = Nothing
      | otherwise = Just $
        (void plus <|> pure ()) *>
        R.fmap' fromIntegral
                (wordRangeDec (fromIntegral (max 0 low), fromIntegral high))
{-# INLINE mkSignedIntRange #-}

absw :: Int -> Word
absw x = if x == minBound
         then fromIntegral (abs (x+1) + 1)
         else fromIntegral (abs x)

-------------------
-- Parsing ranges
-------------------

-- Make a tree based on the range. Keep the tree size small where possible.
-- This is hard to explain in words, so see here for some pictures:
-- https://github.com/meooow25/parser-regex/wiki/Visualizations#int-range

mkWordRangeBase
  :: forall c.
     Word  -- Base
  -> (Int -> Word -> (Word, Word)) -- quotRemPowBase
  -> (Int -> Word)  -- powBase
  -> (Word -> Int)  -- baseLen
  -> (Word -> Word -> RE c Word)  -- Decimal digit range
  -> Word  -- Low
  -> Word  -- High
  -> RE c Word
mkWordRangeBase _ _ _ _ _ low high | low > high = empty
mkWordRangeBase base quotRemPowBase powBase baseLen d low high
  = goTop (baseLen high - 1) True low high
  where
    goTop :: Int -> Bool -> Word -> Word -> RE c Word
    goTop 0 _ l h = d l h
    goTop i lz l h
      | dl == dh       = leading pBase dh dh (goTop (i-1) False l' h')
      | fullL && fullH = leading pBase dl dh (goFull (i-1))
      | fullH          = leading pBase (dl+1) dh (goFull (i-1)) <|> reL
      | fullL          = reH <|> leading pBase dl (dh-1) (goFull (i-1))
      | dl + 1 == dh   = reH <|> reL
      | otherwise      = reH <|> reM <|> reL
      where
        pBase = powBase i
        (dl,l') = quotRemPowBase i l
        (dh,h') = quotRemPowBase i h
        lz' = lz && dl == 0
        fullL = not lz' && l' == 0
        fullH = h' + 1 == pBase
        reL = if lz'
              then goL (i-1) True l'
              else leading pBase dl dl (goL (i-1) False l')
        reH = leading pBase dh dh (goH (i-1) h')
        reM = leading pBase (dl+1) (dh-1) (goFull (i-1))

    goL :: Int -> Bool -> Word -> RE c Word
    goL 0 _ l = d l (base-1)
    goL i lz l
      | not lz && l == 0 = goFull i
      | dl == base-1     = reL
      | otherwise        = reM <|> reL
      where
        pBase = powBase i
        (dl,l') = quotRemPowBase i l
        reL = if lz && dl == 0
              then goL (i-1) True l'
              else leading pBase dl dl (goL (i-1) False l')
        reM = leading pBase (dl+1) (base-1) (goFull (i-1))

    goH :: Int -> Word -> RE c Word
    goH 0 h = d 0 h
    goH i h
      | h + 1 == pBase * base = goFull i
      | dh == 0               = reH
      | otherwise             = reH <|> reM
      where
        pBase = powBase i
        (dh,h') = quotRemPowBase i h
        reH = leading pBase dh dh (goH (i-1) h')
        reM = leading pBase 0 (dh-1) (goFull (i-1))

    goFull :: Int -> RE c Word
    goFull 0 = d 0 (base-1)
    goFull i = leading (powBase i) 0 (base-1) (goFull (i-1))

    leading :: Word -> Word -> Word -> RE c Word -> RE c Word
    leading !pBase dl dh = R.liftA2' (\x y -> x * pBase + y) (d dl dh)
{-# INLINE mkWordRangeBase #-}

---------------------------------
-- Parsing hexadecimal Naturals
---------------------------------

-- Parsing hexadecimal is simple, there is no base conversion involved.
--
-- Step 1: Accumulate the hex digits, packed into Words
-- Step 2: Initialize a ByteArray and fill it with the Words
--
-- Because we create a Nat directly, this makes us depend on ghc-bignum and
-- GHC>=9.0.

stepHex :: NatParseState -> Word -> NatParseState
stepHex (NatParseState acc len ns) d
  | len < maxBoundWordHexLen = NatParseState (acc*16 + d) (len+1) ns
  | otherwise = NatParseState d 1 (WCons acc ns)

finishHex
  :: Word          -- ^ Leading digit
  -> NatParseState -- ^ Everything else
  -> Natural
finishHex !ld (NatParseState acc0 len0 ns0) = case ns0 of
  WNil -> Nat.naturalFromWord (ld `unsafeShiftL` (4*(len0-1)) + acc0)
  WCons n ns1 ->
    let lns = lengthWList ns1 + 2
        wsz = WORD_SIZE_IN_BITS
        !(PrimArray byteArray) = runPrimArray $ do
          ma <- newPrimArray lns
          if len0 == maxBoundWordHexLen
          then do
            let go i n1 WNil = do
                  let n1' = ld `unsafeShiftL` (4*(maxBoundWordHexLen-1)) + n1
                  writePrimArray ma i n1'
                go i n1 (WCons n2 ns2) = do
                  writePrimArray ma i n1
                  go (i+1) n2 ns2
            writePrimArray ma 0 acc0
            go 1 n ns1
          else do
            let go i prv n1 WNil = do
                  let n1' = ld `unsafeShiftL` (4*(maxBoundWordHexLen-1)) + n1
                  writePrimArray ma i (prv + n1' `unsafeShiftL` (4*len0))
                  writePrimArray ma (i+1) (n1' `unsafeShiftR` (wsz - 4*len0))
                go i prv n1 (WCons n2 ns2) = do
                  writePrimArray ma i (prv + n1 `unsafeShiftL` (4*len0))
                  go (i+1) (n1 `unsafeShiftR` (wsz - 4*len0)) n2 ns2
            go 0 acc0 n ns1
          pure ma
    in Nat.NB byteArray
-- finishHex does a bunch of unsafe stuff, so make sure things are correct:
-- * Bit shifts are in [0..wsz-1]
-- * Natural invariants:
--   * If the value fits in a word, it must be NS (via naturalFromWord here).
--   * Otherwise, use a ByteArray# with NB. The highest Word must not be 0.

-----------------------------
-- Parsing decimal Naturals
-----------------------------

-- The implementation below is adapted from the bytestring package.
-- https://github.com/haskell/bytestring/blob/7e11412b9bfb13bcd6b8e7c04765b8f5bd90fd34/Data/ByteString/Lazy/ReadNat.hs
--
-- Step 1: Accumulate the digits, packed into Words.
-- Step 2: Combine the packed Words bottom-up into the result. This is what
--         makes it better than foldl (\acc d -> acc * 10 + d)).
--
-- The obvious foldl approach is O(n^2) for n digits. The combine approach
-- performs O(n/2^i) multiplications of size O(2^i), for i in [0..log_2(n)].
-- If multiplication is O(n^k), this is also O(n^k). We have k < 2,
-- thanks to subquadratic multiplication of GMP-backed Naturals:
-- https://gmplib.org/manual/Multiplication-Algorithms.
--
-- For reference, here's how GMP converts any base (including 10) to a natural
-- using broadly the same approach.
-- https://github.com/alisw/GMP/blob/2bbd52703e5af82509773264bfbd20ff8464804f/mpn/generic/set_str.c

stepDec :: NatParseState -> Word -> NatParseState
stepDec (NatParseState acc len ns) d
  | len < safeWordDecLen = NatParseState (10*acc + d) (len+1) ns
  | otherwise = NatParseState d 1 (WCons acc ns)

finishDec
  :: Word          -- ^ Leading digit
  -> NatParseState -- ^ Everything else
  -> Natural
finishDec !ld (NatParseState acc0 len0 ns0) = combine acc0 len0 ns0
  where
    combine !acc !len ns = case ns of
      WNil -> w2n (10^(len-1) * ld + acc)
      WCons n ns1 -> 10^len * combine1 safeBaseDec (go n ns1) + w2n acc
      where
        go n WNil = let !n' = w2n (highMulDec * ld + n) in [n']
        go n (WCons m WNil) =
          let !n' = w2n (highMulDec * ld + m) * safeBaseDec + w2n n in [n']
        go n (WCons m (WCons n1 ns1)) =
          let !n' = w2n m * safeBaseDec + w2n n in n' : go n1 ns1

    combine1 _ [n] = n
    combine1 base ns1 = combine1 base1 (go ns1)
      where
        !base1 = base * base
        go (n:m:ns) = let !n' = m * base1 + n in n' : go ns
        go ns = ns

w2n :: Word -> Natural
w2n = fromIntegral

safeBaseDec :: Natural
safeBaseDec = fromIntegral (pow10 safeWordDecLen)

highMulDec :: Word
highMulDec = pow10 (safeWordDecLen - 1)

---------------------------
-- Common Natural parsing
---------------------------

data WList = WCons {-# UNPACK #-} !Word !WList | WNil

data NatParseState = NatParseState
  {-# UNPACK #-} !Word      -- ^ acc
  {-# UNPACK #-} !Int       -- ^ length of acc in some base
                 !WList     -- ^ accs, little endian

lengthWList :: WList -> Int
lengthWList = go 0
  where
    go !acc WNil = acc
    go acc (WCons _ ns) = go (acc+1) ns

--------------------
-- Low level stuff
--------------------

-- | Length in base 16.
len16 :: Word -> Int
len16 0 = 1
len16 x = maxBoundWordHexLen - (countLeadingZeros x `div` 4)

-- | 16^i. i must not be large enough to overflow a Word.
pow16 :: Int -> Word
pow16 i = 1 `unsafeShiftL` (4*i)

-- | Length in base 10.
len10 :: Word -> Int
len10 x = go 1 1
  where
    x' = x `quot` 10
    go p i | x' < p = i
    go p i = go (p*10) (i+1)

-- | "999..." repeated safeWordDecLen times is guaranteed to fit in a Word.
safeWordDecLen :: Int

-- | Decimal length of (maxBound :: Word)
maxBoundWordDecLen :: Int

-- | Hexadecimal length of (maxBound :: Word)
maxBoundWordHexLen :: Int

-- | 10^i. i must not be large enough to overflow a Word.
pow10 :: Int -> Word

#if WORD_SIZE_IN_BITS == 32 || WORD_SIZE_IN_BITS == 64

#if WORD_SIZE_IN_BITS == 64
safeWordDecLen = 19
maxBoundWordDecLen = 20
maxBoundWordHexLen = 16
#else
safeWordDecLen = 9
maxBoundWordDecLen = 10
maxBoundWordHexLen = 8
#endif

pow10 p = case p of
  0 -> 1
  1 -> 10
  2 -> 100
  3 -> 1000
  4 -> 10000
  5 -> 100000
  6 -> 1000000
  7 -> 10000000
  8 -> 100000000
  9 -> 1000000000
#if WORD_SIZE_IN_BITS == 64
  10 -> 10000000000
  11 -> 100000000000
  12 -> 1000000000000
  13 -> 10000000000000
  14 -> 100000000000000
  15 -> 1000000000000000
  16 -> 10000000000000000
  17 -> 100000000000000000
  18 -> 1000000000000000000
  19 -> 10000000000000000000
#endif
  _ -> errorWithoutStackTrace "Regex.Internal.Int.pow10: p too large"
#else
#error "unsupported word size"
#endif