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