Pull to refresh

Comments 44

Я вообще-то про Нельсона…
Форт… классная штука, мозги прочищает похлеще Пролога :) И при этому еще и вполне прикладная.
Не форт, но еще одна, более хардкорная стековая виртуальная машина. Если пойти по ссылкам можно узнать про механику происходящего. Особенно поражает генерация достаточно сложной музыки в одну строчку на Си. Вроде даже на хабре была статья.
http://countercomplex.blogspot.com/2011/12/ibniz-hardcore-audiovisual-virtual.html
вторая часть картинок не отображается ни в хроме, ни в сафари
Увы, forthsalon.appspot.com/ не пашет, видно, Хабраэффект. А вот редактор пашет. Вбиваем код, жмём Update. Проба пера:
(Хабр не знает сорцов Форта?)

: sq dup * ;
x 0.5 - sq
y 0.5 - sq
+
sqrt t sin *
: norm * sin 1 + 2 / ;
dup 40 norm
over 60 norm
rot 80 norm
Люблю Форт за DSL'и.
На нём даже хокку — прграмма:

: sq dup * ;
: norm * sin 1 + 2 / ;
: на x 0.5 - sq ;
: мёртвой y 0.5 - sq ;
: ветке + ;
: чернеет sqrt t sin * ;
: ворон. dup 40 norm ;
: осенний over 60 norm ;
: вечер. rot 80 norm ;

на мёртвой ветке
чернеет ворон.
осенний вечер.
Чуть улучшенный пример:

: sq dup * ;
: norm * sin 1 + 2 / ;
: tt t 10 mod ;
: xc tt 10 / ;
: в 0 drop ;
: вечерним x xc 0.25 + 0.75 > 1 * xc - abs 0.25 + ;
: вьюнком - sq ;
: я y 0.5 - sq ;
: плен + ;
: захвачен… sqrt t 60 mod sin * ;
: недвижно dup tt 10 * norm ;
: стою over 70 t 50 mod + norm ;
: забытьи. rot 80 norm ;

вечерним вьюнком
я в плен захвачен… недвижно 
стою в забытьи.
Хабр не знает сорцов Форта?

В общем случае все сводится вот к этому:
Скрытый текст
: INTERPRET ( -> ) \ интерпретировать входной поток
  BEGIN
    PARSE-NAME DUP   \ PARSE-NAME тупо перебирает символы из входного потока в поиске разделителя (пробел и меньше) и возвращает найденное слово
  WHILE
    SFIND   \ Ищем слово в словаре/словарях
    IF
         STATE @   \ Проверка режима работы системы: компиляция или исполнение
         IF COMPILE, ELSE EXECUTE THEN   \ Компилируем слово или исполняем его
    ELSE
         NOTFOUND   \ Зависит от реализации, но в общем случае тут обрабатываем исключение "слово не найдено", обычно тут еще бывает проверка "число?" -> компилируем числовой литерал или кладем число на стек
    THEN
  REPEAT
2DROP  \ Выкидываем уже не нужную строку со стека
;


Если же интересуют подробности, то можно заглянуть сюда (SP-Forth) или сюда (gForth), или даже вот сюда (форт форум).
Я имел в виду синтаксическую подсветку исходников, конечно.
К сожалению, редактор Habrahabr'а с синтаксисом Forth не знаком.
По сути вся поддержка синтаксиса заключается в разделении слов пробелами и прочими непечатными знаками :)
ну там ещё как минимум комментарии и определение функций, плюс можно подсвечивать числа и строки
Емнип, даже комментарии можно переопределить. Числа точно можно. Собственно во многих реализациях 0, 1, 2 и ещё некоторые константы определены как слова с кодом на ассемблере и не проходят обычный разбор числовых литералов.
Да, все верно. Логика обычного литерала в общем случае сводится к следующему: поместить в стек число из области памяти для статических данных. Обычно это следующий адрес для исполнения (да, прямо там же где и код) и пропуск данного адреса (прибавление размера ячейки) или смещение в области пользовательских данных. Говорю так размыто по причине того, что реализаций очень много и у каждого автора на все есть своё мнение =)
А положить ноль или единицу на вершину стека — это по сути одна-две процессорных команды.
oh shi… а можно ли переопределить переопределялку?..
Да. Можно переопределить даже компилятор и вообще практически все что угодно, а при использовании хаков вообще можно сделать все захочется.
Сидишь пишешь что-то на форте — вдруг надоело или еще чего: взял написал пару символов и прям тут же пишешь на си.
В качестве наглядного примера приведу реальный кусок кода из gForth:
Скрытый текст
c-library socket
\c #include <netdb.h>
c-function gethostbyname gethostbyname a -- a ( name -- hostent )
\c #include <unistd.h>
c-function gethostname gethostname a n -- n ( c-addr u -- ior )
\c #include <errno.h>
\c #define get_errno() errno
c-function errno get_errno -- n ( -- value )
\c #include <sys/types.h>
\c #include <sys/socket.h>
c-function socket socket n n n -- n ( class type proto -- fd )
c-function closesocket close n -- n ( fd -- ior )
c-function connect connect n a n -- n ( fd sock size -- err )
c-function send send n a n n -- n ( socket buffer count flags -- size )
c-function recv recv n a n n -- n ( socket buffer count flags -- size )
c-function listen() listen n n -- n ( socket backlog -- err )
c-function bind bind n a n -- n ( socket sockaddr socklen --- err )
c-function accept() accept n a a -- n ( socket sockaddr addrlen -- fd )
\c #include <stdio.h>
c-function fdopen fdopen n a -- a ( fd fileattr -- file )
\c #include <fcntl.h>
c-function fcntl fcntl n n n -- n ( fd n1 n2 -- ior )
\c #include <arpa/inet.h>
c-function htonl htonl n -- n ( x -- x' )
c-function htons htons n -- n ( x -- x' )
c-function ntohl ntohl n -- n ( x -- x' )
\c #define fileno1(file) fileno((FILE*)(file))
c-function fileno fileno1 a -- n ( file* -- fd )
end-c-library

4 4 2Constant int%
2 2 2Constant short%

struct
    cell% field h_name
    cell% field h_aliases
    int% field h_addrtype
    int% field h_length
    cell% field h_addr_list
end-struct hostent

struct
    short% field family
    short% field port
    int% field sin_addr
    cell% 2* field padding
end-struct sockaddr_in

В данном случае, си код сохраняется в отдельный файл, к нему дописывается еще один инклюд с частью кода самого gForth, он скармливается gcc и на выходе получается so библиотека, которая подключается и используется уже в форт-коде.
А вот пример хака:
: IFNOT   \ ( flag -- ) 
  ?COMP  \ Проверка на режим компиляции
  [COMPILE] IF
  0x85 HERE 5 - C!   \ Хак: подменяем скомпилированную ранее инструкцию JE(0x84) на JNE(0x85)
  \ Высокоуровневый вариант:
  \ POSTPONE 0= [COMPILE] IF
; IMMEDIATE
Главное, что есть оператор forget, который возвращает указанному слову его прошлое значение.
Не так. Слово forget переносит текущий указатель области кода на адрес, соответствующий указанному слову. Т.е. затираются все слова, определенные после указанного слова. Т.о. система возвращается в состояние, предшествующее определению данному слову и следующее слово будет записано по адресу «забываемого» слова. Это если в общих чертах. А если чуть углубиться: существуют форт системы с раздельным хранением кода и данных (код и строки, структуры, константы и отеделены друг от друга) и совместным (код и данные перемешаны), есть еще системы с поддержкой многопоточности и т.н. «пользовательских переменных » — переменных, которые для каждого потока разные, плюс существуют разные варианты структуры словарей. Т.о. в какой-то простой системе forget сводится к записи адреса слова в переменную указатель текущего адреса для компиляции, а в какой-то более сложной системе forget настолько сложно сделать, что лучше заняться чем-нибудь полезным и никогда об этом думать.
Да, я просто описал простейший случай.
Интересно, а есть версии Forth с несколькими дополнительными стеками? Было бы здорово иметь в арсенале операторы типа >r1, >r2, >r3 и, соответственно, r1>, r2>, r3>.
Под R обычно обозначает стек возвратов. И он всегда один, т.к. на этот стек сохраняется следующий адрес для перехода перед тем, как происходит вызов слова. Использовать его для временного хранения данных можно, но не желательно, т.к. неосторожное обращение с ним может все поломать. С другой стороны, его часто используют для временных данных с целью ускорения работы программы, т.к. его вершина тоже обычно представлена отдельным регистром или несколькими процессора или он реализован аппаратно (в форт процессорах, например). Кроме того, существуют еще и другие виды стеков: например стек структур управления, стеки циклов, стек для флоат чисел(в х86 он аппаратный), локальные стеки, даже бывают отдельные стеки для адресов, строк и вообще любых других типов данных.
А так, стеков можно наделать сколько душе угодно:
1024 CONSTANT NewStackSize
CREATE NewStack /NewStack NewStackSize ALLOT 0 ,
0 VALUE NewStackDepth
: -TH   \ ( addr n -- addr+n*cell )
    CELLS +
;

: NewStack!   \ ( n -- )
    NewStackDepth 1024 < IF
        NewStack NewStackDepth -TH !
        NewStackDepth 1+ TO NewStackDepth
    ELSE
        ABORT" Переполнение стека NewStack"
    THEN
;

: ClearNewStack
  0 TO NewStackDepth
;

: NewStack@   \ ( -- n )
    NewStackDepth IF
        NewStackDepth 1- TO NewStackDepth
        NewStack NewStackDepth -TH @
    ELSE
        ABORT" Опустошение стека NewStack"
    THEN
;

: NewStackGet   \ ( -- n )
    NewStackDepth IF
        NewStack NewStackDepth 1- -TH @
    ELSE
        0
    THEN
;
Под DOS были популярны варианты с дополнительным FPU-стеком, чтобы напрямую сопроцессором управлять.

Было немало версий с отдельным строковым стеком.

Наконец, на Форте часто реализуют просто универсальные слова для работы с любым стеком с указанием его в качестве аргумента перед соответствующими словами. Например:
: new-stack ( -- stack-id ) ... ; \ Тут реализуем нужные слова
new-stack value s0
5 s0 >s
10 s0 >s
s0 sswap
s0 s> s0 s>

Плюс стандартные слова. При чём разная подсветка в зависимости от типа слов.
А если слова переопределены? Включая тип.
Тогда сам редактор должен быть написан на форте =) Гибкость синтаксиса форта — головная боль для редакторов его исходного кода.
Ну, синтаксический анализ может провести и не форт-программа, но ей нужны полные исходники используемых (явно и не очень) словарей :)
Хитрость в том, что этот цикл обычно присутствует в коде FORTH-машины в виде захардкоженного вручную шитого кода, так как иначе его некому будет интерпретировать :)
UFO just landed and posted this here
Все делаете так, я смотрел сразу после публикации новости, все было на месте, а сейчас и у меня белое. Так что проблемы на их стороне.
Задумка крутая, но Форт там слишком уж урезанный. Нет IF, да и неплохо бы оставлять в стеке лишние значения, а не сбрасывать.
Автор убрал IF и FOR, на примерах объясняет, что без них даже короче программы получаются. В самом деле, в шейдере не будешь писать цикл от 0 до 100500, а 10 повторов вполне можно записать десятью вызовами одного и того же оператора. В рейтрейсинге у меня 20 вызовов, и для окошка 256x256 этого вполне достаточно. А отсутствие IF обходится через операторы > и <. Да, приходится менять привычный образ мышления. Но ради этого всё и затевалось.
Циклы или рекурсия были бы весьма полезны для генерации фракталов:
: xx x .8 - ;
: yy y .5 - ;
: tt t .4 * ;
: a .25 * ;
: mx xx 2 a tt cos * 2 tt * cos a - + ;
: my yy 2 a tt sin * 2 tt * sin a - + ;
: mm 2dup Z* mx my z+ ;
mx my 
mm mm mm mm mm mm
mm mm mm mm mm mm
mm mm mm mm mm mm
mm mm mm mm mm mm
dup * swap dup * +
0 > negate 1 +
dup dup
Хотелось бы прерывать итерации при превышении некоторого порога — в текущем виде оно за границей фрактала, похоже, в бесконечность или NaN уходит и потом вообще сравнение не проходит. И разукрасить опять же можно было бы «классическим» образом. И глубину обсчета менять.

А какая разница? Если пускают и так. Лишние действия.
Когда я делал рейтрейсинг, смекнул, что отслеживать порог не имеет смысла — при малом количестве итераций он всё равно редко достигается, и это лишнее сравнение только мешает. В окошке 256x256 и так всё шустро получается.
Но с фракталами да, посложнее. Хотя, там в галерее есть «разукрашенный» фрактал.
Разукрасить несложно — хотя бы оставить после вычисления комплексное значение вместо модуля. «Классическая» раскраска зависит от количества итераций, по прошествии которых произошел выход значения за заданную границу. Хотя можно при желании и в текущей реализации попытаться прикрутить счетчик. Только удлинится программа, а тут есть какие-то ограничения то ли на длину кода, то ли на сами вычисления — при добавлении еще одной строки mm mm mm mm mm mm вместо фрактала получается однотонный розовый квадрат.
Автор обещал сделать проверку полей на null, чтобы случайно не спамили (сейчас половина размещённых хайку — результат случайного нажатия кнопки submit). Станет чистить базу — поудаляет всех анонимусов. Поэтому имя лучше всё же вписать.
Мои программы, конечно, ужасно нечитаемы.

А не поэтому ли:
первое, что я делаю, это переопределяю команды языка Forth.

И конечно еще очень важны комментарии на уровне хотя бы «оно делает то-то и то-то».
Да, я об этом и говорю. Заменяю «dup» на «d» ради экономии нескольких байт. Цель игры — уложить программу в 128 или в 256 байт. Мы вообще в хабе «демосцена» и «ненормальное программирование», если что. В изначальном-то виде программа занимала в три раза больше места, имена были длинные и понятные. Но после нескольких дней практики читать чужие программы (даже сильно сокращённые) не составляет труда.
Официально транслятор Forth Haiku не понимает комментарии, лишь потом был сделан хак.
Эх, помню, в студенческие годы одна из окончательно добивших (в хорошем смысле) вещей Форта для меня был тот факт, что комментарий (например, "--") — это тоже слово. :)
Вообще, обычно комментарий в форте это обратный слеш \, а два минуса -- это обычно определение нового поля в текущей структуре данных.
0
<size> -- field1_name
<size> -- field2_name
<size> -- field3_name
CONSTANT /structure_name

Впрочем, слово «обычно» зачастую сложно применимо относительно того или иного аспекта в форте.
Саша, привет!
С удовольствием смотрю все созданные тобой эффекты, вот бы еще собрать их в коллекцию и сделать экзешник:)
Space Invaders — огонь! :)

P.S. Brad обычно все-таки на русском пишется Брэд.
Объединять разрозненные эффекты в exe, думаю, нет смысла. Думаю, мы лучше сразу что-то более цельное сделаем.
А пока ещё свежие эффектики
image
Sign up to leave a comment.

Articles