packages feed

zwirn-0.2.2.0: 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
import Zwirn.Stream.Types (Identifier (..))

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 Identifier where
  pretty (TextID t) = pretty t
  pretty (NumID i) = pretty i

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

ppID :: Identifier -> T.Text
ppID = render

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