Введение
С 70-х годов развивается Simplified English, цель которого — определение подмножества языка, понятного широкому кругу неносителей языка. Рекомендуется, например, для технической документации. Автоматические переводчики на таком подмножестве будут работать заведомо корректнее, в идеале генерируя текст, не требующий ручной корректуры.
Если применить этот подход к C# для задачи автоматической конвертации кода в другие языки программирования, то можно выделить подмножество конструкций языка, системных библиотек и технологий, которые потенциально могут транслироваться в широкий круг других языков. Причём конвертации не однократной (миграция), а постоянной для расширения интеграционных возможностей проекта на C# — чтобы в любой момент можно было получить рабочий код на другом языке без необходимости какой-либо его правки.
Позвольте представить: UniSharping
Ограничение C#.NET для решения этой задачи мы назвали U# (Universal Sharp), а процесс конвертации и его инструмент — UniSharping. Исполняемые модули, настройки и документация выложены на GitHub, система бесплатна для некоммерческого использования (Non-Commercial Freeware).
В целях кроссплатформенности Компания Microsoft уже сделала ограничение .NET Framework в плане библиотек и технологий: .NET Core. Это как бы первый шаг в нужном направлении, U# делает второй шаг к «кросспрограммируемости».
Ограничений U# в конструкциях языка оказалось немного – это атавизмы goto и case goto, а также yield, не моделируемый адекватно в автоматическом режиме. Не рекомендуется (хотя и можно) использовать struct, есть нюансы с наименованиями – всё это подробно описывается в отдельном документе. Парсер U# выдаёт ошибки и предупреждения, и для гарантии корректной генерации следует так подкорректировать исходный код C#, чтобы они в идеале совсем исчезли. Если всё-таки нужно сохранить исходный вариант, то можно использовать директивы препроцессора #if JAVA || PHP … #else … #endif. Данные ограничения действуют на уровне движка U# и не подлежат коррекции извне, как и список поддерживаемых языков.
А вот ограничения на уровне системных библиотек заданы не жёстко и конфигурируются извне через специальные текстовые файлы, определяющие, как переводить на соответствующий язык тот или иной класс и его члены. Если есть прямой аналог, то он и указывается, если ситуация сложнее, то пишется или фрагмент кода конечного языка, или вообще специальный (сервисный) класс, решающий нужную задачу. В совсем уж сложных случаях приходится «хардкодить» на уровне движка, но такие ситуации довольно редки (с десяток). Порядок настройки на системные классы и их члены описываются в отдельном документе. Вот список поддержанных классов C# и их членов с аналогами на Java и Python в текущей версии на сайте, там же есть online-демо.
Что касается технологий, то сейчас список ограничен консольным приложением и юнит-тестами (UnitTest). Ну и отдельные Lib-проекты, как частный случай, переводятся в соответствующие конструкции нужного языка.
Для успешного перевода исходный проект C# (solution) должен иметь некоторую запускаемую часть, проверяющую работоспособность в рамках исходного C#. Хорошо, если это обширная система авто-тестов (стандартных UnitTest в разных реализациях или самописных), но по минимуму должно быть хотя бы консольное приложение, которое при запуске без какого-либо пользовательского вмешательства отрабатывает правильно. Необходимость этого очевидна – после генерации на конечный язык можно сразу проверить работоспособность. В идеале все тесты должны работать аналогично C#.
История проекта
Идея такого конвертера витала давно. Мой основной проект SDK Pullenti по обработке естественного языка — идеальный кандидат для конвертации: большой объём сложного и постоянно совершенствуемого кода. Для интеграции с Java приходилось оборачивать в web-сервисы, tcp-сервера и пр.
Летом прошлого года нашлось время и силы для создания первого варианта. Он переводил проект Pullenti на Java, а также сам себя на Java.
Следующие полгода конвертер развивался на нескольких внутренних проектах, которые были в компании, в основном посредством расширения системных классов.
Весной 2018 появилась мысль поддержать и Python, что и было реализовано к лету. Но включение второго языка не было предусмотрено в начальной версии и получилось коряво. Пришлось летом полностью переделать движок для потенциальной возможности нескольких конечных языков. Также настройки на системные классы из хардкода были вынесены во внешние текстовые файлы. Надеюсь, это множество будет расширяться не без вашей помощи.
Дальнейшие планы пока таковы:
- подтянуть Python до уровня Java. Сейчас Python поддержан на уровне Pullenti, однако Java по сравнению с ним ушёл далеко вперёд на других проектах.
- поддержать PHP хотя бы на уровне проекта Pullenti.
- поддержать С++. Да, осознаю, это очень сложно, так как неясно при освобождении памяти — какой указатель является ссылкой, а для какого нужно делать delete. Но есть идеи...
Кому это может пригодиться
В основном тем, кто разрабатывает потенциально кроссплатформенные SDK на C#. Благодаря конвертеру UniSharping их SDK может стать ещё и "кросспрограммным", что расширит круг потенциальных пользователей.
В последнее время в России усилились позиции СПО, которые стали обязательными в большинстве государственных структур и некоторых крупных компаниях. Объяснить, что .NET Core тоже СПО не всегда получится, потому что "Microsoft". Пусть некоторая компания разрабатывает свою информационную систему на C#. Чтобы внедрить продукт в "СПО-компанию", можно выделить логическую часть проекта (back-end), её автоматически конвертировать в релизы по мере необходимости, а визуальную часть (front-end) делать на СПО. То есть продолжать разработку на C#, а на Java только front-end.
Я не исключаю, что в принципе возможна конвертация и web-проектов (с ограничениями, естественно), но у меня нет для этого нужных навыков и информации. Если кто видит такую возможность, то её вполне можно реализовать в UniSharping.
Отмечу, для для реального сложного проекта C# поддержка Java или другого языка потребует некоторых усилий по модификации кода, выделении в проекте портируемой части, "обкладывание" её юнит-тестами. Также настройка ещё неподдержанных системных классов и методов и исправление ошибок самого UniSharping (с моей помощью) — работа ещё та. Но процесс сходящийся, в конце которого проект ожидает бонус "кросс-программности".
Комментарии (60)
BkmzSpb
12.09.2018 18:53+1Ограничений U# в конструкциях языка оказалось немного – это атавизмы goto и case goto, а также yield, не моделируемый адекватно в автоматическом режиме. Не рекомендуется (хотя и можно) использовать struct, есть нюансы с наименованиями – всё это подробно описывается в отдельном документе.
Рискую нарваться на негатив (т.к. не специалист), но
yield
иstruct
это вообще ключевые ключевые слова (keywords) в языке.yield
может быть незаменим при работе с коллекциями т.к. без него не написать iterator methods. А безstruct
я даже не знаю. Я даже не говорю про всякие ништяки типаref struct
,unsafe
. Не уверен что есть 1-к-1 соответствие вJava
, а вPython
тем более.
Как вишенка на торте — сегодня долго возился с кастомным аттрибутом, наболело, поэтому обратил внимание, — согласно документации класс
Attribute
не поддерживается, вType
только 4 элемента, что фактически значит что рефлексии нет (без понятия как это работает вJava
, полагаю там должен быть аналог, проPython
из-за отсутствия статической типизации — вообще хз).
Фактически реализована трансляция каких-то совсем базовых методов базовых типов в аналогичные на другом языке (читай правка синтаксиса). Я прекрасно понимаю аргумент simplified English, но таким способом достаточно сложный проект транслировать невозможно.
Вообще не понятно зачем транслировать в
Python
так как языки ну совсем разного класса.Java
еще я могу понять, и то проблем куча.
Конечно я допускаю, что я абсолютно не прав, так как упустил какие-то важные пункты или просто не вижу кейсов, в которых это могло бы быть полезно.
KonstantinSmith Автор
12.09.2018 19:051. При переводе некоторых чужих проектов я сталкивался с парой десятков yield, и они прекрасно переделывались на List. Хотя можно использовать #if JAVA вариант без yield… #else вариант с yeild #endif Да, есть пример бесконечного цикла, ну тут увы.
2. struct — в документации я подробно описал, что их не запрещено использовать, они переводятся в class, но есть нюансы при присваиваниях. Немного помучившись, в своём проекте я вообще отказался от struct. И что удивительно, на скорость выполнения это никак не повлияло, а я думал, что их использования в моём случае даст выигрыш. Так что можно использовать.
3. Атрибуты не поддерживаются (игнорируются) — ну да, чем то придётся пожертвовать
4. Reflection есть в небольшом объёме, который требуется мне (получить тип объекта, для типа список конструкторов, вызвать конструктор). Но этот список расширяем путём внешних настроек. Невозможно сразу съесть слона (системные классы). Помогите нам — выберите какую-нибудь полезную функцию C#, найдите аналог в Java и опишите.
Да, приходится за всё платить, и мне для кросспрограммируемости в своём проекте Pullenti пришлось кое что переделать. Например, отказаться от плагинного способа загрузки dll с анализаторами обработки текста. Но это мелочи.BkmzSpb
12.09.2018 19:48Хотел посмотреть на ваш проект, но исходников открытых я так понимаю нет. Судя по тому, что я понял из демонстрационного проекта, в случае
Pullenti
описанного функционалаU#
должно хватить, а вот насчет более сложных (в смысле применяемых технологий) проектов — я не уверен.
Для меня больной темой например оказались аттрибуты и
yield
, т.к. существует огромная разница между
IEnumerable<T> YieldMethod<T>(/*...*/) {yield break;}
и аналогичному ему
List<T> ListMethod<T>(/*...*/) {/*???*/}
Первый позволяет потреблять элементы коллекции лениво, или же являться источником бесконечного количества элементов.List<>
в свою очередь вполне себе конкретная структура данных.
Попробую привести пример:
InfiniteSourceOfInt().SomeYieldMethod().Take(5);
должен корректно отработать и в конечном счете вернуть некийIEnumerable<>
из 5 элементов. А вот если
InfiniteSourceOfInt().SomeListMethod().Take(5);
то я полагаю здесь будет бесконечный цикл т.к.SomeListMethod
должен проитерировать всю исходную коллекцию, чтобы сгенерироватьList<>
, а она бесконечная (ну или очень большая, например).
Сейчас обнаружил что
typeof
тоже не поддерживается,is
иas
находят свои аналоги, а вотtype matching
видаvar x = "a_b_c" is string s ? s.Split('_') : throw new InvalidOperationException();
уже нет. Проpattern matching
вcase
блоке вообще молчу.
Прошу прощения, если это звучит негативно, мне оказалось довольно любопытно разобраться как работает ваш транслятор. Summary моих комментариев можно свести к следующему:
Ограничений U# в конструкциях языка оказалось немного
на самом деле ограничений довольно много, и мне сложно представить как я бы писал
U#
-совместимый код.
P.S.: вовремя обновил страницу и увидел комментарий Vadem ниже
async/await, похоже, тоже не поддерживается.
А вот это уже критично.В списке либ отсутствует
Task
, что ставит под вопрос трансляцию кода даже с малой долей параллелизма — фактически при текущих ограничениях любые длительные операции (обращение к БД, вычисления, веб реквесты), которые занимают какое то реальное, ощутимое время, транслировать невозможно. А это действительно минус, увы.
fcoder
12.09.2018 19:561. List всё-таки совсем не замена yield, так как возвращает данные по мере поступления (вычисления или получения из сетевого соединения) и специально создан для тех случаев, когда коллекция целиком может не влезть в память. Непосредственно ключевое слово yield встречается редко потому, что он обёрнут низкоуровневыми драйверами баз данных и прочим linq, которые возвращают IEnumerable, внутри которых ленивая подгрузка данных.
2. Раз struct безусловно переделывается в класс, означает ли это что default(T) работает некорректно? Это достаточно важно, так как и решарпер, и ms code analysis просит убрать ненужную проверку на null в структурах, так как там в принципе не может быть null, а без правильно работающего default, отсутствие null гарантировать уже сложнееKonstantinSmith Автор
12.09.2018 20:061. Ну конечно есть случаи yield, которые нельзя смоделировать List- ом, кто же спорит. Но их обычно не много, и неужели ради нескольких таких мест стоит отказываться от предлагаемой возможности?
2. default(T) для структур в Java преобразуется в new T(). Он во основном нормально работает, но если где то встретилось a = b для структуры, а потом поменялись поля b, то у а они тоже поменяются, так как для классов это один объект. Если таких ситуаций нет или делать a = new(b) (кстати, вроде UniSharping такие ситуации пытается отслеживать вызывать new, я уже не помню — отказался от структур), то всё будет работать.mayorovp
13.09.2018 14:34Это называется не «ньюансы при присваиваниях», а «структуры не работают». Конвертер должен либо вставлять операцию клонирования самостоятельно, либо выдавать ошибку.
kekekeks
12.09.2018 19:07+1Можно взять Bridge.NET, скомпилировать C# в JavaScript, а уже его заэмбеддить в Java/Python. Есть большое подозрение, что процесс будет менее болезненным, чем применение представленного транслятора. Ибо у Bridge.NET полная поддержка самого C#, включая семантику структур, а портированный на JS набор класс BCL достаточно богатый, да и рефлексия работает целиком (хоть и увеличивает размер бандла, если её включить).
Vadem
12.09.2018 19:40Ограничений U# в конструкциях языка оказалось немного – это атавизмы goto и case goto, а также yield, не моделируемый адекватно в автоматическом режиме.
async/await
, похоже, тоже не поддерживается.
А вот это уже критично.KonstantinSmith Автор
12.09.2018 19:47Скажете — поддержим. Важно, чтобы «интерес был не праздным». Если есть реальная потребность такого перевода, то можем обсудить доработки. А если просто так, то да, пока не поддерживается.
Vadem
12.09.2018 20:46У меня интереса никакого к этому проекту нет.
Просто, кажется, что async/await — это одна из самых важных фич в C# на данный момент.
В Python, кстати, она тоже есть.KonstantinSmith Автор
12.09.2018 20:56Спасибо за комментарий, конечно, в ближайшее время поддержим (а для Java смоделируем). Я уже даже выложил 1.2, где async\await корректно обрабатываются. Фича очень важная, просто мне пока не была нужна, поэтому и оказалась нереализованной. Она не входит в множество принципиальных ограничений U# (как goto или unsafe), просто руки не до всего ещё дошли.
Deosis
13.09.2018 08:56Проще работать с байткодом CLR, чем с исходниками.
async/await и прочие новые возможности языка являются сахаром и в MSIL не существует.
Бонусом пойдет поддержка других .NET языков
KonstantinSmith Автор
13.09.2018 15:06Ну может. Поддержать J# — это круто! Интересно, сколько человек в мире его использует, хотя бы 1K наберётся?
Ametrin
13.09.2018 15:38Речь ведь не только и не столько о J#, сколько о VB.NET и F#
KonstantinSmith Автор
13.09.2018 15:43Да, пожалуй. Хотя подозреваю, что по сравнению с C# программистов на других шарпах сильно меньше. Это можно оценить по количеству книг в крупных магазинах, посвящённых тому или иному языку. На F# я не встречал ни одной, по VB# может одна, а остальные 10-15 по C#. По крайней мере, так было год назад. Поддержать VB# в UniSharping — вопрос пары дней, если будет проект, то обсудим.
fcoder
13.09.2018 18:35VB.NET ну очень распространён. Его преподают в старших классах и государственный с муниципальным сектором используют его достаточно широко (много вакансий).
Типичная, кстати, задача — перевод старого проекта c VB6 на VB.NET
ChessMax
12.09.2018 19:56В сторону Haxe не смотрели?
KonstantinSmith Автор
13.09.2018 12:50Одно из условий задачи — на входе проект C#
ChessMax
13.09.2018 14:14Ну так можно написать один конвертер из C# в Haxe, не?
KonstantinSmith Автор
13.09.2018 15:02Хорошая мысля приходит опосля… Да, буду иметь в виду, спасибо, поддержать Haxe!
eugene_bb
12.09.2018 20:15C# замечательный язык. Поэтому с моей точки зрения лучше реализовывать конверторы в C#. Чтобы как можно большему количеству людей можно было легко запрыгнуть на платформу /J
Ну или на крайний случай bi-directional, в этом случае можно прогнать программу в одном направлении, а потом обратно и сравнить результаты с оригиналом.eugene_bb
12.09.2018 20:29И если инструмент предназначается не для единовременного конвертирования, то будет очень полезно иметь hint-ы, с подсказками для явного использования своих конструкций вместо ваших и правил merge с предыдущей версией (например конвертировать только часть кода из файла и делать merge только этого кода).
KonstantinSmith Автор
12.09.2018 20:51Ну разумеется, у меня пока идеи исчерпались, ждём нормального проекта и предложений, чтобы продвинуться дальше. Идея с хинтами (комментариями своего формата) вместе с директивами препроцессора — очень мощный инструмент для этой задачи.
KonstantinSmith Автор
12.09.2018 20:40Так и я об этом же! Можно писать на C#, а получать код на нужном языке. Но только конвертер нужен не «в C#», а «из C#». Так как у ряда заказчиков требование СПО — критичное (см. конец статьи).
eugene_bb
12.09.2018 22:23С моей точки зрения, «в» гораздо полезнее, чем «из». Более-менее большие проекты фактически не реально мигрировать 100% без дополнительных усилий, т.е. на принимающей стороне должны быть люди которые постоянно подпиливают изменения чтобы привести в «приемлемый» вид, поэтому кроме специфичных случаев, всё таки «в».
KonstantinSmith Автор
12.09.2018 22:31Усилия потребуются, но на стороне C#. А вот на приёмной стороне усилий не требуется никаких, поскольку генерируется сразу рабочий вариант. Когда я делаю очередной релиз Pullenti, то SDK на Java и Python получаются автоматом и не требуют никакого ручного участия (и сразу отгружаются скриптом на сайт). Мои коллеги по 3-м проектам на Java не дадут соврать…
eugene_bb
12.09.2018 22:56Да я не против разнообразия в мире. Я писал что по моему мнению «в» полезнее для популяризации C#, но не претендую на истину.
Кстати посоветовал бы добавить поддержку TypeScript, а из него автоматом в JS. Там потенциальная аудитория на порядок больше, плюс как раз реальные сценарии для использования.
У нас были требования чтобы часть клиентского кода на разных языках была синхронизирована с backend в C#.
Сделали на шаблонах T4, всё работало отлично. Плюс добавлялся функционал специфичный для платформы, не требуемый и не существующий на C#
Поэтому я и говорил, что полезно было бы иметь возможность сказать, что для конвертирования берем только указанные файлы, из них только указанные классы и из некоторых классов, только указанные методы.KonstantinSmith Автор
13.09.2018 12:44Такая возможность есть — можно игнорировать указанные namespace, а также использовать #if… #endif для отключения ненужных фрагментов. Раньше даже были поддержаны хинты типа //JAVA ignored для любых конструкций языка, но потом я это обрал — достаточно директив препроцессора.
a-tk
13.09.2018 11:22> Ограничений U# в конструкциях языка оказалось немного – это
> атавизмы goto и case goto,
Конечные автоматы — бай-бай.
> а также yield, не моделируемый адекватно в автоматическом режиме.
Грусть-печаль… Работа с коллекциями, до скорых встреч.
> Не рекомендуется (хотя и можно) использовать struct,
Прощай быстродействие.KonstantinSmith Автор
13.09.2018 12:41Да, бедные Java и Python — они лишены всего этого богатства…
a-tk
13.09.2018 14:27А как же генераторы в Питоне?
KonstantinSmith Автор
13.09.2018 15:01А генераторы в Питоне есть — там всё через генераторы (если имеется в виду generic). Любой метод def f(x, y) уже на генераторах — аналог void f<X, Y>(X x, Y y). Так что здесь конвертация C#->Python просто игнорирует генераторы C#, и всё работает. Но возникают другие проблемы (с одинаковыми именами) — всё это описано в документации.
Ametrin
13.09.2018 15:40думаю, речь о методах типа такого:
def F(x): while x > 0: x -= 1 yield 1
KonstantinSmith Автор
13.09.2018 15:54Ого, не знал про такое в Python! На него я настраивался уже после Java, и не обратил внимание. Значит, yield для Python вполне возможен, и одним ограничением U# меньше, спасибо! А не найдётся ли что-нибудь для goto? ;)
Sirikid
13.09.2018 12:48Объяснить, что .NET Core тоже СПО не всегда получится, потому что "Microsoft".
Просто дичь. В целом идея интересная, а мотивация очень слабая.
fcoder
13.09.2018 19:20+1Вообще говоря, посыл, задаваемый как цель конверсии немного странный.
Если цель получить в результате конверсии СПО, то миграция с языка C# (с лицензией MIT) на Python (имеющий более ограниченный по сравнению с MIT лицензию BSD) или на Java (вирусная GPL или проприеритарная JCP) выглядит довольно непоследовательно.
А что ещё более непоследовательно, так это использовать проприетарный тул для этого с закрытыми исходниками и с непонятной лицензией.
GlukKazan
13.09.2018 15:34А в чём глубинный смысл невыкладывания на GitHub исходного кода, при том, что:
система бесплатна для некоммерческого использования
?KonstantinSmith Автор
13.09.2018 15:49Глубинный смысл в том, что она платна для коммерческого использования. Я без пропитания пока не могу… Хотя проект этот и второстепенный, но труда в него вложено немало. Жалко вот так вот сразу. Может, со временем и выложу.
GlukKazan
13.09.2018 19:10Вы для себя определитесь — или крестик или штаны. Для бесплатного использования в сообществе принято пользоваться открытыми продуктами. Это в общем-то даже важнее чем бесплатность. Для некоммерческого использования, разумеется.
KonstantinSmith Автор
13.09.2018 19:37Хорошо, если Вы не пользуетесь бесплатными продуктами без исходных кодов, то это пожалуйста. Значит, пока UniShapring Вам не подойдёт. Как и всем тем, кто не пишет на C# (он просто не нужен). Или тем, кто разрабатывает мощные GUI-приложения на C# (конвертация малореальна). Как и тем, заказчики кого удовлетворяются .NET. И т.д.
GlukKazan
13.09.2018 19:44Речь здесь не столько обо мне, сколько о вас. Вы не доверяете мне и боитесь что я буду использовать вашу интеллектуальную собственность в коммерческих целях. Имеете полное право. Но почему я должен доверять вам по поводу отсутствия закладок в закрытом коде? И что еще важнее. Понадобилось что-то чуть доработать. Да, можно поискать вас, заплатить денег и вы сделаете. Не вопрос. Но что если вас найти не удастся? Таким образом, ценность бесплатного инструмента без открытых исходников стремительно падает. Я понятно объясняю?
KonstantinSmith Автор
13.09.2018 20:01После столь развернутого объяснения стало чуть понятнее, спасибо.
Интересно, Вы в ресторане, скажем, просите официанта принести исходный продукт для его потребления? А то вдруг повар там что от себя лишнего добавит, а так всегда можно самому скомпилировать блюдо, или вдруг повар пропадёт и не сможет внести изменение, ищи его потом свищи… А автомобиль — с описанием технологии, вдруг потребуется что-нибудь исправить.GlukKazan
13.09.2018 20:04Непродуманные аналогии опасны. Ваша позиция ясна. Дальнейшая дискуссия бессмысленна.
Suvitruf
13.09.2018 16:40Стыдно)
KonstantinSmith Автор
13.09.2018 16:50Странная реакция… Кстати, а Вы сами чем-нибудь поделились с народом на том же GitHub?
Suvitruf
13.09.2018 17:07Сначала добейся, да? (:
У меня в профиле есть ссылка на GitHub профиль. Можете сами посмотреть.KonstantinSmith Автор
13.09.2018 17:43Да, посмотрел, круто! Какому можно бросить камень упрёка, принимается.
GlukKazan
13.09.2018 19:11KonstantinSmith Автор
13.09.2018 19:40Впечатляет, снимаю шляпу!
> Вы для себя определитесь — или крестик или штаны
Крестик и штаны пока оставлю.GlukKazan
13.09.2018 19:45Ваше право. Вы отпугиваете потенциальных пользователей (не меня), только и всего.
igormich88
15.09.2018 01:25А сам UniSharping написан на C#? Может ли его исходный код быть конвертирован в Java или Python?
Suvitruf
.zip архивы на github — это что-то новое. Ну, хоть не Яндекс.Диск.
sergey_kzn
Так это даже не zip, а переименованый rar
KonstantinSmith Автор
Спасибо! Действительно rar (делал архив через rar.exe, и по простоте душевной думал, что с расширением zip он и сделает zip). Переделал, теперь всё как надо.