packages feed

hydra-0.8.0: src/main/haskell/Hydra/Ext/Haskell/Serde.hs

-- | Haskell operator precendence and associativity are drawn from:
--   https://self-learning-java-tutorial.blogspot.com/2016/04/haskell-operator-precedence.html
-- Other operators were investigated using GHCi, e.g. ":info (->)"
-- Operator names are drawn (loosely) from:
--   https://stackoverflow.com/questions/7746894/are-there-pronounceable-names-for-common-haskell-operators

module Hydra.Ext.Haskell.Serde where

import Hydra.Ast
import Hydra.Tools.Serialization
import Hydra.Ext.Haskell.Operators
import qualified Hydra.Ext.Haskell.Ast as H

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


class ToTree a where
  toTree :: a -> Expr

instance ToTree H.Alternative where
  toTree (H.Alternative pat rhs _) = ifx caseOp (toTree pat) (toTree rhs)

instance ToTree H.Assertion where
  toTree sert = case sert of
    H.AssertionClass cls -> toTree cls
    H.AssertionTuple serts -> parenList False (toTree <$> serts)

instance ToTree H.Assertion_Class where
  toTree (H.Assertion_Class name types) = spaceSep [toTree name, commaSep halfBlockStyle (toTree <$> types)]

instance ToTree H.CaseRhs where
  toTree (H.CaseRhs expr) = toTree expr

instance ToTree H.Constructor where
  toTree cons = case cons of
    H.ConstructorOrdinary (H.Constructor_Ordinary name types) -> spaceSep [
      toTree name,
      spaceSep (toTree <$> types)]
    H.ConstructorRecord (H.Constructor_Record name fields) -> spaceSep [
      toTree name,
      curlyBracesList Nothing halfBlockStyle (toTree <$> fields)]

instance ToTree H.ConstructorWithComments where
  toTree (H.ConstructorWithComments body mc) = case mc of
    Nothing -> toTree body
    Just c -> newlineSep [cst $ toHaskellComments c, toTree body]

instance ToTree H.DataDeclaration_Keyword where
  toTree kw = case kw of
    H.DataDeclaration_KeywordData -> cst "data"
    H.DataDeclaration_KeywordNewtype -> cst "newtype"

instance ToTree H.Declaration where
  toTree decl = case decl of
    H.DeclarationData (H.DataDeclaration kw _ hd cons deriv) -> indentBlock $
        [spaceSep [toTree kw, toTree hd, cst "="], constructors]
        ++ if L.null derivCat then [] else [spaceSep [cst "deriving", parenList False (toTree <$> derivCat)]]
      where
        derivCat = L.concat $ h <$> deriv
          where
            h (H.Deriving names) = names
        constructors = orSep halfBlockStyle (toTree <$> cons)
    H.DeclarationType (H.TypeDeclaration hd typ) -> spaceSep [cst "type", toTree hd, cst "=", toTree typ]
    H.DeclarationValueBinding vb -> toTree vb
    H.DeclarationTypedBinding (H.TypedBinding (H.TypeSignature name htype) vb) -> newlineSep [ -- TODO: local bindings
        ifx typeOp (toTree name) (toTree htype),
        toTree vb]

instance ToTree H.DeclarationHead where
  toTree hd = case hd of
    H.DeclarationHeadApplication (H.DeclarationHead_Application fun op) -> spaceSep [toTree fun, toTree op]
--    H.DeclarationHeadParens ... ->
    H.DeclarationHeadSimple name -> toTree name

instance ToTree H.DeclarationWithComments where
  toTree (H.DeclarationWithComments body mc) = case mc of
    Nothing -> toTree body
    Just c -> newlineSep [cst $ toHaskellComments c, toTree body]

instance ToTree H.Expression where
  toTree expr = case expr of
      H.ExpressionApplication app -> toTree app
      H.ExpressionCase cases -> toTree cases
      H.ExpressionConstructRecord r -> toTree r
      H.ExpressionDo statements -> indentBlock $ [cst "do"] ++ (toTree <$> statements)
      H.ExpressionIf ifte -> toTree ifte
    --  H.ExpressionInfixApplication Term_InfixApplication
      H.ExpressionLiteral lit -> toTree lit
      -- Note: the need for extra parens may point to an operator precedence issue
      H.ExpressionLambda lam -> parenthesize $ toTree lam
    --  H.ExpressionLeftSection Term_Section
      H.ExpressionLet (H.Expression_Let bindings inner) -> indentBlock [
          cst "",
          spaceSep [cst "let", customIndentBlock "    " (encodeBinding <$> bindings)],
          spaceSep [cst "in", toTree inner]]
        where
          -- Note: indentation should depend on the length of the pattern
          encodeBinding = indentSubsequentLines "      " . toTree
      H.ExpressionList exprs -> bracketList halfBlockStyle $ toTree <$> exprs
      H.ExpressionParens expr' -> parenthesize $ toTree expr'
    --  H.ExpressionPrefixApplication Term_PrefixApplication
    --  H.ExpressionRightSection Term_Section
      H.ExpressionTuple exprs -> parenList False $ toTree <$> exprs
    --  H.ExpressionTypeSignature Term_TypeSignature
    --  H.ExpressionUpdateRecord Term_UpdateRecord
      H.ExpressionVariable name -> toTree name

instance ToTree H.Expression_Application where
  toTree (H.Expression_Application fun arg) = ifx appOp (toTree fun) (toTree arg)

instance ToTree H.Expression_Case where
  toTree (H.Expression_Case cs alts) = ifx ofOp lhs rhs
    where
      lhs = spaceSep [cst "case", toTree cs]
      rhs = newlineSep (toTree <$> alts)
      ofOp = Op (Symbol "of") (Padding WsSpace $ WsBreakAndIndent "  ") (Precedence 0) AssociativityNone

instance ToTree H.Expression_ConstructRecord where
  toTree (H.Expression_ConstructRecord name updates) = spaceSep [toTree name, brackets curlyBraces halfBlockStyle body]
    where
      body = commaSep halfBlockStyle (fromUpdate <$> updates)
      fromUpdate (H.FieldUpdate fn val) = ifx defineOp (toTree fn) (toTree val)

instance ToTree H.Expression_If where
  toTree (H.Expression_If eif ethen eelse) = ifx ifOp (spaceSep [cst "if", toTree eif]) body
    where
      ifOp = Op (Symbol "") (Padding WsNone $ WsBreakAndIndent "  ") (Precedence 0) AssociativityNone
      body = newlineSep [spaceSep [cst "then", toTree ethen], spaceSep [cst "else", toTree eelse]]

instance ToTree H.Expression_Lambda where
  toTree (H.Expression_Lambda bindings inner) = ifx lambdaOp (prefix "\\" head) body
    where
      head = spaceSep (toTree <$> bindings)
      body = toTree inner

instance ToTree H.Field where
  toTree (H.Field name typ) = spaceSep [toTree name, cst "::", toTree typ]

instance ToTree H.FieldWithComments where
  toTree (H.FieldWithComments field mc) = case mc of
      Nothing -> toTree field
      Just c -> newlineSep [cst $ toHaskellComments c, toTree field]

instance ToTree H.Import where
  toTree (H.Import qual (H.ModuleName name) mod _) = spaceSep $ Y.catMaybes [
      Just $ cst "import",
      if qual then Just (cst "qualified") else Nothing,
      Just $ cst name,
      (\(H.ModuleName m) -> cst $ "as " ++ m) <$> mod]

instance ToTree H.Literal where
  toTree lit = cst $ case lit of
    H.LiteralChar c -> show $ C.chr $ fromIntegral c
    H.LiteralDouble d -> if d < 0 then "(0" ++ show d ++ ")" else show d
    H.LiteralFloat f -> if f < 0 then "(0" ++ show f ++ ")" else show f
    H.LiteralInt i -> if i < 0 then "(0" ++ show i ++ ")" else show i
    H.LiteralInteger i -> show i
    H.LiteralString s -> show s

instance ToTree H.LocalBinding where
  toTree binding = case binding of
    H.LocalBindingSignature ts -> toTree ts
    H.LocalBindingValue vb -> toTree vb

instance ToTree H.Module where
  toTree (H.Module mh imports decls) = doubleNewlineSep $
      headerLine ++ importLines ++ declLines
    where
      headerLine = Y.maybe [] (\h -> [toTree h]) mh
      declLines = toTree <$> decls
      importLines = [newlineSep $ toTree <$> imports | not (L.null imports)]

instance ToTree H.Name where
  toTree name = cst $ case name of
    H.NameImplicit qn -> "?" ++ writeQualifiedName qn
    H.NameNormal qn -> writeQualifiedName qn
    H.NameParens qn -> "(" ++ writeQualifiedName qn ++ ")"

instance ToTree H.ModuleHead where
  toTree (H.ModuleHead mc (H.ModuleName mname) _) = case mc of
    Nothing -> head
    Just c -> newlineSep [cst $ toHaskellComments c, cst "", head]
    where
      head = spaceSep [cst "module", cst mname, cst "where"]

instance ToTree H.Pattern where
  toTree pat = case pat of
      H.PatternApplication app -> toTree app
--      H.PatternAs (H.Pattern_As ) ->
      H.PatternList pats -> bracketList halfBlockStyle $ toTree <$> pats
      H.PatternLiteral lit -> toTree lit
      H.PatternName name -> toTree name
      H.PatternParens pat -> parenthesize $ toTree pat
--      H.PatternRecord (H.Pattern_Record ) ->
      H.PatternTuple pats -> parenList False $ toTree <$> pats
--      H.PatternTyped (H.Pattern_Typed ) ->
      H.PatternWildcard -> cst "_"

instance ToTree H.Pattern_Application where
  toTree (H.Pattern_Application name pats) = spaceSep $ toTree name:(toTree <$> pats)

instance ToTree H.RightHandSide where
  toTree (H.RightHandSide expr) = toTree expr

instance ToTree H.Statement where
  toTree (H.Statement expr) = toTree expr

instance ToTree H.Type where
  toTree htype = case htype of
    H.TypeApplication (H.Type_Application lhs rhs) -> ifx appOp (toTree lhs) (toTree rhs)
    H.TypeCtx (H.Type_Context ctx typ) -> ifx assertOp (toTree ctx) (toTree typ)
    H.TypeFunction (H.Type_Function dom cod) -> ifx arrowOp (toTree dom) (toTree cod)
--  H.TypeInfix Type_Infix
    H.TypeList htype -> bracketList inlineStyle [toTree htype]
--  H.TypeParens Type
    H.TypeTuple types -> parenList False $ toTree <$> types
    H.TypeVariable name -> toTree name

instance ToTree H.TypeSignature where
  toTree (H.TypeSignature name typ) = spaceSep [toTree name, cst "::", toTree typ]

instance ToTree H.ValueBinding where
  toTree vb = case vb of
    H.ValueBindingSimple (H.ValueBinding_Simple pat rhs local) -> case local of
        Nothing -> body
        Just (H.LocalBindings bindings) -> indentBlock [body, indentBlock $ [cst "where"] ++ (toTree <$> bindings)]
      where
        body = ifx defineOp (toTree pat) (toTree rhs)

instance ToTree H.Variable where
  toTree (H.Variable v) = toTree v

toHaskellComments :: String -> String
toHaskellComments c = L.intercalate "\n" $ ("-- | " ++) <$> L.lines c

writeQualifiedName :: H.QualifiedName -> String
writeQualifiedName (H.QualifiedName qualifiers unqual) = L.intercalate "." $ (h <$> qualifiers) ++ [h unqual]
  where
    h (H.NamePart part) = part