packages feed

rzk-0.10.0: src/Language/Rzk/Foil/Names.hs

{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# LANGUAGE DeriveFoldable     #-}
{-# LANGUAGE DeriveFunctor      #-}
{-# LANGUAGE DeriveTraversable  #-}
{-# LANGUAGE FlexibleInstances  #-}
{-# LANGUAGE LambdaCase         #-}
{-# LANGUAGE OverloadedStrings  #-}
{-# LANGUAGE RecordWildCards    #-}

-- | Surface names, binders and modalities.
--
-- These are what the checker shows the user, and they are independent of how a
-- term represents its variables: a 'VarIdent' is a surface identifier (with the
-- position of its defining occurrence), and a 'Binder' records the /names/ a binder
-- introduces — including a pair pattern, which still binds exactly one variable
-- whose components are projections of it.
module Language.Rzk.Foil.Names where

import           Data.Char           (chr, ord)
import           Data.Coerce         (coerce)
import           Data.List           (intercalate)
import           Data.Maybe          (fromMaybe)
import           Data.String         (IsString (..))
import           Data.Set            (Set)
import qualified Data.Set            as Set
import qualified Data.Text           as T

import qualified Language.Rzk.Syntax as Rzk

-- | An identifier that is not in scope becomes a hole under a /marked/ name.
--
-- A free-foil term refers to a variable by name, and an unresolved identifier has
-- none — so the term cannot represent it. Elaboration marks it instead, and the
-- checker reports it when it reaches it, which is what keeps the error where the
-- identifier was used (inside the binders and topes it was written under) rather
-- than at the top of the declaration.
--
-- The marker cannot be mistaken for a hole the user wrote: the grammar forbids @#@
-- in an identifier.
markUnresolved :: VarIdent -> VarIdent
markUnresolved x = fromString ('#' : show x)

unmarkUnresolved :: VarIdent -> Maybe VarIdent
unmarkUnresolved x = case show x of
  '#' : name -> Just (fromString name)
  _          -> Nothing

-- | What a bound name is shown as: the display name standing for the variable
-- itself, and the (freshened) binder, which gives the pattern to print and the
-- component names to fold projections back to.
type Display = (VarIdent, Binder)

-- | The annotation on every node of a typed term: its type, plus its memoised weak
-- head and normal forms.
data TypeInfo term = TypeInfo
  { infoType :: term
  , infoWHNF :: Maybe term
  , infoNF   :: Maybe term
  } deriving (Functor, Foldable, Traversable)

data RzkPosition = RzkPosition
  { rzkFilePath :: Maybe FilePath
  , rzkLineCol  :: Rzk.BNFC'Position
  }

ppRzkPosition :: RzkPosition -> String
ppRzkPosition RzkPosition{..} = intercalate ":" $ concat
  [ [fromMaybe "<stdin>" rzkFilePath]
  , foldMap (\(row, col) -> map show [row, col]) rzkLineCol]

newtype VarIdent = VarIdent { getVarIdent :: Rzk.VarIdent' RzkPosition }

instance Show VarIdent where
  show = Rzk.printTree . getVarIdent

-- | Identifiers are equal when they are spelled the same, whatever their source
-- positions.
--
-- Written out rather than @(==) \`on\` (void . getVarIdent)@, which allocated a
-- position-free copy of the whole syntax node and compared that: identifier
-- equality is on the hot path (every name lookup, every refreshing of a display
-- name, every match of two terms that mention a hole), and profiling put it at 6%
-- of the checker's time.
instance Eq VarIdent where
  VarIdent (Rzk.VarIdent _ x) == VarIdent (Rzk.VarIdent _ y) = x == y

-- | Identifiers are ordered by name, ignoring the source position, so that the
-- order agrees with 'Eq'. Only used to key identifiers in a set or a map.
instance Ord VarIdent where
  compare (VarIdent (Rzk.VarIdent _ x)) (VarIdent (Rzk.VarIdent _ y)) = compare x y

instance IsString VarIdent where
  fromString s = VarIdent (Rzk.VarIdent (RzkPosition Nothing Nothing) (fromString s))

ppVarIdentWithLocation :: VarIdent -> String
ppVarIdentWithLocation (VarIdent var@(Rzk.VarIdent pos _ident)) =
  Rzk.printTree var <> " (" <> ppRzkPosition pos <> ")"

varIdent :: Rzk.VarIdent -> VarIdent
varIdent = varIdentAt Nothing

varIdentAt :: Maybe FilePath -> Rzk.VarIdent -> VarIdent
varIdentAt path (Rzk.VarIdent pos ident) = VarIdent (Rzk.VarIdent (RzkPosition path pos) ident)

fromVarIdent :: VarIdent -> Rzk.VarIdent
fromVarIdent (VarIdent (Rzk.VarIdent (RzkPosition _file pos) ident)) = Rzk.VarIdent pos ident

-- | The display name of a hole from its surface token text. The token includes
-- the leading @?@; an anonymous hole (bare @?@) has no name.
holeName :: T.Text -> Maybe VarIdent
holeName tok =
  case T.drop 1 tok of
    name | T.null name -> Nothing
         | otherwise   -> Just (fromString (T.unpack name))

-- | The surface token text (including the leading @?@) for a hole name.
holeIdentToken :: Maybe VarIdent -> T.Text
holeIdentToken Nothing  = "?"
holeIdentToken (Just x) = "?" <> T.pack (show x)

-- | The name(s) a binder introduces. A binder may name a single (possibly
-- anonymous) variable, or destructure a pair\/tuple via a pattern. The pattern
-- structure is kept around purely so that goals, holes and error messages can
-- show the user's original names (e.g. @t@ and @s@ for @\\ (t , s) -> …@)
-- instead of projections of a fresh variable (e.g. @π₁ x₄@ and @π₂ x₄@).
--
-- Operationally a pair pattern still binds a /single/ variable; the components
-- are projections of it (see 'toScopePattern'). 'Binder' only records the names
-- so they can be restored when rendering.
data Binder
  = BinderVar (Maybe VarIdent)   -- ^ a single variable (@Nothing@ for @_@)
  | BinderPair Binder Binder     -- ^ a pair pattern @(l , r)@
  | BinderUnit                   -- ^ the unit pattern @unit@
  deriving (Eq)

-- | The single name of a binder, if it binds exactly one named variable.
-- A pair\/unit pattern has no single name, so this is 'Nothing' for them.
-- Used wherever the old @Maybe VarIdent@ binder name is still sufficient.
binderName :: Binder -> Maybe VarIdent
binderName (BinderVar mname) = mname
binderName _                 = Nothing

data TModality = Sharp | Flat | Op | Id deriving (Eq, Show)

toModality :: Rzk.Modality -> TModality
toModality Rzk.Sharp{}       = Sharp
toModality Rzk.ASCII_Sharp{} = Sharp
toModality Rzk.Flat{}        = Flat
toModality Rzk.ASCII_Flat{}  = Flat
toModality Rzk.Op{}          = Op
toModality Rzk.ASCII_Op{}    = Op
toModality Rzk.Id{}          = Id

modCompToMods :: Rzk.ModComp -> (TModality, TModality)
modCompToMods (Rzk.Single _ m)      = (Id, toModality m)
modCompToMods (Rzk.Comp _ ext inn)  = (toModality ext, toModality inn)

fromMod :: TModality -> Rzk.Modality
fromMod Sharp = Rzk.Sharp Nothing
fromMod Flat  = Rzk.Flat Nothing
fromMod Op    = Rzk.Op Nothing
fromMod Id    = Rzk.Id Nothing

modsToModComp :: TModality -> TModality -> Rzk.ModComp
modsToModComp Id inn  = Rzk.Single Nothing (fromMod inn)
modsToModComp ext inn = Rzk.Comp Nothing (fromMod ext) (fromMod inn)


-- | A tuple pattern is sugar for nested pairs.
desugarTuple :: Rzk.BNFC'Position -> [Rzk.Pattern] -> Rzk.Pattern -> Rzk.Pattern -> Rzk.Pattern
desugarTuple loc ps p2 p1 =
  case ps of
    []          -> Rzk.PatternPair loc p1 p2
    pLast : ps' -> Rzk.PatternPair loc (desugarTuple loc ps' p2 p1) pLast


toBinder :: Rzk.Pattern -> Binder
toBinder (Rzk.PatternVar _loc (Rzk.VarIdent _ "_")) = BinderVar Nothing
toBinder (Rzk.PatternVar _loc x)                    = BinderVar (Just (varIdent x))
toBinder (Rzk.PatternUnit _loc)                     = BinderUnit
toBinder (Rzk.PatternPair _loc l r)                 = BinderPair (toBinder l) (toBinder r)
toBinder (Rzk.PatternTuple loc p1 p2 ps)            = toBinder (desugarTuple loc (reverse ps) p2 p1)

patternToTerm :: Rzk.Pattern -> Rzk.Term
patternToTerm = ptt
  where
    ptt = \case
      Rzk.PatternVar loc x    -> Rzk.Var loc x
      Rzk.PatternPair loc l r -> Rzk.Pair loc (ptt l) (ptt r)
      Rzk.PatternUnit loc     -> Rzk.Unit loc
      Rzk.PatternTuple loc p1 p2 ps -> patternToTerm (desugarTuple loc (reverse ps) p2 p1)


modalColonModality :: Rzk.ModalColon -> Rzk.Modality
modalColonModality = \case
  Rzk.ModalColonFlat loc        -> Rzk.Flat loc
  Rzk.ModalColonSharp loc       -> Rzk.Sharp loc
  Rzk.ModalColonOp loc          -> Rzk.Op loc
  Rzk.ModalColonId loc          -> Rzk.Id loc
  Rzk.ASCII_ModalColonFlat loc  -> Rzk.Flat loc
  Rzk.ASCII_ModalColonSharp loc -> Rzk.Sharp loc
  Rzk.ASCII_ModalColonOp loc    -> Rzk.Op loc

modalColonToTModality :: Rzk.ModalColon -> TModality
modalColonToTModality = toModality . modalColonModality

fromTModalityToModalColon :: TModality -> Rzk.ModalColon
fromTModalityToModalColon = \case
  Sharp -> Rzk.ModalColonSharp Nothing
  Flat  -> Rzk.ModalColonFlat Nothing
  Op    -> Rzk.ModalColonOp Nothing
  Id    -> Rzk.ModalColonId Nothing

-- | Split a binder term into the individual variables it names. A multi-variable
-- binder like @(x y : A)@ is parsed as the application spine @x y@; this returns
-- @[x, y]@ so each can become its own nested binder. A single binder term (a
-- variable, a pair pattern, …) is returned unchanged as a singleton.
flattenBinderApp :: Rzk.Term -> [Rzk.Term]
flattenBinderApp = \case
  Rzk.App _loc f x -> flattenBinderApp f ++ [x]
  t                -> [t]

unsafeTermToPattern :: Rzk.Term -> Rzk.Pattern
unsafeTermToPattern = ttp
  where
    ttp = \case
      Rzk.Unit loc                        -> Rzk.PatternUnit loc
      Rzk.Var loc x                       -> Rzk.PatternVar loc x
      Rzk.Pair loc l r                    -> Rzk.PatternPair loc (ttp l) (ttp r)
      Rzk.Tuple loc t1 t2 ts              -> Rzk.PatternTuple loc (ttp t1) (ttp t2) (map ttp ts)
      term -> error ("ERROR: expected a pattern but got\n  " ++ Rzk.printTree term)

sigmaParamToTypeSigma :: Rzk.BNFC'Position -> Rzk.SigmaParam -> Rzk.Term -> Rzk.Term
sigmaParamToTypeSigma loc sp body = case sp of
  Rzk.SigmaParam      _ pat ty      -> Rzk.TypeSigma      loc pat ty body
  Rzk.SigmaParamModal _ pat mc ty  -> Rzk.TypeSigmaModal loc pat mc ty body

-- | A projection step: first (@π₁@) or second (@π₂@) component.
data Proj = PFst | PSnd
  deriving (Eq)

-- | Render a 'Binder' as a surface pattern (used to display the binder itself,
-- e.g. @(t , s)@). Anonymous variables become @_@.
binderToPattern :: Binder -> Rzk.Pattern
binderToPattern (BinderVar Nothing)  = Rzk.PatternVar Nothing (fromVarIdent "_")
binderToPattern (BinderVar (Just x)) = Rzk.PatternVar Nothing (fromVarIdent x)
binderToPattern (BinderPair l r)     = Rzk.PatternPair Nothing (binderToPattern l) (binderToPattern r)
binderToPattern BinderUnit           = Rzk.PatternUnit Nothing



-- | A 'VarIdent' that prints as the binder's surface pattern, e.g. @(t , s)@.
-- Used to display a pattern binder in a hole's local context as the pattern
-- itself rather than as the underlying single variable.
binderDisplayName :: Binder -> VarIdent
binderDisplayName = fromString . Rzk.printTree . binderToPattern

-- | The named leaves of a binder, each paired with the projection path that
-- reaches it from the bound variable. For example @(t , (a , b))@ yields
-- @[([PFst], t), ([PSnd, PFst], a), ([PSnd, PSnd], b)]@.
binderPaths :: Binder -> [([Proj], VarIdent)]
binderPaths (BinderVar (Just x)) = [([], x)]
binderPaths (BinderVar Nothing)  = []
binderPaths BinderUnit           = []
binderPaths (BinderPair l r)     =
  [ (PFst : p, n) | (p, n) <- binderPaths l ] ++
  [ (PSnd : p, n) | (p, n) <- binderPaths r ]

-- | The names appearing in a binder.
binderLeaves :: Binder -> [VarIdent]
binderLeaves = map snd . binderPaths

-- | Does this binder destructure a pair\/tuple (as opposed to naming a single
-- variable or @_@)?
binderIsCompound :: Binder -> Bool
binderIsCompound BinderVar{} = False
binderIsCompound _           = True

-- | Refresh the named leaves of a binder so they avoid the given names (and one
-- another). Anonymous leaves and the unit pattern are left unchanged.
freshenBinderLeaves :: [VarIdent] -> Binder -> Binder
freshenBinderLeaves used = snd . go used
  where
    go u (BinderVar (Just x)) = let x' = refreshVar u x in (x' : u, BinderVar (Just x'))
    go u b@(BinderVar Nothing) = (u, b)
    go u BinderUnit            = (u, BinderUnit)
    go u (BinderPair l r)      =
      let (u1, l') = go u l
          (u2, r') = go u1 r
      in (u2, BinderPair l' r')

-- | Decompose a chain of projections applied to a variable into the projection
-- path /from the variable outwards/, matching 'binderPaths'. The outermost
-- projection is applied last, so it goes at the /end/ of the path: e.g.
-- @π₂ (π₁ x)@ (select @π₁@ first, then @π₂@) becomes @Just ([PFst, PSnd], x)@.

defaultVarIdents :: [VarIdent]
defaultVarIdents =
  [ fromString name
  | n <- [1 :: Int ..]
  , let name = "x" <> map digitToSub (show n) ]
  where
    digitToSub c = chr ((ord c - ord '0') + ord '₀')

-- $setup
-- >>> :set -XOverloadedStrings
-- >>> import qualified Data.Text as T
-- >>> import qualified Data.Set as Set

-- | Given a list of used variable names in the current context,
-- generate a unique fresh name based on a given one.
--
-- >>> print $ refreshVar ["x", "y", "x₁", "z"] "x"
-- x₂
refreshVar :: [VarIdent] -> VarIdent -> VarIdent
refreshVar vars x
  | x `elem` vars = refreshVar vars (incVarIdentIndex x)
  | otherwise     = x

-- | Refresh a name against a /set/ of taken ones.
--
-- The list version above is O(taken) per call, and naming a whole context calls it
-- once per entry, which made reading the naming off a context with every top-level
-- definition of a project in it quadratic.
--
-- >>> print $ refreshVarIn (Set.fromList ["x", "y", "x₁", "z"]) "x"
-- x₂
refreshVarIn :: Set VarIdent -> VarIdent -> VarIdent
refreshVarIn taken x
  | x `Set.member` taken = refreshVarIn taken (incVarIdentIndex x)
  | otherwise            = x

incVarIdentIndex :: VarIdent -> VarIdent
incVarIdentIndex (VarIdent (Rzk.VarIdent loc token)) =
  VarIdent (Rzk.VarIdent loc (coerce incIndex token))

-- | Increment the subscript number at the end of the indentifier.
--
-- >>> putStrLn $ T.unpack $ incIndex "x"
-- x₁
-- >>> putStrLn $ T.unpack $ incIndex "x₁₉"
-- x₂₀
incIndex :: T.Text -> T.Text
incIndex s = T.pack $ name <> newIndex
  where
    digitsSub = "₀₁₂₃₄₅₆₇₈₉" :: String
    isDigitSub = (`elem` digitsSub)
    digitFromSub c = chr ((ord c - ord '₀') + ord '0')
    digitToSub c = chr ((ord c - ord '0') + ord '₀')
    (name, index) = break isDigitSub (T.unpack s)
    oldIndexN = read ('0' : map digitFromSub index) :: Int -- FIXME: read
    newIndex = map digitToSub (show (oldIndexN + 1))