Вверх ↑
Ответов: 9906
Рейтинг: 351
#1: 2009-05-03 20:30:32 ЛС | профиль | цитата
AlexKir писал(а):
Кстати рекомендую обратить внимание именно на Форт .
ИМХО этот язык просто создан для НиАсма !
Компактность кода может соперничать с чистым ассемблером!
Наращиваемость конструкций (Гибкость!) не сравнятся ни каким другим языком.
Язык одновременно и высокого и низкого уровня и компилятор
и интерпретатор . (Есть возможность применения ООП)


В общем, на очередном витке познания получилось так, что решил проверить я эти понты
Ну и проверил. Обнаружил кое-что интересное для HiAsm в концепции этого, одного из старейших языков (более 30 лет).
Чем хочу и поделиться.
Вводные фразы, позволяющие хоть немного понимать написанное на Forth решил сам не писать: у фортеров в этом многолетний опыт
Вот "Слово" Автора SP-Forth (проект живой - последнее обновление в январе), и показавшееся мне разумным компромиссом между лаконичностью, и объемом вносимого понимания: http://www.forth.org.ru/~ac/rationale/forth.txt
А я скажу свое "Слово" в дополнение

Остановлюсь на своем понимании КАК ЭТО ПРОИСХОДИТ НА САМОМ ДЕЛЕ, хотя это конечно и особенности конкретной реализации SP-Forth.
Так вот, вся кодовая секция исполняемого файла (как самого spf4.exe, так и получаемого продукта), это просто набор структур данных (неопределенной длины), которые фортеры называют "словарными статьями". А "слово" - это структурная единица программирования в Forth, такая же, как процедуры, функции, и т.п. в других языках.
Все более или менее просто в этой "статье": несколько стандартных полей данных, далее имя этого слова (предыдущее поле определяет его длину), и далее - исполняемый код для этого слова. Адрес входа в этот код прописан в одном из предыдущих "стандартных полей".
Ну и все, по большому счету-то.
Например, в файле spf4.exe - EntryPoint PE-файла прямо указывает на начало секции .text, там стоит всего одна команда call на кодовую часть слова INIT (фортеры называют это xt данного слова), а после этой команды сразу и начинаются словарные статьи.
Т.е., сам компилятор у них состоит исключительно из "словарных статей", и каждая статья может быть переписана фортером заново. Абсолютно любая.
Чем они непрерывно и занимаются, кстати говоря, вместо того, чтобы дело делать - но об этом после

Это было, как устроено. Далее, как это работает...
Получило "слово" INIT управление, вызывает какие-то другие "слова". В общем выходит либо на ожидание ввода с терминала, либо осуществляет ввод с командной строки, и потом перейдет к терминалу, если результаты исполнения не закроют прогу раньше.
Ну хорошо, Вы ввели слово или последовательность таковых.
Оно, в ответ на это, берет и ищет введенное слово среди своих словарных статей, начиная с конца (с последнего введенного). Которые, в свою очередь, организованы как несколько словарей. Существует некий "контекст", определяющий как набор словарей, так и порядок поиска в них. И каждый словарь является связанным списком из словарных статей - одно из "стандартных полей" словарной статьи является указателем на предыдущую статью.
Не находит - неинтересный вариант, Черезов это все прекрасно описывает...
Ну предположим - находит. Дальнейшие действия зависят от режима, в котором пребывает: исполнение, или компиляция.
Если это исполнение, то предает управление в кодовую часть найденного слова - и всего делов.
Если компиляция, то значит уже создана новая словарная статья с "не закрытым концом" (какими словами - попозже), и в нее просто добавляется 5 байт кода команды CALL по адресу исполнения найденного слова. Следующее слово - еще один CALL, и т.д. - получается программа.
Это я рассказал самый простой случай компиляции (в последнее время они занимаются вопросами оптимизации, и могут нагенерировать какой-нибудь инлайн), но именно он передает смысл происходящего.

Вот и все, казалось бы, ничего особо выдающегося. Если бы не одна тонкость, которая была задумана Муром еще при создании языка, и именно она переводит его в разряд языков 4-го поколения, оставляя остальные язЫки на 3-м (тут есть и игра слов: Fourth - четвертый).
Внимание: слова в Forth могут иметь признак IMMEDIATE - немедленного исполнения.
В этом случае, при компиляции слова инструкция CALL не генерируется, а запускается код слова, как и при режиме исполнения.
Что получается: программист может написать код исполняемый не только в Run Time, но и в Compile Time. Код этой программной единицы не попадает в целевую программу, попадают результаты работы этого кода.
Более того, действия исполняемые как в Run Time, так и в Compile Time - написаны на одном и том же языке - в данном случае это Forth.
Такое действительно можно назвать следующим поколением - нет как-то таких языков.
Есть конечно предпроцессинг в C++, условная компиляция - грубо говоря, но это уж настолько убого, по сравнению с возможностями реального языка...
Обычно применяют многоярусные конструкции, типа такого: на первом этапе некий flex и bison (сами являющиеся результатами компиляции неких исходных кодов) долгими трудами преобразуют исходный код в код языка C, а тот уже делает из него целевую программу, которая и достигает исходной цели, которая имелась в виду, когда писались исходники для flex и bison.
Это я специально взял сложный пример, для демонстрации бессмысленности решения задачи "за один заход" классическими языками
Идеология Forth позволяет решать аналогичные (и более сложные) задачи без напрягов.
Самый простой пример - это конструкция IF ... THEN
В Forth, естественно, все наоборот: конструкции слева от IF оставляют на стеке данных некоторый логический флаг, и если он отличен от нуля, то выполняются все слова до THEN (или наоборот - пропускаются). Так вот, не существует такого Run Time кода, который бы пропустил не понять сколько команд. Это IMMEDIATE-слова. Ну скажем IF компилирует некую условную команду проца, оставляя не заполненным место для адреса перехода по условию. И запоминает где-то (в стандарте это названо системным стеком, а по жизни - да тот же самый стек данных во время компиляции) адрес команды в памяти в которой потом можно будет поставить необходимое смещение. И потом это смещение и поставится кодом слова THEN, которое тоже имеет флаг IMMEDIATE.
Вышеописанное, несмотря на свою элементарность, является вычислительным процессом, который происходит в процессе компиляции.
Аналогичное делают ну все компиляторы. Только в этих всех компиляторах повлиять на этот процесс программист никак не может.
Это как раз то, что определяет способ кодогенерации, и то, чем один компилятор 3-го поколения отличается от другого.
А в Forth - может. Может и эти слова переопределить по-другому, а может и новые более хитрые определить.
Вот когда я писал про "внутренний состав", то отметил, что ничего там нет, кроме словарных статей, любое из которых может переопределить прикладной программист.
Но ведь какие-то слова из этих и определяют Способ Кодогенерации.
Следовательно - прикладной программист может сделать себе ЛЮБОЙ способ кодогенерации.
Именно ЛЮБОЙ - вот такой вот фокус, при всей его простоте.

Немного про то, как оно переходит из режима интерпретации в компиляцию, и обратно.
Да нет ничего проще...
Режим, это просто глобальная переменная STATE, изменил ее с нуля на единицу, вот и вся проблема. На значение этой переменной реагирует (да те же IF ... ELSE ... THEN) то слово, которое и решает, чего делать с найденным в словаре.
Исполняем слова, введенные с терминала. Встречается слово ':' - его код и меняет STATE (помимо остального). Следующие слова теперь как бы компилируются, а не исполняются. Встречается слово ';' - оно IMMEDIATE, и выполняясь сразу, возвращает STATE в режим исполнения.
Т.е., как бы без понятия IMMEDIATE и не особо бы и универсальный язык сварился бы.

Еще одно замечание.
Forth обладает всеми признаками интерпретатора. Но, например, в этой конкретной реализации вообще нет виртуальной машины, всегда исполняется чистый код. Единственное общение с текстом (скриптом), это прием информации с терминала или из файла, и причем один раз. Как только принимается скрипт на компиляцию - он сразу же превращается в код, и вся текстовая информация (кроме имен слов) забывается навсегда.
Это "компилируется в код сразу" и придает св-ва интерпретатора - каждое слово всегда готово к исполнению.
Но исполняется уже откомпилированный код, в отличие от классического понятия интерпретатора.
И самое интересное, что это "всегда готов" неотделимо от возможности IMMEDIATE для слова.
Грубо говоря, из IMMEDIATE логически следует необходимость для слов находиться в состоянии "всегда готов" для исполнения. Ну действительно, даже если мы определили простое слово - но ведь следующей строкой мы можем определить IMMEDIATE-слово, которое будет использовать (имеет право) наше. А еще следующей - слово использующее предыдущее IMMEDIATE. И уже обязаны работать коды нашего как бы простого слова.

Получается, что если у нас язык 4-го поколения (пользователь своим скриптом может определить не только семантику исполнения, но и семантику компиляции -- если по научному-то), то он сразу же должен обладать и возможностями интерпретации.
Которые точней можно назвать возможностью "компиляции на лету".


Ну давайте теперь посмотрим на HiAsm.
Коды элементов пакета Delphi - определяют семантику исполнения методов элемента.
Редактор форм - режим интерпретации некоторой части схемы пользователя.
Скрипты FTCG - определяют семантику компиляции методов элемента.
Т.е., ко многим идеям Forth мы подошли параллельно И независимо.
И тех, кто понимает потенциальную силу технологии FTCG, обязательно должна заинтересовать концепция языков 4-го поколения.
Кстати говоря, в Forth Это сделано как-то красивее: и для семантики исполнения, и для семантики компиляции - один и тот же язык.

В общем, если бы в существующих реализациях Forth не было некоторых недостатков, я бы сразу сказал: этот язык сделан специально, чтобы быть базовым в HiAsm.
Кстати говоря, на их форуме тоже бродили идеи типа: "...ах как было бы хорошо, если бы была визуальная среда, генерирующая коды Forth, но делающая для пользователя все супер-понятным, все супер-очевидным, а программирование супер-легким". С нулевым результатом естественно. И изложение немного вольное, конечно...
Так какие же это недостатки...
А такие, что они все время делают Forth-систему, и у них почти не проработан вопрос целевой компиляции.
А пользователям, в основной своей массе, нужна конкретная программа, работающая в конкретном окружении (Ось например), а не сто пудовая Forth-машина.
Ну посмотрите, исходный скрипт сразу превращается в КОД (даже не в объектный модуль) и исходник забывается. Да, это необходимо в процессе разработки. Это оспаривать у меня нет намерений - это идеологическая особенность.
Но эта разработка кончается. Теперь есть лишь одно слово, с которого будет начинать исполнение целевая программа. И нам нужны лишь коды тех слов, которые вызываются из целевого (рекурсивно, естественно), и уж совсем не нужны коды слов немедленного исполнения. Они свое дело уже сделали, но, возможно, подключили использование некоторых других слов.
Но это совсем не вся Forth-система, не говоря уже о заголовках словарных статей.
Вот и попробуйте хотя бы просто перенести только нужные коды в новое место в целевом файле, если у Вас не объектные модули, а исполняемые коды. Тот же вопрос возникает при инлайне.
Без него результирующие коды смотрелись бы совсем глупо - одни CALL-ы впечатления об особом уме не производят.
И что удивительно - они это делают !!! Не поверите - дизасемблируют исполняемые коды и разбираются на лету где там места релокации.
И что еще более удивительно - коды отдельного сложного слова особо глупыми не смотрятся.
Верхом Дзена это конечно не назовешь, но, к примеру, FPC - рядом не стоял. Хотя, обойти FPC по качеству, нельзя называть какой-то особой заслугой.
Критерия инлайна у них вообще нет разумного - его и не может быть в идеологии "всегда готов". Критерий может быть разумным только при целевой компиляции, когда можно произвести анализ всего проекта.
А у них такой: если код слова длиной меньше 32 байт (можно изменить), то оно вместо CALL компилирует инлайн. Преодолевая офигенные трудности по определению точек релокации.

Минусы серьезные, но есть и некоторый плюс: это настолько простой компилятор, что редкий фортер не прошел мимо забавы - собрать свою собственную Forth-систему. Месяц Дзена, и ты уже выходишь на такой уровень (это вовсе не моя - это ИХ информация, и для среднего пользователя).
Много ли программистов на C могут похвастаться тем, что могут собрать свой C-компилятор, по качеству не хуже базового
Или, есть исходники FPC - сколько времени надо для изучения, чтобы выйти на уровень Авторов сего продвинутого языка, чтобы поправить хотя бы один из его глюков.

А идеи, как поправить вышеупомянутое безобразие в Forth, возникают почти сразу. Это действительно супер-универсальный язык, и в нем, даже теоретически - не может быть не разрешимых проблем.
Ну, например...
Термины семантика компиляции/исполнения - не мной придуманы, они просто введены в их стандарте.
В чем проблема: скомпилировавши слово сразу, и забывши его исходный текст (а для системных слов - и не имея такового), мы потеряли много полезной информации.
Предложение такое: иметь два кода для слова - для семантики исполнения (как сегодня), и отдельно для семантики компиляции. Первый код можно в любой момент получить применяя первый. И может оказаться, что этот первый никогда и не понадобиться.
Естественно, код семантики компиляции должен иметь возможность использовать некие глобальные характеристики проекта (например частоту использования данного слова, адрес по которому компилируется ЭТО, и т.п. -- вплоть до раскладки регистров в технологии rubber-stack для данных)
Понятно, что при компиляции, генерируется только второй код.
Первый - при первом использовании слова на исполнение.
Ну а целевая компиляция - использует только семантики компиляции слов, начиная с нуля, но имея ВСЮ информацию о целевом проекте.
Получится, что коды семантики компиляции являются заменой объектных модулей в классическом программировании.

Кстати, еще раз о похожести идей... Сравните с этим:
Dilma, вот смотри, какая работа впереди.
Это если мы хотим сделать революционный продукт

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

При этом наши отличия от других систем компилирования отличаться будут действительно радикально.
Что имеют никсоиды. Набор неких объектных кодов, которые сшивает линкер. Мы вместо этой толпы O-файлов (ах да, они могут объединяться в библиотеки ), будем иметь исполняемые коды, генерирующие части кода, которые будут сшиваться нашей виртуальной машиной.
Надо ли говорить о потенциальных возможностях этого подхода в качестве результирующего кода - даже не знаю...

Мне представляется это качественным скачком, достойным называться революционным
И мы де-факто уже пошли по этому пути.
А никсоиды будут эту O-технологию еще лет 20 двигать. Эволюционно. И аргументировать: "без этого вообще ничего бы не было"



Ну вот, свое "Слово" сказал... А что делать, еще и сам не определился
карма: 9

1
Голосовали:Assasin