Haxe: конвертируем исходный код

    Haxe — очень удобный и практичный язык, но маленькое сообщество и, как результат, небольшое количество библиотек заставляют меня немало времени тратить на подготовку «заголовочных файлов» для интеграции open source библиотек в haxe. Немного об этом языке и о путях преобразования исходного кода на разных языках мне бы и хотелось рассказать ниже.

    С языком программирования haxe (тогда ещё его звали haXe) я познакомился около трёх лет назад и с тех пор мы не расстаёмся. Т.к. этот язык мало освещён на Хабре, то для начала — о haxe «in a nutshell», как поётся в известной песне.

    О haxe в двух словах


    Для тех, кто незнаком с haxe, чуть-чуть вводной информации:
    • синтаксис этого языка почти полностью повторяет ActionScript, который в свою очередь похож на JavaScript, но с типами данных;
    • жёсткая типизация, но с автоматическим выводом типов (для простых случаев);
    • отсутствие жёстко привязанной среды выполнения — компилятор лишь транслирует haxe-код в другие языки (сейчас поддерживаются: neko, php, javascript, flash/actionscript, c++, java, c#; на подходе также python);
    • очень быстрый компилятор.

    Как и другие языки, haxe не является серебряной пулей и, как мне кажется, есть две основные области, где он полезен:
    • написание мультиплатформенных приложений (здесь стоит упомянуть библиотеку для разработки игр OpenFL);
    • написание сложных js-приложений (т.к. их написание сразу на js проблемно ввиду отсутствия типизации).

    Конвертируем код


    Пути для преобразования исходного кода на одном языке в код на другом языке я вижу следующие:
    1. через построение полноценного дерева разбора (Abstract Source Tree = AST);
    2. через использование инструментов, умеющих преобразовывать исходные коды во что-то более простое (наподобие xml);
    3. «грубой силой» через использование регулярных выражений.

    Без сомнения, математически верный путь — первый, т.к. позволяет сделать всё аккуратно и, в идеале, получить на выходе сразу компилируемый текст на другом языке. Минусы — полноценный разбор сложен, чувствителен к деталям. Почитать про построение AST-деревьев можно в литературе по компиляторам (см., например, Ахо А., Сети Р., Ульман Дж., Лам М. — Компиляторы. Принципы, технологии, инструменты).
    Второй путь возможен только при наличии подходящих утилит для исходного языка. Автору доводилось использовать yuidoc при написании генератора haxe-обёртки для популярной js-библиотеки easeljs, благо последняя хорошо документирована.
    Третий путь — через обработку регулярными выражениями — относительно прост, хотя и требует «доводки напильником» результирующего кода. Именно об этом варианте пойдёт речь ниже.

    Окей, регулярка, конвертируй!


    Регулярные выражения имеют огромный, на мой взгляд плюс — быстро пишутся и, всего лишь, пару минусов:
    • в принципе не могут разобрать вложенные (рекуррентные) структуры (с произвольным уровнем вложенности);
    • тяжело читаются (а для больших выражений — и не менее тяжело пишутся).

    Первый недостаток, как показала практика, для большинства языков не очень критичен, особенно если нам не нужно полноценное преобразование, а нужно лишь «выдёргивание» заголовков классов и методов. Второй же можно частично обойти введя константы, хранящие небольшие кусочки регулярных выражений и позволив использовать их для написания более сложных конструкций.

    В результате приходим к наборам правил преобразования, где есть два вида этих самых правил: объявления констант и регулярные выражения для поиска/замены. Вот фрагмент файла правил для преобразования из c# в haxe:

    ID = \b[_a-zA-Z][_a-zA-Z0-9]*\b
    LONGID = ID(?:[.]ID)*
    TYPE = LONGID(?:[<]\s*LONGID(?:\s*,\s*LONGID)*\s*[>])?
    
    // "int[]" => "Array<int>"
    /(TYPE)\s*\[\s*\]/Array<$1>/
    
    // "int v" => "var v:int"
    /(TYPE)\s+(ID)/var $2:$1/
    


    Дело остаётся за малым — написать инструмент, который бы принимал на вход файлы с исходными текстами и файл regex-правил, а на выходе выдавал бы файлы с результатом применения этих правил. И такая утилита была написана (refactor). Ниже я приведу немного кода, чтобы показать (я надеюсь) простоту и лаконичность языка haxe.

    Рассмотрим код класса, читающего файл с правилами, разбирающего — где константы, а где регулярки, и строящего массив регулярных выражений для последующего преобразования исходных файлов:

    import stdlib.Regex; // используем класс Regex из библиотеки stdlib, т.к. стандартный EReg недостаточно умный в смысле замены
    import sys.io.File;
    
    // using ниже подмешивает static-методы класса StringTools ко всем строкам;
    // например, у класса String нет метода replace();
    // мы могли бы писать StringTools.replace("abc", "b", "z"), но благодаря using можем писать "abc".replace("b", "z");
    // однако, всё это лишь сахар - при компиляции сгенерируется код с обычным вызовом static метода
    using StringTools;
    
    class Rules
    {
    	// здесь (default, null) говорит о том, что мы объявляем переменную,
    	// которую позволительно читать извне (default), а вот менять - нельзя (null);
    	// бывает ещё "never" - когда нельзя делать операцию не только извне, но и внутри класса
    	public var regexs(default, null) : Array<Regex>;
    	
    	public function new(rulesFile:String)
    	{
    		var text = File.getContent(rulesFile); // тип для text будет выведен автоматически
    		
    		regexs = [];
    		
    		var lines = text.replace("\r", "").split("\n"); // тип данных для lines не указываем, компилятор выведет сам
    		var consts = new Array<{ name:String, value:String }>(); // также тип данных легко выводится автоматически
    
    		// for в haxe только такой - в формате foreach;
    		// перебрать числа от 0 до 9 можно так: for (n in 0...10)
    		for (line in lines)
    		{
    			line = line.trim();
    			
    			if (line == "" || line.startsWith("//")) continue;
    			
    			var reConst = ~/^([_a-zA-Z][_a-zA-Z0-9]*)\s*[=]\s*(.+?)$/; // регулярка для детектирования константы
    			
    			if (reConst.match(line))
    			{
    				var value = reConst.matched(2);
    				for (const in consts)
    				{
    					value = replaceWord(value, const.name, const.value);
    				}
    				consts.push({ name:reConst.matched(1), value:value });
    			}
    			else
    			{
    				for (const in consts)
    				{
    					line = replaceWord(line, const.name, const.value);
    				}
    				regexs.push(new Regex(line.replace("\t", "")));
    			}
    		}
    	}
    	
    	// метод меняет константу на её значение
    	static function replaceWord(src:String, search:String, replacement:String) : String
    	{
    		var re = new EReg("(^|[^_a-zA-Z0-9])" + search + "($|[^_a-zA-Z0-9])", "g");
    		
    		// map() ищет в строке src по регулярному выражению
    		// и меняет найденное на результат выполнения функции, переданной ему вторым параметром
    		return re.map(src, function(re)
    		{
    			return re.matched(1) + replacement + re.matched(2);
    		});
    	}
    }
    


    Заключение


    Автор уже три года использует haxe для написания web-приложений. Это здорово: возможность писать код клиента и сервера на одном языке + строгая типизация + синтаксис, близкий к js — всё это очень радует.

    Созданный инструмент refactor упростил интеграцию haxe-кода со сторонними библиотеками. Например, недавно с его помощью была создана обёртка для js-библиотеки threejs.

    Надеюсь, мне удалось вас заинтересовать если не языком haxe, то хотя бы подходом к обработке исходных текстов программ. Ведь при помощи этого простого метода можно не только конвертировать программы с языка на язык, но и просто делать текст программы красивым (beautify).
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 24

      0
      Спасибо за статью, к сожалению не могу голосовать.
      Если, честно заинтересовался, так как приходилось использовать ActionScript. Но есть ряд вопросов, один из которых доступные среды разработки, а второй производительность.
        +1
        1) Любой продукт из серии JetBrains
        2) В зависимости от компилятора\конечногого языка и платформы
          0
          «Родная» среда разработки — FlashDevelop. Полная поддержка автодополнения через вызов компилятора, которая на данный момент ещё и работает довольно быстро даже для больших проектов. Из минусов — нет дебаггера. Производительность, как и сказал SerafimArts, зависит от целевой платформы. Практика показала, что код для C++ не очень эффективный (слишком много обвязки и допущений — например, int-ы могут хранить и null — отсюда невозможность использования простого типа данных), а вот код для других платформ очень даже неплох.
            +1
            а вот код для других платформ очень даже неплох.

            За исключением JS. Помнится была демка с «мячиками» прямо в каталоге с примерами и для отрисовки каждого мячика использовался отдельный канвас, а поворот и всякие издевательства осуществлялись средствами CSS.

            Но это было около года назад, как сейчас дело обстоит — не в курсе, может быть что-то поменялось.
              0
              Вы сейчас говорите про библиотеку OpenFL — да, использование её для HTML5-игр пока не оправдано, вы правы. Я лично склоняюсь к использованию easeljs, например.
                0
                Да, верно, скорее всего именно OpenFL.

                Увы, любовался хаксом довольно продолжительное время назад и своего рода полюбил, по крайней мере синтаксис — он великолепен, даже для человека (меня), приверженца динамических языков со слабой типизацией. Решил подождать когда компиль станет код покачественнее генерировать и попробовать запилить какую-нибудь игрушку (именно по этому скорее всего OpenFL тогда смотрел).
                +2
                А сам haxe, как язык, тут совершенно не при чем. Это лишь проблема тех, кто реализовал на нем таким образом эти мячики.
                  0
                  Мячики были представлены в виде объекта спрайта. Есть варианты как сделать это по другому, чтоб openfl при компиляции не делал тучки html тегов?
                    +1
                    Вариант 1. Не использовать OpenFL. Для canvas в самом деле есть очень много фреймворков, а haxe, как я понял, может работать с ними всеми.
                    Вариант 2. Залезть в OpenFL и переписать. Не зря же он Open, в самом деле.
                      +2
                      Вариант 3. Использовать OpenFL, но в качестве бэкенда (OpenFL поддерживает альтернативные бэкенды) для трансляции в html5 использовать bitfive

                      Вариант 4. Использовать Flambe — на момент это возможно лучшее решение для создания html5 игр на haxe.
                0
                Лично для сейчас меня фаворитом является VSCode.
                Со списком самых популярных можно ознакомиться тут: haxe.org/documentation/introduction/editors-and-ides.html
                По поводу генерации С++. Теперь используется как-раз простой тип.
                И при попытки присвоение null к int haxe выдаст ошибку.
              0
              Не смотрели в сторону Typescript? На нем теперь тоже можно писать и клиентский, и серверный код со строгой типизацией, практически идентичный JS.
                +2
                TypeScript основан на JS, в том время, как Haxe, вроде как, можно транслировать во множество языков.
                  +1
                  Да, так и есть, но это довольно сомнительный плюс. Я слабо представляю себе проект, в котором возможность в любой момент сменить целевую платформу важнее, чем обилие и качество готовых библиотек под конкретный язык — а под Haxe их, смею предположить, минимум на порядок меньше чем под PHP или JS.
                    +2
                    Какая-то кроссплатформенная разработка, к примеру)
                      0
                      Думаю, что для ряда случаев вы правы, однако могу привести контпример: пару лет назад написали с другом ряд онлайн-игр на haxe с компиляцией в php, а через некоторое время перекомпилировали их в neko. В результате смогли снизить расходы, избавившись от половины серверов.
                    +1
                    Недавно смотрел. Использовал файлы *.def.ts для генерации аналогичных обёрток для haxe. Синтаксис haxe мне больше нравится.
                      +2
                      Плюсую к синтаксису. Очень удобный и практичный язык, очень легко осваивается (по крайней мере после пыха, шарпа или джавы).
                    +2
                    компилятор лишь транслирует
                    Так и говорите — транслятор.
                      0
                      Решил не нарушать традицию: в англоязычной документации везде используется «compiler». Кроме того, haxe всё же имеет встроенную поддержку компиляции в байт-код машины nekovm.
                        0
                        интересно, а он не умеет компилироватся в байткод java?)
                        0
                        Так ведь изначально он и был только компилятором. Первой целью была именно компиляция в байт-код swf. И только вторым добавился js, в который уже нужно было транслировать. И до сих пор swf — один из самых взрослых таргетов, так что не nekovm единым.)

                    Only users with full accounts can post comments. Log in, please.