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

Documentation: Formalized Comments, Multiline Comments and Documentation generation #1176

Closed
Brochato opened this issue Oct 31, 2018 · 1 comment

Comments

@Brochato
Copy link
Contributor

Brochato commented Oct 31, 2018

Commentaires Formalisées et Documentation

Principe de base

Les commentaires formalisés ont pour but de permettre au développeur de renseigner des métas donnés concernant les Nodes suivants :

  • Fonctions/Procédures
  • Types
  • Programmes
    On récupère ces informations pour générer une documentation en y ajoutant des informations des Nodes eux même comme le nom, le Namespace ou encore la visibilité.

Scanner

Les TokenTypes et leur TokenFamilly sont présents dans TypeCobol/Compiler/Scanner/TokenType.cs.

Tout d’abord les balises ouvrantes et fermantes (des commentaires formalisés et multilignes) ne sont pas prises comme des commentaires standards pour qu’ils passent dans le scanner.

image

Les balises ouvrantes sont traitées dans le switch principale :

image
image
image

Le premier caractère visible par le scanner est le ‘<’ car le ‘*’ se trouve en colonne 7 et le scanner regarde a partir de la colonne 8. On teste quand même la présence du ‘*’ à la position n-1.

On détecte aussi les balises ouvrantes hors colonne 7 pour mettre un warning et quand même créer les Tokens pour ne pas gêner le parsing.

image
image
image

Quand on ajoute un Token de balise ouvrante ou fermante, on modifie le ScanState (MultilineScanState.cs) pour indiquer que l’on se trouve dans le contexte d’un commentaire formalisée.

image
image
image

Pour ne pas générer des Token propres à TypeCobol qui n’ont pas de sens dans le contexte des commentaires formalisées ou des commentaires multilignes, le reste du scan se fait dans un switch séparé du switch principal.

image
image
image

Ainsi, comme le scanner ne passe que dans le switch dédié aux commentaires formalisés si une balise ouvrante est trouvée, la détection de la balise fermante ne se fait que dans ce switch.

Les mots clés sont reconnus si le dernier token significatif est un ‘@’ via ScanCharacterString(startIndex) qui évalue le mot démarrant à startIndex. Si nous sommes dans le contexte d’un commentaire formalisé, on évalue le mot pour savoir à quel mot clé il correspond.

image
image
TokenType.UserDefinedWord est utilisé pour une clé de Params.

Pour autoriser les mots clés dans le nommage des paramètres, on vérifie que si on trouve un mot clé, que nous sommes juste après un ‘-‘ et que le précédent mot clé trouvé est le mot clé ‘@parameters’. Si c’est le cas, on corrige le Type de Token en TokenType.UserDefinedWord.

image

Enfin, si ce n’est ni un mot clé, ni une clé de ‘@parameters’, c’est une valeur. Les valeurs ne pouvant pas précéder d’autres Token sur une ligne, on regroupe tout le reste de la ligne dans un même Token TokenType.FormComsValue grâce à ScanUntilDelimiter(startIndex, tokenType, delimiter)

image

Cette méthode scanne le reste de la ligne et renvoie un Token du type spécifié allant jusqu’à la fin de la ligne ou jusqu’au délimiteur spécifié.
Le délimiteur ici est "*>>>", malgré le fait que ce soit pour l’instant impossible de le rencontrer, comme la balise fermante ne peux se trouver qu’en colonne 7. Cela permettra dans le futur d’avoir une syntaxe comme celle-ci :

*<<< Ici mon commentaire formalisé sur une seule ligne *>>>

Grammaire

La grammaire des commentaires formalisée se décompose comme suit :

image

Elle est composé de : une balise ouvrante, n lignes et une balise fermante.
Il existe trois types de lignes :

  • Les lignes de niveau « extérieur » qui commencent par ‘’@’’ sont composé d’un mot clé + ‘’ : ’’ (optionnel, le ‘’ : ‘’ n’a qu’un but de clarté du code) et une valeur (optionnel)
  • Les lignes de niveau « intérieur » servant à lister des valeurs se composent de ‘’-‘’ + une valeur dans le cas d’un listing simple (pour Todo et Needs) ou de ‘’-‘’ + clé (UserDefinedWord) + ‘’ : ’’ (optionnel, toujours un but de clarté) et une valeur. Un listItem de type clé-valeur ne s’applique que à ‘@params’.
  • Les lignes simplement composé d’une valeur qui sera rattaché à l’instruction précédente.
    Cette grammaire s’intègre au début de celle d’une Fonction, d’une définition de type et d’une Procedure Division ainsi les tokens du commentaire formalisé sont inclue dans le CodeElements suivant et il n’y a donc pas de CodeElements dédiés aux commentaires formalisés

image
image
image

image

CodeElement

Commentaires formalisées

On implémente l’interface IFormalizedCommentable aux CodeElements applicables. Cette interface force ces CodeElements à avoir une propriété FormalizedCommentDocumentation de type FormalizedCommentDocumentation qui contiendra les données du commentaire formalisé.

image
image
image
image

image

La propriété privée _lastParameterRegistered enregistre la dernière clé ajouté à la propriété Parameters pour y rattacher les valeurs seules sur leur ligne (le cas d’une continuation de ligne). En effet, comme c’est un Dictionnaire, la propriété n’est pas ordonnée et n’a donc pas de « dernier » élément comme les listes Needs et ToDo
Les autres Propriétés sont les données renseignables dans un commentaire formalisé.

Il y a deux méthodes Add qui servent à ajouter une valeur à la propriété associée à un mot clé.

void Add(Fields parameter, IToken symbol, bool isContinuation)

Pour les valeurs simples (tout sauf @params). isContinuation sert, dans le cas des listes, à spécifier si c’est la continuité d’une valeur ou une nouvelle valeur, donc de savoir s’il faut créer un nouvelle élément ou concaténer la valeur au dernier élément.

void Add(Fields parameter, string key, string value)

Pour les couples clé-valeur (@params)

Le constructeur s’occupe de décortiquer le contexte retourné par Antlr et ajouter les bonnes valeurs aux bonnes propriétés.

Ces Propriétés sont créées au CodeElementBuilders directement après le parsing d’Antlr.

Commentaires multilignes

Il faut injecter les tokens des commentaires multilignes dans les CodeElements correspondant à leurs lignes pour pouvoir les repérer et les commenter dans la phase de génération de Code.
Ceci est effectué dans CodeElementsParserStep :

image

CrossCheck

Fonctions

Pour les fonctions, on vérifie si il y a des paramètres dans ‘@params’ qui ne correspondent pas à des paramètres présents dans la signature de la Fonction, pour y placer un Warning.

image

De la même manière, on vérifie si des paramètres présents dans la signature de la Fonction ne le sont pas dans la section ‘@params’ des commentaires formalisés pour y placer un Warning.

image

On vérifie enfin s’il y a un mot clé qui est déclaré deux fois pour y placer un Warning. Cette action est factorisée dans une méthode statique car elle est utilisée pour chaque structure acceptant un commentaire formalisé.

image
image

Type Définition

Pour les types on vérifie si il y le champ ‘@params’ déclaré pour y placer un Warning.

image

Enfin on vérifie s’il y a un mot clé qui est déclaré deux fois pour y placer un Warning.

image

Programmes

Pour les fonctions, on vérifie si il y a des paramètres dans‘@params’ qui ne correspondent pas à des paramètres présents dans la section using de la Procedure Division pour y placer un Warning.

image

De la même manière, on vérifie si des paramètres présents dans la section using de la Procedure Division ne le sont pas dans la section ‘@params’ des commentaires formalisées pour y placer un Warning.

image

On vérifie enfin s’il y a un mot clé qui est déclaré deux fois pour y placer un Warning.

image

Procédure Division

On vérifie si la Procédure Division à laquelle est rattachée le commentaire formalisé est bien la Procédure Division d’un Programme et pas d’une déclaration de fonction.

image

Génération de la documentation

La Génération de la documentation se fait en mettant l’argument du CLI OutputFormat à Documentation (-f 4 OU -f Documentation). Le générateur s’occupant de la documentation utilise un Visiteur dédié qui crée un DTO pour chaque TypeDefinition, FunctionDeclaration et Program et les stocke dans une liste de documentation.

image

Les DTOs :

image

A noter que les DTOs possèdent tous un constructeur par défaut vide qui est nécessaire à la sérialisation.

Documentation

image

Documentation est la classe de base qui implémente les propriétés et la logique commune aux trois types de commentaires formalisés. Elle contient tous les champs des commentaires formalisés sauf ‘@params’ qui ne sont utilisés que pour les fonctions et les procédures. Elle contient aussi des informations qui ne se trouvent pas dans les commentaires formalisés comme le nom, la visibilité et le namespace.
Elle initialise ses propriétés avec son constructeur :
A noter que Deprecated ne s’initialise pas comme les autres propriétés car il peut représenter un booléen à true si il contient un string vide, il ne faut donc pas utiliser IsNullOrEmpty().

Documentation possède aussi la méthode CreateAppropriateDocumentation() qui permet de créer un documentation sans savoir si le node est de type Program, FunctionDeclaration ou TypeDefinition. C’est la méthode qui est toujours utilisée pour créer une documentation.

image

C’est aussi Documentation qui implémente les méthodes de sérialisation. Pour l’instant en XML ou JSON, mais si un autre format est nécessaire, il suffit d'implémenter la nouvelle méthode à Documentation.

image

DocumentationDataType

image

DocumentationDataType définie les données communes aux variables présentes dans la documentation comme le type lui-même, ses éventuelles enfants, les paramètres de fonctions et les paramètres de programmes.

Elle est composée de deux constructeurs :
DocumentationDataType(DataDefinition dataDef)
Qui prend en paramètre un DataDefinition, un node qui définit une donnée mais n’est pas serialisable directement. Ce constructeur en extrait la majeure partie de ses données directement et récupère la valeur par défaut de la variable avec son codeElement.

image

Le second constructeur :

DocumentationDataType(DataUsage? usage = null, long maxOccurence = 1,
string defaultValue = null, string typeName = null, string picture = null)

N’est jamais utilisé pour l’instant mais peut être utile si on doit créer un dataType sans avoir de DataDefinition.

image

DocumentationTypeChildren

image

DocumentationTypeChildren regroupe les données des enfants du type si ce dernier est un groupe. En plus du DocumentationDataType, il comporte des propriétés que l’on ne peut retrouver que chez les enfants d’un Type tel que les conditions des niveaux 88, IsBlankWhenZero, Justified etc.
IsSubGroup indique que cet enfant a lui-même des enfants.

Il est composé d’un constructeur prenant en paramètre un DataDefinition qui permet d’instancier toute les propriétés de l’objet.

image

DocumentationForType

image

Représente la documentation propre aux définitions de types. En plus des propriétés héritées de Documentation, elle contient les propriétés définissant son type de donnée DocDataType de type DocumentationDataType, IsBlankWhenZero et Justified qui sont des options utilisables pour un Type et ses enfants mais pas pour des paramètres de fonction/programmes, donc ne figurent pas dans DocumentationDataType. Elle comprend aussi la liste d’enfants du type pour y enregistrer leur DocumentationTypeChildren. À l’instar des Nodes, la liste de childrens ne possède que la DocumentationTypeChildren des enfants directs du type, les sous enfants se trouvent dans la liste de childrens de leurs parents directs.

Le constructeur de DocumentationForType prend le node documentable TypeDefinition pour en tirer, avec son codeElement, un maximum d’informations.

image

DocumentationParameter

image

DocumentationParameter regroupe les données des paramètres des fonctions et des programmes. En plus du DocumentationDataType, il comporte des propriétés que l’on ne peut retrouver que chez les paramètres tel que les informations renseignées dans les commentaires formalisés (@params), le passing type (input, output, inout) et son nom.

Il est composé de deux constructeurs :

public DocumentationParameter(ParameterDescription parameter, string info)

Le premier constructeur prend en paramètre un ParameterDescription contenant les informations liées au dataType et un string qui contient la description du commentaire formalisé si elle existe. Le passing type est contenu dans le ParameterDescription. Ce constructeur est utilisé par les Fonctions.

image

public DocumentationParameter(DataDefinition dataDef, string info, bool determinePassing = true)

Le second constructeur prend en paramètre une DataDefinition contenant les informations liées au dataType, un string qui contient la description du commentaire formalisé si elle existe et un booléen qui indique si on doit déterminer le PassingTypes avec la description du commentaire formalisé ou non. Ce constructeur est utilisé par les Programmes car DataDefinition ne supporte pas le PassingTypes. Il faut donc le déterminer si possible.

image

La reconnaissance du PassingTypes se fait par la méthode suivante

Tuple<PassingTypes, string> DetermineType(string info)

Elle prend en paramètre la description du commentaire formalisé et en extrait le premier mot. Si ce dernier est ‘INPUT’, ‘OUTPUT’, ‘INOUT’ (case insensitive) alors on enregistre le passing type adéquat et on tronque l’info jusqu’au premier caractère alphabétique et on renvoie un Tuple<PassingTypes, string> du passing type trouvé et de la description tronquée. Ainsi, une description du type :
‘Input – Description de mon paramètre’
Donne le Tuple {PassingTypes.Input, ‘Description de mon paramètre’}
Et pas le Tuple {PassingTypes.Input, ‘ - Description de mon paramètre’}

image

DocumentationForFunction

image

En plus des propriétés héritées de Documentation, DocumentationForFunction n’a qu’une liste de DocumentationParameter.

Elle possède un seul constructeur qui construit la liste des paramètres :

image

DocumentationForProgram

image

Comme DocumentationForFunction, DocumentationForProgram n’ajoute qu’une liste de DocumentationParameter aux propriétés héritées de Documentation.

Elle possède un seul constructeur qui construit la liste des paramètres à partir des paramètres spécifiés dans la clause USING de la Procédure Division. À la différence de DocumentationForFunction, le passing type est déterminé à partir du commentaire formalisé et non pas dans la définition du paramètre.

image

DocumentationGenerator & DocumentationBuilder

image
image

Le passage du paramètre CLI OutputFormat à 4 (Documentation) permet d’utiliser le générateur dédié à la documentation DocumentationGenerator au lieu du générateur par défaut.
Il utilise un visiteur, DocumentationBuilder, qui contient une liste de DTOs. Il parcoure les Nodes en créant le DTO approprié selon le node trouvé (Program – Function – TypeDefinition), si ces derniers sont Publics.
Enfin, DocumentationGenerator récupère cette liste et, pour chacun d’eux, les sérialise et les concatène dans this.Destination.

La fonction Generate de DocumentationGenerator :

image

Le Visiteur DocumentationBuilder :

image

CodeGen

Commentaires formalisés

La génération du code est assez simple en théorie puisque nous n’avons qu’à commenter les lignes du commentaire formalisé. En pratique, il n’est pas possible de procéder comme pour les autre Nodes que nous devons commenter, car les commentaires formalisées ne constituent pas de Nodes propres. Ils font parties de codeElements existants et nous ne pouvons que commenter l’intégralité d’un Node.
Les Nodes de Function et de TypeDefinition sont déjà commentées dans le CodeGen. Il ne reste donc que les Nodes de Procedure Division directement enfants des Programs.

Ce Node se génère de deux manières, s’il contient une fonction déclaré en public ou non:

Avec une fonction public, C’est la propriété IEnumerable<TypeCobol.Compiler.Text.ITextLine> Lines du Node ProcedureDivision qui génère le code via son getter.
On parcoure les ConsumedTokens du Node : s’ils sont compris dans un commentaire formalisé, on commente la TokensLine (les autres ConsumedTokens de la TokensLine sont ignorés).

image
image
image

Sans fonction public. C’est la méthode CommentSpecificParts dans LinearNodeSourceCodeMapper qui commente ces lignes.

image

Cette méthode est appelée pour chaque Node et s’occupe de commenter les commentaires formalisés des ProcedureDivision appartenant au Program et les Commentaires Multilignes pouvant apparaitre n’importe où.
Dans les deux cas, la méthode

void CommentBetweenTokens(Node node, TokenType startTokenType, TokenType stopTokenType)

est appelée. Cette méthode commente toutes les lignes se trouvant entre les deux tokens passés en paramètre en injectant un ‘*’ dans leur buffer.

image

Un problème se pose cependant, Razor utilise le caractère ‘@’ comme mot clé. Cela pose donc problème pour les champs des commentaires formalisés. Il faut donc les échapper dans RazorEngine :

image

Enfin il ne reste que le cas de la génération des signatures à commenter dans SignaturesGenerator.
Pour se faire, à la création de chaque ligne à inscrire dans le fichier de sortie, si elle est dans un commentaire formalisé (entre les deux balises), on ajoute la ligne commentée. Sinon, on ajoute la ligne sans y toucher.

image

Commentaires Multilignes

Pour les commentaires multilignes, on distingue deux cas de figures :

  • Les commentaires multilignes appartenant à un Node (avec le commentaire entre deux lignes d’un même node par exemple)
  • Les commentaires multilignes libre donc sans Nodes (comme les commentaires classiques)

Les premiers sont commentés dans LinearNodeSourceCodeMapper avec la méthode CommentSpecificParts. Comme pour les commentaires formalisés, on parcoure chaque Nodes et on commente tout ce qui se trouve entre les balises.

image
image

Le second cas est géré dans DefaultGenerator. Pour chaque ligne n’appartenant à aucun Node, si elles sont entre les balises d’un commentaire multilignes, on les commente.

image

Ajout d’éléments

Nouveau mot clé

Pour ajouter un nouveau mot clé au commentaire formalisé, il faut suivre les étapes suivantes :

  • L’ajouter au Tokens et à la TokenFamily.
  • Dans TokenUtils, modifier la méthode
    TokenType GetFormalComTokenTypeFromTokenString(string tokenString)
    pour que le scanner reconnaisse le nouveau mot clé.
  • Inclure le nouveau dans CobolWords.g4 et dans la grammaire dans la liste des formalizedCommentParam.
  • Ajouter sa propriété à FormalizedCommentDocumentation, choisir le type selon les valeurs qu’on peut y mettre :
    • string pour une valeur simple et un flag
    • List pour une série de valeurs
    • Dictionary<string, string> pour une série de couples clé – valeurs
  • Extraire sa valeur dans le constructeur de FormalizedCommentDocumentation et l’ajouter à la propriété créée précédemment
  • Ajouter sa propriété à Documentation, DocumentationForType, DocumentationForFunction ou DocumentationForProgram selon ce à quoi s’applique le mot clé et y assigner la valeur extraite et transformée si besoin du commentaire formalisé.

Nouveau Node commentable

Pour ajouter un nouveau Node pouvant contenir un commentaire formalisé, il faut suivre les étapes suivantes :

  • Créer le Node désiré implémentant l’interface IDocumentable.
  • Créer un nouveau CodeElement implémentant IFormalizedCommentable ou si c’est un CodeElement existant qui sera utilisé, lui faire implémenter IFormalizedCommentable.
  • Modifier ou créer la grammaire de ce CodeElement en y ajoutant formalisedComment à l’emplacement souhaité.
  • Dans CodeElementBuilder, modifier la méthode liée à ce Node pour y mettre l’instanciation de FormalizedCommentDocumentation si le contexte Antlr contiens formalizedComment(), donc si Antlr a repéré un commentaire formalisé pour ce Node. Il faut passer au constructeur de FormalizedCommentDocumentation les formalizedCommentLine du contexte Antlr.
  • Vérifier si besoin dans CrossCheck que les champs du commentaire formalisé sont correctement remplis.
  • Il faut ensuite créer son DTO dans Documentation.cs héritant de la classe Documentation. Si la documentation du Node doit avoir des propriétés autre que celles communes dans documentation, alors il faut les ajouter à ce DTO et les instancier dans son constructeur.
  • Enfin, pour que la documentation se fasse correctement, dans DocumentationGenerator, au visiteur DocumentationBuilder, il faut ajouter une méthode Visit prenant en paramètre ce Node, y construire son DTO et l’ajouter à la liste de DTOs du visiteur.

En cas de modification des commentaires formalisés il est bon de créer des tests unitaires ou de mettre à jour les tests unitaires dédiés.

  • Test du processus complet :

    • CLI/test/ressources/documentation/CLIArguments.txt

    • CLI/test/ressources/documentation/input/DocGen.tcbl

    • CLI/test/ressources/documentation/output_expected/DocGen.json

    • CLI/test/ressources/documentation/output_expected/error.txt

  • Test du CodeGen :

    • Codegen/test/resources/input/TypeCobol/FormalizedComments.tcbl

    • Codegen/test/resources/input/TypeCobol/MultilinesComments.tcbl

    • Codegen/test/resources/output/TypeCobol/FormalizedComments.tcbl

    • Codegen/test/resources/output/TypeCobol/MultilinesComments.tcbl

  • Test de sérialisation et CodeElement :

    • TypeCobol.Test/Parser/Documentation/DocumentationCombined.rdz.json

    • TypeCobol.Test/Parser/Documentation/DocumentationCombined.rdz.tcbl

    • TypeCobol.Test/Parser/Documentation/DocumentationCombined.rdzDoc.txt

    • TypeCobol.Test/Parser/Documentation/DocumentationFunctions.rdz.json

    • TypeCobol.Test/Parser/Documentation/DocumentationFunctions.rdz.tcbl

    • TypeCobol.Test/Parser/Documentation/DocumentationFunctions.rdzDoc.txt

    • TypeCobol.Test/Parser/Documentation/DocumentationPrograms.rdz.json

    • TypeCobol.Test/Parser/Documentation/DocumentationPrograms.rdz.tcbl

    • TypeCobol.Test/Parser/Documentation/DocumentationPrograms.rdzDoc.txt

    • TypeCobol.Test/Parser/Documentation/DocumentationTypeDefs.rdz.json

    • TypeCobol.Test/Parser/Documentation/DocumentationTypeDefs.rdz.tcbl

    • TypeCobol.Test/Parser/Documentation/DocumentationTypeDefs.rdzDoc.txt

@smedilol smedilol added this to the v1.3 Codegen refacto milestone Nov 5, 2018
@maxime645 maxime645 removed their assignment Nov 26, 2018
@smedilol
Copy link
Contributor

Doc referenced in the wiki under How to extend the grammar and semantic check (full example)

@maxime645 maxime645 mentioned this issue May 10, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants