Появилась идея посмотреть, как будет выглядеть объектно-ориентированный подход в 1С, язык которой очень ограничен в средствах и не предусматривает определение классов. Программа по автоматическому переводу определений классов C# в другой язык позволила бы менять генерируемый код по мере появления новых идей. Поиски средств реализации привели к проекту Roslyn – открытому компилятору C#.
Roslyn – это открытая платформа компиляции C# и Visual Basic. Roslyn выполняет два основных действия: строит синтаксическое дерево (парсинг) и компилирует синтаксическое дерево. Дополнительно позволяет анализировать исходный код, рекурсивно обходить его, работать с проектами Visual Studio, выполнять код на лету.
Обратите внимание, что на данный момент Roslyn в стадии Бета. Исходя из этого, со временем в компиляторе может что-то поменяться.
Подключить Roslyn в проект можно через Nuget:
Для удобства в коде лучше сразу подключить три пространства имен
Получить синтаксическое дерево кода из строки (или файла) можно так:
Синтаксическое дерево представляет из себя иерархию объектов, наследованных от 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) и т.д. Не забывайте вызвать в каждом переопределенном методе базовый метод, чтобы не останавливать рекурсии.
Вызов рекурсивного обхода можно запустить следующим образом:
В синтаксическом дереве нет информации о типах. Информация об используемых типах появляется после вызова:
После этого вызова можно получать информацию об используемых типах, вызывая, например для класса
Теперь, имея информацию, о типе, можно узнать какой класс унаследовал данный тип:
Или получить все члены типа через type.GetMembers()
Код не претендует на полноту и правильность, так как имеет цель получить общее представление об ООП-подходе в 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 комментируются через // и вставляются в текст как есть.
В итоге код
Будет переведен в код 1С: Предприятие
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
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); } 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(("\r\n" + node.ToString()).Replace("\r\n", "\r\n" + indents + "//")); base.VisitExpressionStatement(node); } public override void VisitReturnStatement(ReturnStatementSyntax node) { var indents = new String('\t', Tabs); Writer.WriteLine(("\r\n" + node.ToString()).Replace("\r\n", "\r\n" + indents + "//")); base.VisitReturnStatement(node); } //public override void VisitBlock(BlockSyntax node) //{ // Writer.WriteLine(node.ToString()); // base.VisitBlock(node); //} } }
Результат работы
В итоге код
Исходный код на 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); КонецПроцедуры;
