From 51568fb46fe9c63ec6680f06432c5219a069d5cf Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Tue, 15 Aug 2017 13:02:55 +0900 Subject: [PATCH 1/5] Set default None for optional fields --- src/Nirum/Targets/Python.hs | 97 ++++++++++++++++++++++++------ test/nirum_fixture/fixture/foo.nrm | 7 +++ test/python/primitive_test.py | 10 ++- 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/src/Nirum/Targets/Python.hs b/src/Nirum/Targets/Python.hs index 5a3b845..48e6783 100644 --- a/src/Nirum/Targets/Python.hs +++ b/src/Nirum/Targets/Python.hs @@ -60,6 +60,7 @@ import qualified Data.SemVer as SV import qualified Data.Set as S import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8, encodeUtf8) +import Data.Function (on) import System.FilePath (joinPath) import qualified Text.Email.Validate as E import Text.InterpolatedString.Perl6 (q, qq) @@ -320,16 +321,20 @@ toIndentedCodes f traversable concatenator = T.intercalate concatenator $ map f traversable compileParameters :: (ParameterName -> ParameterType -> Code) - -> [(T.Text, Code)] + -> [(T.Text, Code, Bool)] -> Code -compileParameters gen nameTypePairs = - toIndentedCodes (uncurry gen) nameTypePairs ", " +compileParameters gen nameTypeTriples = + toIndentedCodes + (\ (n, t, o) -> gen n t `T.append` if o then "=None" else "") + nameTypeTriples ", " -compileFieldInitializers :: DS.DeclarationSet Field -> CodeGen Code -compileFieldInitializers fields = do +compileFieldInitializers :: DS.DeclarationSet Field -> Int -> CodeGen Code +compileFieldInitializers fields depth = do initializers <- forM (toList fields) compileFieldInitializer - return $ T.intercalate "\n " initializers + return $ T.intercalate indentSpaces initializers where + indentSpaces :: T.Text + indentSpaces = "\n" `T.append` T.replicate depth " " compileFieldInitializer :: Field -> CodeGen Code compileFieldInitializer (Field fieldName' fieldType' _) = case fieldType' of @@ -432,11 +437,19 @@ compileUnionTag :: Source -> Name -> Tag -> CodeGen Code compileUnionTag source parentname d@(Tag typename' fields _) = do typeExprCodes <- mapM (compileTypeExpression source) [typeExpr | (Field _ typeExpr _) <- toList fields] - let className = toClassName' typename' + let optionFlags = [ case typeExpr of + OptionModifier _ -> True + _ -> False + | (Field _ typeExpr _) <- toList fields + ] + className = toClassName' typename' tagNames = map (toAttributeName' . fieldName) (toList fields) - nameNTypes = zip tagNames typeExprCodes + getOptFlag :: (T.Text, Code, Bool) -> Bool + getOptFlag (_, _, flag) = flag + nameTypeTriples = L.sortBy (compare `on` getOptFlag) + (zip3 tagNames typeExprCodes optionFlags) slotTypes = toIndentedCodes - (\ (n, t) -> [qq|('{n}', {t})|]) nameNTypes ",\n " + (\ (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 " @@ -458,7 +471,27 @@ compileUnionTag source parentname d@(Tag typename' fields _) = do typeRepr <- typeReprCompiler arg <- parameterCompiler ret <- returnCompiler - initializers <- compileFieldInitializers fields + pyVer <- getPythonVersion + initializers <- compileFieldInitializers fields $ case pyVer of + Python3 -> 2 + Python2 -> 3 + let initParams = compileParameters arg nameTypeTriples + inits = case pyVer of + Python2 -> [qq| + def __init__(self, **kwargs): + def __init__($initParams): + $initializers + pass + __init__(**kwargs) + validate_union_type(self) + |] + Python3 -> [qq| + def __init__(self{ if null nameTypeTriples + then T.empty + else ", *, " `T.append` initParams }) -> None: + $initializers + validate_union_type(self) + |] return [qq| class $className($parentClass): {compileDocstringWithFields " " d fields} @@ -474,9 +507,7 @@ class $className($parentClass): def __nirum_tag_types__(): return [$slotTypes] - def __init__(self, {compileParameters arg nameNTypes}){ ret "None" }: - $initializers - validate_union_type(self) + { inits :: T.Text } def __repr__(self){ ret "str" }: return '\{0\}(\{1\})'.format( @@ -669,12 +700,20 @@ compileTypeDeclaration src d@TypeDeclaration { typename = typename' fieldList = toList fields typeExprCodes <- mapM (compileTypeExpression src) [typeExpr | (Field _ typeExpr _) <- fieldList] - let fieldNames = map toAttributeName' [ name' + let optionFlags = [ case typeExpr of + OptionModifier _ -> True + _ -> False + | (Field _ typeExpr _) <- fieldList + ] + fieldNames = map toAttributeName' [ name' | (Field name' _ _) <- fieldList ] - nameTypePairs = zip fieldNames typeExprCodes + getOptFlag :: (T.Text, Code, Bool) -> Bool + getOptFlag (_, _, flag) = flag + nameTypeTriples = L.sortBy (compare `on` getOptFlag) + (zip3 fieldNames typeExprCodes optionFlags) slotTypes = toIndentedCodes - (\ (n, t) -> [qq|'{n}': {t}|]) nameTypePairs ",\n " + (\ (n, t, _) -> [qq|'{n}': {t}|]) nameTypeTriples ",\n " slots = toIndentedCodes (\ n -> [qq|'{n}'|]) fieldNames ",\n " nameMaps = toIndentedCodes toNamePair @@ -693,7 +732,27 @@ compileTypeDeclaration src d@TypeDeclaration { typename = typename' arg <- parameterCompiler ret <- returnCompiler typeRepr <- typeReprCompiler - initializers <- compileFieldInitializers fields + pyVer <- getPythonVersion + initializers <- compileFieldInitializers fields $ case pyVer of + Python3 -> 2 + Python2 -> 3 + let initParams = compileParameters arg nameTypeTriples + inits = case pyVer of + Python2 -> [qq| + def __init__(self, **kwargs): + def __init__($initParams): + $initializers + pass + __init__(**kwargs) + validate_record_type(self) + |] + Python3 -> [qq| + def __init__(self{ if null nameTypeTriples + then T.empty + else ", *, " `T.append` initParams }) -> None: + $initializers + validate_record_type(self) + |] let clsType = arg "cls" "type" return [qq| class $className(object): @@ -710,9 +769,7 @@ class $className(object): def __nirum_field_types__(): return \{$slotTypes\} - def __init__(self, {compileParameters arg nameTypePairs}){ret "None"}: - $initializers - validate_record_type(self) + {inits :: T.Text} def __repr__(self){ret "bool"}: return '\{0\}(\{1\})'.format( diff --git a/test/nirum_fixture/fixture/foo.nrm b/test/nirum_fixture/fixture/foo.nrm index e04cad4..9af7bf0 100644 --- a/test/nirum_fixture/fixture/foo.nrm +++ b/test/nirum_fixture/fixture/foo.nrm @@ -100,3 +100,10 @@ record person ( record people ( {person} people ); + +record product ( + text name, + int64? price, + bool sale, + uri? url, +); diff --git a/test/python/primitive_test.py b/test/python/primitive_test.py index 87b5198..92169f7 100644 --- a/test/python/primitive_test.py +++ b/test/python/primitive_test.py @@ -8,8 +8,8 @@ from fixture.foo import (CultureAgnosticName, EastAsianName, EvaChar, FloatUnbox, Gender, ImportedTypeUnbox, Irum, Line, MixedName, NullService, - Point1, Point2, Point3d, Pop, PingService, Rnb, - Run, Stop, Way, WesternName) + Point1, Point2, Point3d, Pop, PingService, Product, + Rnb, Run, Stop, Way, WesternName) from fixture.foo.bar import PathUnbox, IntUnbox, Point from fixture.qux import Path, Name @@ -249,3 +249,9 @@ def test_service(): PingService().ping(nonce=u'nonce') with raises(TypeError): PingService().ping(wrongkwd=u'a') + + +def test_optional_initializer_test(): + product = Product(name=u'coffee', sale=False) + assert product.price is None + assert product.url is None From 6427752f783c9157b272e11e00c0da25a472e0cf Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 16 Aug 2017 11:12:31 +0900 Subject: [PATCH 2/5] Rename the test for record optional initializer --- test/python/primitive_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/primitive_test.py b/test/python/primitive_test.py index 92169f7..ff330a7 100644 --- a/test/python/primitive_test.py +++ b/test/python/primitive_test.py @@ -251,7 +251,7 @@ def test_service(): PingService().ping(wrongkwd=u'a') -def test_optional_initializer_test(): +def test_record_optional_initializer(): product = Product(name=u'coffee', sale=False) assert product.price is None assert product.url is None From 01772984ba66789a8d161f0e90d9405aa14ba849 Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 16 Aug 2017 11:44:01 +0900 Subject: [PATCH 3/5] Add assertions to test_record_optional_initializer --- test/python/primitive_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/python/primitive_test.py b/test/python/primitive_test.py index ff330a7..1d335a1 100644 --- a/test/python/primitive_test.py +++ b/test/python/primitive_test.py @@ -253,5 +253,7 @@ def test_service(): def test_record_optional_initializer(): product = Product(name=u'coffee', sale=False) + assert product.name == u'coffee' assert product.price is None + assert not product.sale assert product.url is None From 9eeebb521a4d110545b759773b950930ec923ba6 Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 16 Aug 2017 12:06:56 +0900 Subject: [PATCH 4/5] Add a test for union optional initializer --- test/nirum_fixture/fixture/foo.nrm | 4 ++++ test/python/primitive_test.py | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/nirum_fixture/fixture/foo.nrm b/test/nirum_fixture/fixture/foo.nrm index 9af7bf0..e284aab 100644 --- a/test/nirum_fixture/fixture/foo.nrm +++ b/test/nirum_fixture/fixture/foo.nrm @@ -107,3 +107,7 @@ record product ( bool sale, uri? url, ); + +union animal = cat + | dog (text name, text? kind, int64 age, int64? weight) + ; diff --git a/test/python/primitive_test.py b/test/python/primitive_test.py index 1d335a1..a4b90aa 100644 --- a/test/python/primitive_test.py +++ b/test/python/primitive_test.py @@ -5,7 +5,7 @@ from nirum.service import Service from six import PY3 -from fixture.foo import (CultureAgnosticName, EastAsianName, +from fixture.foo import (CultureAgnosticName, Dog, EastAsianName, EvaChar, FloatUnbox, Gender, ImportedTypeUnbox, Irum, Line, MixedName, NullService, Point1, Point2, Point3d, Pop, PingService, Product, @@ -257,3 +257,11 @@ def test_record_optional_initializer(): assert product.price is None assert not product.sale assert product.url is None + + +def test_union_tags_optional_initializer(): + dog = Dog(name=u"Max", age=10) + assert dog.name == u"Max" + assert dog.kind is None + assert dog.age == 10 + assert dog.weight is None From 4dd29234b7f1b49f83fcdf8f50e740aee57421e6 Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 16 Aug 2017 12:16:45 +0900 Subject: [PATCH 5/5] Place tests in the right place --- test/python/primitive_test.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/python/primitive_test.py b/test/python/primitive_test.py index a4b90aa..6466069 100644 --- a/test/python/primitive_test.py +++ b/test/python/primitive_test.py @@ -159,6 +159,14 @@ def test_record_with_one_field(): assert Line(length=3).__nirum_serialize__() == expected +def test_record_optional_initializer(): + product = Product(name=u'coffee', sale=False) + assert product.name == u'coffee' + assert product.price is None + assert not product.sale + assert product.url is None + + def test_union(): assert isinstance(MixedName, type) assert MixedName.Tag.western_name.value == 'western_name' @@ -232,6 +240,14 @@ def test_union_with_special_case(): assert Stop().__nirum_tag__.value == 'stop' +def test_union_tags_optional_initializer(): + dog = Dog(name=u"Max", age=10) + assert dog.name == u"Max" + assert dog.kind is None + assert dog.age == 10 + assert dog.weight is None + + def test_service(): assert issubclass(NullService, Service) assert issubclass(PingService, Service) @@ -249,19 +265,3 @@ def test_service(): PingService().ping(nonce=u'nonce') with raises(TypeError): PingService().ping(wrongkwd=u'a') - - -def test_record_optional_initializer(): - product = Product(name=u'coffee', sale=False) - assert product.name == u'coffee' - assert product.price is None - assert not product.sale - assert product.url is None - - -def test_union_tags_optional_initializer(): - dog = Dog(name=u"Max", age=10) - assert dog.name == u"Max" - assert dog.kind is None - assert dog.age == 10 - assert dog.weight is None