packages feed

hydra-0.8.0: src/main/haskell/Hydra/Tools/Serialization.hs

-- | Utilities for constructing Hydra code trees

module Hydra.Tools.Serialization where

import Hydra.Ast

import qualified Data.List as L
import qualified Data.Maybe as Y


angleBraces :: Brackets
angleBraces = Brackets (sym "<") (sym ">")

angleBracesList ::  BlockStyle -> [Expr] -> Expr
angleBracesList style els = case els of
  [] -> cst "<>"
  _ -> brackets angleBraces style $ commaSep style els

bracketList :: BlockStyle -> [Expr] -> Expr
bracketList style els = case els of
  [] -> cst "[]"
  _ -> brackets squareBrackets style $ commaSep style els

brackets :: Brackets -> BlockStyle -> Expr -> Expr
brackets br style e = ExprBrackets $ BracketExpr br e style

commaSep :: BlockStyle -> [Expr] -> Expr
commaSep = symbolSep ","

curlyBlock :: BlockStyle -> Expr -> Expr
curlyBlock style e = curlyBracesList Nothing style [e]

curlyBraces :: Brackets
curlyBraces = Brackets (sym "{") (sym "}")

curlyBracesList :: Maybe String -> BlockStyle -> [Expr] -> Expr
curlyBracesList msymb style els = case els of
  [] -> cst "{}"
  _ -> brackets curlyBraces style $ symbolSep (Y.fromMaybe "," msymb) style els

cst :: String -> Expr
cst = ExprConst . Symbol

customIndent :: String -> String -> String
customIndent idt s = L.intercalate "\n" $ (idt ++) <$> lines s

customIndentBlock :: String -> [Expr] -> Expr
customIndentBlock idt els = case els of
  [x] -> x
--  (head:rest) -> ifx idtOp head $ indentSep idt rest
  (head:rest) -> ifx idtOp head $ newlineSep rest
    where
      idtOp = Op (sym "") (Padding WsSpace $ WsBreakAndIndent idt) (Precedence 0) AssociativityNone

dotSep :: [Expr] -> Expr
dotSep = sep $ Op (sym ".") (Padding WsNone WsNone) (Precedence 0) AssociativityNone

doubleNewlineSep :: [Expr] -> Expr
doubleNewlineSep = sep $ Op (sym "") (Padding WsBreak WsBreak) (Precedence 0) AssociativityNone

doubleSpace :: String
doubleSpace = "  "

fullBlockStyle :: BlockStyle
fullBlockStyle = BlockStyle (Just doubleSpace) True True

halfBlockStyle :: BlockStyle
halfBlockStyle = BlockStyle (Just doubleSpace) True False

ifx :: Op -> Expr -> Expr -> Expr
ifx op lhs rhs = ExprOp $ OpExpr op lhs rhs

indent :: String -> String
indent = customIndent doubleSpace

indentBlock :: [Expr] -> Expr
indentBlock = customIndentBlock doubleSpace

indentLines :: [Expr] -> Expr
indentLines els = ifx topOp (cst "") (newlineSep els)
  where
    topOp = Op (sym "") (Padding WsNone (WsBreakAndIndent doubleSpace)) (Precedence 0) AssociativityNone

indentSubsequentLines :: String -> Expr -> Expr
indentSubsequentLines idt e = ExprIndent $ IndentedExpression (IndentStyleSubsequentLines idt) e

infixWs :: String -> Expr -> Expr -> Expr
infixWs op l r = spaceSep [l, cst op, r]

infixWsList :: String -> [Expr] -> Expr
infixWsList op opers = spaceSep $ L.foldl (\e r -> if L.null e then [r] else r:opExpr:e) [] $ L.reverse opers
  where
    opExpr = cst op

inlineStyle :: BlockStyle
inlineStyle = BlockStyle Nothing False False

newlineSep :: [Expr] -> Expr
newlineSep = sep $ Op (sym "") (Padding WsNone WsBreak) (Precedence 0) AssociativityNone

noPadding :: Padding
noPadding = Padding WsNone WsNone

noSep :: [Expr] -> Expr
noSep = sep $ Op (sym "") (Padding WsNone WsNone) (Precedence 0) AssociativityNone

num :: Int -> Expr
num = cst . show

op :: String -> Int -> Associativity -> Op
op s p = Op (Symbol s) (Padding WsSpace WsSpace) (Precedence p)

orOp :: Bool -> Op
orOp newlines = Op (sym "|") (Padding WsSpace (if newlines then WsBreak else WsSpace)) (Precedence 0) AssociativityNone -- No source

orSep :: BlockStyle -> [Expr] -> Expr
orSep style l = case l of
  [] -> cst ""
  [x] -> x
  (h:r) -> ifx (orOp newlines) h $ orSep style r
  where
    newlines = blockStyleNewlineBeforeContent style

parenList :: Bool -> [Expr] -> Expr
parenList newlines els = case els of
    [] -> cst "()"
    _ -> brackets parentheses style $ commaSep style els
  where
    style = if newlines && L.length els > 1 then halfBlockStyle else inlineStyle

parens :: Expr -> Expr
parens = brackets parentheses inlineStyle

parentheses :: Brackets
parentheses = Brackets (sym "(") (sym ")")

parenthesize :: Expr -> Expr
parenthesize exp = case exp of
  ExprBrackets (BracketExpr br e newlines) -> ExprBrackets (BracketExpr br (parenthesize e) newlines)
  ExprConst _ -> exp
  ExprIndent (IndentedExpression style e) -> ExprIndent (IndentedExpression style (parenthesize e))
  ExprOp (OpExpr op@(Op _ _ prec assoc) lhs rhs) -> ExprOp (OpExpr op lhs2 rhs2)
    where
      lhs' = parenthesize lhs
      rhs' = parenthesize rhs
      lhs2 = case lhs' of
        ExprOp (OpExpr (Op _ _ lprec lassoc) _ _) -> case prec `compare` lprec of
          LT -> lhs'
          GT -> parens lhs'
          EQ -> if assocLeft assoc && assocLeft lassoc
            then lhs'
            else parens lhs'
        _ -> lhs'
      rhs2 = case rhs' of
        ExprOp (OpExpr (Op _ _ rprec rassoc) _ _) -> case prec `compare` rprec of
          LT -> rhs'
          GT -> parens rhs'
          EQ -> if assocRight assoc && assocRight rassoc
            then rhs'
            else parens rhs'
        _ -> rhs'
      assocLeft a = a == AssociativityLeft || a == AssociativityNone || a == AssociativityBoth
      assocRight a = a == AssociativityRight || a == AssociativityNone || a == AssociativityBoth

prefix :: String -> Expr -> Expr
prefix p = ifx preOp (cst "")
  where
    preOp = Op (sym p) (Padding WsNone WsNone) (Precedence 0) AssociativityNone

printExpr :: Expr -> String
printExpr e = case e of
  ExprConst (Symbol s) -> s
  ExprIndent (IndentedExpression style expr) -> L.intercalate "\n" $ case style of
      IndentStyleAllLines idt -> (idt ++) <$> lns
      IndentStyleSubsequentLines idt -> case lns of
        [x] -> [x]
        (head:rest) -> head:((idt ++) <$> rest)
    where
      lns = lines $ printExpr expr
  ExprOp (OpExpr (Op (Symbol sym) (Padding padl padr) _ _) l r) -> lhs ++ pad padl ++ sym ++ pad padr ++ rhs
    where
      lhs = idt padl $ printExpr l
      rhs = idt padr $ printExpr r
      idt ws s = case ws of
        WsBreakAndIndent idt -> customIndent idt s
        _ -> s
      pad ws = case ws of
        WsNone -> ""
        WsSpace -> " "
        WsBreak -> "\n"
        WsBreakAndIndent _ -> "\n"
        WsDoubleBreak -> "\n\n"
  ExprBrackets (BracketExpr (Brackets (Symbol l) (Symbol r)) e style) ->
      l ++ pre ++ ibody ++ suf ++ r
    where
      body = printExpr e
      ibody = case doIndent of
         Nothing -> body
         Just idt -> customIndent idt body
      pre = if nlBefore then "\n" else ""
      suf = if nlAfter then "\n" else ""
      BlockStyle doIndent nlBefore nlAfter = style

sep :: Op -> [Expr] -> Expr
sep op els =  case els of
  [] -> cst ""
  [x] -> x
  (h:r) -> ifx op h $ sep op r

spaceSep :: [Expr] -> Expr
spaceSep = sep $ Op (sym "") (Padding WsSpace WsNone) (Precedence 0) AssociativityNone

squareBrackets :: Brackets
squareBrackets = Brackets (sym "[") (sym "]")

sym :: String -> Symbol
sym = Symbol

symbolSep :: String -> BlockStyle -> [Expr] -> Expr
symbolSep symb style l = case l of
    [] -> cst ""
    [x] -> x
    (h:r) -> ifx commaOp h $ symbolSep symb style r
  where
    break = case L.length $ L.filter id [blockStyleNewlineBeforeContent style, blockStyleNewlineAfterContent style] of
      0 -> WsSpace
      1 -> WsBreak
      2 -> WsDoubleBreak
    commaOp = Op (sym symb) (Padding WsNone break) (Precedence 0) AssociativityNone -- No source