{-# LANGUAGE CPP, MagicHash, UnboxedTuples, NumericUnderscores #-}
{-# OPTIONS_GHC -Wno-overflowed-literals #-}
{-|
Module      : Parsley.Internal.Backend.Machine.PosOps
Description : Collection of platform dependent position operations
License     : BSD-3-Clause
Maintainer  : Jamie Willis
Stability   : experimental

This module contains the implementations of updates on positions: these depend on the number of
bits in a word, or if the @full-width-positions@ flag was set on the @parsley-core@ library.

@since 1.8.0.0
-}
module Parsley.Internal.Backend.Machine.PosOps (
    initPos, tabWidth,
    extractLine, extractCol,
    liftPos,
    updatePos, updatePosQ,
    updatePosNewlineOnly, updatePosNewlineOnlyQ,
    shiftLineAndSetCol, shiftCol, shiftAlignAndShiftCol,
    shiftLineAndSetColQ, shiftColQ, shiftAlignAndShiftColQ,
    toNextTab
  ) where

#include "MachDeps.h"
#if WORD_SIZE_IN_BITS < 64
#define FULL_WIDTH_POSITIONS
#endif

import Data.Bits                                   ( (.&.), (.|.)
#ifndef FULL_WIDTH_POSITIONS
                                                   , unsafeShiftL
#endif
                                                   )
import Parsley.Internal.Backend.Machine.Types.Base (Pos)
import Parsley.Internal.Common                     (Code)
import GHC.Exts                                    (Int(..), Word(W#))
import GHC.Prim                                    ( plusWord#, and#, or#, word2Int#
#ifdef FULL_WIDTH_POSITIONS
                                                   , minusWord#
#else
                                                   , uncheckedShiftRL#
#endif
                                                   )

{-|
Advances a column to the next tab column.

@since 2.1.0.0
-}
toNextTab :: Word -> Word
toNextTab :: Word -> Word
toNextTab Word
x = (Word
x forall a. Num a => a -> a -> a
+ forall a. Num a => a
tabWidth forall a. Num a => a -> a -> a
- Word
1) forall a. Bits a => a -> a -> a
.&. forall a. Num a => a -> a
negate forall a. Num a => a
tabWidth forall a. Bits a => a -> a -> a
.|. Word
1

{-|
Given a dynamic character and a static position, produces a new dynamic position.

@since 2.1.0.0
-}
updatePos :: Code Char -> Word -> Word -> Code Pos
updatePos :: Code Char -> Word -> Word -> Code Word#
updatePos Code Char
c Word
line Word
col = [||updatePos# $$(liftPos line col) $$c||]

{-|
Given a dynamic character and a static position, produces a new dynamic position.
For this variant, newlines are the only character than can affect the update.

@since 2.1.0.0
-}
updatePosNewlineOnly :: Code Char -> Word -> Code Pos
updatePosNewlineOnly :: Code Char -> Word -> Code Word#
updatePosNewlineOnly Code Char
c Word
line = [||updatePos0ColNewlineOnly# $$(liftPos line 0) $$c||]

{-|
Given a dynamic character and a dynamic position, returns the representation of the updated position.

@since 2.1.0.0
-}
updatePosQ :: Code Char -> Code Pos -> Code Pos
updatePosQ :: Code Char -> Code Word# -> Code Word#
updatePosQ Code Char
c Code Word#
pos = [||updatePos# $$pos $$c||]

{-|
Given a dynamic character and a dynamic position, returns the representation of the updated position.
For this variant, newlines are the only character than can affect the update.

@since 2.1.0.0
-}
updatePosNewlineOnlyQ :: Code Char -> Code Pos -> Code Pos
updatePosNewlineOnlyQ :: Code Char -> Code Word# -> Code Word#
updatePosNewlineOnlyQ Code Char
c Code Word#
pos = [||updatePosNewlineOnly# $$pos $$c||]

{-|
Shift a static postion's column by a given amount

@since 2.1.0.0
-}
shiftCol :: Word -- ^ The amount to shift the column by
         -> Word -- ^ The line
         -> Word -- ^ The column
         -> (Word, Word)
shiftCol :: Word -> Word -> Word -> (Word, Word)
shiftCol Word
n Word
line Word
col = (Word
line, Word
col forall a. Num a => a -> a -> a
+ Word
n)

{-|
Shift a static line by a given amount and then set the column

@since 2.1.0.0
-}
shiftLineAndSetCol :: Word -- ^ The amount to shift the line by
                   -> Word -- ^ The new column
                   -> Word -- ^ The line
                   -> (Word, Word)
shiftLineAndSetCol :: Word -> Word -> Word -> (Word, Word)
shiftLineAndSetCol Word
n Word
col Word
line = (Word
line forall a. Num a => a -> a -> a
+ Word
n, Word
col)

{-|
Shift a static position by first adjusting the column, then aligning to a tab, then shifting further

@since 2.1.0.0
-}
shiftAlignAndShiftCol :: Word -- ^ Amount to shift column by before the tab shift
                      -> Word -- ^ Amount to shift column by after the tab shift
                      -> Word -- ^ The line
                      -> Word -- ^ The new column
                      -> (Word, Word)
shiftAlignAndShiftCol :: Word -> Word -> Word -> Word -> (Word, Word)
shiftAlignAndShiftCol Word
firstBy Word
thenBy Word
line Word
col = (Word
line, Word -> Word
toNextTab (Word
col forall a. Num a => a -> a -> a
+ Word
firstBy) forall a. Num a => a -> a -> a
+ Word
thenBy)

{-|
The initial position used by the parser. This is some representation of (1, 1).

@since 1.8.0.0
-}
initPos :: (Word, Word)
initPos :: (Word, Word)
initPos = (Word
1, Word
1)

{-|
The size of a tab.

@since 2.1.0.0
-}
tabWidth :: Num a => a
tabWidth :: forall a. Num a => a
tabWidth = a
4

{-# INLINEABLE updatePos# #-}
updatePos# :: Pos -> Char -> Pos
{-# INLINE updatePosNewlineOnly# #-}
updatePosNewlineOnly# :: Pos -> Char -> Pos
{-# INLINEABLE updatePos0ColNewlineOnly# #-}
updatePos0ColNewlineOnly# :: Pos -> Char -> Pos

{-|
Shift a dynamic postion's column by a given amount

@since 2.1.0.0
-}
shiftColQ :: Word -- ^ The amount to shift the column by
          -> Code Pos
          -> Code Pos
{-|
Shift a dynamic line by a given amount and then set the column

@since 2.1.0.0
-}
shiftLineAndSetColQ :: Word -- ^ The amount to shift the line by
                    -> Word -- ^ The new column
                    -> Code Pos
                    -> Code Pos
{-|
Shift a dynamic position by first adjusting the column, then aligning to a tab, then shifting further

@since 2.1.0.0
-}
shiftAlignAndShiftColQ :: Word -- ^ Amount to shift column by before the tab shift
                       -> Word -- ^ Amount to shift column by after the tab shift
                       -> Code Pos
                       -> Code Pos

{-|
Given the opaque representation of a position, extracts the line number out of it.

@since 1.8.0.0
-}
extractLine :: Code Pos -> Code Int

{-|
Given the opaque representation of a position, extracts the column number out of it.

@since 1.8.0.0
-}
extractCol :: Code Pos -> Code Int

{-|
Converts a static position into a dynamic one.

@since 2.1.0.0
-}
liftPos :: Word -> Word -> Code Pos

#ifndef FULL_WIDTH_POSITIONS

-- This is referred to directly in generated code, leave optimised primitives
updatePos# :: Word# -> Char -> Word#
updatePos# Word#
pos Char
'\n' = (Word#
pos Word# -> Word# -> Word#
`and#` Word#
0xffffffff_00000000##) Word# -> Word# -> Word#
`plusWord#` Word#
0x00000001_00000001##
updatePos# Word#
pos Char
'\t' = ((Word#
pos Word# -> Word# -> Word#
`plusWord#` Word#
0x00000000_00000003##) Word# -> Word# -> Word#
`and#` Word#
0xffffffff_fffffffc##) Word# -> Word# -> Word#
`or#` Word#
0x00000000_00000001##
updatePos# Word#
pos Char
_    = Word#
pos Word# -> Word# -> Word#
`plusWord#` Word#
0x00000000_00000001##

-- This is referred to directly in generated code, leave optimised primitives
updatePosNewlineOnly# :: Word# -> Char -> Word#
updatePosNewlineOnly# Word#
pos = Word# -> Char -> Word#
updatePos0ColNewlineOnly# (Word#
pos Word# -> Word# -> Word#
`and#` Word#
0xffffffff_00000000##)

-- This is referred to directly in generated code, leave optimised primitives
updatePos0ColNewlineOnly# :: Word# -> Char -> Word#
updatePos0ColNewlineOnly# Word#
pos0Col Char
'\n' = Word#
pos0Col Word# -> Word# -> Word#
`plusWord#` Word#
0x00000001_00000000##
updatePos0ColNewlineOnly# Word#
pos0Col Char
_ = Word#
pos0Col

shiftLineAndSetColQ :: Word -> Word -> Code Word# -> Code Word#
shiftLineAndSetColQ Word
n Word
col Code Word#
qpos = [|| ($$qpos `and#` 0xffffffff_00000000##) `plusWord#` $$(liftPos n col) ||]
shiftColQ :: Word -> Code Word# -> Code Word#
shiftColQ (W# Word#
n) Code Word#
qpos = [|| $$qpos `plusWord#` n ||]
shiftAlignAndShiftColQ :: Word -> Word -> Code Word# -> Code Word#
shiftAlignAndShiftColQ Word
firstBy Word
thenBy Code Word#
qpos =
  let !(W# Word#
pre) = Word
firstBy forall a. Num a => a -> a -> a
+ Word
3 -- offset first, then add 3 to overshoot
      !(W# Word#
mask) = -Word
4         -- constant fold this into raw literal
      !(W# Word#
post) = Word
thenBy forall a. Num a => a -> a -> a
+ Word
1 -- add the offset of tab boundary from power of two, then remaining positions
  in if Word
thenBy forall a. Eq a => a -> a -> Bool
== Word
0 then [|| (($$qpos `plusWord#` pre) `and#` mask) `or#` 0x00000000_00000001## ||] -- because tab widths are multiples of two
     else                [|| (($$qpos `plusWord#` pre) `and#` mask) `plusWord#` post ||]

extractLine :: Code Word# -> Code Int
extractLine Code Word#
qpos = [||I# (word2Int# ($$qpos `uncheckedShiftRL#` 32#))||]
extractCol :: Code Word# -> Code Int
extractCol Code Word#
qpos = [||I# (word2Int# ($$qpos `and#` 0x00000000_ffffffff##))||]

liftPos :: Word -> Word -> Code Word#
liftPos Word
line Word
col = let !(W# Word#
p) = (Word
line forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
32) forall a. Bits a => a -> a -> a
.|. Word
col in [||p||]

#else
-- This is referred to directly in generated code, leave optimised primitives
updatePos# (# line, _ #)   '\n' = (# line `plusWord#` 1##, 1## #)
updatePos# (# line, col #) '\t' = (# line, ((col `plusWord#` 3##) `and#` (0## `minusWord#` 4##)) `or#` 1## #) -- nearest tab boundary `c + (4 - (c - 1) % 4)`
updatePos# (# line, col #) _    = (# line, col `plusWord#` 1## #)

-- This is referred to directly in generated code, leave optimised primitives
updatePosNewlineOnly# = updatePos0ColNewlineOnly#

-- This is referred to directly in generated code, leave optimised primitives
updatePos0ColNewlineOnly# (# line, _ #) '\n' = (# line `plusWord#` 1##, 0## #)
updatePos0ColNewlineOnly# pos           _    = pos

shiftLineAndSetColQ (W# n) (W# col) qpos = [|| case $$qpos of (# line, _ #) -> (# line `plusWord#` n, col #) ||]
shiftColQ (W# n) qpos = [|| case $$qpos of (# line, col #) -> (# line, col `plusWord#` n #) ||]
shiftAlignAndShiftColQ firstBy thenBy qpos =
  let !(W# pre) = firstBy + 3 -- offset first, then add 3 to overshoot
      !(W# mask) = -4         -- constant fold this into raw literal
      !(W# post) = thenBy + 1 -- add the offset of tab boundary from power of two, then remaining positions
  in [|| case $$qpos of
           (# line, col #) -> (# line,
             $$(if thenBy == 0 then [|| ((col `plusWord#` pre) `and#` mask) `or#` 1## ||] -- because tab widths are multiples of two
                else                [|| ((col `plusWord#` pre) `and#` mask) `plusWord#` post ||]) #) ||]

extractLine qpos = [|| case $$qpos of (# line, _ #) -> I# (word2Int# line) ||]
extractCol qpos = [|| case $$qpos of (# _, col #) -> I# (word2Int# col) ||]

liftPos (W# line) (W# col) = [||(# line, col #)||]
#endif