typed-peg-0.1.0.0: src/PEG/Indent.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
-- | Indentation-sensitive parsing via column intervals and relations.
--
-- This module implements the algebraic model of indentation from
-- /Layout-sensitive grammars and combinators/ (Adams, 2013).
-- Columns are represented as integer positions; valid columns are maintained
-- as an 'Interval'. Each parser step filters the interval through a 'RelD'
-- (a monotone column relation), allowing constructs such as \"must be
-- indented more than the enclosing block\".
--
-- == Predefined relations
--
-- * 'eqR' — same column (align with enclosing block)
-- * 'geR' — greater-or-equal column (standard indented block)
-- * 'gtR' — strictly greater column
-- * 'anyR' — any column (no constraint)
-- * 'gapR' — indented by a fixed offset
-- * 'constR' — fixed column
-- * 'offsetR'— shifted by @k@ columns
module PEG.Indent
( Bound (..)
, Interval (..)
, emptyI
, fullI
, singletonI
, nullI
, memberI
, interI
, RelD (..)
, Rel (..)
, relName
, image
, preimage
, eqR
, gtR
, geR
, anyR
, gapR
, constR
, offsetR
) where
import GHC.TypeLits (Symbol)
data Bound = Fin !Int | Inf
deriving (Eq, Show)
instance Ord Bound where
compare Inf Inf = EQ
compare Inf (Fin _) = GT
compare (Fin _) Inf = LT
compare (Fin a) (Fin b) = compare a b
data Interval = Interval { ivLo :: !Int, ivHi :: !Bound }
deriving (Eq, Show)
emptyI :: Interval
emptyI = Interval 1 (Fin 0)
fullI :: Interval
fullI = Interval 0 Inf
singletonI :: Int -> Interval
singletonI i = Interval i (Fin i)
nullI :: Interval -> Bool
nullI (Interval lo hi) = Fin lo > hi
memberI :: Int -> Interval -> Bool
memberI i (Interval lo hi) = i >= lo && Fin i <= hi
interI :: Interval -> Interval -> Interval
interI (Interval l1 h1) (Interval l2 h2) = Interval (max l1 l2) (min h1 h2)
data RelD = RelD
{ rdName :: String
, rdDom :: Interval
, rdLo :: Int -> Int
, rdHi :: Int -> Bound
, rdInvLo :: Int -> Int
, rdInvHi :: Int -> Bound
, rdModeLo :: Bound
, rdModeHi :: Bound
, rdModeInvLo :: Bound
, rdModeInvHi :: Bound
}
newtype Rel (n :: Symbol) = Rel { relD :: RelD }
relName :: Rel n -> String
relName = rdName . relD
instance Show (Rel n) where
show = relName
clampMode :: Bound -> Interval -> Maybe Int
clampMode (Fin m) (Interval lo hi) = Just $ case hi of
Inf -> max lo m
Fin h -> max lo (min m h)
clampMode Inf (Interval lo hi) = case hi of
Inf -> Nothing
Fin h -> Just (max lo h)
supOver :: Bound -> (Int -> Bound) -> Interval -> Bound
supOver mode f iv = maybe Inf f (clampMode mode iv)
infOver :: Bound -> (Int -> Int) -> Interval -> Int
infOver mode f iv = maybe 0 f (clampMode mode iv)
image :: RelD -> Interval -> Interval
image rd i0
| nullI i = emptyI
| otherwise = Interval (infOver (rdModeLo rd) (rdLo rd) i)
(supOver (rdModeHi rd) (rdHi rd) i)
where
i = interI i0 (rdDom rd)
preimage :: RelD -> Interval -> Interval
preimage rd i
| nullI i = emptyI
| otherwise = Interval (infOver (rdModeInvLo rd) (rdInvLo rd) i)
(supOver (rdModeInvHi rd) (rdInvHi rd) i)
eqR :: Rel "="
eqR = Rel RelD
{ rdName = "="
, rdDom = fullI
, rdLo = id
, rdHi = Fin
, rdInvLo = id
, rdInvHi = Fin
, rdModeLo = Fin 0
, rdModeHi = Inf
, rdModeInvLo = Fin 0
, rdModeInvHi = Inf
}
gapR :: Int -> Rel "gap"
gapR = Rel . gapD "gap"
gapD :: String -> Int -> RelD
gapD name k = RelD
{ rdName = name
, rdDom = Interval k Inf
, rdLo = const 0
, rdHi = \i -> Fin (i - k)
, rdInvLo = \i -> i + k
, rdInvHi = const Inf
, rdModeLo = Fin 0
, rdModeHi = Inf
, rdModeInvLo = Fin 0
, rdModeInvHi = Fin 0
}
gtR :: Rel ">"
gtR = Rel (gapD ">" 1)
geR :: Rel ">="
geR = Rel (gapD ">=" 0)
anyR :: Rel "~"
anyR = Rel RelD
{ rdName = "~"
, rdDom = fullI
, rdLo = const 0
, rdHi = const Inf
, rdInvLo = const 0
, rdInvHi = const Inf
, rdModeLo = Fin 0
, rdModeHi = Fin 0
, rdModeInvLo = Fin 0
, rdModeInvHi = Fin 0
}
constR :: Int -> Rel "const"
constR c = Rel RelD
{ rdName = "const " ++ show c
, rdDom = singletonI c
, rdLo = const 0
, rdHi = const Inf
, rdInvLo = const c
, rdInvHi = const (Fin c)
, rdModeLo = Fin 0
, rdModeHi = Fin 0
, rdModeInvLo = Fin 0
, rdModeInvHi = Fin 0
}
offsetR :: Int -> Rel "offset"
offsetR k = Rel RelD
{ rdName = "+" ++ show k
, rdDom = Interval k Inf
, rdLo = \i -> i - k
, rdHi = \i -> Fin (i - k)
, rdInvLo = \i -> i + k
, rdInvHi = \i -> Fin (i + k)
, rdModeLo = Fin 0
, rdModeHi = Inf
, rdModeInvLo = Fin 0
, rdModeInvHi = Inf
}