better prettyprinting

This commit is contained in:
William Ball 2024-12-08 12:40:52 -08:00
parent 7f9d029ff9
commit 8bc8e5e171
7 changed files with 110 additions and 79 deletions

View file

@ -51,7 +51,7 @@ actOnParse :: String -> (Expr -> InputT IO ()) -> InputT IO ()
actOnParse input action = either outputStrLn (action . elaborate) $ parseExpr "repl" (pack input) actOnParse input action = either outputStrLn (action . elaborate) $ parseExpr "repl" (pack input)
printErrorOrResult :: Env -> (Expr -> ReaderT Env Result Expr) -> Expr -> InputT IO () printErrorOrResult :: Env -> (Expr -> ReaderT Env Result Expr) -> Expr -> InputT IO ()
printErrorOrResult env action expr = putTextLn $ either toText pretty $ runReaderT (action expr) env printErrorOrResult env action expr = putTextLn $ either toText prettyT $ runReaderT (action expr) env
parseActPrint :: Env -> String -> (Expr -> ReaderT Env Result Expr) -> InputT IO () parseActPrint :: Env -> String -> (Expr -> ReaderT Env Result Expr) -> InputT IO ()
parseActPrint env input action = actOnParse input (printErrorOrResult env action) parseActPrint env input action = actOnParse input (printErrorOrResult env action)
@ -75,7 +75,7 @@ repl = do
Just Quit -> pure env Just Quit -> pure env
Just DumpEnv -> lift (dumpEnv env) >> loop env Just DumpEnv -> lift (dumpEnv env) >> loop env
Just (TypeQuery input) -> parseActPrint env input (findType []) >> loop env Just (TypeQuery input) -> parseActPrint env input (findType []) >> loop env
Just (ValueQuery input) -> lookupAct env input (putTextLn . pretty . _val) >> loop env Just (ValueQuery input) -> lookupAct env input (putTextLn . prettyT . _val) >> loop env
Just (Normalize input) -> parseActPrint env input normalize >> loop env Just (Normalize input) -> parseActPrint env input normalize >> loop env
Just (WeakNormalize input) -> parseActPrint env input whnf >> loop env Just (WeakNormalize input) -> parseActPrint env input whnf >> loop env
Just (DumpDebug input) -> either outputStrLn (outputStrLn . show) (parseDef "repl" (pack input)) >> loop env Just (DumpDebug input) -> either outputStrLn (outputStrLn . show) (parseDef "repl" (pack input)) >> loop env

View file

@ -3,10 +3,6 @@
-- import basic logic definitions from <logic.pg> -- import basic logic definitions from <logic.pg>
@include logic.pg @include logic.pg
@include algebra.pg
def comp (A B C : *) (g : B -> C) (f : A -> B) (x : A) : C :=
g (f x);
-- }}} -- }}}
@ -473,7 +469,8 @@ def one_plus_one_two : eq nat (plus one one) two :=
-- We will now prove a couple standard properties of addition. -- We will now prove a couple standard properties of addition.
-- First, associativity, namely that n + (m + p) = (n + m) + p. -- First, associativity, namely that n + (m + p) = (n + m) + p.
def plus_assoc : assoc nat plus := fun (n m : nat) => def plus_assoc : forall (n m p : nat), eq nat (plus n (plus m p)) (plus (plus n m) p)
:= fun (n m : nat) =>
-- We prove this via induction on p for any fixed n and m. -- We prove this via induction on p for any fixed n and m.
nat_ind nat_ind
(fun (p : nat) => eq nat (plus n (plus m p)) (plus (plus n m) p)) (fun (p : nat) => eq nat (plus n (plus m p)) (plus (plus n m) p))

View file

@ -22,7 +22,7 @@ findLevel g a = do
whnf s >>= \case whnf s >>= \case
Level i -> pure i Level i -> pure i
Star -> pure $ -1 -- HACK: but works, so... Star -> pure $ -1 -- HACK: but works, so...
_ -> throwError $ NotASort a s _ -> throwError $ NotASort a
validateType :: Context -> Expr -> ReaderT Env Result () validateType :: Context -> Expr -> ReaderT Env Result ()
validateType g a = void $ findLevel g a validateType g a = void $ findLevel g a
@ -39,9 +39,9 @@ compSort _ _ (Level i) Star
| i == 0 = pure Star | i == 0 = pure Star
| otherwise = pure $ Level i | otherwise = pure $ Level i
compSort _ _ (Level i) (Level j) = pure $ Level $ max i j compSort _ _ (Level i) (Level j) = pure $ Level $ max i j
compSort a b left right compSort a b left _
| isSort left = throwError $ NotASort b right | isSort left = throwError $ NotASort b
| otherwise = throwError $ NotASort a left | otherwise = throwError $ NotASort a
findType :: Context -> Expr -> ReaderT Env Result Expr findType :: Context -> Expr -> ReaderT Env Result Expr
findType _ Star = pure $ Level 0 findType _ Star = pure $ Level 0

View file

@ -1,25 +1,35 @@
module Errors where module Errors where
import Expr import Expr
import Prettyprinter
import Prettyprinter.Render.Text
import Prelude hiding (group)
data Error data Error
= UnboundVariable Text = UnboundVariable Text
| NotASort Expr Expr | NotASort Expr
| ExpectedPiType Expr Expr | ExpectedPiType Expr Expr
| NotEquivalent Expr Expr Expr | NotEquivalent Expr Expr Expr
| PNMissingType Text | PNMissingType Text
| DuplicateDefinition Text | DuplicateDefinition Text
| EmptySection Text
deriving (Eq, Ord) deriving (Eq, Ord)
instance Pretty Error where
pretty (UnboundVariable x) = "Unbound variable: '" <> pretty x <> "'"
pretty (NotASort x) = group $ hang 4 ("Term:" <> line <> pretty x) <> line <> "is not a type"
pretty (ExpectedPiType x t) = group $ hang 4 ("Term:" <> line <> pretty x) <> hang 4 ("is not a function, instead is type" <> line <> pretty t)
pretty (NotEquivalent a a' e) =
group $
hang 4 ("Cannot unify" <> line <> pretty a)
<> line
<> hang 4 ("with" <> line <> pretty a')
<> line
<> hang 4 ("when evaluating" <> line <> pretty e)
pretty (PNMissingType x) = "Axiom '" <> pretty x <> "' missing type ascription"
pretty (DuplicateDefinition n) = "'" <> pretty n <> "' already defined"
instance ToText Error where instance ToText Error where
toText (UnboundVariable x) = "Unbound variable: '" <> x <> "'" toText = renderStrict . layoutSmart (LayoutOptions (AvailablePerLine 100 1.0)) . pretty
toText (NotASort x t) = "Expected '" <> pretty x <> "' to have type * or □, instead found '" <> pretty t <> "'"
toText (ExpectedPiType x t) = "'" <> pretty x <> "' : '" <> pretty t <> "' is not a function"
toText (NotEquivalent a a' e) = "Cannot unify '" <> pretty a <> "' with '" <> pretty a' <> "' when evaluating '" <> pretty e <> "'"
toText (PNMissingType x) = "Axiom '" <> x <> "' missing type ascription"
toText (DuplicateDefinition n) = "'" <> n <> "' already defined"
toText (EmptySection var) = "Tried to declare variable " <> var <> " without a section"
instance ToString Error where instance ToString Error where
toString = toString . toText toString = toString . toText

View file

@ -26,7 +26,7 @@ emptyEnv :: Env
emptyEnv = M.empty emptyEnv = M.empty
showEnvEntry :: Text -> Definition -> Text showEnvEntry :: Text -> Definition -> Text
showEnvEntry k Def{_ty = t} = k <> " : " <> pretty t showEnvEntry k Def{_ty = t} = k <> " : " <> prettyT t
dumpEnv :: Env -> IO () dumpEnv :: Env -> IO ()
dumpEnv = void . M.traverseWithKey ((putTextLn .) . showEnvEntry) dumpEnv = void . M.traverseWithKey ((putTextLn .) . showEnvEntry)

View file

@ -1,5 +1,10 @@
module Expr where module Expr where
import qualified Data.List.NonEmpty as NE
import Prettyprinter
import Prettyprinter.Render.Text
import Prelude hiding (group)
data Expr where data Expr where
Var :: Text -> Integer -> Expr Var :: Text -> Integer -> Expr
Free :: Text -> Expr Free :: Text -> Expr
@ -12,6 +17,9 @@ data Expr where
Let :: Text -> Maybe Expr -> Expr -> Expr -> Expr Let :: Text -> Maybe Expr -> Expr -> Expr -> Expr
deriving (Show, Ord) deriving (Show, Ord)
instance Pretty Expr where
pretty = prettyExpr 0 . cleanNames
instance Eq Expr where instance Eq Expr where
(Var _ n) == (Var _ m) = n == m (Var _ n) == (Var _ m) = n == m
(Free s) == (Free t) = s == t (Free s) == (Free t) = s == t
@ -53,21 +61,28 @@ incIndices = shiftIndices 1 0
{- --------------------- PRETTY PRINTING ----------------------------- -} {- --------------------- PRETTY PRINTING ----------------------------- -}
parenthesize :: Text -> Text data Param = Param Text Expr
parenthesize s = "(" <> s <> ")" data ParamGroup = ParamGroup [Text] Expr
data Binding = Binding Text [ParamGroup] Expr
type Param = (Text, Expr) instance Pretty Param where
type ParamGroup = ([Text], Expr) pretty (Param x ty) = group $ parens $ pretty x <+> ":" <+> pretty ty
type Binding = (Text, [ParamGroup], Expr)
instance Pretty ParamGroup where
pretty (ParamGroup vars ty) = group $ parens $ align (sep (map pretty vars)) <+> ":" <+> pretty ty
instance Pretty Binding where
pretty (Binding var [] body) = group $ parens $ pretty var <+> ":=" <+> pretty body
pretty (Binding var params body) = group $ parens $ pretty var <+> align (sep (map pretty params)) <+> ":=" <+> pretty body
collectLambdas :: Expr -> ([Param], Expr) collectLambdas :: Expr -> ([Param], Expr)
collectLambdas (Abs x ty _ body) = ((x, ty) : params, final) collectLambdas (Abs x ty _ body) = (Param x ty : params, final)
where where
(params, final) = collectLambdas body (params, final) = collectLambdas body
collectLambdas e = ([], e) collectLambdas e = ([], e)
collectLets :: Expr -> ([Binding], Expr) collectLets :: Expr -> ([Binding], Expr)
collectLets (Let x _ val body) = ((x, params', val') : bindings, final) collectLets (Let x _ val body) = (Binding x params' val' : bindings, final)
where where
(bindings, final) = collectLets body (bindings, final) = collectLets body
(params, val') = collectLambdas val (params, val') = collectLambdas val
@ -76,11 +91,17 @@ collectLets e = ([], e)
collectPis :: Expr -> ([Param], Expr) collectPis :: Expr -> ([Param], Expr)
collectPis p@(Pi "" _ _ _) = ([], p) collectPis p@(Pi "" _ _ _) = ([], p)
collectPis (Pi x ty _ body) = ((x, ty) : params, final) collectPis (Pi x ty _ body) = (Param x ty : params, final)
where where
(params, final) = collectPis body (params, final) = collectPis body
collectPis e = ([], e) collectPis e = ([], e)
collectApps :: Expr -> NonEmpty Expr
collectApps (App e1 e2) = e2 :| toList rest
where
rest = collectApps e1
collectApps e = pure e
cleanNames :: Expr -> Expr cleanNames :: Expr -> Expr
cleanNames (App m n) = App (cleanNames m) (cleanNames n) cleanNames (App m n) = App (cleanNames m) (cleanNames n)
cleanNames (Abs x ty a body) = Abs x (cleanNames ty) a (cleanNames body) cleanNames (Abs x ty a body) = Abs x (cleanNames ty) a (cleanNames body)
@ -92,25 +113,13 @@ cleanNames e = e
groupParams :: [Param] -> [ParamGroup] groupParams :: [Param] -> [ParamGroup]
groupParams = foldr addParam [] groupParams = foldr addParam []
where where
addParam :: (Text, Expr) -> [([Text], Expr)] -> [([Text], Expr)] addParam :: Param -> [ParamGroup] -> [ParamGroup]
addParam (x, t) [] = [([x], t)] addParam (Param x t) [] = [ParamGroup [x] t]
addParam (x, t) l@((xs, s) : rest) addParam (Param x t) l@(ParamGroup xs s : rest)
| incIndices t == s = (x : xs, t) : rest | incIndices t == s = ParamGroup (x : xs) t : rest
| otherwise = ([x], t) : l | otherwise = ParamGroup [x] t : l
showParamGroup :: ParamGroup -> Text printLevel :: Integer -> Doc ann
showParamGroup (ids, ty) = parenthesize $ unwords ids <> " : " <> pretty ty
showBinding :: Binding -> Text
showBinding (ident, params, val) =
parenthesize $
ident
<> " "
<> unwords (map showParamGroup params)
<> " := "
<> pretty val
printLevel :: Integer -> Text
printLevel k printLevel k
| k == 0 = "" | k == 0 = ""
| k == 1 = "" | k == 1 = ""
@ -122,40 +131,54 @@ printLevel k
| k == 7 = "" | k == 7 = ""
| k == 8 = "" | k == 8 = ""
| k == 9 = "" | k == 9 = ""
| k < 0 = show k | k < 0 = pretty k
| otherwise = printLevel (k `div` 10) <> printLevel (k `mod` 10) | otherwise = printLevel (k `div` 10) <> printLevel (k `mod` 10)
helper :: Integer -> Expr -> Text prettyExpr :: Integer -> Expr -> Doc ann
helper _ (Var s _) = s prettyExpr k expr = case expr of
helper _ (Free s) = s Var s _ -> pretty s
helper _ (Axiom s) = s Free s -> pretty s
helper _ Star = "*" Axiom s -> pretty s
helper _ (Level i) Star -> "*"
| i == 0 = "" Level i
| otherwise = "" <> printLevel i | i == 0 -> ""
helper k (App e1 e2) = if k > 3 then parenthesize res else res | otherwise -> "" <> printLevel i
App{}
| k > 3 -> parens application
| otherwise -> application
where where
res = helper 3 e1 <> " " <> helper 4 e2 (top :| rest) = NE.reverse $ collectApps expr
helper k (Pi "" t1 _ t2) = if k > 2 then parenthesize res else res application = group $ hang 4 $ prettyExpr 3 top <> line <> align (sep $ map (prettyExpr 4) rest)
Pi "" t1 _ t2
| k > 2 -> parens piType
| otherwise -> piType
where where
res = helper 3 t1 <> " -> " <> helper 2 t2 piType = group $ prettyExpr 3 t1 <+> "->" <+> align (prettyExpr 2 t2)
helper k e@(Pi{}) = if k > 2 then parenthesize res else res Pi{} -> group $ hang 4 $ "" <+> align (sep grouped) <> "," <> line <> align (prettyExpr 0 body)
where where
(params, body) = collectPis e (params, body) = collectPis expr
grouped = showParamGroup <$> groupParams params grouped = pretty <$> groupParams params
res = "" <> unwords grouped <> " , " <> pretty body Abs{} ->
helper k e@(Abs{}) = if k >= 1 then parenthesize res else res if k >= 1
then parens lambdaForm
else lambdaForm
where where
(params, body) = collectLambdas e (params, body) = collectLambdas expr
grouped = showParamGroup <$> groupParams params grouped = pretty <$> groupParams params
res = "λ " <> unwords grouped <> " => " <> pretty body lambdaForm = group $ hang 4 $ "λ" <+> align (sep grouped) <+> "=>" <> line <> align (prettyExpr 0 body)
helper _ e@(Let{}) = res Let{} ->
group $
vsep
[ "let" <+> align bindings
, "in" <+> align (prettyExpr 0 body)
, "end"
]
where where
(binds, body) = collectLets e (binds, body) = collectLets expr
res = "let " <> unwords (map showBinding binds) <> " in " <> pretty body <> " end" bindings = sep $ map pretty binds
pretty :: Expr -> Text prettyT :: Expr -> Text
pretty = helper 0 . cleanNames prettyT = renderStrict . layoutSmart defaultLayoutOptions . pretty
prettyS :: Expr -> String prettyS :: Expr -> String
prettyS = toString . pretty prettyS = toString . prettyT

View file

@ -42,6 +42,7 @@ library perga-lib
, megaparsec , megaparsec
, mtl , mtl
, parser-combinators , parser-combinators
, prettyprinter
mixins: base hiding (Prelude) mixins: base hiding (Prelude)
, relude (Relude as Prelude) , relude (Relude as Prelude)
, relude , relude