packages feed

nirum-0.5.0: src/Nirum/Targets/Python.hs

{-# OPTIONS_GHC -Wno-orphans #-}
{-# LANGUAGE ExtendedDefaultRules #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Nirum.Targets.Python
    ( Python (..)
    , Source (..)
    , compileModule
    , compileTypeDeclaration
    , parseModulePath
    ) where

import Control.Monad (forM)
import Control.Monad.State (modify)
import qualified Data.List as L
import Data.Maybe (catMaybes, fromMaybe, isJust)
import GHC.Exts (IsList (toList))

import qualified Data.ByteString.Lazy
import qualified Data.HashMap.Strict as HM
import qualified Data.Map.Strict as M
import qualified Data.SemVer as SV
import qualified Data.Set as S
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8)
import System.FilePath (joinPath)
import Text.Blaze (Markup)
import qualified Text.Blaze.Renderer.Utf8
import qualified Text.Email.Validate as E
import Text.Heterocephalus (compileText)
import Text.InterpolatedString.Perl6 (qq)

import qualified Nirum.Constructs.Annotation as A
import Nirum.Constructs.Annotation.Internal hiding (Text, annotations, name)
import qualified Nirum.Constructs.Annotation.Internal as AI
import Nirum.Constructs.Declaration hiding (annotations, name)
import qualified Nirum.Constructs.DeclarationSet as DS
import qualified Nirum.Constructs.Identifier as I
import Nirum.Constructs.Module hiding (imports)
import Nirum.Constructs.ModulePath ( ModulePath
                                   , fromIdentifiers
                                   , hierarchy
                                   )
import Nirum.Constructs.Name (Name (Name))
import qualified Nirum.Constructs.Name as N
import Nirum.Constructs.Service ( Method ( Method
                                         , errorType
                                         , methodAnnotations
                                         , methodName
                                         , parameters
                                         , returnType
                                         )
                                , Parameter (Parameter)
                                , Service (Service)
                                )
import Nirum.Constructs.TypeDeclaration as TD
import Nirum.Constructs.TypeExpression hiding (type')
import Nirum.Docs.ReStructuredText (ReStructuredText, render)
import Nirum.Package hiding (target)
import Nirum.Package.Metadata ( Author (Author, name, email)
                              , Metadata ( authors
                                         , description
                                         , license
                                         , target
                                         , version
                                         )
                              , MetadataError ( FieldError
                                              , FieldTypeError
                                              , FieldValueError
                                              )
                              , Node (VString)
                              , Target ( CompileError
                                       , CompileResult
                                       , compilePackage
                                       , parseTarget
                                       , showCompileError
                                       , targetName
                                       , toByteString
                                       )
                              , stringField
                              , tableField
                              , textArrayField
                              , versionField
                              )
import qualified Nirum.Package.ModuleSet as MS
import qualified Nirum.Package.Metadata as MD
import Nirum.Targets.Python.CodeGen
import Nirum.Targets.Python.Deserializers
import Nirum.Targets.Python.Serializers
import Nirum.Targets.Python.TypeExpression
import Nirum.Targets.Python.Validators
import Nirum.TypeInstance.BoundModule as BM

type Package' = Package Python
type CompileError' = Nirum.Targets.Python.CodeGen.CompileError

data Source = Source { sourcePackage :: Package'
                     , sourceModule :: BoundModule Python
                     } deriving (Eq, Ord, Show)

sourceImportPath :: Source -> T.Text
sourceImportPath (Source (Package MD.Metadata { MD.target = t } _ _) bm) =
    toImportPath t (BM.modulePath bm)

sourceDirectory :: PythonVersion -> T.Text
sourceDirectory Python2 = "src-py2"
sourceDirectory Python3 = "src"

enumerate :: [a] -> [(Int, a)]
enumerate = zip [0 ..]

toEnumMemberName :: Name -> T.Text
toEnumMemberName name'
  | attributeName `elem` memberKeywords = attributeName `T.snoc` '_'
  | otherwise = attributeName
  where
    memberKeywords :: [T.Text]
    memberKeywords = ["mro"]
    attributeName :: T.Text
    attributeName = toAttributeName' name'

compileFieldInitializer :: Field -> CodeGen Code
compileFieldInitializer (Field fieldName' fieldType' _) =
    case fieldType' of
        SetModifier _ -> do
            b <- importBuiltins
            return [qq|self.$attributeName = $b.frozenset($attributeName)|]
        ListModifier _ -> do
            insertThirdPartyImportsA [ ( "nirum.datastructures"
                                       , [("_list_type", "List")]
                                       )
                                     ]
            return [qq|self.$attributeName = _list_type($attributeName)|]
        _ -> return [qq|self.$attributeName = $attributeName|]
  where
    attributeName :: Code
    attributeName = toAttributeName' fieldName'

compileDocs :: Documented a => a -> Maybe ReStructuredText
compileDocs = fmap render . docsBlock

quoteDocstring :: ReStructuredText -> Code
quoteDocstring rst = T.concat ["r'''", rst, "\n'''\n"]

compileDocstring' :: Documented a => Code -> a -> [ReStructuredText] -> Code
compileDocstring' indentSpace d extra =
    case (compileDocs d, extra) of
        (Nothing, []) -> "\n"
        (result, extra') -> indent indentSpace $ quoteDocstring $
            T.append (fromMaybe "" result) $
                     T.concat ['\n' `T.cons` e `T.snoc` '\n' | e <- extra']

compileDocstring :: Documented a => Code -> a -> Code
compileDocstring indentSpace d = compileDocstring' indentSpace d []

compileDocstringWithFields :: Documented a => Code -> a -> [Field] -> Code
compileDocstringWithFields indentSpace decl fields' =
    compileDocstring' indentSpace decl extra
  where
    extra :: [ReStructuredText]
    extra =
        [ case compileDocs f of
              Nothing -> T.concat [ ".. attribute:: "
                                  , toAttributeName' n
                                  , "\n"
                                  ]
              Just docs' -> T.concat [ ".. attribute:: "
                                     , toAttributeName' n
                                     , "\n\n"
                                     , indent "   " docs'
                                     ]
        | f@(Field n _ _) <- toList fields'
        ]

compileDocstringWithParameters :: Code
                               -> Method
                               -> [MethodParameterCode]
                               -> Code
compileDocstringWithParameters indentSpace decl methodParameterCodes =
    compileDocstring'
        indentSpace
        decl
        [ case compileDocs (mpcParam mpc) of
              Nothing ->
                  [qq|:param {paramName}: (:class:`{simplifyTypeExpr tx}`)
|]
              Just docs' ->
                  [qq|:param {paramName}: (:class:`{simplifyTypeExpr tx}`)
{indentN (9 + (T.length paramName)) docs'}
|]
        | mpc@MethodParameterCode { mpcTypeExpr = tx } <- methodParameterCodes
        , paramName <- [mpcAttributeName mpc]
        ]
  where
    indentN :: Int -> Code -> Code
    indentN n = indent (T.replicate n " ")
    simplifyTypeExpr :: Code -> Code
    simplifyTypeExpr = T.takeWhileEnd (/= '.')

compileDocsComment :: Documented a => Code -> a -> Code
compileDocsComment indentSpace d =
    case compileDocs d of
        Nothing -> "\n"
        Just rst -> indent (indentSpace `T.append` "#: ") rst

data FieldCode = FieldCode
    { fcField :: Field
    , fcInitializer :: Code
    , fcTypeExpr :: Code
    , fcValidator :: Validator
    , fcDeserializer :: Markup
    }

fcAttributeName :: FieldCode -> T.Text
fcAttributeName = toAttributeName' . fieldName . fcField

fcBehindName :: FieldCode -> T.Text
fcBehindName = toBehindSnakeCaseText . fieldName . fcField

fcOptional :: FieldCode -> Bool
fcOptional FieldCode { fcField = Field { fieldType = OptionModifier _ } } = True
fcOptional _ = False

fcTypePredicateCode :: FieldCode -> Code
fcTypePredicateCode = typePredicateCode . fcValidator

fcValueValidators :: FieldCode -> [ValueValidator]
fcValueValidators = valueValidators . fcValidator

toFieldCodes :: Source
             -> (Field -> Code)
             -> (Field -> Code)
             -> (Field -> Code)
             -> DS.DeclarationSet Field
             -> CodeGen [FieldCode]
toFieldCodes src vIn vOut vError = flip (forM . toList) $ \ field -> do
    typeExpr <- compileTypeExpression' src (Just $ fieldType field)
    initializer <- compileFieldInitializer field
    validator <- compileValidator' src
        (fieldType field)
        (toAttributeName' $ fieldName field)
    deserializer <- compileDeserializer' src (fieldType field)
        (vIn field)
        (vOut field)
        (vError field)
    return FieldCode
        { fcField = field
        , fcInitializer = initializer
        , fcTypeExpr = typeExpr
        , fcValidator = validator
        , fcDeserializer = deserializer
        }

instance Eq FieldCode where
    a == b =
        t a == t b
      where
          t :: FieldCode
            -> (Field, Code, Code, Validator, Data.ByteString.Lazy.ByteString)
          t fc =
              ( fcField fc
              , fcInitializer fc
              , fcTypeExpr fc
              , fcValidator fc
              , Text.Blaze.Renderer.Utf8.renderMarkup $ fcDeserializer fc
              )

-- When [FieldCode] is sorted optional fields go back.
instance Ord FieldCode where
    a <= b = fcOptional a <= fcOptional b

data MethodCode = MethodCode
    { mcMethod :: Method
    , mcRTypeExpr :: Code
    , mcETypeExpr :: Code
    , mcParams :: [MethodParameterCode]
    , mcRSerializer :: Code -> Markup
    , mcRDeserializer :: Maybe Markup
    , mcESerializer :: Code -> Markup
    , mcEDeserializer :: Maybe Markup
    }

mcAttributeName :: MethodCode -> T.Text
mcAttributeName = toAttributeName' . methodName . mcMethod

mcBehindName :: MethodCode -> T.Text
mcBehindName = toBehindSnakeCaseText . methodName . mcMethod

toMethodCode :: Source
             -> Code
             -> Code
             -> Code
             -> (Parameter -> Code)
             -> (Parameter -> Code)
             -> (Parameter -> Code)
             -> Method
             -> CodeGen MethodCode
toMethodCode source
             vInput vOutput vError
             paramVInput paramVOutput paramVError
             method@Method { returnType = rType, errorType = eType } = do
    rTypeExpr <- compileTypeExpression' source rType
    errTypeExpr <- compileTypeExpression' source eType
    params' <- sequence $ (`map` toList (parameters method)) $ \ param ->
        toMethodParameterCode source
            (paramVInput param)
            (paramVOutput param)
            (paramVError param)
            param
    resultSerializer <- serializer rType
    errorSerializer <- serializer eType
    resultDeserializer <- coalesce rType $ \ t ->
        compileDeserializer' source t vInput vOutput vError
    errorDeserializer <- coalesce eType $ \ t ->
        compileDeserializer' source t vInput vOutput vError
    return MethodCode
        { mcMethod = method
        , mcRTypeExpr = rTypeExpr
        , mcETypeExpr = errTypeExpr
        , mcParams = params'
        , mcRSerializer = resultSerializer
        , mcRDeserializer = resultDeserializer
        , mcESerializer = errorSerializer
        , mcEDeserializer = errorDeserializer
        }
  where
    coalesce :: Maybe a -> (a -> CodeGen b) -> CodeGen (Maybe b)
    coalesce (Just v) f = Just <$> f v
    coalesce Nothing _ = return Nothing
    serializer :: Maybe TypeExpression -> CodeGen (Code -> Markup)
    serializer Nothing =
        return $ \ funcName -> [compileText|(#{funcName}) = None|]
    serializer (Just type_) = do
        typing <- importStandardLibrary "typing"
        builtins <- importBuiltins
        typeExpr <- compileTypeExpression' source $ Just type_
        Validator predicateCode' valueValidators' <-
            compileValidator' source type_ "input"
        return $ \ funcName -> [compileText|
def #{funcName}(input):
    if not (#{predicateCode'}):
        raise #{builtins}.TypeError(
            'expected a value of ' + #{typing}._type_repr(#{typeExpr}) +
            ', not ' + repr(input)
        )
%{ forall ValueValidator valuePredCode valueErrorMsg <- valueValidators' }
    elif not (#{valuePredCode}):
        raise #{builtins}.ValueError(#{stringLiteral valueErrorMsg})
%{ endforall }
    return #{compileSerializer' source type_ "input"}
        |]

data MethodParameterCode = MethodParameterCode
    { mpcParam :: Parameter
    , mpcTypeExpr :: Code
    , mpcValidator :: Validator
    , mpcDeserializer :: Markup
    }

mpcAttributeName :: MethodParameterCode -> T.Text
mpcAttributeName MethodParameterCode { mpcParam = Parameter pName _ _ } =
    toAttributeName' pName

mpcBehindName :: MethodParameterCode -> T.Text
mpcBehindName MethodParameterCode { mpcParam = Parameter pName _ _ } =
    toBehindSnakeCaseText pName

mpcOptional :: MethodParameterCode -> Bool
mpcOptional mpc = case mpcType mpc of
    OptionModifier _ -> True
    _ -> False

mpcType :: MethodParameterCode -> TypeExpression
mpcType MethodParameterCode { mpcParam = Parameter _ typeExpr _ } = typeExpr

mpcTypePredicateCode :: MethodParameterCode -> Code
mpcTypePredicateCode = typePredicateCode . mpcValidator

mpcValueValidators :: MethodParameterCode -> [ValueValidator]
mpcValueValidators = valueValidators . mpcValidator

toMethodParameterCode :: Source
                      -> Code
                      -> Code
                      -> Code
                      -> Parameter
                      -> CodeGen MethodParameterCode
toMethodParameterCode source vInput vOutput vError
                      parameter@(Parameter pName pType _) = do
    typeExpr <- compileTypeExpression' source $ Just pType
    validator <- compileValidator' source pType $ toAttributeName' pName
    deserializer <- compileDeserializer' source pType vInput vOutput vError
    return MethodParameterCode
        { mpcParam = parameter
        , mpcTypeExpr = typeExpr
        , mpcValidator = validator
        , mpcDeserializer = deserializer
        }

defaultDeserializerErrorHandler :: CodeGen Code
defaultDeserializerErrorHandler = do
    modify $ \ c@CodeGenContext { globalDefinitions = defs } ->
        c { globalDefinitions = S.insert code defs }
    return funcName
  where
    funcName :: Code
    funcName = "__nirum_default_deserialization_error_handler__"
    code :: Code
    code = [qq|
class $funcName(object):
    def __init__(self, on_error=None):
        self.on_error = on_error
        self.errors = set()
        self.errored = False
    def __call__(self, field, message):
        self.errored = True
        if self.on_error is None:
            self.errors.add((field, message))
        else:
            self.on_error(field, message)
    def raise_error(self):
        """Raise :exc:`ValueError` if there's no overridden ``on_error``
        callback and ever errored.
        """
        if self.errored and self.on_error is None:
            raise ValueError(
                chr(0xa).join(
                    sorted(e[0] + ': ' + e[1] for e in self.errors)
                )
            )
    |]

compileUnionTag :: Source -> Name -> Tag -> CodeGen Markup
compileUnionTag source parentname d@(Tag typename' fields' _) = do
    abc <- collectionsAbc
    fieldCodes <- toFieldCodes source
        (\ f -> [qq|value.get('{toBehindSnakeCaseText (fieldName f)}')|])
        (\ f -> [qq|rv_{toAttributeName' (fieldName f)}|])
        (\ f -> [qq|error_{toAttributeName' (fieldName f)}|])
        fields'
    pyVer <- getPythonVersion
    defaultErrorHandler <- defaultDeserializerErrorHandler
    return [compileText|
class #{className}(#{parentClass}):
#{compileDocstringWithFields "    " d (map fcField fieldCodes)}
    __slots__ = (
%{ forall fieldCode <- fieldCodes }
        '#{fcAttributeName fieldCode}',
%{ endforall }
    )
    __nirum_type__ = 'union'
    __nirum_tag__ = #{parentClass}.Tag.#{toEnumMemberName typename'}

%{ case pyVer }
%{ of Python2 }
    def __init__(self, **kwargs):
%{ of Python3 }
    def __init__(
%{ if null fieldCodes }
        self
%{ else }
        self, *
%{ forall fieldCode <- L.sort fieldCodes }
        , #{fcAttributeName fieldCode}: #{fcTypeExpr fieldCode}
%{ if fcOptional fieldCode }
            =None
%{ endif }
%{ endforall }
%{ endif }
    ) -> None:
%{ endcase }
        def __init__(
%{ forall (i, fieldCode) <- enumerate (L.sort fieldCodes) }
%{ if i > 0 }
            ,
%{ endif }
            #{fcAttributeName fieldCode}
%{ case pyVer }
%{ of Python3 }
                : #{fcTypeExpr fieldCode}
%{ of Python2 }
%{ endcase }
%{ if fcOptional fieldCode }
                =None
%{ endif }
%{ endforall }
        ):
%{ forall fc <- fieldCodes }
            if not (#{fcTypePredicateCode fc}):
                raise TypeError(
                    '#{fcAttributeName fc} must be a value of ' +
                    __import__('typing')._type_repr(#{fcTypeExpr fc}) +
                    ', not ' + repr(#{fcAttributeName fc})
                )
%{ forall ValueValidator fValuePredCode fValueErrorMsg <- fcValueValidators fc }
            elif not (#{fValuePredCode}):
                raise ValueError(
                    'invalid #{fcAttributeName fc}: '
                    #{stringLiteral fValueErrorMsg}
                )
%{ endforall }
%{ endforall }
%{ forall FieldCode { fcInitializer = initializer } <- fieldCodes }
            #{initializer}
%{ endforall }
            pass  # it's necessary when there are no parameters at all
%{ case pyVer }
%{ of Python2 }
        __init__(**kwargs)
%{ of Python3 }
        __init__(
%{ forall fieldCode <- fieldCodes }
            #{fcAttributeName fieldCode}=#{fcAttributeName fieldCode},
%{ endforall }
        )
%{ endcase }

    def __nirum_serialize__(self):
        return {
            '_type': '#{behindParentTypename}',
            '_tag': '#{behindTagName}',
%{ forall fc@FieldCode { fcField = Field { fieldType = fType } } <- fieldCodes }
            '#{fcBehindName fc}':
#{compileSerializer' source fType $ T.append "self." $ fcAttributeName fc},
%{ endforall }
        }

    @classmethod
%{ case pyVer }
%{ of Python2 }
    def __nirum_deserialize__(cls, value, on_error=None):
%{ of Python3 }
    def __nirum_deserialize__(
        cls: type,
        value,
        on_error: typing.Optional[
            typing.Callable[[typing.Tuple[str, str]], None]
        ]=None
    ) -> typing.Optional['#{className}']:
%{ endcase }
        handle_error = #{defaultErrorHandler}(on_error)
        if isinstance(value, #{abc}.Mapping):
            try:
                tag = value['_tag']
            except KeyError:
                handle_error('._tag', 'Expected to exist.')
            else:
                if tag == '#{toBehindSnakeCaseText typename'}':
%{ forall fieldCode@FieldCode { fcDeserializer = deserializer } <- fieldCodes }
                    error_#{fcAttributeName fieldCode} = lambda ef, em: \
                        handle_error('.#{fcBehindName fieldCode}' + ef, em)
%{ if fcOptional fieldCode }
                    if '#{fcBehindName fieldCode}' not in value:
                        value['#{fcBehindName fieldCode}'] = None
#{indent "                    " deserializer}
%{ else }
                    if '#{fcBehindName fieldCode}' in value:
#{indent "                        " deserializer}
                    else:
                        error_#{fcAttributeName fieldCode}(
                            '', 'Expected to exist.'
                        )
%{ endif }
%{ endforall }
                    pass  # No-op; just for convenience' sake of the compiler
                else:
                    handle_error(
                        '._tag',
                        'Expected to be a "#{toBehindSnakeCaseText typename'}".'
                    )
        else:
            handle_error('', 'Expected an object.')
        handle_error.raise_error()
        if not handle_error.errored:
            return cls(
%{ forall fieldCode <- fieldCodes }
                #{fcAttributeName fieldCode}=rv_#{fcAttributeName fieldCode},
%{ endforall }
            )

%{ case pyVer }
%{ of Python2 }
    def __eq__(self, other):
%{ of Python3 }
    def __eq__(self, other: '#{parentClass}') -> bool:
%{ endcase }
        return isinstance(other, #{className}) and all(
            getattr(self, attr) == getattr(other, attr)
            for attr in self.__slots__
        )

%{ case pyVer }
%{ of Python2 }
    def __ne__(self, other):
%{ of Python3 }
    def __ne__(self, other: '#{parentClass}') -> bool:
%{ endcase }
        return not self == other

%{ case pyVer }
%{ of Python2 }
    def __hash__(self):
%{ of Python3 }
    def __hash__(self) -> int:
%{ endcase }
        return hash((
%{ forall fieldCode <- fieldCodes }
            self.#{fcAttributeName fieldCode},
%{ endforall }
        ))

%{ case pyVer }
%{ of Python2 }
    def __repr__(self):
%{ of Python3 }
    def __repr__(self) -> bool:
%{ endcase }
        return ''.join([
            '#{sourceImportPath source}.#{parentClass}.#{className}(',
%{ forall (i, fieldCode) <- enumerate fieldCodes }
%{ if i > 0 }
            ', ',
%{ endif }
            '#{fcAttributeName fieldCode}=',
            repr(self.#{fcAttributeName fieldCode}),
%{ endforall }
            ')'
        ])


#{parentClass}.#{className} = #{className}
if hasattr(#{parentClass}, '__qualname__'):
    (#{className}).__qualname__ = '#{parentClass}.#{className}'
|]
  where
    className :: T.Text
    className = toClassName' typename'
    behindParentTypename :: T.Text
    behindParentTypename = I.toSnakeCaseText $ N.behindName parentname
    behindTagName :: T.Text
    behindTagName = I.toSnakeCaseText $ N.behindName typename'
    parentClass :: T.Text
    parentClass = toClassName' parentname

compileTypeExpression' :: Source -> Maybe TypeExpression -> CodeGen Code
compileTypeExpression' Source { sourceModule = boundModule } =
    compileTypeExpression boundModule

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

compileValidator' :: Source -> TypeExpression -> Code -> CodeGen Validator
compileValidator' Source { sourceModule = boundModule } =
    compileValidator boundModule

compileDeserializer' :: Source
                     -> TypeExpression
                     -> Code
                     -> Code
                     -> Code
                     -> CodeGen Markup
compileDeserializer' Source { sourceModule = boundModule } =
    compileDeserializer boundModule

compileTypeDeclaration :: Source -> TypeDeclaration -> CodeGen Markup
compileTypeDeclaration _ TypeDeclaration { type' = PrimitiveType {} } =
    return ""  -- never used
compileTypeDeclaration src d@TypeDeclaration { typename = typename'
                                             , type' = Alias ctype
                                             } = do
    ctypeExpr <- compileTypeExpression' src (Just ctype)
    return [compileText|
%{ case compileDocs d }
%{ of Just rst }
#: #{rst}
%{ of Nothing }
%{ endcase }
#{toClassName' typename'} = #{ctypeExpr}
    |]
compileTypeDeclaration src d@TypeDeclaration { typename = typename'
                                             , type' = UnboxedType itype
                                             , typeAnnotations = annots
                                             } = do
    let className = toClassName' typename'
    itypeExpr <- compileTypeExpression' src (Just itype)
    insertStandardImport "typing"
    pyVer <- getPythonVersion
    Validator typePred valueValidatorsProto <-
        compileValidator' src itype "value"
    valueValidators' <- case A.lookup "numeric-constraints" annots of
        Just A.Annotation { A.arguments = args } -> do
            let constraintValidators =
                    [ case (name', value) of
                        ("min", Integer v) ->
                            Just $ ValueValidator
                                       [qq|value >= ($v)|]
                                       [qq|value is less than $v|]
                        ("max", Integer v) ->
                            Just $ ValueValidator
                                       [qq|value <= ($v)|]
                                       [qq|value is greater than $v|]
                        _ -> Nothing
                    | (name', value) <- toList args
                    ]
            if all isJust constraintValidators
            then return $ catMaybes constraintValidators
            else fail "Unsupported arguments on @numeric-constraints"
        Nothing -> return valueValidatorsProto
    deserializer <- compileDeserializer' src itype "value" "rv" "on_error"
    defaultErrorHandler <- defaultDeserializerErrorHandler
    return [compileText|
class #{className}(object):
#{compileDocstring "    " d}

    __nirum_type__ = 'unboxed'

%{ case pyVer }
%{ of Python2 }
    def __init__(self, value):
%{ of Python3 }
    def __init__(self, value: '#{itypeExpr}') -> None:
%{ endcase }
        if not (#{typePred}):
            raise TypeError(
                'expected {0}, not {1!r}'.format(
                    typing._type_repr(#{itypeExpr}),
                    value
                )
            )
%{ forall ValueValidator predCode msg <- valueValidators' }
        if not (#{predCode}):
            raise ValueError(#{stringLiteral msg})
%{ endforall }
        self.value = value  # type: #{itypeExpr}

%{ case pyVer }
%{ of Python2 }
    def __ne__(self, other):
        return not self == other

    def __eq__(self, other):
%{ of Python3 }
    def __eq__(self, other) -> bool:
%{ endcase }
        return (isinstance(other, #{className}) and
                self.value == other.value)

%{ case pyVer }
%{ of Python2 }
    def __hash__(self):
%{ of Python3 }
    def __hash__(self) -> int:
%{ endcase }
        return hash(self.value)

    def __nirum_serialize__(self):
        return (#{compileSerializer' src itype "self.value"})

    @classmethod
%{ case pyVer }
%{ of Python2 }
    def __nirum_deserialize__(cls, value, on_error=None):
%{ of Python3 }
    def __nirum_deserialize__(
        cls: type,
        value: typing.Any,
        on_error: typing.Optional[
            typing.Callable[[typing.Tuple[str, str]], None]
        ]=None
    ) -> typing.Optional['#{className}']:
%{ endcase }
        on_error = #{defaultErrorHandler}(on_error)
#{indent "        " deserializer}
        on_error.raise_error()
        if not on_error.errored:
            return cls(rv)

%{ case pyVer }
%{ of Python2 }
    def __repr__(self):
        return '{0.__module__}.{0.__name__}({1!r})'.format(
            type(self), self.value
        )
%{ of Python3 }
    def __repr__(self) -> str:
        return '{0}({1!r})'.format(typing._type_repr(type(self)), self.value)
%{ endcase }

%{ case pyVer }
%{ of Python2 }
    def __hash__(self):
%{ of Python3 }
    def __hash__(self) -> int:
%{ endcase }
        return hash(self.value)
|]
compileTypeDeclaration _ d@TypeDeclaration { typename = typename'
                                           , type' = EnumType members'
                                           } = do
    let className = toClassName' typename'
    insertStandardImport "enum"
    insertStandardImport "typing"
    baseString <- baseStringClass
    pyVer <- getPythonVersion
    defaultErrorHandler <- defaultDeserializerErrorHandler
    return [compileText|
class #{className}(enum.Enum):
#{compileDocstring "    " d}

%{ forall member@(EnumMember memberName@(Name _ behind) _) <- toList members' }
#{compileDocsComment "    " member}
    #{toEnumMemberName memberName} = '#{I.toSnakeCaseText behind}'
%{ endforall }

%{ case pyVer }
%{ of Python2 }
    def __nirum_serialize__(self):
%{ of Python3 }
    def __nirum_serialize__(self) -> str:
%{ endcase }
        return self.value

    @classmethod
%{ case pyVer }
%{ of Python2 }
    def __nirum_deserialize__(cls, value, on_error=None):
%{ of Python3 }
    def __nirum_deserialize__(
        cls: type,
        value: str,
        on_error: typing.Optional[
            typing.Callable[[typing.Tuple[str, str]], None]
        ]=None
    ) -> '#{className}':
%{ endcase }
        on_error = #{defaultErrorHandler}(on_error)
        if isinstance(value, #{baseString}):
            member = value.replace('-', '_')
            try:
                result = cls(member)
            except ValueError:
                on_error(
                    '',
                    'Expected a string of a member name, but the given '
                    'string is not a member.  Available member names are: '
                    + ', '.join('"{0}"'.format(m.value) for m in cls)
                )
        else:
            on_error(
                '',
                'Expected a string of a member name, but the given value '
                'is not a string.'
            )
        on_error.raise_error()
        if not on_error.errored:
            return result


# Since enum.Enum doesn't allow to define non-member when the class is defined,
# __nirum_type__ should be defined after the class is defined.
#{className}.__nirum_type__ = 'enum'
|]
compileTypeDeclaration src d@TypeDeclaration { typename = Name tnFacial tnBehind
                                             , type' = RecordType fields'
                                             } = do
    let className = toClassName tnFacial
    insertStandardImport "typing"
    abc <- collectionsAbc
    pyVer <- getPythonVersion
    fieldCodes <- toFieldCodes src
        (\ f -> [qq|value.get('{toBehindSnakeCaseText (fieldName f)}')|])
        (\ f -> [qq|rv_{toAttributeName' (fieldName f)}|])
        (\ f -> [qq|error_{toAttributeName' (fieldName f)}|])
        fields'
    defaultErrorHandler <- defaultDeserializerErrorHandler
    return [compileText|
class #{className}(object):
#{compileDocstringWithFields "    " d (map fcField fieldCodes)}
    __slots__ = (
%{ forall fieldCode <- fieldCodes }
        '#{fcAttributeName fieldCode}',
%{ endforall }
    )
    __nirum_type__ = 'record'

%{ case pyVer }
%{ of Python2 }
    def __init__(self, **kwargs):
%{ of Python3 }
    def __init__(
%{ if null fieldCodes }
        self
%{ else }
        self, *
%{ forall fieldCode <- L.sort fieldCodes }
        , #{fcAttributeName fieldCode}: #{fcTypeExpr fieldCode}
%{ if fcOptional fieldCode }
            =None
%{ endif }
%{ endforall }
%{ endif }
    ) -> None:
%{ endcase }
        def __init__(
%{ forall (i, fieldCode) <- enumerate (L.sort fieldCodes) }
%{ if i > 0 }
            ,
%{ endif }
            #{fcAttributeName fieldCode}
%{ case pyVer }
%{ of Python3 }
                : #{fcTypeExpr fieldCode}
%{ of Python2 }
%{ endcase }
%{ if fcOptional fieldCode }
                =None
%{ endif }
%{ endforall }
        ):
%{ forall fc <- fieldCodes }
            if not (#{fcTypePredicateCode fc}):
                raise TypeError(
                    '#{fcAttributeName fc} must be a value of ' +
                    __import__('typing')._type_repr(#{fcTypeExpr fc}) +
                    ', not ' + repr(#{fcAttributeName fc})
                )
%{ forall ValueValidator fValuePredCode fValueErrorMsg <- fcValueValidators fc }
            elif not (#{fValuePredCode}):
                raise ValueError(
                    'invalid #{fcAttributeName fc}: '
                    #{stringLiteral fValueErrorMsg}
                )
%{ endforall }
%{ endforall }
%{ forall FieldCode { fcInitializer = initializer } <- fieldCodes }
            #{initializer}
%{ endforall }
            pass  # it's necessary when there are no parameters at all
%{ case pyVer }
%{ of Python2 }
        __init__(**kwargs)
%{ of Python3 }
        __init__(
%{ forall fieldCode <- fieldCodes }
            #{fcAttributeName fieldCode}=#{fcAttributeName fieldCode},
%{ endforall }
        )
%{ endcase }

%{ case pyVer }
%{ of Python2 }
    def __repr__(self):
%{ of Python3 }
    def __repr__(self) -> bool:
%{ endcase }
        return ''.join([
            '#{sourceImportPath src}.#{className}(',
%{ forall (i, fieldCode) <- enumerate fieldCodes }
%{ if i > 0 }
            ', ',
%{ endif }
            '#{fcAttributeName fieldCode}=',
            repr(self.#{fcAttributeName fieldCode}),
%{ endforall }
            ')'
        ])

%{ case pyVer }
%{ of Python2 }
    def __eq__(self, other):
%{ of Python3 }
    def __eq__(self, other: '#{className}') -> bool:
%{ endcase }
        return isinstance(other, #{className}) and all(
            getattr(self, attr) == getattr(other, attr)
            for attr in self.__slots__
        )

%{ case pyVer }
%{ of Python2 }
    def __ne__(self, other):
%{ of Python3 }
    def __ne__(self, other: '#{className}') -> bool:
%{ endcase }
        return not self == other

    def __nirum_serialize__(self):
        return {
            '_type': '#{I.toSnakeCaseText tnBehind}',
%{ forall fc@FieldCode { fcField = Field { fieldType = fType } } <- fieldCodes }
            '#{fcBehindName fc}':
#{compileSerializer' src fType $ T.append "self." $ fcAttributeName fc},
%{ endforall }
        }

    @classmethod
%{ case pyVer }
%{ of Python2 }
    def __nirum_deserialize__(cls, value, on_error=None):
%{ of Python3 }
    def __nirum_deserialize__(
        cls: type,
        value,
        on_error: typing.Optional[
            typing.Callable[[typing.Tuple[str, str]], None]
        ]=None
    ) -> typing.Optional['#{className}']:
%{ endcase }
        on_error = #{defaultErrorHandler}(on_error)
        if isinstance(value, #{abc}.Mapping):
%{ forall fieldCode@FieldCode { fcDeserializer = deserializer } <- fieldCodes }
            error_#{fcAttributeName fieldCode} = lambda ef, em: \
                on_error('.#{fcBehindName fieldCode}' + ef, em)
%{ if fcOptional fieldCode }
            if '#{fcBehindName fieldCode}' not in value:
                value['#{fcBehindName fieldCode}'] = None
#{indent "            " deserializer}
%{ else }
            if '#{fcBehindName fieldCode}' in value:
#{indent "                " deserializer}
            else:
                error_#{fcAttributeName fieldCode}('', 'Expected to exist.')
%{ endif }
%{ endforall }
        else:
            on_error('', 'Expected an object.')
        on_error.raise_error()
        if not on_error.errored:
            return cls(
%{ forall fieldCode <- fieldCodes }
                #{fcAttributeName fieldCode}=rv_#{fcAttributeName fieldCode},
%{ endforall }
            )

%{ case pyVer }
%{ of Python2 }
    def __hash__(self):
%{ of Python3 }
    def __hash__(self) -> int:
%{ endcase }
        return hash((
%{ forall fieldCode <- fieldCodes }
            self.#{fcAttributeName fieldCode},
%{ endforall }
        ))
|]
compileTypeDeclaration src
                       d@TypeDeclaration { typename = typename'
                                         , type' = union@UnionType {}
                                         , typeAnnotations = annotations
                                         } = do
    tagCodes <- mapM (compileUnionTag src typename') tags'
    abc <- collectionsAbc
    insertStandardImport "typing"
    insertStandardImport "enum"
    insertThirdPartyImportsA [("nirum.datastructures", [("map_type", "Map")])]
    baseString <- baseStringClass
    defaultErrorHandler <- defaultDeserializerErrorHandler
    pyVer <- getPythonVersion
    return [compileText|
class #{className}(#{T.intercalate "," $ compileExtendClasses annotations}):
#{compileDocstring "    " d}

    __nirum_type__ = 'union'

    class Tag(enum.Enum):
%{ forall (Tag tn _ _) <- tags' }
        #{toEnumMemberName tn} = '#{toBehindSnakeCaseText tn}'
%{ endforall }

%{ case pyVer }
%{ of Python2 }
    def __init__(self, *args, **kwargs):
%{ of Python3 }
    def __init__(self, *args, **kwargs) -> None:
%{ endcase }
        raise NotImplementedError(
            "{0} cannot be instantiated "
            "since it is an abstract class.  Instantiate a concrete subtype "
            "of it instead.".format(typing._type_repr(self))
        )

%{ case pyVer }
%{ of Python2 }
    def __nirum_serialize__(self):
%{ of Python3 }
    def __nirum_serialize__(self) -> typing.Mapping[str, object]:
%{ endcase }
        raise NotImplementedError(
            "{0} cannot be instantiated "
            "since it is an abstract class.  Instantiate a concrete subtype "
            "of it instead.".format(typing._type_repr(self))
        )

    @classmethod
%{ case pyVer }
%{ of Python2 }
    def __nirum_deserialize__(cls, value, on_error=None):
%{ of Python3 }
    def __nirum_deserialize__(
        cls: type,
        value,
        on_error: typing.Optional[
            typing.Callable[[typing.Tuple[str, str]], None]
        ]=None
    ) -> '#{className}':
%{ endcase }
        handle_error = #{defaultErrorHandler}(on_error)
        if isinstance(value, #{abc}.Mapping):
            try:
                tag = value['_tag']
            except KeyError:
%{ case defaultTag union }
%{ of Just dt }
                tag = '#{toBehindSnakeCaseText $ tagName dt}'
                value = dict(value)
                value['_tag'] = tag
%{ of Nothing }
                handle_error('._tag', 'Expected to exist.')
%{ endcase }
        else:
            handle_error('', 'Expected an object.')
        if handle_error.errored:
            pass
%{ forall (Tag tn _ _) <- tags' }
        elif tag == '#{toBehindSnakeCaseText tn}':
            rv = #{toClassName' tn}.__nirum_deserialize__(
                value, handle_error
            )
%{ endforall }
        elif isinstance(tag, #{baseString}):
            handle_error(
                '._tag',
                'Expected one of the following strings: '
%{ forall (i, (Tag tn _ _)) <- enumerate tags' }
%{ if i < 1 }
%{ elseif i < pred (length tags') }
                ', '
%{ else }
                ', or '
%{ endif }
                '"#{toBehindSnakeCaseText tn}"'
%{ endforall }
                '.'
            )
        else:
            handle_error(
                '._tag',
                'Expected a string, but the given value is not a string.'
            )
        handle_error.raise_error()
        if not handle_error.errored:
            return rv

%{ forall tagCode <- tagCodes }
#{tagCode}

%{ endforall }

#{className}.__nirum_tag_classes__ = map_type({
%{ forall (Tag tn _ _) <- tags' }
    #{className}.Tag.#{toEnumMemberName tn}: #{toClassName' tn},
%{ endforall }
})
|]
  where
    tags' :: [Tag]
    tags' = DS.toList $ tags union
    className :: T.Text
    className = toClassName' typename'
    compileExtendClasses :: A.AnnotationSet -> [Code]
    compileExtendClasses annotations' =
        if null extendClasses
            then ["object"]
            else extendClasses
      where
        extendsClassMap :: M.Map I.Identifier Code
        extendsClassMap = [("error", "Exception")]
        extendClasses :: [Code]
        extendClasses = catMaybes
            [ M.lookup annotationName extendsClassMap
            | (A.Annotation annotationName _) <- A.toList annotations'
            ]

compileTypeDeclaration src d@ServiceDeclaration { serviceName = name'
                                                , service = Service methods
                                                } = do
    insertThirdPartyImportsA
        [ ("nirum.constructs", [("name_dict_type", "NameDict")])
        , ("nirum.datastructures", [(nirumMapName, "Map")])
        , ("nirum.exc", [ ("_unexpected_nirum_response_error"
                          , "UnexpectedNirumResponseError"
                          )
                        ]
          )
        , ("nirum.service", [("service_type", "Service")])
        , ("nirum.transport", [("transport_type", "Transport")])
        ]
    abc <- collectionsAbc
    builtins <- importBuiltins
    typing <- importStandardLibrary "typing"
    pyVer <- getPythonVersion
    methodCodes <- sequence $ (`map` toList methods) $ toMethodCode
        src
        "value" "rv" "on_error"
        (const "value") (const "rv") (const "on_error")
    defaultErrorHandler <- defaultDeserializerErrorHandler
    return [compileText|
class #{className}(service_type):
#{compileDocstring "    " d}
    __nirum_type__ = 'service'
    __nirum_service_methods__ = {
%{ forall mc <- methodCodes }
        '#{mcAttributeName mc}': {
            '_v': 2,
            '_return': lambda: #{mcRTypeExpr mc},
            '_names': name_dict_type([
%{ forall mpc <- mcParams mc }
                ('#{mpcAttributeName mpc}', '#{mpcBehindName mpc}'),
%{ endforall }
            ]),
%{ forall mpc <- mcParams mc }
            '#{mpcAttributeName mpc}': lambda: #{mpcTypeExpr mpc},
%{ endforall }
        },
%{ endforall }
    }
    __nirum_method_names__ = name_dict_type([
%{ forall mc <- methodCodes }
        ('#{mcAttributeName mc}', '#{mcBehindName mc}'),
%{ endforall }
    ])
    __nirum_method_annotations__ = #{methodAnnotations'}

    @staticmethod
    def __nirum_method_error_types__(k, d=None):
%{ forall mc <- methodCodes }
        if k == '#{mcAttributeName mc}':
            return #{mcETypeExpr mc}
%{ endforall }
        return d

%{ forall mc <- methodCodes }
    def #{mcAttributeName mc}(
        self,
%{ case pyVer }
%{ of Python3 }
%{ forall mpc <- mcParams mc }
        #{mpcAttributeName mpc}: '#{mpcTypeExpr mpc}',
%{ endforall }
    ) -> '#{mcRTypeExpr mc}':
%{ of Python2 }
%{ forall mpc <- mcParams mc }
        #{mpcAttributeName mpc},
%{ endforall }
    ):
%{ endcase }
#{compileDocstringWithParameters "        " (mcMethod mc) (mcParams mc)}
        raise NotImplementedError(
            '#{className} has to implement #{mcAttributeName mc}()'
        )

    #{mcAttributeName mc}.__nirum_argument_serializers__ = {
    }  # type: typing.Mapping[str, typing.Callable[[object], object]]
    #{mcAttributeName mc}.__nirum_argument_deserializers__ = {
    }
%{ forall mpc <- mcParams mc }
    def __nirum_argument_serializer__(#{mpcAttributeName mpc}):
        if not (#{mpcTypePredicateCode mpc}):
            raise #{builtins}.TypeError(
                '#{mpcAttributeName mpc} must be a value of ' +
                #{typing}._type_repr(#{mpcTypeExpr mpc}) + ', not ' +
                #{builtins}.repr(#{mpcAttributeName mpc})
            )
%{ forall ValueValidator pValuePredCode pValueErrMsg <- mpcValueValidators mpc}
        elif not (#{pValuePredCode}):
            raise #{builtins}.ValueError(
                'invalid #{mpcAttributeName mpc}: '
                #{stringLiteral pValueErrMsg}
            )
%{ endforall }
        return #{compileSerializer' src (mpcType mpc) (mpcAttributeName mpc)}
    __nirum_argument_serializer__.__name__ = '#{mpcAttributeName mpc}'
    #{mcAttributeName mc}.__nirum_argument_serializers__[
        '#{mpcAttributeName mpc}'] = __nirum_argument_serializer__
    del __nirum_argument_serializer__

    def __nirum_argument_deserializer__(value, on_error=None):
        on_error = #{defaultErrorHandler}(on_error)
#{indent "        " (mpcDeserializer mpc)}
        on_error.raise_error()
        if not on_error.errored:
            return rv
    __nirum_argument_deserializer__.__name__ = '#{mpcBehindName mpc}'
    #{mcAttributeName mc}.__nirum_argument_deserializers__[
        '#{mpcBehindName mpc}'] = __nirum_argument_deserializer__
    del __nirum_argument_deserializer__
%{ endforall }

    def __nirum_serialize_arguments__(
%{ case pyVer }
%{ of Python3 }
%{ forall mpc <- mcParams mc }
        #{mpcAttributeName mpc}: '#{mpcTypeExpr mpc}',
%{ endforall }
    ) -> '#{mcRTypeExpr mc}':
%{ of Python2 }
%{ forall mpc <- mcParams mc}
        #{mpcAttributeName mpc},
%{ endforall }
    ):
%{ endcase }
        return {
%{ forall mpc <- mcParams mc }
            '#{mpcBehindName mpc}':
                #{className}.#{mcAttributeName mc}
                    .__nirum_argument_serializers__['#{mpcAttributeName mpc}'](
                    #{mpcAttributeName mpc}
                ),
%{ endforall }
        }
    #{mcAttributeName mc}.__nirum_serialize_arguments__ = \
        __nirum_serialize_arguments__
    del __nirum_serialize_arguments__

    def __nirum_deserialize_arguments__(value, on_error=None):
        on_error = #{defaultErrorHandler}(on_error)
        table = #{className}.#{mcAttributeName mc} \
            .__nirum_argument_deserializers__
        if isinstance(value, #{abc}.Mapping):
            result = {}
%{ forall mpc <- mcParams mc }
            try:
                field_value = value['#{mpcBehindName mpc}']
            except KeyError:
%{ if mpcOptional mpc }
                result['#{mpcAttributeName mpc}'] = None
%{ else }
                on_error('.#{mpcBehindName mpc}', 'Expected to exist.')
%{ endif }
            else:
                result['#{mpcAttributeName mpc}'] = \
                    table['#{mpcBehindName mpc}'](
                        field_value,
                        lambda f, m: on_error('.#{mpcBehindName mpc}' + f, m)
                    )
%{ endforall }
        else:
            on_error('', 'Expected an object.')
        on_error.raise_error()
        if not on_error.errored:
            return result
    #{mcAttributeName mc}.__nirum_deserialize_arguments__ = \
        __nirum_deserialize_arguments__
    del __nirum_deserialize_arguments__

#{indent "    " (mcRSerializer mc "__nirum_serialize_result__")}
    #{mcAttributeName mc}.__nirum_serialize_result__ = \
        __nirum_serialize_result__
    del __nirum_serialize_result__

%{ case mcRDeserializer mc }
%{ of Just resultDeserializer }
    def __nirum_deserialize_result__(value, on_error=None):
        on_error = #{defaultErrorHandler}(on_error)
#{indent "        " resultDeserializer}
        on_error.raise_error()
        if not on_error.errored:
            return rv
    #{mcAttributeName mc}.__nirum_deserialize_result__ = \
        __nirum_deserialize_result__
    del __nirum_deserialize_result__
%{ of Nothing }
    #{mcAttributeName mc}.__nirum_deserialize_result__ = None
%{ endcase }

#{indent "    " (mcESerializer mc "__nirum_serialize_error__")}
    #{mcAttributeName mc}.__nirum_serialize_error__ = \
        __nirum_serialize_error__
    del __nirum_serialize_error__

%{ case mcEDeserializer mc }
%{ of Just errorDeserializer }
    def __nirum_deserialize_error__(value, on_error=None):
        on_error = #{defaultErrorHandler}(on_error)
#{indent "        " errorDeserializer}
        on_error.raise_error()
        if not on_error.errored:
            return rv
    #{mcAttributeName mc}.__nirum_deserialize_error__ = \
        __nirum_deserialize_error__
    del __nirum_deserialize_error__
%{ of Nothing }
    #{mcAttributeName mc}.__nirum_deserialize_error__ = None
%{ endcase }
%{ endforall }


class #{className}_Client(#{className}):
    """The client object of :class:`{className}`."""

%{ case pyVer }
%{ of Python3 }
    def __init__(self, transport: transport_type) -> None:
%{ of Python2 }
    def __init__(self, transport):
%{ endcase }
        if not isinstance(transport, transport_type):
            raise TypeError(
                'expected an instance of {0.__module__}.{0.__name__}, not '
                '{1!r}'.format(transport_type, transport)
            )
        self.__nirum_transport__ = transport  # type: transport_type

%{ forall mc <- methodCodes }
    def #{mcAttributeName mc}(
        self, *args, **kwargs
%{ case pyVer }
%{ of Python3 }
    ) -> '#{mcRTypeExpr mc}':
%{ of Python2 }
    ):
%{ endcase }
        prototype = #{className}.#{mcAttributeName mc}
        successful, serialized = self.__nirum_transport__(
            '#{mcBehindName mc}',
            payload=prototype.__nirum_serialize_arguments__(*args, **kwargs),
            # FIXME Give annotations.
            service_annotations={},
            method_annotations=self.__nirum_method_annotations__,
            parameter_annotations={}
        )
        on_deserializer_error = #{defaultErrorHandler}()
        if successful:
            deserializer = prototype.__nirum_deserialize_result__
        else:
            deserializer = prototype.__nirum_deserialize_error__
        if callable(deserializer):
            result = deserializer(serialized, on_deserializer_error)
        elif not successful:
            raise _unexpected_nirum_response_error(serialized)
        else:
            result = None
        on_deserializer_error.raise_error()
        if successful:
            return result
        raise result
%{ endforall }

#{className}.Client = #{className}_Client
#{className}.Client.__name__ = 'Client'
if hasattr(#{className}.Client, '__qualname__'):
    #{className}.Client.__qualname__ = '#{className}.Client'
|]
  where
    nirumMapName :: T.Text
    nirumMapName = "map_type"
    className :: T.Text
    className = toClassName' name'
    commaNl :: [T.Text] -> T.Text
    commaNl = T.intercalate ",\n"
    toKeyItem :: I.Identifier -> T.Text -> T.Text
    toKeyItem ident v = [qq|'{toAttributeName ident}': {v}|]
    wrapMap :: T.Text -> T.Text
    wrapMap items = [qq|$nirumMapName(\{$items\})|]
    compileAnnotation :: I.Identifier -> A.AnnotationArgumentSet -> T.Text
    compileAnnotation ident annoArgument =
        toKeyItem ident $
            wrapMap $ T.intercalate ","
                [ [qq|'{toAttributeName ident'}': {annoArgToText value}|]
                | (ident', value) <- M.toList annoArgument
                ]
      where
        escapeSingle :: T.Text -> T.Text
        escapeSingle = T.strip . T.replace "'" "\\'"
        annoArgToText :: AnnotationArgument -> T.Text
        annoArgToText (AI.Text t) = [qq|u'''{escapeSingle t}'''|]
        annoArgToText (Integer i) = T.pack $ show i
    compileMethodAnnotation :: Method -> T.Text
    compileMethodAnnotation Method { methodName = mName
                                   , methodAnnotations = annoSet
                                   } =
        toKeyItem (N.facialName mName) $ wrapMap annotationDict
      where
        annotationDict :: T.Text
        annotationDict = T.intercalate ","
            [ compileAnnotation ident annoArgSet :: T.Text
            | (ident, annoArgSet) <- M.toList $ A.annotations annoSet
            ]
    methodAnnotations' :: T.Text
    methodAnnotations' = wrapMap $ commaNl $ map compileMethodAnnotation
        (toList methods)

compileTypeDeclaration _ Import {} =
    return ""  -- Nothing to compile

compileModuleBody :: Source -> CodeGen Markup
compileModuleBody src@Source { sourceModule = boundModule } = do
    let types' = boundTypes boundModule
    typeCodes <- mapM (compileTypeDeclaration src) $ toList types'
    return [compileText|
%{forall typeCode <- typeCodes}
#{typeCode}
%{endforall}
|]

compileModule :: PythonVersion
              -> Source
              -> Either CompileError' ( S.Set T.Text
                                      , M.Map (Int, Int) (S.Set T.Text)
                                      , Markup
                                      )
compileModule pythonVersion' source = do
    let (result, context) = runCodeGen (compileModuleBody source)
                                       (empty pythonVersion')
    let deps = S.union
            (dependencies context)
            (require "nirum" "nirum" $ M.keysSet $ thirdPartyImports context)
    let standardImportSet' = standardImportSet context
    let optDeps = M.unionWith S.union
            (optionalDependencies context)
            [ ((3, 4), require "enum34" "enum" standardImportSet')
            , ((3, 5), require "typing" "typing" standardImportSet')
            ]
    let fromImports = M.assocs (localImportsMap context) ++
                      M.assocs (thirdPartyImports context)
    let globalDefs = globalDefinitions context
    code <- result
    return $ (deps, optDeps,)
        [compileText|# -*- coding: utf-8 -*-
#{compileDocstring "" $ sourceModule source}
%{ forall (alias, import') <- M.assocs (standardImports context) }
%{ if import' == alias }
import #{import'}
%{ else }
import #{import'} as #{alias}
%{ endif }
%{ endforall }

%{ forall (from, nameMap) <- fromImports }
from #{from} import (
%{ forall (alias, name) <- M.assocs nameMap }
%{ if (alias == name) }
    #{name},
%{ else }
    #{name} as #{alias},
%{ endif }
%{ endforall }
)
%{ endforall }

%{ forall globalDef <- S.toList globalDefs }
#{globalDef}
%{ endforall }

#{code}
|]
  where
    has :: S.Set T.Text -> T.Text -> Bool
    has set module' = module' `S.member` set ||
                      any (T.isPrefixOf $ module' `T.snoc` '.') set
    require :: T.Text -> T.Text -> S.Set T.Text -> S.Set T.Text
    require pkg module' set =
        if set `has` module' then S.singleton pkg else S.empty

compilePackageMetadata :: Package'
                       -> (S.Set T.Text, M.Map (Int, Int) (S.Set T.Text))
                       -> Markup
compilePackageMetadata Package
                           { metadata = MD.Metadata
                                 { authors = authors'
                                 , version = version'
                                 , description = description'
                                 , license = license'
                                 , MD.keywords = keywords'
                                 , target = target'@Python
                                       { packageName = packageName'
                                       , minimumRuntimeVersion = minRuntimeVer
                                       , classifiers = classifiers'
                                       }
                                 }
                           , modules = modules'
                           }
                       (deps, optDeps) =
    [compileText|# -*- coding: utf-8 -*-
import sys

from setuptools import setup, __version__ as setuptools_version

install_requires = [
%{ forall p <- S.toList deps }
%{ if (p == "nirum") }
    'nirum >= #{SV.toText minRuntimeVer}',
%{ else }
    (#{stringLiteral p}),
%{ endif }
%{ endforall }
]
polyfill_requires = {
%{ forall ((major, minor), deps') <- M.toList optDeps }
    (#{major}, #{minor}): [
%{ forall dep' <- S.toList deps' }
        (#{stringLiteral dep'}),
%{ endforall }
    ],
%{ endforall }
}

%{ if (not $ M.null optDeps) }
# '<' operator for environment markers are supported since setuptools 17.1.
# Read PEP 496 for details of environment markers.
setup_requires = ['setuptools >= 17.1']
if tuple(map(int, setuptools_version.split('.'))) < (17, 1):
    extras_require = {}
    if 'bdist_wheel' not in sys.argv:
        for (major, minor), deps in polyfill_requires.items():
            if sys.version_info < (major, minor):
                install_requires.extend(deps)
    envmarker = ":python_version=='{0}.{1}'"
    python_versions = [(2, 6), (2, 7),
                       (3, 3), (3, 4), (3, 5), (3, 6)]  # FIXME
    for pyver in python_versions:
        extras_require[envmarker.format(*pyver)] = list({
            d
            for v, vdeps in polyfill_requires.items()
            if pyver < v
            for d in vdeps
        })
else:
    extras_require = {
        ":python_version<'{0}.{1}'".format(*pyver): deps
        for pyver, deps in polyfill_requires.items()
    }
%{ else }
setup_requires = []
extras_require = {}
%{ endif }


SOURCE_ROOT = #{stringLiteral $ sourceDirectory Python3}

if sys.version_info < (3, 0):
    SOURCE_ROOT = #{stringLiteral $ sourceDirectory Python2}

# TODO: long_description, url
setup(
    name=#{stringLiteral packageName'},
    version=#{stringLiteral $ SV.toText version'},
    description=#{nStringLiteral description'},
    license=#{nStringLiteral license'},
    keywords=#{stringLiteral $ T.intercalate " " keywords'},
    classifiers=[
%{ forall classifier <- classifiers' }
    #{stringLiteral classifier},
%{ endforall }
    ],
    author=', '.join([
%{ forall Author { name = name } <- authors' }
    (#{stringLiteral name}),
%{ endforall }
    ]),
    author_email=', '.join([
%{ forall Author { email = email' } <- authors' }
%{ case email' }
%{ of Just authorEmail }
    (#{stringLiteral $ decodeUtf8 $ E.toByteString authorEmail}),
%{ of Nothing }
%{ endcase }
%{ endforall }
    ]),
    package_dir={'': SOURCE_ROOT},
    packages=[#{strings $ toImportPaths target' $ MS.keysSet modules'}],
    provides=[#{strings $ toImportPaths target' $ MS.keysSet modules'}],
    requires=[#{strings $ S.toList deps}],
    setup_requires=setup_requires,
    install_requires=install_requires,
    extras_require=extras_require,
    entry_points={
        'nirum.modules': [
%{ forall modPath <- MS.keys modules' }
            '#{normalizeModulePath modPath} = #{toImportPath target' modPath}',
%{ endforall }
        ],
        'nirum.classes': [
%{ forall (modPath, Module types' _) <- MS.toList modules' }
%{ forall typeName <- catMaybes (typeNames types') }
            '#{normalizeModulePath modPath}.#{I.toNormalizedText typeName} = '
            '#{toImportPath target' modPath}:#{toClassName typeName}',
%{ endforall }
%{ endforall }
        ],
    },
)
|]
  where
    normalizeModulePath :: ModulePath -> T.Text
    normalizeModulePath = T.intercalate "." . map I.toNormalizedText . toList
    typeNames :: DS.DeclarationSet TD.TypeDeclaration -> [Maybe I.Identifier]
    typeNames types' =
        [ case td of
              TD.TypeDeclaration { TD.typename = (Name n _) } -> Just n
              TD.ServiceDeclaration { TD.serviceName = (Name n _) } -> Just n
              TD.Import {} -> Nothing
        | td <- DS.toList types'
        ]
    nStringLiteral :: Maybe T.Text -> T.Text
    nStringLiteral (Just value) = stringLiteral value
    nStringLiteral Nothing = "None"
    strings :: [Code] -> Code
    strings values = T.intercalate ", " $ map stringLiteral (L.sort values)

manifestIn :: Markup
manifestIn = [compileText|recursive-include src *.py
recursive-include src-py2 *.py
|]

compilePackage' :: Package'
                -> M.Map FilePath (Either CompileError' Markup)
compilePackage' package@Package { metadata = MD.Metadata { target = target' }
                                } =
    M.fromList $
        initFiles ++
        [ ( f
          , case cd of
                Left e -> Left e
                Right (_, _, cd') -> Right cd'
          )
        | (f, cd) <- modules'
        ] ++
        [ ("setup.py", Right $ compilePackageMetadata package allDependencies)
        , ("MANIFEST.in", Right manifestIn)
        ]
  where
    toPythonFilename :: ModulePath -> [FilePath]
    toPythonFilename mp = [ T.unpack (toAttributeName i)
                          | i <- toList $ renameModulePath' target' mp
                          ] ++ ["__init__.py"]
    versions :: [PythonVersion]
    versions = [Python2, Python3]
    toFilename :: T.Text -> ModulePath -> FilePath
    toFilename sourceRootDirectory mp =
        joinPath $ T.unpack sourceRootDirectory : toPythonFilename mp
    initFiles :: [(FilePath, Either CompileError' Markup)]
    initFiles = [ (toFilename (sourceDirectory ver) mp', Right [compileText||])
                | mp <- MS.keys (modules package)
                , mp' <- S.elems (hierarchy mp)
                , ver <- versions
                ]
    modules' :: [ ( FilePath
                  , Either CompileError' ( S.Set T.Text
                                         , M.Map (Int, Int) (S.Set T.Text)
                                         , Markup
                                         )
                  )
                ]
    modules' =
        [ ( toFilename (sourceDirectory ver) modulePath'
          , compileModule ver $ Source package boundModule
          )
        | (modulePath', _) <- MS.toAscList (modules package)
        , Just boundModule <- [resolveBoundModule modulePath' package]
        , ver <- versions
        ]
    allDependencies :: (S.Set T.Text, M.Map (Int, Int) (S.Set T.Text))
    allDependencies =
        ( S.unions [deps | (_, Right (deps, _, _)) <- modules']
        , M.unionsWith S.union [oDeps | (_, Right (_, oDeps, _)) <- modules']
        )

parseModulePath :: T.Text -> Maybe ModulePath
parseModulePath string =
    mapM I.fromText identTexts >>= fromIdentifiers
  where
    identTexts :: [T.Text]
    identTexts = T.split (== '.') string

instance Target Python where
    type CompileResult Python = Markup
    type CompileError Python = CompileError'
    targetName _ = "python"
    parseTarget table = do
        name' <- stringField "name" table
        minRuntime <- case versionField "minimum_runtime" table of
            Left (FieldError _) -> Right minimumRuntime
            otherwise' -> otherwise'
        renameTable <- case tableField "renames" table of
            Right t -> Right t
            Left (FieldError _) -> Right HM.empty
            otherwise' -> otherwise'
        renamePairs <- sequence
            [ case (parseModulePath k, v) of
                  (Just modulePath', VString v') -> case parseModulePath v' of
                      Just altPath -> Right (modulePath', altPath)
                      Nothing -> Left $ FieldValueError [qq|renames.$k|]
                          [qq|expected a module path, not "$v'"|]
                  (Nothing, _) -> Left $ FieldValueError [qq|renams.$k|]
                      [qq|expected a module path as a key, not "$k"|]
                  _ -> Left $ FieldTypeError [qq|renames.$k|] "string" $
                                             MD.fieldType v
            | (k, v) <- HM.toList renameTable
            ]
        classifiers' <- case textArrayField "classifiers" table of
            Right t -> Right t
            Left (FieldError _) -> Right []
            otherwise' -> otherwise'
        return Python { packageName = name'
                      , minimumRuntimeVersion = max minRuntime minimumRuntime
                      , renames = M.fromList renamePairs
                      , classifiers = classifiers'
                      }
    compilePackage = compilePackage'
    showCompileError _ e = e
    toByteString _ =
        Data.ByteString.Lazy.toStrict . Text.Blaze.Renderer.Utf8.renderMarkup