Pull to refresh

Comments 28

Спасибо за подробный и полезный материал! Имею возражение только по списку сравнения из раздела про «велосипед».
Есть такой язык – Haxe. И там практически все это есть из коробки. И макро апи для работы с AST, и Printer который прямо в консольку напечатает кусок AST в виде уже готового кода. И таргетов намного больше. И сурсмапы из коробки работают.
Вот так, например, выглядит макрос, который в любой, отмеченный метой, класс
– докинет интовое поле
– если есть метод main(), то докинет в него вывод приветствия
– выведет все поля класса в виде кода в консоль во время компиляции.

public static function macroTrace() {
        var fields = Context.getBuildFields();

        for (f in fields) {
            switch f {
              case {name:"main",  kind:FFun({ expr:{expr:EBlock(exprs)}})}: 
                  exprs.push(macro trace("hi"));
              case _:
            }
        }

        fields.push(
            {name:"genVar",
            kind:FVar(macro:Int, macro 5),
            pos:Context.currentPos()
            }
        );
        var p = new Printer();
        for (f in fields) {
            trace(p.printField(f));
        }
        return fields;
    }


Поиграться можно здесь. Код макроса на вкладке Source 2, вывод полей – Compiler output.

Haxe, конечно, классный язык, но в качестве DSL он слабо подходит. Для обычного пользователя он слишком сложен. Для анализируемых конфигураций он слишком привязан к Haxe. А возможности AST ограничены лишь моделью Haxe.

Ну, если пользователю не показывать всякие макросы, то на уровне прикладного кода он довольно прост. Если DSL ориентированы вообще для не-программистов, то преимуществ у Haxe, конечно, будет меньше. Тем не менее, если даже написать свой макро, который парсит произвольный язык и строит Haxe-AST, то мы сразу получаем возможность выгонять его на кучу таргетов – это все равно хорошее подспорье, я считаю. Поскольку в языке представлено достаточно много концепций, то выразить в терминах его AST можно очень многое, хоть оно и не «абстрактное».
По крайней мере, под определение «пайплайна здорового человека» он подходит больше, чем все конкуренты из статьи, поэтому быть упомянутым в качестве альтернатив он достоин.
Что то мало решённых задач с ресурса rosettacode.org на Haxe (всего 52-а решения)

Если нужен «экстремально» простой и гибкий язык программирования, то даже в качестве DSL — Domain Specific Language применяют Forth (Форт).
При программировании микроконтроллеров и зачастую как основной инструментарий.

Была и статья на Хабр Универсальный DSL. Возможно ли это?
где Форт рассматривался в качестве DSL языка C# проекта.

Вот и ещё пример — EFrt проект полной реализации Форт на C#

P.S. Конечно же, «поверх» JavaScript JeForth.3we — Форт тоже делают

И на Github достаточно ещё проектов по связке Форт и JS

image

Mako.JS — Демо игры проекта запускаемые в браузере, с Форт в качестве DSL
Mako on Github

image

Forth имеет постфиксную нотацию, что для людей крайне не удобно. Но её легко превратить в префиксную, которую воспринимать уже гораздо легче.

Например, возьмём эту программу на форте:

25 10 MULT 50 SUM LOG2

И напишем эквивалентную в префиксной нотации:

LOG2 SUM
    MULT
        25
        10
    50
Да, это и другие возможности доступны и при использовании Форт, но если бы только специфичность Форт была только в постфиксе, то это был бы не Форт, а что то близкое к калькуляторному языку MK-61 и вряд ли Форт, на протяжении 50-ти лет своего существования смог бы войти тогда в современный топ 50-ти языков программирования по версии IEEE Spectrum

P.S. Кстати, страшилки по поводу неудобства «формата» записи Форт программ немного преувеличена в отношении к нему и эта «страшилка Forth» не на первом месте по степени грамотного понимания и использования Форт языка, есть и другие, но серое вещество мозга достаточно гибкая система при её «переформатировании». Форт, как «искуственный» компьютерный язык, гораздо проще в освоении чем, например, иностранный естественный язык по поводу которого нет «предвзятых» взглядов.

В России Форт начался и так же быстро сощёл со сцены в мутные 90-е годы безвременья.
Баранов С.Н. «Язык Форт в СССР и России»

Да, надо ещё очень внимательно следить за содержимым стека. Ибо одно неверное движение, и программа ведёт себя непредсказуемым образом. С этим людям ещё сложнее управляться. Поэтому для тог же WASM кроме постфиксной нотации с ручным управлением стеком, есть и префиксная, где стеком управляет компилятор, а программист управляет лишь виртуальными регистрами.

Если следить за стэком становится следить сложно в Форт программе, то что то не правильно делается по его использованию. :)
Обычно происходят мелкие перестановки 2-3 элементов стэка и они не сложно понимаемы. Для других — «особо тяжёлых случаев» никак не ложащихся в алгоритм в Форт реализации можно использовать, например, локальные переменные появившееся в Форт стандарте 94года.

И, конечно, Форт, как язык нацеленный на использование метапрограмирования и DSL наиболее значимо себя проявляет именно при использовании таких техник его использования неплохо описанных во 2-й книге за авторством Броуди «Мышление Форт».

P.S. И, локально оттестировать поведение слова не сложно в Форт.

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

А, она в нём в целом не нужна т.к. на стеке лежат числа в размере 1-й ячейки хранения принятой в том или ином Форте 16/32/64 а возможность по типизации их предоставляется программисту по применению слов с разными именами ( +, D+ ...) оперирующими сколько, как и в каком размере оперировать ими на стэке.
Если, программист, по определению не даун, то это им легко должно легко пониматься, а если что пошло не так, то последствия его действий не заставят себя ждать (Форт упадёт и/или выведет информацию об эксепшене)
Для чисел плавающей арифметики есть отдельный стэк со словами подобными F+, F-…

P.S. Конечно, в более серьёзном Форт инструментарии может в процессе трансляции программы выводится и проверяться соответствие типов данных и операций применённых к ним, но обычно на это все использующие Форт обычно кладут «болт» и особо не парятся :)
Хотя есть варианты Форт -типа StrongForth где разработчики его стали контролировать типизацию в их Форт.

Если интересен более продвинутый «Форт», то есть например Factor — язык программирования. где на стэк помещаются уже более комплексные сущности чем числа.
Что то мало решённых задач с ресурса rosettacode.org на Haxe (всего 52-а решения)

А вы не могли бы продолжить мысль? Это по каким-то критериям должно влиять на принятие решения о выборе инструмента?
Если нужен «экстремально» простой и гибкий язык программирования, то даже в качестве DSL — Domain Specific Language применяют Forth (Форт).
При программировании микроконтроллеров и зачастую как основной инструментарий.

Применять-то применяют совершенно разное. И выбирают по совершенно разным критериям, в зависимости от ситуации. Я бы предположил, что если бы была возможность оценить количество всего, что можно посчитать DSL, то в «наколенных» условиях, коих большинство, победили бы JSON/XML, так как для них вообще не надо задумываться о лексерах-парсерах-синтаксических деревьях, все инструменты для разбора доступны с любого языка/платфомы.
Была и статья на Хабр Универсальный DSL. Возможно ли это?
где Форт рассматривался в качестве DSL языка C# проекта.

Посыл автора я не совсем понял. Особенно вот эту сентенцию
Из-за отсутствия избыточных конструкций всего несколько строк на DSL могут реализовать довольно сложный функционал

Я не понимаю, как ограничение доступных средств может обеспечить возможность более короткой записи логики. На самом деле, здесь происходит подмена понятий, а «выразительность» достигается комплектными командами. При предоставлении такого же апи, запись этой логики на «родительском» языке вряд ли будет сильно длиннее.
Кроме того, предлагается самостоятельно велосипедить с нуля интерпретатор. Это может оказаться хорошим решением в некоторых ситуациях, но вряд ли во всех. На месте пользователя DSL из той статьи я бы предпочел что-то на основе XML или хотя бы JSON.

Вообще, когда речь заходит про «запилить по-быстрому язык для своей системы с ноля», то в первую очередь в голову придет лисп из-за простоты и надежности. Хотя он далеко не самый юзер-фрэндли для пользователя.
В других ситуациях хорошим решением будет встроить в систему каую-нибудь lua, выствив для нее нужный апи. Это уже и не совсем DSL, но области применимости могут пересекаться.
А вы не могли бы продолжить мысль? Это по каким-то критериям должно влиять на принятие решения о выборе инструмента?

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

Сентенция с Форт, в общем понимании его как языка программирования приводит к возможности записи каких то «макросов» близко по форме к естественным языкам.

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

т.е., наши привычки и освоенные взгляды — наши же «оковы» из «недоступности» понимания, что пути решения задач этим не ограничивается.

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

Форт ещё проще т.к. в нём нет скобок в Лисп понимании и он прямолинеен по последовательности выполнения слов при трансляции кода программы в исполнение.
На уровне трансляции Форт программ в базисе Форт нет даже понятия синтаксиса языка, кроме того, что мы имеем дело со словами — самодостаточными сущностями во взаимодействии с другими словами и создающими свой контекст при их использовании для других слов.
Уф… :)
А сколько «типовых примеров» в штуках вам надо, чтобы сравнивать языки? 50 мало, я понял.
Если серьезно, то не вижу, какая разница 10 или 20 сортировок написано на языке для примера, они используют одни и те же конструкции. Гораздо эффективнее заглянуть на сайт языка, часто на главной странице есть выразительный пример. У Haxe кроме документации есть еще и весьма интересный кукбук.
Чем больше, тем лучше и желательно с их описанием
и если есть кукбук, то не должно (или должно) возникнуть сложностей с их размещением и в рамках этого ресурса и возможно со ссылками и в самой «Книге рецепов для IT повара на Hexe»
(или должно)

Не думаю, что кто-то будет возражать, если вы перенесете примеры из кукбука на розетту или куда-либо еще.
Как ни «странно», но на Хабр есть Хаб по Haxe («эпизодически» пополняемый статьями одним пользователем, в последнее время, и количеством подпичиков — 759)

P.S. Не являясь пользователем Haxe мне сложно перенести примеры из кукбоок на ресурс rosettacode.org.
Дополню предыдущее сообщение.
Спасибо за ссылки по Haxe. Поправлю ссылку на хаб, у вас лишняя кавычка затесалась, и поэтому она не работает. Этого видео не встречал раньше. Оно довольно интересное, но ему 8 лет, и оно очень сильно отстало от реалий. Neko VM сейчас deprecated, на ее место пришли HashLink и режим интерпретатора. Но для сервера нет смысла тащить отдельную ВМ, так как сейчас haxe транспилируется и в php, и в питон, и в JVM-байткод (а также в js, cpp, c#, lua), так что haxe-код можно выполнять в рамках существующих серверов в совершенно разных стеках.
А, есть примеры транспиляциии в Haxe из других языков?
Или это «сложно/нецелесобразно»?
Такие примеры есть, но не очень много. В первую очередь, это транспиляция из ActionScript3 – это позволяет относительно «дешево» переносить проекты с умершего флэша на актуальные платформы. Насколько я знаю, у компании достаточно обширная кодовая база на AS3, которую они продолжают развивать. Haxe используется как промежуточный шаг при компиляции веб-клиета. Было несколько попыток для тайпскрипта, например, а также для go. Об их функциональности и работоспособность я ничего не знаю.

А как у него с, собственно, сорсмаппингом при трансляции в другие языки?

Сорсмапы в чистом виде поддерживаются для двух таргетов: Js и PHP. Но сорсмапы имеют смысл, когда они поддерживаются инструментарием целевого языка. Помощь в отладке в том или ином виде есть на всех таргетах, то есть стектрейс по haxe-исходникам получить можно. Для Hashlink и Cpp, насколько я помню, дебаггер работает нормально и позволяет в VSCode перемещатсья по исходникам из стектрейса, смотреть переменные, ставить брейкпоинты. HashLink позволяет транслировать в С. В плюсовом коде маппинги вписываются непосредственно в транслированный код, выглядит это как-то так:

Фрагмент оттранслированного в cpp класса
HX_LOCAL_STACK_FRAME(_hx_pos_cf91c14e89d01160_8_writeInt8Attribute,"MeshUtils","writeInt8Attribute",0xc120e75e,"MeshUtils.writeInt8Attribute","MeshUtils.hx",8,0xfe3ad45a)
HX_LOCAL_STACK_FRAME(_hx_pos_cf91c14e89d01160_24_writeFloatAttribute,"MeshUtils","writeFloatAttribute",0xf3619395,"MeshUtils.writeFloatAttribute","MeshUtils.hx",24,0xfe3ad45a)

void MeshUtils_obj::__construct() { }

Dynamic MeshUtils_obj::__CreateEmpty() { return new MeshUtils_obj; }

void *MeshUtils_obj::_hx_vtable = 0;

Dynamic MeshUtils_obj::__Create(::hx::DynamicArray inArgs)
{
	::hx::ObjectPtr< MeshUtils_obj > _hx_result = new MeshUtils_obj();
	_hx_result->__construct();
	return _hx_result;
}

bool MeshUtils_obj::_hx_isInstanceOf(int inClassId) {
	return inClassId==(int)0x00000001 || inClassId==(int)0x48150a24;
}

void MeshUtils_obj::writeInt8Attribute( ::data::AttribSet attrs, ::haxe::io::Bytes bytes,::String attName,int firstVert,int vertCount, ::Dynamic provider){
            	HX_STACKFRAME(&_hx_pos_cf91c14e89d01160_8_writeInt8Attribute)
            	HX_STACK_ARG(attrs,"attrs")
            	HX_STACK_ARG(bytes,"bytes")
            	HX_STACK_ARG(attName,"attName")
            	HX_STACK_ARG(firstVert,"firstVert")
            	HX_STACK_ARG(vertCount,"vertCount")
            	HX_STACK_ARG(provider,"provider")
HXLINE(   8)
HXLINE(   9)		HX_VARI(  ::Dynamic,descr) = attrs->getDescr(attName);
HXLINE(  10)		if (::hx::IsNotEq( descr->__Field(HX_("type",ba,f2,08,4d),::hx::paccDynamic),1 )) {
HXLINE(  11)			HX_STACK_DO_THROW(::haxe::Exception_obj::thrown(HX_("wrong type",8d,aa,d4,e5)));
            		}
HXLINE(  12)		HX_VAR( int,size);
HXDLIN(  12)		switch((int)(( (int)(descr->__Field(HX_("type",ba,f2,08,4d),::hx::paccDynamic)) ))){
            			case (int)0: {
HXLINE(  12)				size = 4;
            			}
            			break;
            			case (int)1: {
HXLINE(  12)				size = 1;
            			}
            			break;
            			case (int)2: {
HXLINE(  12)				size = 2;
            			}
            			break;
            			case (int)3: {
HXLINE(  12)				size = 4;
            			}
            			break;
            		}


Если речь идет об отладке прикладного кода, то великолепно работает связка flash+idea. Быстрая компиляция и очень удобный дебаггер от JetBrains. Когда я работал над юнити проектами, то стектрейсы из редактора тоже нормально мапились (там c#).

В качестве предупреждения: если хотите маппить стек трейс ошибок (например, из логов приложения) на исходники - забудьте по Safari как про страшный сон. Ну или помните - и игнорируйте.

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

Насчет js.tree. А как его расширять доп метаданными?

К примеру, для генерации сорсмапов нам для каждого узла AST надо знать позицию в исходном файле. Babel это записывает в поле range каждого узла. Как это будет выглядеть в js.tree?

Все tree-ноды имеют поле span, через которое можно получить координаты. Чтобы их увидеть в AST можно их заимпринтить, как тут. Это полезно при передаче между процессами, чтобы при сериализации не терялись ссылки на исходники и сами исходники.

В общем же случае, в любой узел можно подкладывать кастомный узел с метаданными. Например, добавим аннотации типов:

const
	foo @ [,] any
	[,]
		@ [,] |
			number
			string
		123 @ number
		bar @ string

Просто нужно не забыть эти узлы вырезать перед трансляцией в JS.

Как вводная часть статьи выглядит для ничего не подозревающего человека:

Сейчас вы видите законченное приложение на $shmol:

$my_app

Оно состоит из панельки, внутри которой расположен чекбокс. И вместе они связаны двусторонней связью по заданным свойствам. В этой 1 строчке кода есть даже поддержка локализации. Эквивалентный код на JavaScript занимает в 25 раз больше места.

@nin-jin, у вас есть подробное описание того, как работает hack()? Из примеров и ссылок на tree.hyoo.ru не совсем понятно как интерпретируются наборы скобочек со знаками препинания и как происходит поиск и подмена узлов в исходном дереве. Можно ли описать трансформацию нескольких узлов в дереве по шаблону, например поменять местами узлы сохраняя все их листья как есть.

Проще, наверно, просто код глянуть. Хак проходится по всем дочерним узлам, смотря на их типы. Если для типа задан хендлер, то заменяет узел на тот список узлов, что что он вернёт. Дефолтный хендлер возвращает клон узла, применяя хак к каждому ребёнку. Таким образом, если не задать никаких хендлеров, то хак просто склонирует всё дерево. Ну а в кастомном хендлере можно делать что хочешь. Можно вернуть поддерево как есть без какого-либо процессинга, создавать новые деревья, хачить сторонние деревья и тд.

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

Sign up to leave a comment.