packages feed

zwirn-0.2.3.1: src/zwirn-lang/Zwirn/Language/Pretty.hs

{-# OPTIONS_GHC -Wno-orphans #-}

module Zwirn.Language.Pretty where

{-
    Pretty.hs - prettyprinter for zwirn
    Copyright (C) 2025, Martin Gius

    This library is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this library.  If not, see <http://www.gnu.org/licenses/>.
-}

import qualified Data.Text as T
import Prettyprinter
import Prettyprinter.Render.Text (renderStrict)
import Text.Read (readMaybe)
import Zwirn.Language.Location (Located (..), RealSrcLoc (..), SrcLoc (..), noLoc)
import Zwirn.Language.Syntax
import Zwirn.Language.TypeCheck.Constraint (TypeError (..))
import Zwirn.Language.TypeCheck.Types

instance (Pretty a) => Pretty (Located a) where
  pretty (Located _ x) = pretty x

instance Pretty Term where
  pretty (TVar x) = pretty x
  pretty (TNum x) = case (readMaybe (T.unpack x) :: Maybe Int) of
    Just i -> pretty i
    Nothing -> pretty x
  pretty (TText x) = pretty x
  pretty (TMacro x) = "!" <> pretty x
  pretty (TBracket x) = parens $ pretty x
  pretty TRest = "~"
  pretty (TRepeat x Nothing) = pretty x <> "!"
  pretty (TRepeat x (Just i)) = pretty x <> sep (replicate i "!")
  pretty (TSeq [t]) = pretty t
  pretty (TSeq ts) = group $ brackets $ sep $ map pretty ts
  pretty (TStack ts) = alignedBetween (map pretty ts) lbracket rbracket comma
  pretty (TAlt ts) = group $ alignedBetween (map pretty ts) langle rangle space
  pretty (TChoice _ ts) = group $ alignedBetween (map pretty ts) lbracket rbracket pipe
  pretty (TPoly x y) = pretty x <> "%" <> pretty y
  pretty (TApp x y) = pretty x <+> pretty y
  pretty (TLambda vs x) = "\\" <> hcat (punctuate space $ map (pretty . T.unpack) vs) <+> "->" <+> pretty x
  pretty (TIfThenElse x y (Just z)) = "if" <+> pretty x <+> "then" <+> pretty y <+> "else" <+> pretty z
  pretty (TIfThenElse x y Nothing) = "if" <+> pretty x <+> "then" <+> pretty y
  pretty (TSectionL t n) = pretty t <+> pretty (unpackOp $ lValue n)
  pretty (TSectionR n t) = pretty (unpackOp $ lValue n) <+> pretty t
  pretty (TEnum Run x y) = brackets (pretty x <+> ".." <+> pretty y)
  pretty (TEnumThen Run x y z) = brackets (pretty x <+> pretty y <+> ".." <+> pretty z)
  pretty (TEnum Cord x y) = brackets (pretty x <+> ", .." <+> pretty y)
  pretty (TEnumThen Cord x y z) = brackets (pretty x <> comma <+> pretty y <+> ".." <+> pretty z)
  pretty (TEnum Choice x y) = brackets (pretty x <+> "| .." <+> pretty y)
  pretty (TEnumThen Choice x y z) = brackets (pretty x <+> pipe <+> pretty y <+> ".." <+> pretty z)
  pretty (TEnum Alt x y) = angles (pretty x <+> ".." <+> pretty y)
  pretty (TEnumThen Alt x y z) = angles (pretty x <+> pretty y <+> ".." <+> pretty z)
  pretty inf@(TInfix {}) = startThenAlign (pretty x) (map (\(op, y) -> pretty (unpackOp $ lValue op) <+> pretty y) xs)
    where
      (x, xs) = infixChain (noLoc inf)

alignedBetween :: [Doc a] -> Doc a -> Doc a -> Doc a -> Doc a
alignedBetween [] _ _ _ = mempty
alignedBetween (x : xs) l r s = align $ vcat $ (l <> x) : map (s <>) xs ++ [r]

startThenAlign :: Doc ann -> [Doc ann] -> Doc ann
startThenAlign x xs = x <+> align (vsep xs)

unpackOp :: T.Text -> String
unpackOp t = filter (\c -> c /= '(' && c /= ')') $ T.unpack t

infixChain :: LocTerm -> (LocTerm, [(LocVar, LocTerm)])
infixChain (Located _ (TInfix x op y)) = let (t, cs) = infixChain y in (x, (op, t) : cs)
infixChain t = (t, [])

parensIf :: Bool -> Doc a -> Doc a
parensIf True = parens
parensIf False = id

instance Pretty Type where
  pretty (TypeArr a b) = parensIf (isArrow a) (pretty a) <+> "->" <+> pretty b
    where
      isArrow (Located _ TypeArr {}) = True
      isArrow _ = False
  pretty (TypeVar a) = pretty a
  pretty (TypeCon a) = pretty a

instance Pretty Predicate where
  pretty (IsIn c t) = pretty c <+> pretty t

prettyPredicates :: [Predicate] -> Doc a
prettyPredicates ps = parensIf (length ps > 1) (hcat (punctuate comma (map pretty ps)))

instance (Pretty a) => Pretty (Qualified a) where
  pretty (Qual [] _ t) = pretty t
  pretty (Qual ps _ t) = prettyPredicates ps <+> "=>" <+> pretty t

instance Pretty Scheme where
  pretty (Forall _ t) = pretty t

instance Pretty SrcLoc where
  pretty NoLoc = "NoLoc"
  pretty (SrcLoc (RealSrcLoc _ lst cst len cen)) = parens $ vcat $ punctuate comma [pretty lst, pretty cst, pretty len, pretty cen]

renderDoc :: Doc a -> T.Text
renderDoc = renderStrict . layoutPretty defaultLayoutOptions

render :: (Pretty a) => a -> T.Text
render = renderDoc . pretty

pptype :: Type -> T.Text
pptype = render

ppscheme :: Scheme -> T.Text
ppscheme = render

ppterm :: Term -> T.Text
ppterm = render

ppTermHasType :: (LocTerm, Scheme) -> T.Text
ppTermHasType (t, s) = renderDoc $ pretty t <+> "::" <+> pretty s

instance Pretty TypeError where
  pretty (UnificationFail (Located _ (a, b))) = "Cannot unify types:" <+> pretty a <+> "~" <+> pretty b
  pretty (InfiniteType a b) = "Cannot construct the infinite type:" <+> pretty a <+> "=" <+> pretty b
  pretty (Ambigious cs) = vsep ["Cannot not match expected type: '" <> pretty a <> "' with actual type: '" <> pretty b <> "'\n" | Located _ (a, b) <- cs]
  pretty (UnboundVariable a) = "Variable not in scope:" <+> pretty a
  pretty (NoInstance (Located _ (IsIn c x))) = "No instance for" <+> pretty c <+> pretty x
  pretty NotImplemented = "Case not implemented in type-checker."

instance Pretty Command where
  pretty (TypeCommand t) = ":t" <+> pretty t
  pretty (ShowCommand t) = ":show" <+> pretty t
  pretty (InfoCommand t) = ":info" <+> pretty t
  pretty (SetCommand t) = ":set" <+> pretty t
  pretty (UnsetCommand t) = ":unset" <+> pretty t
  pretty (LoadCommand t) = ":load" <+> pretty t
  pretty ResetConfigCommand = ":resetconfig"
  pretty ShowConfigPathCommand = ":showconfig"
  pretty ResetEnvCommand = ":reset"
  pretty StatusCommand = ":status"
  pretty EnvCommand = ":env"

instance Pretty Definition where
  pretty (Definition x xs t) = pretty x <+> vsep (map pretty xs) <+> "=" <+> pretty t

instance Pretty DynamicDefinition where
  pretty (DynamicDefinition x t) = pretty x <+> "<-" <+> pretty t

instance Pretty MacroDefinition where
  pretty (MacroDefinition x t) = "!" <> pretty x <+> "=" <+> pretty t

instance Pretty Syntax where
  pretty (Exec t) = pretty t
  pretty (Def d) = pretty d
  pretty (DynDef d) = pretty d
  pretty (MacroDef d) = pretty d
  pretty (Command c) = pretty c