martes, 11 de abril de 2023

CSharpSyntaxWalker

En el post anterior, exploramos diferentes enfoques para separar partes del árbol de sintaxis. Este enfoque funciona bien cuando solo está interesado en partes específicas de la sintaxis (métodos, clases, declaración de lanzamiento, etc.). Es excelente para identificar ciertas partes del árbol de sintaxis para una mayor investigación.

Sin embargo, a veces nos gustaría operar en todos los nodos y tokens dentro de un árbol. Alternativamente, el orden en que visita estos nodos puede ser importante. Quizás esté intentando convertir C# en VB.Net. O tal vez le gustaría analizar un archivo C# y generar un archivo HTML estático con la coloración correcta. Ambos programas requerirían que visitáramos todos los nodos y tokens dentro de un árbol de sintaxis en el orden correcto.

La clase abstracta CSharpSyntaxWalker nos permite construir nuestro propio objeto que recorre la sintaxis y que puede visitar todos los nodos, tokens y trivia. Simplemente podemos heredar de CSharpSyntaxWalker y sobreescribir el método Visit() para visitar todos los nodos dentro del árbol.


public class CustomWalker : CSharpSyntaxWalker

{

    static int Tabs = 0;

    public override void Visit(SyntaxNode node)

    {

        Tabs++;

        var indents = new String('\t', Tabs);

        Console.WriteLine(indents + node.Kind());

        base.Visit(node);

        Tabs—;

    }

}


static void Main(string[] args)

{

    var tree = CSharpSyntaxTree.ParseText(@"

        public class MyClass

        {

            public void MyMethod()

            {

            }

            public void MyMethod(int n)

            {

            }

       ");

    

    var walker = new CustomWalker();

    walker.Visit(tree.GetRoot());

}


Este breve ejemplo contiene una implementación de CSharpSyntaxWalker llamada CustomWalker. CustomWalker sobreescribe el método Visit() e imprime el tipo de nodo que se está visitando actualmente. Es importante tener en cuenta que CustomWalker.Visit() también llama al método base.Visit(SyntaxNode). Esto permite que CSharpSyntaxWalker visite todos los nodos secundarios del nodo actual.


La salida para este programa:


Podemos ver claramente los distintos nodos del árbol de sintaxis y su relación entre sí. Hay dos MethodDeclarations hermanos que comparten la misma ClassDeclaration principal.

Este ejemplo anterior solo visita los nodos de un árbol de sintaxis, pero también podemos modificar CustomWalker para visitar tokens y trivias. La clase abstracta CSharpSyntaxWalker tiene un constructor que nos permite especificar la profundidad con la que queremos visitar.

Podemos modificar el ejemplo anterior para imprimir los nodos y sus tokens correspondientes en cada profundidad del árbol de sintaxis.


public class DeeperWalker : CSharpSyntaxWalker

{

    static int Tabs = 0;

    //NOTE: Make sure you invoke the base constructor with 

    //the correct SyntaxWalkerDepth. Otherwise VisitToken() will never get run.

    public DeeperWalker() : base(SyntaxWalkerDepth.Token)

    {

    }

    public override void Visit(SyntaxNode node)

    {

        Tabs++;

        var indents = new String('\t', Tabs);

        Console.WriteLine(indents + node.Kind());

        base.Visit(node);

        Tabs—;

    }


    public override void VisitToken(SyntaxToken token)

    {

        var indents = new String('\t', Tabs);

        Console.WriteLine(indents + token);

        base.VisitToken(token);

    }

}


Es importante pasar el argumento SyntaxWalkerDepth adecuado a CSharpSyntaxWalker. De lo contrario, nunca se llama al método VisitToken().


El resultado cuando usamos este CSharpSyntaxWalker:



La muestra anterior y esta comparten el mismo árbol de sintaxis. La salida contiene los mismos nodos de sintaxis, pero agregamos los tokens de sintaxis correspondientes para cada nodo.

En los ejemplos anteriores, visitamos todos los nodos y todos los tokens dentro de un árbol de sintaxis. Sin embargo, a veces solo nos gustaría visitar ciertos nodos, pero en el orden predefinido que proporciona CSharpSyntaxWalker. Afortunadamente, la API nos permite filtrar los nodos que nos gustaría visitar según su sintaxis.

En lugar de visitar todos los nodos como hicimos en ejemplos anteriores, lo siguiente solo visita los nodos ClassDeclarationSyntax y MethodDeclarationSyntax. Es extremadamente simple, simplemente imprime la concatenación del nombre de la clase con el nombre del método.


public class ClassMethodWalker : CSharpSyntaxWalker

{

    string className = String.Empty;

    public override void VisitClassDeclaration(ClassDeclarationSyntax node)

    {

        className = node.Identifier.ToString();

        base.VisitClassDeclaration(node);

    }


    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)

    {

        string methodName = node.Identifier.ToString();

        Console.WriteLine(className + '.' + methodName);

        base.VisitMethodDeclaration(node);

    }

}


static void Main(string[] args)

{

    var tree = CSharpSyntaxTree.ParseText(@"

    public class MyClass

    {

        public void MyMethod()

        {

        }

    }

    public class MyOtherClass

    {

        public void MyMethod(int n)

        {

        }

    }

   ");


    var walker = new ClassMethodWalker();

    walker.Visit(tree.GetRoot());

}


Esta muestra simplemente genera:

MiClase.MiMétodo

MiOtraClase.MiMétodo


CSharpSyntaxWalker actúa como una gran API para analizar árboles de sintaxis. Permite lograr mucho sin recurrir al modelo semántico y forzar una compilación (posiblemente) costosa. Siempre que sea importante inspeccionar los árboles de sintaxis y el orden, CSharpSyntaxWalker suele ser lo que está buscando.


No hay comentarios.:

Publicar un comentario