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

Runtime free serializers #201

Merged
merged 5 commits into from
Dec 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 163 additions & 63 deletions src/Nirum/Targets/Python.hs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import Nirum.Constructs.TypeDeclaration ( EnumMember (EnumMember)
, RecordType
, UnboxedType
, UnionType
, primitiveTypeIdentifier
)
, TypeDeclaration (..)
)
Expand Down Expand Up @@ -254,6 +255,9 @@ renameModulePath renameMap path' =
renameMP :: Python -> ModulePath -> ModulePath
renameMP Python { renames = table } = renameModulePath table

thd3 :: (a, b, c) -> c
thd3 (_, _, v) = v

-- | The set of Python reserved keywords.
-- See also: https://docs.python.org/3/reference/lexical_analysis.html#keywords
keywords :: S.Set T.Text
Expand Down Expand Up @@ -450,33 +454,10 @@ compileUnionTag :: Source -> Name -> Tag -> CodeGen Code
compileUnionTag source parentname d@(Tag typename' fields _) = do
typeExprCodes <- mapM (compileTypeExpression source)
[Just typeExpr | (Field _ typeExpr _) <- toList fields]
let optionFlags = [ case typeExpr of
OptionModifier _ -> True
_ -> False
| (Field _ typeExpr _) <- toList fields
]
className = toClassName' typename'
tagNames = map (toAttributeName' . fieldName) (toList fields)
getOptFlag :: (T.Text, Code, Bool) -> Bool
getOptFlag (_, _, flag) = flag
nameTypeTriples = L.sortBy (compare `on` getOptFlag)
let nameTypeTriples = L.sortBy (compare `on` thd3)
(zip3 tagNames typeExprCodes optionFlags)
slotTypes = toIndentedCodes
(\ (n, t, _) -> [qq|('{n}', {t})|]) nameTypeTriples ",\n "
slots = if length tagNames == 1
then [qq|'{head tagNames}'|] `T.snoc` ','
else toIndentedCodes (\ n -> [qq|'{n}'|]) tagNames ",\n "
hashTuple = if null tagNames
then "self.__nirum_tag__"
else [qq|({attributes},)|] :: T.Text
where
attributes :: T.Text
attributes = toIndentedCodes (\ n -> [qq|self.{n}|]) tagNames ", "
nameMaps = toIndentedCodes
toNamePair
(map fieldName $ toList fields)
",\n "
parentClass = toClassName' parentname
insertThirdPartyImportsA
[ ("nirum.validate", [("validate_union_type", "validate_union_type")])
, ("nirum.constructs", [("name_dict_type", "NameDict")])
Expand Down Expand Up @@ -522,6 +503,13 @@ class $className($parentClass):

{ inits :: T.Text }

def __nirum_serialize__(self):
return \{
'_type': '{behindParentTypename}',
'_tag': '{behindTagName}',
$fieldSerializers
\}

def __repr__(self){ ret "str" }:
return (
$parentClass.__module__ + '.$parentClass.$className(' +
Expand All @@ -547,10 +535,47 @@ $parentClass.$className = $className
if hasattr($parentClass, '__qualname__'):
$className.__qualname__ = $parentClass.__qualname__ + '.{className}'
|]
where
optionFlags :: [Bool]
optionFlags = [ case typeExpr of
OptionModifier _ -> True
_ -> False
| (Field _ typeExpr _) <- toList fields
]
className :: T.Text
className = toClassName' typename'
behindParentTypename :: T.Text
behindParentTypename = I.toSnakeCaseText $ N.behindName parentname
tagNames :: [T.Text]
tagNames = map (toAttributeName' . fieldName) (toList fields)
behindTagName :: T.Text
behindTagName = I.toSnakeCaseText $ N.behindName typename'
slots :: Code
slots = if length tagNames == 1
then [qq|'{head tagNames}'|] `T.snoc` ','
else toIndentedCodes (\ n -> [qq|'{n}'|]) tagNames ",\n "
hashTuple :: Code
hashTuple = if null tagNames
then "self.__nirum_tag__"
else [qq|({toIndentedCodes (T.append "self.") tagNames ", "},)|]
fieldList :: [Field]
fieldList = toList fields
nameMaps :: Code
nameMaps = toIndentedCodes toNamePair (map fieldName fieldList) ",\n "
parentClass :: T.Text
parentClass = toClassName' parentname
fieldSerializers :: Code
fieldSerializers = T.intercalate ",\n"
[ T.concat [ "'", I.toSnakeCaseText (N.behindName fn), "': "
, compileSerializer source ft
[qq|self.{toAttributeName' fn}|]
]
| Field fn ft _ <- fieldList
]
compilePrimitiveType :: PrimitiveTypeIdentifier -> CodeGen Code
compilePrimitiveType primitiveTypeIdentifier = do
compilePrimitiveType primitiveTypeIdentifier' = do
pyVer <- getPythonVersion
case (primitiveTypeIdentifier, pyVer) of
case (primitiveTypeIdentifier', pyVer) of
(Bool, _) -> return "bool"
(Bigint, _) -> return "int"
(Decimal, _) -> do
Expand Down Expand Up @@ -611,7 +636,65 @@ compileTypeExpression source (Just modifier) = do
MapModifier _ _ -> undefined -- never happen!
compileTypeExpression _ Nothing =
return "None"


compileSerializer :: Source -> TypeExpression -> Code -> Code
compileSerializer Source { sourceModule = boundModule } =
compileSerializer' boundModule

compileSerializer' :: BoundModule Python -> TypeExpression -> Code -> Code
compileSerializer' mod' (OptionModifier typeExpr) pythonVar =
compileSerializer' mod' typeExpr pythonVar
compileSerializer' mod' (SetModifier typeExpr) pythonVar =
compileSerializer' mod' (ListModifier typeExpr) pythonVar
compileSerializer' mod' (ListModifier typeExpr) pythonVar =
[qq|[($serializer) for __{pythonVar}__elem__ in ($pythonVar)]|]
where
serializer :: Code
serializer = compileSerializer' mod' typeExpr [qq|__{pythonVar}__elem__|]
compileSerializer' mod' (MapModifier kt vt) pythonVar =
[qq|\{({compileSerializer' mod' kt $ T.concat ["__", pythonVar, "__k__"]}):
({compileSerializer' mod' vt $ T.concat ["__", pythonVar, "__v__"]})
for __{pythonVar}__k__, __{pythonVar}__v__ in ($pythonVar).items()\}|]
compileSerializer' mod' (TypeIdentifier typeId) pythonVar =
case lookupType typeId mod' of
Missing -> "None" -- must never happen
Local (Alias t) -> compileSerializer' mod' t pythonVar
Imported modulePath' (Alias t) ->
case resolveBoundModule modulePath' (boundPackage mod') of
Nothing -> "None" -- must never happen
Just foundMod -> compileSerializer' foundMod t pythonVar
Local PrimitiveType { primitiveTypeIdentifier = p } ->
compilePrimitiveTypeSerializer p pythonVar
Imported _ PrimitiveType { primitiveTypeIdentifier = p } ->
compilePrimitiveTypeSerializer p pythonVar
Local EnumType {} -> serializerCall
Imported _ EnumType {} -> serializerCall
Local RecordType {} -> serializerCall
Imported _ RecordType {} -> serializerCall
Local UnboxedType {} -> serializerCall
Imported _ UnboxedType {} -> serializerCall
Local UnionType {} -> serializerCall
Imported _ UnionType {} -> serializerCall
where
serializerCall :: Code
serializerCall = [qq|$pythonVar.__nirum_serialize__()|]

compilePrimitiveTypeSerializer :: PrimitiveTypeIdentifier -> Code -> Code
compilePrimitiveTypeSerializer Bigint var = var
compilePrimitiveTypeSerializer Decimal var = [qq|str($var)|]
compilePrimitiveTypeSerializer Int32 var = var
compilePrimitiveTypeSerializer Int64 var = var
compilePrimitiveTypeSerializer Float32 var = var
compilePrimitiveTypeSerializer Float64 var = var
compilePrimitiveTypeSerializer Text var = var
compilePrimitiveTypeSerializer Binary var =
[qq|__import__('base64').b64encode($var).decode('ascii')|]
compilePrimitiveTypeSerializer Date var = [qq|($var).isoformat()|]
compilePrimitiveTypeSerializer Datetime var = [qq|($var).isoformat()|]
compilePrimitiveTypeSerializer Bool var = var
compilePrimitiveTypeSerializer Uuid var = [qq|str($var)|]
compilePrimitiveTypeSerializer Uri var = var

compileTypeDeclaration :: Source -> TypeDeclaration -> CodeGen Code
compileTypeDeclaration _ TypeDeclaration { type' = PrimitiveType {} } =
return "" -- never used
Expand All @@ -635,7 +718,6 @@ compileTypeDeclaration src d@TypeDeclaration { typename = typename'
let className = toClassName' typename'
itypeExpr <- compileTypeExpression src (Just itype)
insertThirdPartyImports [ ("nirum.validate", ["validate_boxed_type"])
, ("nirum.serialize", ["serialize_boxed_type"])
, ("nirum.deserialize", ["deserialize_boxed_type"])
]
arg <- parameterCompiler
Expand Down Expand Up @@ -665,8 +747,8 @@ class $className(object):
def __hash__(self){ ret "int" }:
return hash(self.value)

def __nirum_serialize__(self){ ret "typing.Any" }:
return serialize_boxed_type(self)
def __nirum_serialize__(self):
return ({ compileSerializer src itype "self.value" })

@classmethod
def __nirum_deserialize__(
Expand Down Expand Up @@ -725,33 +807,14 @@ $className.__nirum_type__ = 'enum'
compileTypeDeclaration src d@TypeDeclaration { typename = typename'
, type' = RecordType fields
} = do
let className = toClassName' typename'
fieldList = toList fields
typeExprCodes <- mapM (compileTypeExpression src)
[Just typeExpr | (Field _ typeExpr _) <- fieldList]
let optionFlags = [ case typeExpr of
OptionModifier _ -> True
_ -> False
| (Field _ typeExpr _) <- fieldList
]
fieldNames = map toAttributeName' [ name'
| (Field name' _ _) <- fieldList
]
getOptFlag :: (T.Text, Code, Bool) -> Bool
getOptFlag (_, _, flag) = flag
nameTypeTriples = L.sortBy (compare `on` getOptFlag)
let nameTypeTriples = L.sortBy (compare `on` thd3)
(zip3 fieldNames typeExprCodes optionFlags)
slotTypes = toIndentedCodes
(\ (n, t, _) -> [qq|'{n}': {t}|]) nameTypeTriples ",\n "
slots = toIndentedCodes (\ n -> [qq|'{n}'|]) fieldNames ",\n "
nameMaps = toIndentedCodes
toNamePair
(map fieldName $ toList fields)
",\n "
hashText = toIndentedCodes (\ n -> [qq|self.{n}|]) fieldNames ", "
importTypingForPython3
insertThirdPartyImports [ ("nirum.validate", ["validate_record_type"])
, ("nirum.serialize", ["serialize_record_type"])
, ("nirum.deserialize", ["deserialize_record_type"])
]
insertThirdPartyImportsA [ ( "nirum.constructs"
Expand Down Expand Up @@ -790,9 +853,7 @@ class $className(object):
$slots,
)
__nirum_type__ = 'record'
__nirum_record_behind_name__ = (
'{I.toSnakeCaseText $ N.behindName typename'}'
)
__nirum_record_behind_name__ = '{behindTypename}'
__nirum_field_names__ = name_dict_type([$nameMaps])

@staticmethod
Expand All @@ -817,8 +878,11 @@ class $className(object):
def __ne__(self, other){ ret "bool" }:
return not self == other

def __nirum_serialize__(self){ret "typing.Mapping[str, typing.Any]"}:
return serialize_record_type(self)
def __nirum_serialize__(self):
return \{
'_type': '{behindTypename}',
$fieldSerializers
\}

@classmethod
def __nirum_deserialize__($clsType, value){ ret className }:
Expand All @@ -827,6 +891,37 @@ class $className(object):
def __hash__(self){ret "int"}:
return hash(($hashText,))
|]
where
className :: T.Text
className = toClassName' typename'
fieldList :: [Field]
fieldList = toList fields
behindTypename :: T.Text
behindTypename = I.toSnakeCaseText $ N.behindName typename'
optionFlags :: [Bool]
optionFlags = [ case typeExpr of
OptionModifier _ -> True
_ -> False
| (Field _ typeExpr _) <- fieldList
]
fieldNames :: [T.Text]
fieldNames = [toAttributeName' name' | Field name' _ _ <- fieldList]
slots :: Code
slots = toIndentedCodes (\ n -> [qq|'{n}'|]) fieldNames ",\n "
nameMaps :: Code
nameMaps = toIndentedCodes
toNamePair
(map fieldName $ toList fields)
",\n "
hashText :: Code
hashText = toIndentedCodes (\ n -> [qq|self.{n}|]) fieldNames ", "
fieldSerializers :: Code
fieldSerializers = T.intercalate ",\n"
[ T.concat [ "'", I.toSnakeCaseText (N.behindName fn), "': "
, compileSerializer src ft [qq|self.{ toAttributeName' fn}|]
]
| Field fn ft _ <- fieldList
]
compileTypeDeclaration src
d@TypeDeclaration { typename = typename'
, type' = UnionType tags
Expand All @@ -842,8 +937,7 @@ compileTypeDeclaration src
(\ (t, b) -> [qq|$t = '{b}'|]) enumMembers' "\n "
importTypingForPython3
insertEnumImport
insertThirdPartyImports [ ("nirum.serialize", ["serialize_union_type"])
, ("nirum.deserialize", ["deserialize_union_type"])
insertThirdPartyImports [ ("nirum.deserialize", ["deserialize_union_type"])
]
insertThirdPartyImportsA [ ( "nirum.constructs"
, [("name_dict_type", "NameDict")]
Expand Down Expand Up @@ -874,8 +968,12 @@ class $className({T.intercalate "," $ compileExtendClasses annotations}):
"of it instead.".format({typeRepr "type(self)"})
)

def __nirum_serialize__(self){ ret "typing.Mapping[str, typing.Any]" }:
return serialize_union_type(self)
def __nirum_serialize__(self):
raise NotImplementedError(
"\{0\} cannot be instantiated "
"since it is an abstract class. Instantiate a concrete subtype "
"of it instead.".format({typeRepr "type(self)"})
)

@classmethod
def __nirum_deserialize__(
Expand Down Expand Up @@ -935,7 +1033,6 @@ compileTypeDeclaration
ret <- returnCompiler
insertStandardImport "json"
insertThirdPartyImports [ ("nirum.deserialize", ["deserialize_meta"])
, ("nirum.serialize", ["serialize_meta"])
]
insertThirdPartyImportsA
[ ("nirum.constructs", [("name_dict_type", "NameDict")])
Expand Down Expand Up @@ -1051,10 +1148,10 @@ class {className}_Client($className):
paramNameMap params = toIndentedCodes
toNamePair [pName | Parameter pName _ _ <- params] ",\n "
compileClientPayload :: Parameter -> CodeGen Code
compileClientPayload (Parameter pName _ _) = do
compileClientPayload (Parameter pName pt _) = do
let pName' = toAttributeName' pName
return [qq|'{I.toSnakeCaseText $ N.behindName pName}':
serialize_meta({pName'})|]
({compileSerializer src pt pName'})|]
compileClientMethod :: Method -> CodeGen Code
compileClientMethod Method { methodName = mName
, parameters = params
Expand Down Expand Up @@ -1086,7 +1183,10 @@ class {className}_Client($className):
result_type = $rtypeExpr
else:
$errorCode
result = deserialize_meta(result_type, serialized)
if result_type is None:
result = None
else:
result = deserialize_meta(result_type, serialized)
if successful:
return result
raise result
Expand Down
5 changes: 5 additions & 0 deletions test/nirum_fixture/fixture/foo.nrm
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,8 @@ record product (
union animal = cat
| dog (text name, text? kind, int64 age, int64? weight)
;

service sample-service (
sample-method (animal a, product b/bb, gender c, way d/dd,
uuid e, binary f/ff, bigint g, text h/hh),
);
Loading