{-# LANGUAGE DerivingStrategies #-}
{-|
Module      : Parsley.Internal.Backend.Machine.Types.Offset
Description : Statically refined offsets.
License     : BSD-3-Clause
Maintainer  : Jamie Willis
Stability   : experimental

This module contains the statically refined `Offset` type,
which can be used to reason about input in different parts of
a parser as it is evaluated.

@since 1.4.0.0
-}
module Parsley.Internal.Backend.Machine.Types.Offset (
    Offset, mkOffset, offset, moveOne, moveN, same
  ) where

import Parsley.Internal.Backend.Machine.InputRep    (Rep)
import Parsley.Internal.Common.Utils                (Code)

{-|
Augments a regular @'Code' ('Rep' o)@ with information about its origins and
how much input is known to have been consumed since it came into existence.
This can be used to statically evaluate handlers (see
`Parsley.Internal.Backend.Machine.Types.Statics.staHandlerEval`).

@since 1.5.0.0
-}
data Offset o = Offset {
    -- | The underlying code that represents the current offset into the input.
    Offset o -> Code (Rep o)
offset :: Code (Rep o),
    -- | The unique identifier that determines where this offset originated from.
    Offset o -> Word
unique :: Word,
    -- | The amount of input that has been consumed on this offset since it was born.
    Offset o -> Amount
moved  :: Amount
  }

data Amount = Amount Word {- ^ The multiplicity. -} Word {- ^ The additive offset. -}
  deriving stock Amount -> Amount -> Bool
(Amount -> Amount -> Bool)
-> (Amount -> Amount -> Bool) -> Eq Amount
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Amount -> Amount -> Bool
$c/= :: Amount -> Amount -> Bool
== :: Amount -> Amount -> Bool
$c== :: Amount -> Amount -> Bool
Eq

{-|
Given two `Offset`s, this determines whether or not they represent the same
offset into the input stream at runtime. This comparison only makes sense when
both `Offset`s share the same origin, hence the @Maybe@.

@since 1.4.0.0
-}
same :: Offset o -> Offset o -> Maybe Bool
same :: Offset o -> Offset o -> Maybe Bool
same Offset o
o1 Offset o
o2
  | Offset o -> Word
forall o. Offset o -> Word
unique Offset o
o1 Word -> Word -> Bool
forall a. Eq a => a -> a -> Bool
== Offset o -> Word
forall o. Offset o -> Word
unique Offset o
o2 = Bool -> Maybe Bool
forall a. a -> Maybe a
Just (Offset o -> Amount
forall o. Offset o -> Amount
moved Offset o
o1 Amount -> Amount -> Bool
forall a. Eq a => a -> a -> Bool
== Offset o -> Amount
forall o. Offset o -> Amount
moved Offset o
o2)
  | Bool
otherwise = Maybe Bool
forall a. Maybe a
Nothing

{-|
Updates an `Offset` with its new underlying representation of a real
runtime offset and records that another character has been consumed.

@since 1.4.0.0
-}
moveOne :: Offset o -> Code (Rep o) -> Offset o
moveOne :: Offset o -> Code (Rep o) -> Offset o
moveOne = Maybe Word -> Offset o -> Code (Rep o) -> Offset o
forall o. Maybe Word -> Offset o -> Code (Rep o) -> Offset o
moveN (Word -> Maybe Word
forall a. a -> Maybe a
Just Word
1)

{-|
Updates an `Offset` with its new underlying representation of a real
runtime offset and records that several more characters have been consumed.
Here, `Nothing` represents an unknown but non-zero amount of characters.

@since 1.5.0.0
-}
moveN :: Maybe Word -> Offset o -> Code (Rep o) -> Offset o
moveN :: Maybe Word -> Offset o -> Code (Rep o) -> Offset o
moveN Maybe Word
n Offset o
off Code (Rep o)
o = Offset o
off { offset :: Code (Rep o)
offset = Code (Rep o)
o, moved :: Amount
moved = Offset o -> Amount
forall o. Offset o -> Amount
moved Offset o
off Amount -> Amount -> Amount
`add` Maybe Word -> Amount
toAmount Maybe Word
n }
  where
    toAmount :: Maybe Word -> Amount
    toAmount :: Maybe Word -> Amount
toAmount Maybe Word
Nothing = Word -> Word -> Amount
Amount Word
1 Word
0
    toAmount (Just Word
n) = Word -> Word -> Amount
Amount Word
0 Word
n

{-|
Makes a fresh `Offset` that has not had any input consumed off of it
yet.

@since 1.4.0.0
-}
mkOffset :: Code (Rep o) -> Word -> Offset o
mkOffset :: Code (Rep o) -> Word -> Offset o
mkOffset Code (Rep o)
offset Word
unique = Code (Rep o) -> Word -> Amount -> Offset o
forall o. Code (Rep o) -> Word -> Amount -> Offset o
Offset Code (Rep o)
offset Word
unique (Word -> Word -> Amount
Amount Word
0 Word
0)

add :: Amount -> Amount -> Amount
add :: Amount -> Amount -> Amount
add a1 :: Amount
a1@(Amount Word
n Word
i) a2 :: Amount
a2@(Amount Word
m Word
j)
  -- If the multiplicites don't match then this is _even_ more unknowable
  | Word
n Word -> Word -> Bool
forall a. Eq a => a -> a -> Bool
/= Word
m, Word
n Word -> Word -> Bool
forall a. Eq a => a -> a -> Bool
/= Word
0, Word
m Word -> Word -> Bool
forall a. Eq a => a -> a -> Bool
/= Word
0 = Word -> Word -> Amount
Amount (Word
n Word -> Word -> Word
forall a. Num a => a -> a -> a
+ Word
m) Word
0
  -- This is a funny case, it shouldn't happen and it's not really clear what happens if it does
  | Word
n Word -> Word -> Bool
forall a. Eq a => a -> a -> Bool
/= Word
0, Word
m Word -> Word -> Bool
forall a. Eq a => a -> a -> Bool
/= Word
0         = [Char] -> Amount
forall a. HasCallStack => [Char] -> a
error ([Char]
"adding " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Amount -> [Char]
forall a. Show a => a -> [Char]
show Amount
a1 [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
" and " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Amount -> [Char]
forall a. Show a => a -> [Char]
show Amount
a2 [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
" makes no sense?")
  -- If one of the multiplicites is 0 then the offset can be added
  | Bool
otherwise              = Word -> Word -> Amount
Amount (Word -> Word -> Word
forall a. Ord a => a -> a -> a
max Word
n Word
m) (Word
i Word -> Word -> Word
forall a. Num a => a -> a -> a
+ Word
j)

-- Instances
instance Show (Offset o) where
  show :: Offset o -> [Char]
show Offset o
o = Word -> [Char]
forall a. Show a => a -> [Char]
show (Offset o -> Word
forall o. Offset o -> Word
unique Offset o
o) [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"+" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Amount -> [Char]
forall a. Show a => a -> [Char]
show (Offset o -> Amount
forall o. Offset o -> Amount
moved Offset o
o)

instance Show Amount where
  show :: Amount -> [Char]
show (Amount Word
n Word
m) = Word -> [Char]
forall a. Show a => a -> [Char]
show Word
n [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"*n+" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Word -> [Char]
forall a. Show a => a -> [Char]
show Word
m