Опыты с автоматическим переводом кода C# в 1С-код через Roslyn

by Elisy 26. December 2014 16:18

Появилась идея посмотреть, как будет выглядеть объектно-ориентированный подход в 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 комментируются через // и вставляются в текст как есть.

Исходный код Walker.cs

Walker.cs (12,20 kb)
 

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



В итоге код

Исходный код на C#

          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С:Предприятие

Модуль ПространствоИмен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);
КонецПроцедуры;

Walker.cs (12,20 kb)

Tags:

1C.Net | 1С:Предприятие | C#

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

Richmedia.us

Добро пожаловать на сайт Richmedia.us

1 2 3 4 5 6 7 8

RecentComments

Comment RSS