Skip to content

Commit

Permalink
Python docstrings. Close nirum-lang#102
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Mar 23, 2017
1 parent 413e5e5 commit 1071db3
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 60 deletions.
173 changes: 120 additions & 53 deletions src/Nirum/Targets/Python.hs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import Text.InterpolatedString.Perl6 (q, qq)

import qualified Nirum.CodeGen as C
import Nirum.CodeGen (Failure)
import Nirum.Constructs.Declaration (Documented (docsBlock))
import qualified Nirum.Constructs.DeclarationSet as DS
import qualified Nirum.Constructs.Identifier as I
import Nirum.Constructs.ModulePath ( ModulePath
Expand Down Expand Up @@ -101,6 +102,7 @@ import Nirum.Constructs.TypeExpression ( TypeExpression ( ListModifier
, TypeIdentifier
)
)
import Nirum.Docs.ReStructuredText (ReStructuredText, render)
import Nirum.Package ( BoundModule
, Package (Package, metadata, modules)
, TypeLookup (Imported, Local, Missing)
Expand Down Expand Up @@ -302,6 +304,58 @@ toIndentedCodes f traversable concatenator =
quote :: T.Text -> T.Text
quote s = [qq|'{s}'|]

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 -> DS.DeclarationSet 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
]

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

indent :: Code -> Code -> Code
indent space =
T.intercalate "\n" . map indentLn . T.lines
where
indentLn :: Code -> Code
indentLn line
| T.null line = T.empty
| otherwise = space `T.append` line

typeReprCompiler :: CodeGen (Code -> Code)
typeReprCompiler = do
ver <- getPythonVersion
Expand Down Expand Up @@ -329,12 +383,8 @@ returnCompiler = do
Python2 -> ""
Python3 -> [qq| -> $r|]

compileUnionTag :: Source
-> Name
-> Name
-> DS.DeclarationSet Field
-> CodeGen Code
compileUnionTag source parentname typename' fields = do
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'
Expand Down Expand Up @@ -367,8 +417,7 @@ compileUnionTag source parentname typename' fields = do
ret <- returnCompiler
return [qq|
class $className($parentClass):
# TODO: docstring

{compileDocstringWithFields " " d fields}
__slots__ = (
$slots
)
Expand Down Expand Up @@ -469,15 +518,23 @@ compileTypeExpression source modifier = do
compileTypeDeclaration :: Source -> TypeDeclaration -> CodeGen Code
compileTypeDeclaration _ TypeDeclaration { type' = PrimitiveType {} } =
return "" -- never used
compileTypeDeclaration src TypeDeclaration { typename = typename'
, type' = Alias ctype } = do
compileTypeDeclaration src d@TypeDeclaration { typename = typename'
, type' = Alias ctype
} = do
ctypeExpr <- compileTypeExpression src ctype
return [qq|
# TODO: docstring
$docsComment
{toClassName' typename'} = $ctypeExpr
|]
compileTypeDeclaration src TypeDeclaration { typename = typename'
, type' = UnboxedType itype } = do
where
docsComment :: Code
docsComment =
case compileDocs d of
Nothing -> ""
Just rst -> indent "#: " rst
compileTypeDeclaration src d@TypeDeclaration { typename = typename'
, type' = UnboxedType itype
} = do
let className = toClassName' typename'
itypeExpr <- compileTypeExpression src itype
insertThirdPartyImports [ ("nirum.validate", ["validate_boxed_type"])
Expand All @@ -489,8 +546,7 @@ compileTypeDeclaration src TypeDeclaration { typename = typename'
ret <- returnCompiler
return [qq|
class $className(object):
# TODO: docstring

{compileDocstring " " d}
__nirum_inner_type__ = $itypeExpr

def __init__(self, { arg "value" itypeExpr }){ ret "None" }:
Expand Down Expand Up @@ -525,22 +581,29 @@ class $className(object):
def __hash__(self){ ret "int" }:
return hash(self.value)
|]
compileTypeDeclaration _ TypeDeclaration { typename = typename'
, type' = EnumType members } = do
compileTypeDeclaration _ d@TypeDeclaration { typename = typename'
, type' = EnumType members
} = do
let className = toClassName' typename'
memberNames = T.intercalate
"\n "
[ [qq|{toAttributeName' memberName} = '{I.toSnakeCaseText bn}'|]
| EnumMember memberName@(Name _ bn) _ <- toList members
"\n"
[ T.concat [ compileDocsComment " " m
, "\n "
, toAttributeName' memberName
, " = '"
, I.toSnakeCaseText bn
, "'"
]
| m@(EnumMember memberName@(Name _ bn) _) <- toList members
]
insertEnumImport
arg <- parameterCompiler
ret <- returnCompiler
return [qq|
class $className(enum.Enum):
# TODO: docstring
{compileDocstring " " d}

$memberNames
$memberNames

def __nirum_serialize__(self){ ret "str" }:
return self.value
Expand All @@ -552,19 +615,21 @@ class $className(enum.Enum):
){ ret $ quote className }:
return cls(value.replace('-', '_')) # FIXME: validate input
|]
compileTypeDeclaration src TypeDeclaration { typename = typename'
, type' = RecordType fields } = do
typeExprCodes <- mapM (compileTypeExpression src)
[typeExpr | (Field _ typeExpr _) <- toList fields]
compileTypeDeclaration src d@TypeDeclaration { typename = typename'
, type' = RecordType fields
} = do
let className = toClassName' typename'
fieldNames = map toAttributeName' [ name'
| (Field name' _ _) <- toList fields
fieldList = toList fields
typeExprCodes <- mapM (compileTypeExpression src)
[typeExpr | (Field _ typeExpr _) <- fieldList]
let fieldNames = map toAttributeName' [ name'
| (Field name' _ _) <- fieldList
]
nameNTypes = zip fieldNames typeExprCodes
nameTypePairs = zip fieldNames typeExprCodes
slotTypes = toIndentedCodes
(\ (n, t) -> [qq|'{n}': {t}|]) nameNTypes ",\n "
(\ (n, t) -> [qq|'{n}': {t}|]) nameTypePairs ",\n "
slots = toIndentedCodes (\ n -> [qq|'{n}'|]) fieldNames ",\n "
initialArgs gen = toIndentedCodes (uncurry gen) nameNTypes ", "
initialArgs gen = toIndentedCodes (uncurry gen) nameTypePairs ", "
initialValues = toIndentedCodes
(\ n -> [qq|self.{n} = {n}|]) fieldNames "\n "
nameMaps = toIndentedCodes
Expand All @@ -584,8 +649,7 @@ compileTypeDeclaration src TypeDeclaration { typename = typename'
let clsType = arg "cls" "type"
return [qq|
class $className(object):
# TODO: docstring

{compileDocstringWithFields " " d fields}
__slots__ = (
$slots,
)
Expand Down Expand Up @@ -629,11 +693,12 @@ class $className(object):
def __hash__(self){ret "int"}:
return hash(($hashText,))
|]
compileTypeDeclaration src TypeDeclaration { typename = typename'
, type' = UnionType tags } = do
fieldCodes <- mapM (uncurry (compileUnionTag src typename')) tagNameNFields
compileTypeDeclaration src d@TypeDeclaration { typename = typename'
, type' = UnionType tags
} = do
tagCodes <- mapM (compileUnionTag src typename') $ toList tags
let className = toClassName' typename'
fieldCodes' = T.intercalate "\n\n" fieldCodes
tagCodes' = T.intercalate "\n\n" tagCodes
enumMembers = toIndentedCodes
(\ (t, b) -> [qq|$t = '{b}'|]) enumMembers' "\n "
importTypingForPython3
Expand All @@ -647,6 +712,7 @@ compileTypeDeclaration src TypeDeclaration { typename = typename'
arg <- parameterCompiler
return [qq|
class $className(object):
{compileDocstring " " d}

__nirum_union_behind_name__ = '{I.toSnakeCaseText $ N.behindName typename'}'
__nirum_field_names__ = name_dict_type([
Expand All @@ -673,13 +739,9 @@ class $className(object):
return deserialize_union_type(cls, value)


$fieldCodes'
$tagCodes'
|]
where
tagNameNFields :: [(Name, DS.DeclarationSet Field)]
tagNameNFields = [ (tagName, fields)
| (Tag tagName fields _) <- toList tags
]
enumMembers' :: [(T.Text, T.Text)]
enumMembers' = [ ( toAttributeName' tagName
, I.toSnakeCaseText $ N.behindName tagName
Expand All @@ -689,13 +751,13 @@ $fieldCodes'
nameMaps :: T.Text
nameMaps = toIndentedCodes
toNamePair
[name' | (name', _) <- tagNameNFields]
[name' | Tag name' _ _ <- toList tags]
",\n "
compileTypeDeclaration
src@Source { sourcePackage = Package { metadata = metadata' } }
ServiceDeclaration { serviceName = name'
, service = Service methods
} = do
d@ServiceDeclaration { serviceName = name'
, service = Service methods
} = do
let methods' = toList methods
methodMetadata <- mapM compileMethodMetadata methods'
let methodMetadata' = commaNl methodMetadata
Expand All @@ -713,7 +775,7 @@ compileTypeDeclaration
]
return [qq|
class $className(service_type):

{compileDocstring " " d}
__nirum_schema_version__ = \'{SV.toText $ version metadata'}\'
__nirum_service_methods__ = \{
{methodMetadata'}
Expand All @@ -737,13 +799,21 @@ class {className}_Client(client_type, $className):
commaNl :: [T.Text] -> T.Text
commaNl = T.intercalate ",\n"
compileMethod :: Method -> CodeGen Code
compileMethod (Method mName params rtype _etype _anno) = do
compileMethod m@(Method mName params rtype _etype _anno) = do
let mName' = toAttributeName' mName
params' <- mapM compileMethodParameter $ toList params
let paramDocs = [ T.concat [ ":param "
, toAttributeName' pName
, maybe "" (T.append ": ") $ compileDocs p
-- TODO: types
]
| p@(Parameter pName _ _) <- toList params
]
rtypeExpr <- compileTypeExpression src rtype
ret <- returnCompiler
return [qq|
def {mName'}(self, {commaNl params'}){ ret rtypeExpr }:
{compileDocstring' " " m paramDocs}
raise NotImplementedError('$className has to implement {mName'}()')
|]
compileMethodParameter :: Parameter -> CodeGen Code
Expand Down Expand Up @@ -814,11 +884,7 @@ compileModuleBody :: Source -> CodeGen Code
compileModuleBody src@Source { sourceModule = boundModule } = do
let types' = types boundModule
typeCodes <- mapM (compileTypeDeclaration src) $ toList types'
let moduleCode = T.intercalate "\n\n" typeCodes
return [qq|
# TODO: docs
$moduleCode
|]
return $ T.intercalate "\n\n" typeCodes

data InstallRequires =
InstallRequires { dependencies :: S.Set T.Text
Expand Down Expand Up @@ -857,6 +923,7 @@ compileModule pythonVersion' source =
(Left errMsg, _) -> Left errMsg
(Right code, context) -> codeWithDeps context $
[qq|# -*- coding: utf-8 -*-
{compileDocstring "" $ sourceModule source}
{imports $ standardImports context}

{fromImports $ localImports context}
Expand Down
32 changes: 25 additions & 7 deletions test/nirum_fixture/fixture/foo.nrm
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ import fixture.foo.bar (path-unbox, point, int-unbox);
import fixture.qux (path, name);

unboxed float-unbox (float64);
# Unboxed type docs.

unboxed imported-type-unbox (path-unbox);
unboxed way (path);

type irum = name;
# Type alias docs.

enum gender = female/yeoseong
| male
;
enum gender
# Enum docs.
= female/yeoseong
| male
;
enum eva-char = soryu-asuka-langley
| ayanami-rei
| ikari-shinji
Expand All @@ -18,11 +23,14 @@ enum eva-char = soryu-asuka-langley
;

record point1 (
# Record docs.
bigint left/x,
# Record field docs.
bigint top,
);
record point2 (
int-unbox left,
# Record field docs.
int-unbox top,
);
record point3d (
Expand All @@ -47,15 +55,25 @@ union mixed-name = western-name ( text first-name
)
| culture-agnostic-name (text fullname)
;
union music = pop (text country)
| rnb/rhythm-and-ballad (text country)
;
union music
# Union docs.
= pop ( text country
# Tag field docs.
)
# Tag docs.
| rnb/rhythm-and-ballad (text country)
;
union status = run
| stop
;

service null-service ();

service ping-service (
bool ping (text nonce),
# Service docs.
bool ping (
# Method docs.
text nonce,
# Parameter docs.
),
);
Loading

0 comments on commit 1071db3

Please sign in to comment.