martes, 4 de abril de 2023

Roslyn, Compiler-as-a-Service parte 2

Los principales tipos de Syntax Nodes se derivan de la clase base SyntaxNode. El conjunto de Syntax Nodes no es extensible. Estas clases forman la construcción para crear declaraciones, sentencias, cláusulas y expresiones. No son terminales, lo que significa que tienen subnodos por los que se puede navegar a través de propiedades o métodos escritos.


/* tree is given after parsing the Hello World example with the

Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText method */

var syntaxRoot = tree.GetRoot();

var Demo = syntaxRoot.DescendantNodes().OfType<ClassDeclarationSyntax>().First();


Los Syntax Tokens son terminales y no pueden ser padres de otros nodos o tipos de tokens. Representan identificadores, palabras clave, literales y puntuaciones. Ofrece propiedades para acceder a los valores analizados desde el código fuente de entrada.

Syntax Trivia representan porciones de texto insignificantes, que pueden aparecer como espacios en blanco, comentarios y directivas de preprocesador en cualquier posición dentro de la entrada de origen. No forman parte de la sintaxis del lenguaje normal y no se agregarán como elementos secundarios de los nodos de sintaxis. Tampoco tienen un nodo principal. La clase base se llama SyntaxTrivia.

Los nodos, tokens y trivias contienen Spans para el posicionamiento y la cantidad de ocurrencias de caracteres para asociar la ubicación correcta desde la fuente de entrada. Esto se puede utilizar para la depuración o la determinación de información de errores.

Los Kinds o tipos se utilizan como propiedades para nodos, tokens y trivias para distinguir los tipos correspondientes y proporcionar las conversiones correctas. La clase SyntaxKind se puede representar como un tipo de enumeración en el lenguaje de destino C# o VB.

Al analizar el código fuente, pueden ocurrir errores, que pueden ubicarse y marcarse como incorrectos o incompletos, por ejemplo, falta un token o no es válido. El analizador puede omitir tokens no válidos y continuar buscando el siguiente token válido para continuar con el análisis. Los tokens omitidos se adjuntarán como Trivia Node of Kind SkippedToken.

Roslyn proporciona una rica API para crear unidades de compilación AST por código. Utiliza el patrón de fábrica y proporciona muchas clases y métodos estáticos o enumeraciones, que se pueden usar a través del encadenamiento para construir estas soluciones. El encadenamiento es un diseño de llamada de método fluido utilizado en lenguajes de programación orientados a objetos, donde cada método devuelve un objeto, lo que permite llamar a la siguiente declaración sin requerir variables que almacenen los resultados intermedios. El siguiente código cortado muestra un ejemplo de cómo usar la API de Roslyn para hacer una declaración de clase simple.

var tree = SyntaxFactory.CompilationUnit().AddMembers(

    SyntaxFactory.NamespaceDeclaration(

        SyntaxFactory.IdentifierName("Example")).AddMembers(

            SyntaxFactory.ClassDeclaration("Demo").AddMembers(

                SyntaxFactory.MethodDeclaration(

                    SyntaxFactory.PredefinedType(

                        SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "Main")

                    .AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword))

                    .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))

                    .WithBody(SyntaxFactory.Block()).AddBodyStatements(

                         SyntaxFactory.ReturnStatement())

           )

     )

);

Aunque la representación del árbol sintáctico del código fuente de entrada ofrece muchas funciones, solo analiza las estructuras léxica y sintáctica. Para cubrir completamente todos los aspectos de los lenguajes de programación también es necesario definir su semántica, que marca su comportamiento. Las designaciones de variables locales y miembro pueden superponerse entre sí y diferir entre sus alcances y las reglas de acción deben integrarse dentro de esos cierres. Para ello, Roslyn utiliza símbolos. Un símbolo representa un elemento, que lleva los metadatos recibidos de la fuente de entrada. Se puede acceder a estos a través de la tabla de símbolos proporcionada, también representada en una estructura de árbol, comenzando desde el elemento raíz, que es el espacio de nombres global. Los símbolos se derivan de la interfaz ISymbol, mientras que el compilador proporciona las propiedades y los métodos. Los símbolos ofrecen espacios de nombres, tipos y miembros entre el código fuente y los metadatos y sus conceptos de lenguaje son similares a la API de Reflection utilizada por el sistema de tipos CLR. También es importante saber que el acceso al árbol del modelo semántico desencadena una compilación, lo que significa que es más costoso en comparación con el acceso al árbol sintáctico.

Cada símbolo contiene información sobre

  • la ubicación de la declaración (en el fuente o en los metadatos)
  • en qué espacio de nombres o tipo existe este símbolo
  • la información si el símbolo es abstracto, estático, sellado, etc.

El siguiente fragmento de código muestra cómo agregar información semántica al ejemplo de Hello World declarado anteriormente.


/* tree is given after parsing the Hello World example with the Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText method */ 

var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly. Location); 

var compilation = CSharpCompilation.Create("DemoCompilation", syntaxTrees: new[] { tree }, references: new[] { mscorlib }); 

var model = compilation.GetSemanticModel(tree)


Roslyn es una rica plataforma de servicio de compilación que permite un acceso profundo al marco .NET de Microsoft y sus subconjuntos de lenguajes admitidos. Aunque Roslyn ofrece una variedad de posibilidades de bajo nivel para construir AST, se abstrae muy bien de estas capas y proporciona una API limpia e intuitiva. 


No hay comentarios.:

Publicar un comentario