Para que soluções com diferenças não essenciais tenham a mesma representação, o Representer deve aplicar normalizações. Em geral, o processo de criar uma representação normalizada é assim:
representation.txt (veja a interface)Note, porém, que a representação não precisa ser uma AST: ela pode ser código comum (normalizado), o que funcionar melhor para a sua trilha.
Para ajudar você a começar, vamos apresentar agora algumas estratégias de normalização comuns.
Observação 1: os exemplos de normalização não se acumulam; cada um mostra apenas uma normalização específica. Um Representer de verdade aplicaria todas em sequência.
Observação 2: o código destas diretrizes estará em C#, mas as diretrizes não dependem da linguagem.
Para que as representações não dependam dos nomes usados, nomes definidos por quem escreve o código (como variáveis, funções etc.) podem ser substituídos por placeholders. Nesse caso, você deve gerar um mapping.json (veja a interface).
É importante notar que todos os nomes idênticos devem ser substituídos pelo mesmo placeholder, independentemente do escopo.
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;
}
}
Espaços em branco inconsistentes são tão frequentes que normalizá-los é uma etapa de normalização comum. Os finais de linha também devem ser normalizados.
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));
}
}
Em muitas linguagens, quem escreve o código tem certa liberdade para definir um bloco (ou escopo). Por exemplo, na maioria das linguagens parecidas com C, o escopo é declarado entre chaves. Normalmente não faz diferença colocar as chaves na mesma linha ou na linha seguinte, então dá para normalizar isso.
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;
}
}
Nem todo trecho de código é significativo para o Representer, e esses trechos podem ser removidos. Por exemplo, na maioria das linguagens os comentários não são significativos e podem ser removidos sem problema.
/*
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";
}
}
Em alguns casos, a ordem do código não importa. Para evitar que o mesmo código em ordens diferentes crie representações diferentes, pode ser útil ordená-lo. Normalmente os itens a ordenar são funções ou declarações. Pode ser complicado encontrar a(s) métrica(s) de ordenação e também complicado implementar. Uma métrica poderia ser quantos nós filhos da AST um nó contém; outra, o nome do tipo do primeiro nó filho.
Este exemplo ordena por algum tipo de tamanho de função:
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;
}
}