{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveFunctor              #-}

-- | All `Context`-related operations

module Morte.Context (
    -- * Context
      Context
    , empty
    , insert
    , lookup
    , toList
    ) where

import Control.DeepSeq (NFData)
import Data.Text.Lazy (Text)
import Prelude hiding (lookup)

{-| Bound variable names and their types

    Variable names may appear more than once in the `Context`.  The `Var` @x\@n@
    refers to the @n@th occurrence of @x@ in the `Context` (using 0-based
    numbering).
-}
newtype Context a = Context { getContext :: [(Text, a)] }
    deriving (Functor, NFData)

{-| An empty context with no key-value pairs

> toList empty = []
-}
empty :: Context a
empty = Context []

-- | Add a key-value pair to the `Context`
insert :: Text -> a -> Context a -> Context a
insert k v (Context kvs) = Context ((k, v) : kvs)
{-# INLINABLE insert #-}

{-| Lookup a key by name and index

> lookup k 0 (insert k v0              ctx ) = Just v0
> lookup k 1 (insert k v0 (insert k v1 ctx)) = Just v1
-}
lookup :: Text -> Int -> Context a -> Maybe a
lookup k n0 (Context kvs0) = loop kvs0 n0
  where
    loop ((k', v):kvs) n | k /= k'   = loop kvs    n
                         | n >  0    = loop kvs $! n - 1
                         | n == 0    = Just v
                         | otherwise = Nothing
    loop  []           _             = Nothing
{-# INLINABLE lookup #-}

-- | Return all key-value associations as a list
toList :: Context a -> [(Text, a)]
toList = getContext
{-# INLINABLE toList #-}