axel-0.0.12: src/Axel/Pretty.hs
module Axel.Pretty where
import Axel.Prelude
import Axel.Parse (unhygenisizeIdentifier)
import Axel.Parse.AST
import qualified Axel.Sourcemap as SM
import Axel.Utils.Foldable (mapWithPrev)
import Axel.Utils.Recursion (bottomUpFmap)
import Axel.Utils.Text (handleCharEscapes)
import Control.Lens (ala, under)
import Data.Maybe (fromMaybe)
import Data.Semigroup (Max(Max), (<>))
import qualified Data.Text as T
import Data.Text.Lens (unpacked)
import qualified Data.Text.Prettyprint.Doc as P
import Data.Text.Prettyprint.Doc ((<+>))
import qualified Data.Text.Prettyprint.Doc.Render.Text as P
render :: P.Doc a -> Text
render = P.renderStrict . P.layoutSmart (P.LayoutOptions P.Unbounded)
maximumExpressionLength :: Int
maximumExpressionLength = 60
columnWidth :: Text -> Int
columnWidth = ala Max foldMap . map T.length . T.lines
sexp :: [P.Doc a] -> P.Doc a
sexp [] = mempty
sexp [x] = x
sexp (x:xs) =
let align cons = x `cons` xs
oneLiner = align (\y ys -> y <+> P.hsep ys)
balanced = align (\y ys -> y <+> P.align (P.vsep ys))
multiLiner = align (\y ys -> P.align (P.vsep (y : ys)))
shortEnough doc = columnWidth (render doc) <= maximumExpressionLength
in if | shortEnough oneLiner -> oneLiner
| shortEnough balanced -> balanced
| otherwise -> multiLiner
privilegedFormToAxelPretty :: [Expression ann] -> P.Doc a
privilegedFormToAxelPretty (Symbol _ "data":name:rest) =
"data" <+> sexp (toAxelPretty name : map toAxelPretty rest)
privilegedFormToAxelPretty (Symbol _ "def":name:tySig:rest) =
"def" <+>
P.align
(P.vsep
[ (toAxelPretty name <+> toAxelPretty tySig)
, sexp (map toAxelPretty rest)
])
privilegedFormToAxelPretty (Symbol _ "defmacro":name:rest) =
"defmacro" <+> sexp (toAxelPretty name : map toAxelPretty rest)
privilegedFormToAxelPretty (Symbol _ "::":name:rest) =
"::" <+> sexp (toAxelPretty name : map toAxelPretty rest)
privilegedFormToAxelPretty (Symbol _ "=":name:args:rest) =
"=" <+>
sexp ((toAxelPretty name <+> toAxelPretty args) : map toAxelPretty rest)
privilegedFormToAxelPretty (Symbol _ "=macro":name:args:rest) =
"=macro" <+>
sexp ((toAxelPretty name <+> toAxelPretty args) : map toAxelPretty rest)
privilegedFormToAxelPretty (Symbol _ "let":bindings:rest) =
"let" <+> sexp (toAxelPretty bindings : map toAxelPretty rest)
privilegedFormToAxelPretty (Symbol _ "if":bindings:rest) =
"if" <+> sexp (toAxelPretty bindings : map toAxelPretty rest)
privilegedFormToAxelPretty (Symbol _ "case":bindings:rest) =
"case" <+> sexp (toAxelPretty bindings : map toAxelPretty rest)
privilegedFormToAxelPretty (Symbol _ "import":name:rest) =
"import" <+> sexp (toAxelPretty name : map toAxelPretty rest)
privilegedFormToAxelPretty (Symbol _ "importq":name:rest) =
"importq" <+> sexp (toAxelPretty name : map toAxelPretty rest)
privilegedFormToAxelPretty (Symbol _ "importm":name:rest) =
"importm" <+> sexp (toAxelPretty name : map toAxelPretty rest)
privilegedFormToAxelPretty xs = sexp $ map toAxelPretty xs
toAxelPretty :: Expression ann -> P.Doc a
toAxelPretty (LiteralChar _ x) = "#\\" <> P.pretty x
toAxelPretty (LiteralFloat _ x) = P.pretty x
toAxelPretty (LiteralInt _ x) = P.pretty x
toAxelPretty (LiteralString _ x) =
P.dquotes $ P.pretty (under unpacked handleCharEscapes x)
toAxelPretty (SExpression _ (Symbol _ "applyInfix":xs)) =
P.braces $ sexp (map toAxelPretty xs)
toAxelPretty (SExpression _ (Symbol _ "list":xs)) =
P.brackets $ sexp (map toAxelPretty xs)
toAxelPretty (SExpression _ [Symbol _ "quote", x]) = "\'" <> toAxelPretty x
toAxelPretty (SExpression _ [Symbol _ "quasiquote", x]) = "`" <> toAxelPretty x
toAxelPretty (SExpression _ [Symbol _ "unquote", x]) = "~" <> toAxelPretty x
toAxelPretty (SExpression _ [Symbol _ "unquoteSplicing", x]) =
"~@" <> toAxelPretty x
toAxelPretty (Symbol _ x) = P.pretty x
toAxelPretty (SExpression _ xs) = P.parens $ privilegedFormToAxelPretty xs
spaceStatements :: [SM.Expression] -> [P.Doc a]
spaceStatements =
mapWithPrev $ \prev x ->
let isImport (SExpression _ (Symbol _ id':_)) =
id' `elem` ["import", "importq", "importm"]
isImport _ = False
needsSpacing = not $ isImport x && fromMaybe False (isImport <$> prev)
maybeSpacer =
if needsSpacing
then P.line
else mempty
unhygenisize =
bottomUpFmap $ \case
Symbol ann symbol -> Symbol ann $ unhygenisizeIdentifier symbol
node -> node
in toAxelPretty (unhygenisize x) <> maybeSpacer
prettifyProgram :: [SM.Expression] -> Text
prettifyProgram = render . P.vsep . spaceStatements