Pour que des solutions qui ne diffèrent que par des détails non essentiels aient la même représentation, le representer doit appliquer des normalisations. De manière générale, le processus de création d'une représentation normalisée se déroule comme ceci :
representation.txt (voir l'interface)Note cependant que la représentation ne doit pas nécessairement être un AST : elle peut tout aussi bien être du code (normalisé) classique, selon ce qui marche le mieux pour ton parcours.
Pour t'aider à démarrer, on va maintenant présenter quelques stratégies de normalisation courantes.
Remarque 1 : les exemples de normalisation ne s'enchaînent pas, ils illustrent chacun une normalisation précise. Un vrai representer aura tout intérêt à les appliquer successivement.
Remarque 2 : le code de ces recommandations sera en C#, mais les recommandations sont indépendantes du langage.
Pour que les représentations soient indépendantes des noms, les noms définis par l'utilisateur (variables, fonctions, etc.) peuvent être remplacés par des espaces réservés. Dans ce cas, il faut produire un mapping.json (voir l'interface).
Il est important de noter que tous les noms identiques doivent être remplacés par le même espace réservé, quelle que soit la portée.
public static class Fake
{
public static int Test(int input)
{
var test = input + 2;
return test;
}
}
public static class PLACEHOLDER_1
{
public static int PLACEHOLDER_2(int PLACEHOLDER_3)
{
var PLACEHOLDER_4 = PLACEHOLDER_3 + 2;
return PLACEHOLDER_4;
}
}
Les espaces incohérents sont si fréquents que les normaliser est une étape de normalisation courante. Les fins de ligne doivent elles aussi être normalisées.
using System;
public static class Fake
{
public static DateTime Add (DateTime birthDate)
{
return birthDate.Add( TimeSpan.FromSeconds ( 10 ) ) ;
}
}
public static class Fake
{
public static DateTime Add(DateTime birthDate)
{
return birthDate.Add(TimeSpan.FromSeconds(10));
}
}
Dans beaucoup de langages, l'utilisateur dispose d'une certaine liberté pour définir un bloc (ou une portée). Par exemple, dans la plupart des langages de type C, la portée se déclare entre accolades. En général, peu importe qu'on les place sur la même ligne ou sur la suivante, ce qui permet de normaliser ce point.
public static class Fake {
public static int Test() {
if (1 > 2) {
return 0;
}
return 1;
}
}
public static class Fake
{
public static int Test()
{
if (1 > 2)
{
return 0;
}
return 1;
}
}
Tous les morceaux de code n'ont pas d'importance pour le representer, et peuvent donc être supprimés. Par exemple, dans la plupart des langages, les commentaires sont non significatifs et peuvent être supprimés sans risque.
/*
These are some very nice
comments spanning multiple lines
*/
public static class Fake
{
// Nice method
public static string Test()
{
return "Test"; // This is very nice
}
}
public static class Fake
{
public static string Test()
{
return "Test";
}
}
Dans certains cas, l'ordre du code n'a pas d'importance. Pour éviter que le même code dans un ordre différent produise des représentations différentes, il peut être utile de le trier. En général, les éléments à trier sont des fonctions ou des déclarations. Trouver le ou les critères de tri peut être délicat, tout comme leur implémentation. Un critère pourrait être le nombre de nœuds enfants que contient un nœud de l'AST, un autre le nom du type du premier nœud enfant.
Cet exemple trie selon une sorte de longueur de fonction :
public static class Fake
{
public static string Test2()
{
int a = "Test2";
return a;
}
public static string Test()
{
return "Test";
}
}
public static class Fake
{
public static string Test()
{
return "Test";
}
public static string Test2()
{
int a = "Test2";
return a;
}
}