UniSharping: конвертирование кода C# в Java и Python

    Введение


    С 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 (с моей помощью) — работа ещё та. Но процесс сходящийся, в конце которого проект ожидает бонус "кросс-программности".

    Поделиться публикацией
    Комментарии 61
      +4
      .zip архивы на github — это что-то новое. Ну, хоть не Яндекс.Диск.
        0
        Так это даже не zip, а переименованый rar
          –1
          Спасибо! Действительно rar (делал архив через rar.exe, и по простоте душевной думал, что с расширением zip он и сделает zip). Переделал, теперь всё как надо.
        +5
        Ограничений 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 еще я могу понять, и то проблем куча.


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

          0
          1. При переводе некоторых чужих проектов я сталкивался с парой десятков yield, и они прекрасно переделывались на List. Хотя можно использовать #if JAVA вариант без yield… #else вариант с yeild #endif Да, есть пример бесконечного цикла, ну тут увы.
          2. struct — в документации я подробно описал, что их не запрещено использовать, они переводятся в class, но есть нюансы при присваиваниях. Немного помучившись, в своём проекте я вообще отказался от struct. И что удивительно, на скорость выполнения это никак не повлияло, а я думал, что их использования в моём случае даст выигрыш. Так что можно использовать.
          3. Атрибуты не поддерживаются (игнорируются) — ну да, чем то придётся пожертвовать
          4. Reflection есть в небольшом объёме, который требуется мне (получить тип объекта, для типа список конструкторов, вызвать конструктор). Но этот список расширяем путём внешних настроек. Невозможно сразу съесть слона (системные классы). Помогите нам — выберите какую-нибудь полезную функцию C#, найдите аналог в Java и опишите.

          Да, приходится за всё платить, и мне для кросспрограммируемости в своём проекте Pullenti пришлось кое что переделать. Например, отказаться от плагинного способа загрузки dll с анализаторами обработки текста. Но это мелочи.
            +2

            Хотел посмотреть на ваш проект, но исходников открытых я так понимаю нет. Судя по тому, что я понял из демонстрационного проекта, в случае 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, что ставит под вопрос трансляцию кода даже с малой долей параллелизма — фактически при текущих ограничениях любые длительные операции (обращение к БД, вычисления, веб реквесты), которые занимают какое то реальное, ощутимое время, транслировать невозможно. А это действительно минус, увы.

              +1
              1. List всё-таки совсем не замена yield, так как возвращает данные по мере поступления (вычисления или получения из сетевого соединения) и специально создан для тех случаев, когда коллекция целиком может не влезть в память. Непосредственно ключевое слово yield встречается редко потому, что он обёрнут низкоуровневыми драйверами баз данных и прочим linq, которые возвращают IEnumerable, внутри которых ленивая подгрузка данных.

              2. Раз struct безусловно переделывается в класс, означает ли это что default(T) работает некорректно? Это достаточно важно, так как и решарпер, и ms code analysis просит убрать ненужную проверку на null в структурах, так как там в принципе не может быть null, а без правильно работающего default, отсутствие null гарантировать уже сложнее
                –1
                1. Ну конечно есть случаи yield, которые нельзя смоделировать List- ом, кто же спорит. Но их обычно не много, и неужели ради нескольких таких мест стоит отказываться от предлагаемой возможности?
                2. default(T) для структур в Java преобразуется в new T(). Он во основном нормально работает, но если где то встретилось a = b для структуры, а потом поменялись поля b, то у а они тоже поменяются, так как для классов это один объект. Если таких ситуаций нет или делать a = new(b) (кстати, вроде UniSharping такие ситуации пытается отслеживать вызывать new, я уже не помню — отказался от структур), то всё будет работать.
                  0
                  Это называется не «ньюансы при присваиваниях», а «структуры не работают». Конвертер должен либо вставлять операцию клонирования самостоятельно, либо выдавать ошибку.
              +3

              Можно взять Bridge.NET, скомпилировать C# в JavaScript, а уже его заэмбеддить в Java/Python. Есть большое подозрение, что процесс будет менее болезненным, чем применение представленного транслятора. Ибо у Bridge.NET полная поддержка самого C#, включая семантику структур, а портированный на JS набор класс BCL достаточно богатый, да и рефлексия работает целиком (хоть и увеличивает размер бандла, если её включить).

              0
              Ограничений U# в конструкциях языка оказалось немного – это атавизмы goto и case goto, а также yield, не моделируемый адекватно в автоматическом режиме.

              async/await, похоже, тоже не поддерживается.
              А вот это уже критично.
                +1
                Скажете — поддержим. Важно, чтобы «интерес был не праздным». Если есть реальная потребность такого перевода, то можем обсудить доработки. А если просто так, то да, пока не поддерживается.
                  –1
                  У меня интереса никакого к этому проекту нет.
                  Просто, кажется, что async/await — это одна из самых важных фич в C# на данный момент.
                  В Python, кстати, она тоже есть.
                    0
                    Спасибо за комментарий, конечно, в ближайшее время поддержим (а для Java смоделируем). Я уже даже выложил 1.2, где async\await корректно обрабатываются. Фича очень важная, просто мне пока не была нужна, поэтому и оказалась нереализованной. Она не входит в множество принципиальных ограничений U# (как goto или unsafe), просто руки не до всего ещё дошли.
                      0
                      дубль
                      +1

                      Проще работать с байткодом CLR, чем с исходниками.
                      async/await и прочие новые возможности языка являются сахаром и в MSIL не существует.


                      Бонусом пойдет поддержка других .NET языков

                        0
                        Ну может. Поддержать J# — это круто! Интересно, сколько человек в мире его использует, хотя бы 1K наберётся?
                          0
                          Речь ведь не только и не столько о J#, сколько о VB.NET и F#
                            0
                            Да, пожалуй. Хотя подозреваю, что по сравнению с C# программистов на других шарпах сильно меньше. Это можно оценить по количеству книг в крупных магазинах, посвящённых тому или иному языку. На F# я не встречал ни одной, по VB# может одна, а остальные 10-15 по C#. По крайней мере, так было год назад. Поддержать VB# в UniSharping — вопрос пары дней, если будет проект, то обсудим.
                              0
                              VB.NET ну очень распространён. Его преподают в старших классах и государственный с муниципальным сектором используют его достаточно широко (много вакансий).

                              Типичная, кстати, задача — перевод старого проекта c VB6 на VB.NET
                          –1
                          Какая разница?
                          Главное чтобы в итоге из кода на C# получался адекватный код на других яызках.
                          И AST как раз в этом плане работать гораздо проще чем с MSIL.
                      +2
                      В сторону Haxe не смотрели?
                        0
                        Одно из условий задачи — на входе проект C#
                          0
                          Ну так можно написать один конвертер из C# в Haxe, не?
                            0
                            Хорошая мысля приходит опосля… Да, буду иметь в виду, спасибо, поддержать Haxe!
                        +3
                        C# замечательный язык. Поэтому с моей точки зрения лучше реализовывать конверторы в C#. Чтобы как можно большему количеству людей можно было легко запрыгнуть на платформу /J

                        Ну или на крайний случай bi-directional, в этом случае можно прогнать программу в одном направлении, а потом обратно и сравнить результаты с оригиналом.
                          0
                          И если инструмент предназначается не для единовременного конвертирования, то будет очень полезно иметь hint-ы, с подсказками для явного использования своих конструкций вместо ваших и правил merge с предыдущей версией (например конвертировать только часть кода из файла и делать merge только этого кода).
                            0
                            Ну разумеется, у меня пока идеи исчерпались, ждём нормального проекта и предложений, чтобы продвинуться дальше. Идея с хинтами (комментариями своего формата) вместе с директивами препроцессора — очень мощный инструмент для этой задачи.
                            0
                            Так и я об этом же! Можно писать на C#, а получать код на нужном языке. Но только конвертер нужен не «в C#», а «из C#». Так как у ряда заказчиков требование СПО — критичное (см. конец статьи).
                              0
                              С моей точки зрения, «в» гораздо полезнее, чем «из». Более-менее большие проекты фактически не реально мигрировать 100% без дополнительных усилий, т.е. на принимающей стороне должны быть люди которые постоянно подпиливают изменения чтобы привести в «приемлемый» вид, поэтому кроме специфичных случаев, всё таки «в».
                                0
                                Усилия потребуются, но на стороне C#. А вот на приёмной стороне усилий не требуется никаких, поскольку генерируется сразу рабочий вариант. Когда я делаю очередной релиз Pullenti, то SDK на Java и Python получаются автоматом и не требуют никакого ручного участия (и сразу отгружаются скриптом на сайт). Мои коллеги по 3-м проектам на Java не дадут соврать…
                                  0
                                  Да я не против разнообразия в мире. Я писал что по моему мнению «в» полезнее для популяризации C#, но не претендую на истину.

                                  Кстати посоветовал бы добавить поддержку TypeScript, а из него автоматом в JS. Там потенциальная аудитория на порядок больше, плюс как раз реальные сценарии для использования.

                                  У нас были требования чтобы часть клиентского кода на разных языках была синхронизирована с backend в C#.

                                  Сделали на шаблонах T4, всё работало отлично. Плюс добавлялся функционал специфичный для платформы, не требуемый и не существующий на C#

                                  Поэтому я и говорил, что полезно было бы иметь возможность сказать, что для конвертирования берем только указанные файлы, из них только указанные классы и из некоторых классов, только указанные методы.
                                    0
                                    Такая возможность есть — можно игнорировать указанные namespace, а также использовать #if… #endif для отключения ненужных фрагментов. Раньше даже были поддержаны хинты типа //JAVA ignored для любых конструкций языка, но потом я это обрал — достаточно директив препроцессора.
                            0
                            > Ограничений U# в конструкциях языка оказалось немного – это
                            > атавизмы goto и case goto,
                            Конечные автоматы — бай-бай.

                            > а также yield, не моделируемый адекватно в автоматическом режиме.
                            Грусть-печаль… Работа с коллекциями, до скорых встреч.

                            > Не рекомендуется (хотя и можно) использовать struct,
                            Прощай быстродействие.
                              –2
                              Да, бедные Java и Python — они лишены всего этого богатства…
                                +1
                                А как же генераторы в Питоне?
                                  –2
                                  А генераторы в Питоне есть — там всё через генераторы (если имеется в виду generic). Любой метод def f(x, y) уже на генераторах — аналог void f<X, Y>(X x, Y y). Так что здесь конвертация C#->Python просто игнорирует генераторы C#, и всё работает. Но возникают другие проблемы (с одинаковыми именами) — всё это описано в документации.
                                    +1
                                    думаю, речь о методах типа такого:
                                    def F(x):
                                        while x > 0:
                                            x -= 1
                                            yield 1
                                    
                                      0
                                      Ого, не знал про такое в Python! На него я настраивался уже после Java, и не обратил внимание. Значит, yield для Python вполне возможен, и одним ограничением U# меньше, спасибо! А не найдётся ли что-нибудь для goto? ;)
                                        0
                                        Аналогов goto, к сожалению, в Python нет =(
                                          0
                                          Говорят, полезно погрузиться в область ;)
                                +1
                                Объяснить, что .NET Core тоже СПО не всегда получится, потому что "Microsoft".

                                Просто дичь. В целом идея интересная, а мотивация очень слабая.

                                  +2
                                  Вообще говоря, посыл, задаваемый как цель конверсии немного странный.

                                  Если цель получить в результате конверсии СПО, то миграция с языка C# (с лицензией MIT) на Python (имеющий более ограниченный по сравнению с MIT лицензию BSD) или на Java (вирусная GPL или проприеритарная JCP) выглядит довольно непоследовательно.

                                  А что ещё более непоследовательно, так это использовать проприетарный тул для этого с закрытыми исходниками и с непонятной лицензией.
                                  0
                                  А в чём глубинный смысл невыкладывания на GitHub исходного кода, при том, что:
                                  система бесплатна для некоммерческого использования
                                  ?
                                    –2
                                    Глубинный смысл в том, что она платна для коммерческого использования. Я без пропитания пока не могу… Хотя проект этот и второстепенный, но труда в него вложено немало. Жалко вот так вот сразу. Может, со временем и выложу.
                                      +1
                                      Вы для себя определитесь — или крестик или штаны. Для бесплатного использования в сообществе принято пользоваться открытыми продуктами. Это в общем-то даже важнее чем бесплатность. Для некоммерческого использования, разумеется.
                                        0
                                        Хорошо, если Вы не пользуетесь бесплатными продуктами без исходных кодов, то это пожалуйста. Значит, пока UniShapring Вам не подойдёт. Как и всем тем, кто не пишет на C# (он просто не нужен). Или тем, кто разрабатывает мощные GUI-приложения на C# (конвертация малореальна). Как и тем, заказчики кого удовлетворяются .NET. И т.д.
                                          0
                                          Речь здесь не столько обо мне, сколько о вас. Вы не доверяете мне и боитесь что я буду использовать вашу интеллектуальную собственность в коммерческих целях. Имеете полное право. Но почему я должен доверять вам по поводу отсутствия закладок в закрытом коде? И что еще важнее. Понадобилось что-то чуть доработать. Да, можно поискать вас, заплатить денег и вы сделаете. Не вопрос. Но что если вас найти не удастся? Таким образом, ценность бесплатного инструмента без открытых исходников стремительно падает. Я понятно объясняю?
                                            –2
                                            После столь развернутого объяснения стало чуть понятнее, спасибо.
                                            Интересно, Вы в ресторане, скажем, просите официанта принести исходный продукт для его потребления? А то вдруг повар там что от себя лишнего добавит, а так всегда можно самому скомпилировать блюдо, или вдруг повар пропадёт и не сможет внести изменение, ищи его потом свищи… А автомобиль — с описанием технологии, вдруг потребуется что-нибудь исправить.
                                              +1
                                              Непродуманные аналогии опасны. Ваша позиция ясна. Дальнейшая дискуссия бессмысленна.
                                      +1
                                      Стыдно)
                                        0
                                        Странная реакция… Кстати, а Вы сами чем-нибудь поделились с народом на том же GitHub?
                                          +2
                                          Сначала добейся, да? (:
                                          У меня в профиле есть ссылка на GitHub профиль. Можете сами посмотреть.
                                            –1
                                            Да, посмотрел, круто! Какому можно бросить камень упрёка, принимается.
                                              –1
                                              Это была невинная шутка, а не упрёк)
                                                0
                                                Прошу прощения, это я промазал.
                                            0
                                            Вы сами чем-нибудь поделились с народом на том же GitHub

                                            Это пойдёт?
                                              0
                                              Впечатляет, снимаю шляпу!
                                              > Вы для себя определитесь — или крестик или штаны
                                              Крестик и штаны пока оставлю.
                                                0
                                                Ваше право. Вы отпугиваете потенциальных пользователей (не меня), только и всего.
                                        0

                                        А сам UniSharping написан на C#? Может ли его исходный код быть конвертирован в Java или Python?

                                          0
                                          Да, может, и это один из тестов корректности

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое