rzk-0.10.0: src/Rzk/TypeCheck/Display.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Showing a term to the user.
--
-- A term of the core names its variables by 'Foil.Name' (an @Int@), so anything
-- user-facing — an error, a trace of a judgement, a hole's goal — has to say what
-- each name is /called/. That is a 'Naming': a display name and a display binder
-- per name in scope, plus the supply of fresh names for the binders the printer
-- meets on the way down.
--
-- This replaces the old @var -> VarIdent@ threading (@name@, @nameInc@,
-- @BinderNames@, @ppTermInContext@ — four copies of the same idea), and with it
-- the unwinding loop that rebuilt those names one binder at a time.
module Rzk.TypeCheck.Display where
import Control.Monad.Foil (NameMap)
import qualified Control.Monad.Foil as Foil
import Control.Monad.Foil.Internal (NameMap (..))
import qualified Data.IntMap as IntMap
import Data.List (nub, (\\))
import qualified Data.Set as Set
import Language.Rzk.Foil.Print (fromTerm)
import Language.Rzk.Foil.Syntax
import Language.Rzk.Foil.Names (Binder (..), Display,
TypeInfo (..), VarIdent,
binderIsCompound, binderLeaves,
binderToPattern, defaultVarIdents,
freshenBinderLeaves, fromVarIdent,
refreshVarIn)
import qualified Language.Rzk.Syntax as Rzk
import Rzk.TypeCheck.Context
-- | What every name in scope is called, and what the printer may call the
-- binders it has yet to meet.
data Naming n = Naming
{ namingOf :: NameMap n Display
, namingUsed :: [VarIdent]
-- ^ the display names taken, so a bound binder is refreshed away from them
, namingSupply :: [VarIdent]
-- ^ the names left over, for anonymous binders
}
-- | Read the naming off a context.
--
-- A named binder keeps its name, refreshed only if an outer name has already
-- taken it. An anonymous one draws from the supply. A pattern binder has its
-- component names freshened as a group, so that the pattern shown in the context
-- and the projections folded inside a term agree on them.
--
-- Entries are named oldest binding first (see 'ctxBound'), so an outer binder
-- keeps its name and an inner one is the one refreshed away from it.
namingOfContext :: Context n -> Naming n
namingOfContext ctx = Naming
{ namingOf = NameMap (IntMap.fromList entries)
, namingUsed = used
, namingSupply = defaultVarIdents \\ used
}
where
(entries, usedSet) = go Set.empty defaultVarIdents (varsInScope ctx)
used = Set.toList usedSet
-- Name the entries in binding order, each avoiding the names already taken.
-- The taken names are a set: this runs once per entry, and a context can hold
-- every top-level definition of a project.
go taken _supply [] = ([], taken)
go taken supply ((v, info) : rest) =
case varOrig info of
BinderVar (Just x) ->
let x' = refreshVarIn taken x
in name (x', BinderVar (Just x')) [x'] supply
BinderVar Nothing ->
case supply of
x : supply' -> name (x, BinderVar (Just x)) [x] supply'
[] -> panicImpossible "not enough fresh variables"
binder ->
-- A pattern binder: the variable itself needs a placeholder name (it is
-- only shown when the whole point is used, and then it prints as the
-- pattern), and its leaves are freshened together.
case supply of
x : supply' ->
let binder' = freshenBinderLeaves (Set.toList taken) binder
in name (x, binder') (x : binderLeaves binder') supply'
[] -> panicImpossible "not enough fresh variables"
where
name display claimed supply' =
let (acc, taken') = go (foldr Set.insert taken claimed) supply' rest
in ((Foil.nameId v, display) : acc, taken')
-- | A term already rendered for the user, kept as surface syntax rather than a
-- string so that a consumer may still inspect it.
--
-- Its 'Show' prints the surface syntax, which is what the old @Term'@ did, so a
-- rendered goal or candidate reads the same as it always has (@\\ (t, s) → ?@).
newtype Rendered = Rendered { getRendered :: Rzk.Term }
instance Show Rendered where
show = Rzk.printTree . getRendered
-- | Two rendered terms are equal when they read the same. (The surface AST
-- carries source positions, which a rendered term should not be judged by.)
instance Eq Rendered where
l == r = show l == show r
-- | A term as surface syntax, with the context's names.
--
-- A binder /inside/ the term is freshened only against the names the term itself
-- mentions — not against everything in scope. A type shows the binder it was
-- written with (@Σ (a : A), B a@), even where the context happens to have an @a@ of
-- its own: the two are different variables, and shadowing is what binders are for.
renderTerm :: Naming n -> Term n -> Rendered
renderTerm naming t = Rendered (fromTerm used supply (namingOf naming) t)
where
used = nub $ concat
[ x : binderLeaves binder
| v <- freeVarsOfTerm t
, let (x, binder) = displayOf naming v
]
supply = defaultVarIdents \\ used
-- | A term shown to the user.
ppTerm :: Naming n -> Term n -> String
ppTerm naming = show . renderTerm naming
-- | A typed term shown as @term : type@, as the old @ppFoldT@ did. A variable is
-- shown bare: its type is in the context, not on the node.
ppTermT :: Naming n -> TermT n -> String
ppTermT naming t =
case typeInfoOf t of
Nothing -> ppTerm naming (untyped t)
Just info -> ppTerm naming (untyped t) <> " : " <> ppTerm naming (untyped (infoType info))
-- | What a name is called, and the (freshened) binder it was introduced by.
displayOf :: Naming n -> Foil.Name n -> Display
displayOf naming name = Foil.lookupName name (namingOf naming)
-- | A variable as the user sees it: a pattern binder shows as its pattern
-- (@(t, s)@), anything else by its display name.
ppName :: Naming n -> Foil.Name n -> String
ppName naming name =
case Foil.lookupName name (namingOf naming) of
(_, binder) | binderIsCompound binder -> Rzk.printTree (binderToPattern binder)
(x, _) -> Rzk.printTree (fromVarIdent x)
panicImpossible :: String -> a
panicImpossible msg = error $ unlines
[ "PANIC! Impossible happened (" <> msg <> ")!"
, "Please, report a bug at https://github.com/rzk-lang/rzk/issues"
]