Roslyn для автоматического перевода кода C# в 1С-код

в 5:54, , рубрики: .net, 1c 8.2, 1c:предприятие, roslyn, roslyn project, Visual Studio

Появилась идея посмотреть, как будет выглядеть объектно-ориентированный подход в 1С, язык которой очень ограничен в средствах и не предусматривает определение классов. Программа по автоматическому переводу определений классов C# в другой язык позволила бы менять генерируемый код по мере появления новых идей. Поиски средств реализации привели к проекту Roslyn – открытому компилятору C#.

Roslyn – это открытая платформа компиляции C# и Visual Basic. Roslyn выполняет два основных действия: строит синтаксическое дерево (парсинг) и компилирует синтаксическое дерево. Дополнительно позволяет анализировать исходный код, рекурсивно обходить его, работать с проектами Visual Studio, выполнять код на лету.

Обратите внимание, что на данный момент Roslyn в стадии Бета. Исходя из этого, со временем в компиляторе может что-то поменяться.

Roslyn – открытый компилятор C#

Подключить Roslyn в проект можно через Nuget:

Install-Package Microsoft.CodeAnalysis –Pre

Для удобства в коде лучше сразу подключить три пространства имен

using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis;

Получить синтаксическое дерево кода из строки (или файла) можно так:

SyntaxTree tree = CSharpSyntaxTree.ParseText(codeString);

Синтаксическое дерево представляет из себя иерархию объектов, наследованных от SyntaxNode. Объекты созданы на все случаи жизни. Примеры: ClassDeclarationSyntax — определение класса, NamespaceDeclarationSyntax – определение пространства имен, PropertyDeclarationSyntax – определение свойства, AccessorDeclarationSyntax – определение метода доступа к свойству (get/set), BlockSyntax – содержимое блока (между фигурными скобками), ExpressionStatementSyntax – выражение и т.д.

Если есть задача рекурсивно пройти все элементы дерева, можно создать свой класс Walker и наследовать его от CSharpSyntaxWalker. Базовый класс позволяет переопределять общий метод Visit(SyntaxNode node) или большое множество специализированных, вида: void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node), void VisitClassDeclaration(ClassDeclarationSyntax node), void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) и т.д. Не забывайте вызвать в каждом переопределенном методе базовый метод, чтобы не останавливать рекурсии.

Вызов рекурсивного обхода можно запустить следующим образом:

var walker = new Walker();
walker.Visit(tree.GetRoot());

В синтаксическом дереве нет информации о типах. Информация об используемых типах появляется после вызова:

var compilation = CSharpCompilation.Create("1ccode").WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)).AddReferences(new MetadataFileReference(typeof(object).Assembly.Location)).AddSyntaxTrees(tree);
var Model = Compilation.GetSemanticModel(tree);

После этого вызова можно получать информацию об используемых типах, вызывая, например для класса

var classSymbol = Model.GetDeclaredSymbol(classDeclarationSyntax);

Теперь, имея информацию, о типе, можно узнать какой класс унаследовал данный тип:

var type = type.BaseType;

Или получить все члены типа через type.GetMembers()

Автоматический перевод кода C# в код 1С

Код не претендует на полноту и правильность, так как имеет цель получить общее представление об ООП-подходе в 1С.

Для перевода C#-кода в код 1С был создан класс Walker, наследованный от CSharpSyntaxWalker. Walker перебирает все определения и строит на выходе 1С-код.

Класс производит следующие преобразования.

Пространство имен переводится методом VisitNamespaceDeclaration в модуль 1С, где точки в названии заменены на знаки подчеркивания.

Понятия класс в 1С нет, поэтому определение класса в методе VisitClassDeclaration пропускается. Имя класса будет присутствовать в названии каждой функции и процедуры 1С, чтобы обозначить принадлежность к одному типу. Присутствующие в базовых классах методы, но отсутствующие в текущем классе через DeclareBaseClassMethodsToImplement и DeclareBaseClassPropertiesToImplement определяются с вызовом «базовых» функций/процедур 1С.

Конструкторы в VisitConstructorDeclaration переводятся в определения функций 1С с именем класса, первым параметром _this и списком параметров. Если нет вызова другого конструктора этого класса, происходит инициализация всех полей класса в структуре. Определяется вызов других конструкторов.

Определение свойств в VisitPropertyDeclaration пропускаются. Важны определения их методов доступа.

Методы доступа свойств в VisitAccessorDeclaration переводятся в определения с именами <название класса>_Получить_<имя свойства> и <название класса>_Установить_<имя свойства>. Если они авто-реализованные (auto-implemented), то генерируется код доступа к переменной _this._private_<название класса>_<имя свойства>.

Для методов в VisitMethodDeclaration генерируются определения 1С-процедур.

Выражения и «возвраты» в VisitExpressionStatement и VisitReturnStatement комментируются через // и вставляются в текст как есть.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis;


namespace Roslyn
{
    public class Walker : CSharpSyntaxWalker
    {

        SyntaxTree Tree { get; set; }
        CSharpCompilation Compilation { get; set; }
        SemanticModel Model { get; set; }
        TextWriter Writer { get; set; }
        public Walker(TextWriter writer, SyntaxTree tree, CSharpCompilation compilation) : base()
        {
            Writer = writer;
            Tree = tree;
            Compilation = compilation;
            Model = Compilation.GetSemanticModel(tree);
        }
<habracut />
        Dictionary<ClassDeclarationSyntax, FieldDeclarationSyntax[]> _classFields = new Dictionary<ClassDeclarationSyntax, FieldDeclarationSyntax[]>();

        NamespaceDeclarationSyntax _currentNamespace;
        ClassDeclarationSyntax _currentClass;
        PropertyDeclarationSyntax _currentProperty;

        private int Tabs = 0;
        public override void Visit(SyntaxNode node)
        {
            //Tabs++;
            //var indents = new String('t', Tabs);
            //Writer.WriteLine(indents + node.GetType().Name + "/" + node.CSharpKind());
            base.Visit(node);
            //Tabs--;

        }

        public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
        {
            _currentNamespace = node;

            Writer.WriteLine("Модуль " + node.Name.ToString().Replace(".", "_"));
            base.VisitNamespaceDeclaration(node);
        }

        public override void VisitClassDeclaration(ClassDeclarationSyntax node)
        {
            _currentClass = node;

            var fields = node.ChildNodes().OfType<FieldDeclarationSyntax>().ToArray();
            _classFields[node] = fields;

            Writer.WriteLine();
            Writer.WriteLine(string.Format("//Класс {0}", node.Identifier));
            base.VisitClassDeclaration(node);

            DeclareBaseClassPropertiesToImplement(node);
            DeclareBaseClassMethodsToImplement(node);

        }

        void DeclareBaseClassMethodsToImplement(ClassDeclarationSyntax classNode)
        {
            var classSymbol = Model.GetDeclaredSymbol(classNode);

            List<string> processedMembers = new List<string>();
            var type = classSymbol;
            while (type != null)
            {
                foreach(var member in type.GetMembers())
                {
                    var declarators = member.DeclaringSyntaxReferences;
                    if (declarators == null || declarators.Length == 0)
                        continue;
                    if (declarators.Length != 1)
                        throw new NotImplementedException();

                    var memberNode = declarators[0].GetSyntax() as MethodDeclarationSyntax;
                    if (memberNode == null)
                        continue;

                    if (processedMembers.Any(m=>m == member.Name))
                        continue;
                    processedMembers.Add(member.Name);

                    if (type == classSymbol)
                        //Skip original class members. Declare only base classes
                        continue;

                    Writer.WriteLine();
                    Writer.WriteLine(string.Format("Процедура {0}_{1}(_this)", _currentClass.Identifier, memberNode.Identifier));
                    Writer.WriteLine(string.Format("    {0}_{1}(_this);", type.Name, member.Name));
                    Writer.WriteLine(string.Format("КонецПроцедуры;"));
                }
                type = type.BaseType;
            }
        }

        void DeclareBaseClassPropertiesToImplement(ClassDeclarationSyntax classNode)
        {
            var classSymbol = Model.GetDeclaredSymbol(classNode);

            List<string> processedMembers = new List<string>();
            var type = classSymbol;
            while (type != null)
            {
                foreach(var member in type.GetMembers())
                {
                    var declarators = member.DeclaringSyntaxReferences;
                    if (declarators == null || declarators.Length == 0)
                        continue;
                    if (declarators.Length != 1)
                        throw new NotImplementedException();

                    var memberNode = declarators[0].GetSyntax() as PropertyDeclarationSyntax;
                    if (memberNode == null)
                        continue;

                    if (processedMembers.Any(m => m == memberNode.Identifier.ToString()))
                        continue;
                    processedMembers.Add(memberNode.Identifier.ToString());

                    if (type == classSymbol)
                        //Skip original class members. Declare only base classes
                        continue;

                    Writer.WriteLine();
                    Writer.WriteLine(string.Format("Функция {0}_Получить_{1}(_this)", _currentClass.Identifier, memberNode.Identifier));
                    Writer.WriteLine(string.Format("    Возврат {0}_Получить_{1}(_this);", type.Name, member.Name));
                    Writer.WriteLine(string.Format("КонецФункции;"));

                    Writer.WriteLine();
                    Writer.WriteLine(string.Format("Процедура {0}_Установить_{1}(_this, value)", _currentClass.Identifier, memberNode.Identifier));
                    Writer.WriteLine(string.Format("    {0}_Установить_{1}(_this);", type.Name, member.Name));
                    Writer.WriteLine(string.Format("КонецПроцедуры;"));
                }
                type = type.BaseType;
            }
        }

        public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
        {
            Writer.WriteLine();

            var symbol = Model.GetDeclaredSymbol(node);

            List<string> parameters = new List<string>();
            parameters.Add("_this");
            parameters.AddRange(node.ParameterList.Parameters.Select(m => m.Identifier.ToString()).ToArray());

            Writer.WriteLine(string.Format("Функция {0}({1}){2}", node.Identifier, String.Join(", ", parameters), " Экспорт"));
            Writer.WriteLine();

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

            //Initialize members first if no this constructor initializer (:this()) call
            if (!node.DescendantNodes().OfType<ConstructorInitializerSyntax>().Any(m=>m.CSharpKind() == SyntaxKind.ThisConstructorInitializer) && _classFields.ContainsKey(_currentClass))
            {
                Writer.WriteLine(indents + String.Format("//Инициализация полей"));
                //Writer.WriteLine(String.Format("_this = Новый Структура();"));
                foreach (var field in _classFields[_currentClass])
                {
                    Writer.WriteLine(String.Format(indents + "_this.Вставить("{0}", {1})", field.Declaration.Variables[0].Identifier, field.Declaration.Variables[0].Initializer.Value));
                }
            }

            if (node.Initializer != null)
            {
                List<string> arguments = new List<string>();
                arguments.Add("_this");
                arguments.AddRange(node.Initializer.ArgumentList.Arguments.Select(m => m.Expression.ToString()).ToArray());

                if (node.Initializer.ThisOrBaseKeyword.CSharpKind() == SyntaxKind.BaseKeyword)
                {
                    Writer.WriteLine(indents + String.Format("//Вызов конструктора базового класса"));
                    Writer.WriteLine(indents + String.Format("{0}({1});", _currentClass.BaseList.Types[0], String.Join(", ", arguments)));
                }
                else if (node.Initializer.CSharpKind() == SyntaxKind.ThisConstructorInitializer)
                {
                    Writer.WriteLine(indents + String.Format("//Вызов другого конструктора"));
                    Writer.WriteLine(indents + String.Format("{0}({1});", _currentClass.Identifier, String.Join(", ", arguments)));
                }
            }

            Writer.WriteLine(String.Format(indents + "_this.Вставить("__type", "{0}.{1}")", symbol.ContainingNamespace.Name, symbol.ContainingType.Name));

            base.VisitConstructorDeclaration(node);
            Tabs--;

            Writer.WriteLine(indents + string.Format("Возврат _this;"));
            Writer.WriteLine(string.Format("КонецФункции; //{0}({1}){2}", node.Identifier, String.Join(", ", parameters), " Экспорт"));
        }

        public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
        {
            _currentProperty = node;

            var symbol = Model.GetDeclaredSymbol(node);

            base.VisitPropertyDeclaration(node);
        }
        public override void VisitAccessorDeclaration(AccessorDeclarationSyntax node)
        {
            Writer.WriteLine();
            if (node.CSharpKind() == SyntaxKind.GetAccessorDeclaration)
            {
                Writer.WriteLine(string.Format("Функция {0}_Получить_{1}(_this)", _currentClass.Identifier, _currentProperty.Identifier));
            }
            else if (node.CSharpKind() == SyntaxKind.SetAccessorDeclaration)
            {
                Writer.WriteLine(string.Format("Процедура {0}_Установить_{1}(_this, value)", _currentClass.Identifier, _currentProperty.Identifier));
            }

            Tabs++;

            if (node.Body == null)
            {
                //auto implemented
                var indents = new String('t', Tabs);
                if (node.CSharpKind() == SyntaxKind.GetAccessorDeclaration)
                {
                    Writer.WriteLine(indents + string.Format("Возврат _this._private_{0}_{1};", _currentClass.Identifier, _currentProperty.Identifier));
                }
                else if (node.CSharpKind() == SyntaxKind.SetAccessorDeclaration)
                {
                    Writer.WriteLine(indents + string.Format("_this._private_{0}_{1} = value;", _currentClass.Identifier, _currentProperty.Identifier));
                }
            }

            base.VisitAccessorDeclaration(node);
            Tabs--;

            if (node.CSharpKind() == SyntaxKind.GetAccessorDeclaration)
            {
                Writer.WriteLine(string.Format("КонецФункции;"));
            }
            else if (node.CSharpKind() == SyntaxKind.SetAccessorDeclaration)
            {
                Writer.WriteLine(string.Format("КонецПроцедуры;"));
            }
        }

        public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
        {
            Writer.WriteLine();
            Writer.WriteLine(string.Format("Процедура {0}_{1}(_this)", _currentClass.Identifier, node.Identifier));

            Tabs++;
            base.VisitMethodDeclaration(node);
            Tabs--;

            Writer.WriteLine(string.Format("КонецПроцедуры;"));
        }

        public override void VisitExpressionStatement(ExpressionStatementSyntax node)
        {
            var indents = new String('t', Tabs);
            Writer.WriteLine(("rn" + node.ToString()).Replace("rn", "rn" + indents + "//"));
            base.VisitExpressionStatement(node);
        }
        public override void VisitReturnStatement(ReturnStatementSyntax node)
        {
            var indents = new String('t', Tabs);
            Writer.WriteLine(("rn" + node.ToString()).Replace("rn", "rn" + indents + "//"));
            base.VisitReturnStatement(node);
        }
        //public override void VisitBlock(BlockSyntax node)
        //{
        //    Writer.WriteLine(node.ToString());
        //    base.VisitBlock(node);
        //}
    }
}

Результат работы

В итоге код

namespace ПространствоИмен1.ПИ2
{
    public class А
    {
        public А()
        {
            Свойство1 = "Конструктор А";
        }

        private int _поле1 = 10;        
        public int Поле1 {get {return _поле1;} set {_поле1 = value;}}        

        public string Свойство1 {get; set;}
        public void Метод1()
        {
            Свойство1 = "Метод1";
        }
    }

    public class Б : А
    {
        private int _поле1 = 20;        

        public Б() : base()
        {
            Свойство1 = "Конструктор Б";
            Метод1();
        }

        public Б(int i) : this()
        {
            Свойство1 = "Конструктор Б(int i)";
            Метод1();
        }
    }
}    

Будет переведен в код 1С: Предприятие

Модуль ПространствоИмен1_ПИ2

//Класс А

Функция А(_this) Экспорт

	//Инициализация полей
	_this.Вставить("_поле1", 10)
	_this.Вставить("__type", "ПИ2.А")

	//Свойство1 = "Конструктор А";
	Возврат _this;
КонецФункции; //А(_this) Экспорт

Функция А_Получить_Поле1(_this)

	//return _поле1;
КонецФункции;

Процедура А_Установить_Поле1(_this, value)

	//_поле1 = value;
КонецПроцедуры;

Функция А_Получить_Свойство1(_this)
	Возврат _this._private_А_Свойство1;
КонецФункции;

Процедура А_Установить_Свойство1(_this, value)
	_this._private_А_Свойство1 = value;
КонецПроцедуры;

Процедура А_Метод1(_this)

	//Свойство1 = "Метод1";
КонецПроцедуры;

//Класс Б

Функция Б(_this) Экспорт

	//Инициализация полей
	_this.Вставить("_поле1", 20)
	//Вызов конструктора базового класса
	А(_this);
	_this.Вставить("__type", "ПИ2.Б")

	//Свойство1 = "Конструктор Б";

	//Метод1();
	Возврат _this;
КонецФункции; //Б(_this) Экспорт

Функция Б(_this, i) Экспорт

	//Вызов другого конструктора
	Б(_this);
	_this.Вставить("__type", "ПИ2.Б")

	//Свойство1 = "Конструктор Б(int i)";

	//Метод1();
	Возврат _this;
КонецФункции; //Б(_this, i) Экспорт

Функция Б_Получить_Поле1(_this)
    Возврат А_Получить_Поле1(_this);
КонецФункции;

Процедура Б_Установить_Поле1(_this, value)
    А_Установить_Поле1(_this);
КонецПроцедуры;

Функция Б_Получить_Свойство1(_this)
    Возврат А_Получить_Свойство1(_this);
КонецФункции;

Процедура Б_Установить_Свойство1(_this, value)
    А_Установить_Свойство1(_this);
КонецПроцедуры;

Процедура Б_Метод1(_this)
    А_Метод1(_this);
КонецПроцедуры;

Автор: Elisy

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js