Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable sum of product styling #35

Merged
merged 13 commits into from
Dec 2, 2021
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@JsonClassDiscriminator("tag")
@Parcelize
@Serializable
sealed class Enum : Parcelable {
@Parcelize
@Serializable
@SerialName("dataCons0")
data class DataCons0(val contents: Record0) : Enum()

@Parcelize
@Serializable
@SerialName("dataCons1")
object DataCons1 : Enum()
}
14 changes: 14 additions & 0 deletions .golden/kotlinEnumSumOfProductWithTaggedObjectStyleSpec/golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@JsonClassDiscriminator("tag")
@Parcelize
@Serializable
sealed class Enum : Parcelable {
@Parcelize
@Serializable
@SerialName("dataCons0")
data class DataCons0(val contents: Record0) : Enum()

@Parcelize
@Serializable
@SerialName("dataCons1")
data class DataCons1(val contents: Record1) : Enum()
}
Comment on lines +1 to +14
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tadfisher Yeah?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah yeah yeah!

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@Parcelize
@Serializable
data class Record0(
val record0Field0: Int,
val record0Field1: Int,
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@Parcelize
@Serializable
data class Record0(
val record0Field0: Int,
val record0Field1: Int,
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@Parcelize
@Serializable
data class Record1(
val record1Field0: Int,
val record1Field1: Int,
) : Parcelable
2 changes: 2 additions & 0 deletions moat.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ test-suite spec
Common
SumOfProductSpec
SumOfProductWithLinkEnumInterfaceSpec
SumOfProductWithTaggedObjectAndSingleNullarySpec
SumOfProductWithTaggedObjectStyleSpec
Moat
Moat.Class
Moat.Pretty.Kotlin
Expand Down
22 changes: 15 additions & 7 deletions src/Moat.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ module Moat
-- ** Option type and defaults
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes here are just passing options down to enumExp

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Options,
defaultOptions,
EncodingStyle (..),
SumOfProductEncodingOptions (..),
defaultSumOfProductEncodingOptions,

-- ** Helper type for omissions
KeepOrDiscard (..),
Expand All @@ -64,6 +67,7 @@ module Moat
omitFields,
omitCases,
makeBase,
sumOfProductEncodingOptions,

-- * Pretty-printing

Expand Down Expand Up @@ -662,7 +666,7 @@ consToMoatType ::
consToMoatType o@Options {..} parentName instTys variant ts bs = \case
[] -> do
value <- lift $ newName "value"
matches <- liftCons (mkVoid parentName instTys ts)
matches <- liftCons (mkVoid o parentName instTys ts)
lift $ lamE [varP value] (caseE (varE value) matches)
cons -> do
-- TODO: use '_' instead of matching
Expand Down Expand Up @@ -695,7 +699,7 @@ consToMoatType o@Options {..} parentName instTys variant ts bs = \case
cases <- forM cons' (liftEither . mkCase o)
ourMatch <-
matchProxy
=<< lift (enumExp parentName instTys dataInterfaces dataProtocols dataAnnotations cases dataRawValue ts bs)
=<< lift (enumExp parentName instTys dataInterfaces dataProtocols dataAnnotations cases dataRawValue ts bs sumOfProductEncodingOptions)
pure [pure ourMatch]

liftCons :: (Functor f, Applicative g) => f a -> f [g a]
Expand Down Expand Up @@ -831,7 +835,7 @@ mkTypeTag Options {..} typName instTys = \case
mkName
(nameStr typName ++ "Tag")
let tag = tagExp typName parentName field False
matchProxy =<< lift (enumExp parentName instTys dataInterfaces dataProtocols dataAnnotations [] dataRawValue [tag] (False, Nothing, []))
matchProxy =<< lift (enumExp parentName instTys dataInterfaces dataProtocols dataAnnotations [] dataRawValue [tag] (False, Nothing, []) sumOfProductEncodingOptions)
_ -> throwError $ NotANewtype typName

-- make a newtype into a type alias
Expand Down Expand Up @@ -860,16 +864,17 @@ mkTypeAlias typName instTys = \case
-- | Make a void type (empty enum)
mkVoid ::
() =>
Options ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like a good use of ReaderT Options - maybe put that in with MoatM?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah lol i see that you already have this as a refactoring potential!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is definitely getting tedious to pass these around over and over.

-- | type name
Name ->
-- | type variables
[Type] ->
-- | tags
[Exp] ->
MoatM Match
mkVoid typName instTys ts =
mkVoid Options {..} typName instTys ts =
matchProxy
=<< lift (enumExp typName instTys [] [] [] [] Nothing ts (False, Nothing, []))
=<< lift (enumExp typName instTys [] [] [] [] Nothing ts (False, Nothing, []) sumOfProductEncodingOptions)

mkNewtype ::
() =>
Expand Down Expand Up @@ -1462,12 +1467,14 @@ enumExp ::
[Exp] ->
-- | Make base?
(Bool, Maybe MoatType, [Protocol]) ->
SumOfProductEncodingOptions ->
Q Exp
enumExp parentName tyVars ifaces protos anns cases raw tags bs =
enumExp parentName tyVars ifaces protos anns cases raw tags bs sop =
do
enumInterfaces_ <- Syntax.lift ifaces
enumAnnotations_ <- Syntax.lift anns
enumProtocols_ <- Syntax.lift protos
sumOfProductEncodingOptions_ <- Syntax.lift sop
applyBase bs $
RecConE
'MoatEnum
Expand All @@ -1479,7 +1486,8 @@ enumExp parentName tyVars ifaces protos anns cases raw tags bs =
('enumCases, ListE cases),
('enumRawValue, rawValueE raw),
('enumPrivateTypes, ListE []),
('enumTags, ListE tags)
('enumTags, ListE tags),
('enumSumOfProductEncodingOption, sumOfProductEncodingOptions_)
]

newtypeExp ::
Expand Down
98 changes: 82 additions & 16 deletions src/Moat/Pretty/Kotlin.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ module Moat.Pretty.Kotlin
where

import qualified Data.Char as Char
import Data.Functor ((<&>))
import Data.List (intercalate)
import Data.Maybe (catMaybes)
import Moat.Types

-- | Convert a 'MoatData' into a canonical representation in Kotlin
Expand All @@ -16,7 +18,7 @@ prettyKotlinData :: MoatData -> String
prettyKotlinData = \case
MoatStruct {..} ->
""
++ prettyAnnotations structAnnotations
++ prettyAnnotations Nothing noIndent structAnnotations
++ "data class "
++ prettyMoatTypeHeader structName structTyVars
++ "("
Expand All @@ -31,10 +33,11 @@ prettyKotlinData = \case
enumName
enumTyVars
enumCases
enumSumOfProductEncodingOption
indents
MoatNewtype {..} ->
""
++ prettyAnnotations newtypeAnnotations
++ prettyAnnotations Nothing noIndent newtypeAnnotations
++ "value class "
++ prettyMoatTypeHeader newtypeName newtypeTyVars
++ "(val "
Expand Down Expand Up @@ -120,15 +123,22 @@ prettyMoatTypeHeader :: String -> [String] -> String
prettyMoatTypeHeader name [] = name
prettyMoatTypeHeader name tyVars = name ++ "<" ++ intercalate ", " tyVars ++ ">"

prettyAnnotations :: [Annotation] -> String
prettyAnnotations = concatMap (\ann -> "@" ++ prettyAnnotation ann ++ "\n")
-- | This function will take a name and the indentation level and render
-- annotations in the style '@{string}\n...'. The name parameter is only used
-- when a 'SerialName' annotation is given for a sum of product
prettyAnnotations :: Maybe String -> String -> [Annotation] -> String
prettyAnnotations mCaseNm indents =
concatMap (\ann -> indents <> "@" <> ann <> "\n")
. catMaybes
. fmap prettyAnnotation
where
prettyAnnotation :: Annotation -> String
prettyAnnotation :: Annotation -> Maybe String
prettyAnnotation = \case
JvmInline -> "JvmInline"
Parcelize -> "Parcelize"
Serializable -> "Serializable"
RawAnnotation s -> s
JvmInline -> Just "JvmInline"
Parcelize -> Just "Parcelize"
Serializable -> Just "Serializable"
SerialName -> mCaseNm <&> \caseNm -> "SerialName(\"" <> caseNm <> "\")"
RawAnnotation s -> Just s

prettyInterfaces :: [Interface] -> String
prettyInterfaces [] = ""
Expand Down Expand Up @@ -193,6 +203,44 @@ prettyApp t1 t2 =
(args, ret) -> (e1 : args, ret)
go e1 e2 = ([e1], e2)

prettyTaggedObject ::
String ->
[Annotation] ->
[(String, [(Maybe String, MoatType)])] ->
String ->
SumOfProductEncodingOptions ->
String
prettyTaggedObject parentName anns cases indents SumOfProductEncodingOptions {..} =
intercalate
"\n\n"
( cases <&> \case
(caseNm, [(_, Concrete {concreteName = concreteName})]) ->
prettyAnnotations (Just caseNm) indents anns
++ indents
++ "data class "
++ toUpperFirst caseNm
++ "(val "
++ contentsFieldName
++ ": "
++ concreteName
++ ") : "
++ parentName
++ "()"
(caseNm, []) ->
prettyAnnotations (Just caseNm) indents anns
++ indents
++ "object "
++ toUpperFirst caseNm
++ " : "
++ parentName
++ "()"
(caseNm, _) ->
error $
"prettyTaggedObject: The data constructor "
<> caseNm
<> " can have zero or one concrete type constructor!"
)

prettyEnum ::
() =>
[Annotation] ->
Expand All @@ -204,12 +252,14 @@ prettyEnum ::
[String] ->
-- | cases
[(String, [(Maybe String, MoatType)])] ->
-- | encoding style
SumOfProductEncodingOptions ->
-- | indents
String ->
String
prettyEnum anns ifaces name tyVars cases indents
prettyEnum anns ifaces name tyVars cases sop@SumOfProductEncodingOptions {..} indents
| isCEnum cases =
prettyAnnotations (dontAddSerializeToEnums anns)
prettyAnnotations Nothing noIndent (dontAddSerializeToEnums anns)
++ "enum class "
++ prettyMoatTypeHeader name tyVars
++ prettyInterfaces ifaces
Expand All @@ -218,12 +268,25 @@ prettyEnum anns ifaces name tyVars cases indents
++ prettyCEnumCases indents (map fst cases)
++ "}"
| allConcrete cases =
prettyAnnotations anns
++ "sealed class "
++ prettyMoatTypeHeader name tyVars
++ prettyInterfaces ifaces
case encodingStyle of
TaggedFlatObjectStyle ->
prettyAnnotations Nothing noIndent anns
++ "sealed class "
++ prettyMoatTypeHeader name tyVars
++ prettyInterfaces ifaces
TaggedObjectStyle ->
prettyAnnotations
Nothing
noIndent
(sumAnnotations ++ anns)
++ "sealed class "
++ prettyMoatTypeHeader name tyVars
++ prettyInterfaces ifaces
++ " {\n"
++ prettyTaggedObject name anns cases indents sop
++ "\n}"
| otherwise =
prettyAnnotations (dontAddSerializeToEnums anns)
prettyAnnotations Nothing noIndent (dontAddSerializeToEnums anns)
++ "enum class "
++ prettyMoatTypeHeader name tyVars
++ prettyInterfaces ifaces
Expand Down Expand Up @@ -254,3 +317,6 @@ toUpperFirst :: String -> String
toUpperFirst = \case
[] -> []
(c : cs) -> Char.toUpper c : cs

noIndent :: String
noIndent = ""
Loading