LINUX.ORG.RU

Stodin DSL. Тема 3. Тетрис.

 , ,


0

1

Очередная тема про язык программирования без скобочек.

Здесь на форуме кто-то предлагал тест для самодельных язычков и прочих средств разработки: создание тетриса. Тест пройден:

https://github.com/kupriyanov-sn/StodinDSL/tree/master/examples/sdl_tetris

Сделано с применением основной библиотеки SDL2. Теоретически, будет работать под Linux, хотя сборку проверял только под Windows.

При этом разбор кода прост как для человека, так и для программных анализаторов.

a + 3

увеличивает a на 3.

answer = a * b

меняет a?

Создание переменной может сочетаться с цепочной:

*tStart @int @func

Что попадает в func? Случайное число? 0?

*d @double d1 / 3.3

d1 при этом изменяется?

В чём вообще смысл делать язык с «функциями, не возвращающими значения» и операторами без приоритетов? Есть аналогичный J, но на том хоть с матрицами реально удобно работать. А здесь почти любая программа получается многословнее, чем на C++.

monk ★★★★★ ()
Ответ на: комментарий от monk

меняет a?

нет. Меняются только значения слева от первой функции или оператора. Остальное передаётся как константная ссылка или значение. Соответственно, меняет только answer.

Что попадает в func?

В func попадает ссылка на tStart. При записи в таком формате подразумевается что в начале func значение tStart будет задано.

А здесь почти любая программа получается многословнее, чем на C++.

Это не так. Я проводил замеры. Взял примеры отсюда:

https://github.com/cedelmaier/primeSieveProjects

и реализовал свой вариант:

https://github.com/kupriyanov-sn/StodinDSL/tree/master/examples/prime_sieves

Результаты были такие:

python	1419
stodin	2193
nim-4	2241
d	2298
rust	2460
go	2660
java	2855
c	2944

У C++ будет наверное на уровне rust или D. И это в примере, где сплошные формулы и мой язык заведомо в проигрышном положении.

Kogrom ()
Последнее исправление: Kogrom (всего исправлений: 1)
Ответ на: комментарий от anonymous

Скомпилированный код:

#include "_stodin_module_prime_sieves.h"
namespace _stodin_module_prime_sieves{
void eratosthenes(   // 3
    int64_t &counter, int64_t &maxPrime   // 4
    , const int64_t &limit){   // 5
    counter = 0;   // 7
    maxPrime = 2;   // 8
    __stodin_array<__stodin_bool> nums;resize(nums, limit, 1);   // 9
    nums.at(0) = 0;nums.at(1) = 0;   // 10
    double dlimit;_stodin_lib_math::sqrt(dlimit, static_cast<double>(limit));dlimit += 1.5;    // 11
    for(int64_t i = 2; i < static_cast<int64_t>(dlimit); i += 1){   // 13
        if(nums.at(i)){   // 14
            int64_t start  {i};start *= i;    // 15
            for(int64_t j = start; j < limit; j += i){   // 16
                nums.at(j) = 0;   // 17
    }}}
    for(int64_t i = 0; i < limit; i += 1){   // 18
        if(nums.at(i)){   // 19
            counter += 1;    // 20
            maxPrime = i;   // 21
}}}
void iprimes2(   // 23
    int64_t &counter, int64_t &maxPrime   // 24
    , const int64_t &limit){   // 25
    counter = 1;   // 27
    maxPrime = 7;   // 28
    int64_t lmtbf  {limit};lmtbf -= 3; lmtbf /= 2; lmtbf += 1;    // 29
    __stodin_array<__stodin_bool> nums;resize(nums, lmtbf, 1);   // 30
    double end;_stodin_lib_math::sqrt(end, limit);end -= 3; end /= 2; end += 1;    // 31
    for(int64_t i = 0; i < static_cast<int64_t>(end); i += 1){   // 32
        if(nums.at(i)){   // 33
            int64_t p  {i};p += i; p += 3;    // 34
            int64_t s  {i};s += 1;    // 35
            s *= p; s += i;    // 36
            for(int64_t j = s; j < lmtbf; j += p){   // 37
                nums.at(j) = 0;   // 38
    }}}
    for(int64_t i = 0; i < lmtbf; i += 1){   // 40
        if(nums.at(i)){   // 41
            counter += 1;    // 42
            maxPrime = i;maxPrime += i; maxPrime += 3;    // 43
}}}
void primes235(   // 45
    int64_t &counter, int64_t &maxPrime   // 46
    , const int64_t &limit){   // 47
    counter = 3;   // 49
    maxPrime = 7;   // 50
    __stodin_array<int64_t> modPrimes  {7, 11, 13, 17, 19, 23, 29, 31};   // 51
    __stodin_array<int64_t> gaps  {4, 2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 2, 4, 6, 2, 6};   // 52
    __stodin_array<int64_t> ndxs  {0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 7, 7, 7, 7, 7, 7};   // 53
    int64_t lmtbf  {limit};lmtbf += 23; lmtbf /= 30; lmtbf *= 8;    // 54
    double d;_stodin_lib_math::sqrt(d, static_cast<double>(limit));d -= 7;    // 55
    int64_t lmtsqrt  {static_cast<int64_t>(d)};   // 56
    int64_t fIndex  {lmtsqrt};fIndex %= 30;    // 57
    lmtsqrt /= 30; lmtsqrt *= 8; lmtsqrt += ndxs.at(fIndex); lmtsqrt += 1;    // 58
    __stodin_array<__stodin_bool> nums;resize(nums, lmtbf, 1);   // 59
    for(int64_t i = 0; i < lmtsqrt; i += 1){   // 60
        if(nums.at(i)){   // 61
            int64_t ci  {i};ci &= 7;    // 62
            int64_t p  {i};p >>= 3; p *= 30; p += modPrimes.at(ci);    // 63
            int64_t s  {p};s *= p; s -= 7;    // 64
            int64_t p8  {p};p8 <<= 3;    // 65
            for(int64_t j = 0; j < 8; j += 1){   // 66
                int64_t sMod30  {s};sMod30 %= 30;    // 67
                int64_t c  {s};c /= 30; c *= 8; c += ndxs.at(sMod30);    // 68
                for(int64_t jj = c; jj < lmtbf; jj += p8){   // 69
                    nums.at(jj) = 0;   // 70
                }
                int64_t t  {p};t *= gaps.at(ci);    // 71
                s += t;    // 72
                ci += 1;    // 73
    }}}
    int64_t ndxsIdx  {limit};ndxsIdx -= 7; ndxsIdx %= 30;    // 74
    int64_t end  {ndxs.at(ndxsIdx)};end += lmtbf; end -= 7;    // 75
    for(int64_t i = 0; i < end; i += 1){   // 76
        if(nums.at(i)){   // 77
            counter += 1;    // 78
            int64_t idx  {i};idx &= 7;    // 79
            maxPrime = i;maxPrime >>= 3; maxPrime *= 30; maxPrime += modPrimes.at(idx);    // 80
}}}
void _stodin_main(){   // 83
    int64_t counter  {0};int64_t maxPrime  {0};   // 84
    eratosthenes(counter, maxPrime, 1024);   // 85
    if(counter == 172){}else{throw logic_error("#Assert! 86, line: prime_sieves - assert counter 172 ");}   // 86
    if(maxPrime == 1021){}else{throw logic_error("#Assert! 87, line: prime_sieves - assert maxPrime 1021 ");}   // 87
    print("eratosthenes, counter: ");print(counter);print(", maxPrime: ");print(maxPrime);print('\n');   // 88
    iprimes2(counter, maxPrime, 1024);   // 89
    if(counter == 172){}else{throw logic_error("#Assert! 90, line: prime_sieves - assert counter 172 ");}   // 90
    if(maxPrime == 1021){}else{throw logic_error("#Assert! 91, line: prime_sieves - assert maxPrime 1021 ");}   // 91
    print("iprimes2, counter: ");print(counter);print(", maxPrime: ");print(maxPrime);print('\n');   // 92
    primes235(counter, maxPrime, 1024);   // 93
    if(counter == 172){}else{throw logic_error("#Assert! 94, line: prime_sieves - assert counter 172 ");}   // 94
    if(maxPrime == 1021){}else{throw logic_error("#Assert! 95, line: prime_sieves - assert maxPrime 1021 ");}   // 95
    print("primes235, counter: ");print(counter);print(", maxPrime: ");print(maxPrime);print("\n\n");   // 96
    for(int64_t a: {10, 20, 100, 2000}){   // 98
        eratosthenes(counter, maxPrime, a);   // 99
        print("eratosthenes(");print(a);print("), counter: ");print(counter);print(", maxPrime: ");print(maxPrime);print('\n');   // 100
        iprimes2(counter, maxPrime, a);   // 101
        print("iprimes2(");print(a);print("), counter: ");print(counter);print(", maxPrime: ");print(maxPrime);print('\n');   // 102
        primes235(counter, maxPrime, a);   // 103
        print("primes235(");print(a);print("), counter: ");print(counter);print(", maxPrime: ");print(maxPrime);print("\n\n");   // 104
}}

}
Kogrom ()
Ответ на: комментарий от anonymous

Скорее всего и Microsoft-ский компилятор можно прикрутить …

Скорее всего. Я почти не использую нестандартных конструкций. Но g++ использую для кроссплатформенности. У меня все консольные примеры собираются под Linux. И сам язык тоже собирается и работает под Linux.

Kogrom ()
Ответ на: комментарий от monk

Я считал только сами 3 функции расчёта (eratosthenes, iprimes2, primes235), без функции main, комментариев и символов конца строки. Для Nim отступ считал в 4 пробела. Функция main различается в примерах и было бы некорректно сравнивать.

Тут надо сказать, что, например, в варианте на питоне у меня не все функции корректно отработали, а в варианте на си автор навертел каких-то хитрых маллоков. Но я не стал к этому придираться. Замер у меня был приблизительный.

Забыл в прошлом комментарии сказать про смысл. У меня язык примитивный (17 страниц - описание синтаксиса с примерами), и особо изменять я его не планирую. Соответственно, утилиты на нём будут жить дольше, чем на каком-нибудь питоне. Есть, конечно, риск, что разработчики C++ поломают совместимость, но мне проще поменять только компилятор, а не переписывать все утилиты.

При использовании GUI риск переписывания утилит увеличивается, но я планирую со временем заменить SDL на какой-нибудь модуль graph, который сокроет работу конкретной применяемой библиотеки.

Kogrom ()
Ответ на: комментарий от Kogrom

Вот первые две функции идентично на C++

void eratosthenes(int *counter, int *maxPrime, int limit)
{
    *counter = 0;
    *maxPrime = 0;
    vector<bool> nums(limit, true);
    nums[0] = nums[1] = false;
    for(int i=2; i<(int)(sqrt(limit) + 1.5); i++) {
        if(nums[i])
            for(int j=i*i;j<limit;j+=i)
                nums[j] = false;
    }
    for(int i=0; i<=limit; i++) {
        if(nums[i]) {
            (*counter)++;
            *maxPrime = i;
        }
    }
}

void iprimes2(int *counter, int *maxPrime, int limit)
{
    *counter = 1;
    *maxPrime = 7;
    int lmtbf = (limit-3)/2+1;
    vector<bool> nums(lmtbf, true);
    for(int i = 0; i < (int)((sqrt(limit)-3)/2+1); i++)
        if(nums[i]) {
            int p = i+i+3;
            for(int j = (i+1)*p+i; j<=lmtbf; j+=p)
                nums[j] = false;
        }
    for(int i = 0; i<=lmtbf; i++)
        if(nums[i]) {
            (*counter)++;
            *maxPrime = i+i+3;
        }
}

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

monk ★★★★★ ()
Ответ на: комментарий от monk

Для данных двух функций примерно то на то и выходит, если nums я буду заполнять не циклом, а через fill, а в примере на C++ вместо i+i+3 писать i + i + 3.

Вероятно, выигрыш у меня будет в третьей функции за счёт инициализации векторов, если в C++ не экономить на пробелах.

Но в любом случае, в математике stodin должен полностью сливать. А он идёт вровень. В чисто императивном коде он уйдёт вперёд.

Kogrom ()
Ответ на: комментарий от Kogrom

Но в любом случае, в математике stodin должен полностью сливать.

Почему? В нём же формулы полностью работают. Только приоритеты поломаны.

Сливать он должен на сложных контрольных конструкциях, так как параметры для for придётся заносить в переменные, while нет, а для if тоже нужна переменная.

monk ★★★★★ ()
Ответ на: комментарий от monk

Почему? В нём же формулы полностью работают.

Не работают формулы, в которых нужна неявная переменная, то есть с приоритетами или скобками, например:

a = b * c + d * e + f * g;
a = (b + c) * (d + e) * (f + g);

для if тоже нужна переменная

Изначально я планировал цепочки (чейнинг) встраивать и в if, но это решается либо лямбдой с замыканием, либо набором скрытых переменных, если использую if-elif-elif-…-else. Оба эти решения я посчитал ненадёжными и нарушающими эстетику. Так же и с for. Что касается while, то в коде на других языках я его использую только для бесконечного цикла while(true), а для этого в stodin есть пустой for.

С другой стороны, в тетрисе, например, есть такое выражение:

res? < cellY BOX_HEIGHT; cellX BOX_WIDTH >= cellY 0; cellX 0 == cells|cellY|cellX 0

которое транслируется в код:

if(res){res = cellY < BOX_HEIGHT;}if(res){res = cellX < BOX_WIDTH;}if(res){res = cellY >= 0;}if(res){res = cellX >= 0;}if(res){res = cells.at(cellY).at(cellX) == 0;}

И здесь C++ проигрывает. Возможно, в данном случае можно немного упростить и оптимизировать, используя &&, но это не сильно спасёт и будет не универсально.

Kogrom ()
Ответ на: комментарий от anonymous

Без работы на онтопике нещитово!

Теперь работает и в Linux. Правда для этого пришлось испохабить всю функцию генерации Makefile.

Проверял 64-битной xubuntu. Для доказательства бинарик выложил тут: https://yadi.sk/d/XzZ-BFsLzJ-G-w?w=1

Kogrom ()
Ответ на: комментарий от Kogrom

res? < cellY BOX_HEIGHT; cellX BOX_WIDTH >= cellY 0; cellX 0 == cells|cellY|cellX 0

Вот это то самое «язык только для письма». И трёх-аргументный (точнее 1m+2c) логический оператор (про что явно не указано в документации, но хоть есть примеры). И проверка на res после каждой операции без дополнительных операторов «?». Хотя интуитивно из документации ожидается, что должно получиться

if(res){res = cellY < BOX_HEIGHT; res = cellX < BOX_WIDTH; res = cellY >= 0; res = cellX >= 0; res = cells.at(cellY).at(cellX) == 0;}

а для приведённого поведения должно быть что-то вроде

res? < cellY BOX_HEIGHT ? cellX BOX_WIDTH ? >= cellY 0 ? cellX 0 == cells|cellY|cellX 0
monk ★★★★★ ()
Ответ на: комментарий от monk

Вот это то самое «язык только для письма».

Это теоретические рассуждения. Я прошёлся по примерам, которые писал пол года назад. Так как код создавался давно, то я его уже полностью забыл. Никаких затруднений в чтении у меня не возникло. Единственный странный код, на котором я задержался на секунду был такой:

flag a; flag? b; flag? c @scan

Через секунду я понял, что эта цепочка делает.

Справедливости ради надо сказать, что в коде почти не встречаются цепочки с булевым результатом.

В Вашем примере с предлагаемым изменением синтаксиса утерян знак вопроса перед последним оператором. То есть Вы и сами в конце уже устали везде ставить этот знак вопроса. Зачем же мне его вводить?

Kogrom ()
Ответ на: комментарий от Kogrom

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

Претензии не к синтаксису, а к антиинтуитивности и недостаточности документации. APL и J для тех, кто на них постоянно пишет, тоже затруднений в чтении не вызывают.

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

monk ★★★★★ ()
Ответ на: комментарий от monk

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

Kogrom ()
Ответ на: комментарий от Kogrom

Как некое интересное явление - для других.

Тогда переименуй «функции». Возникает когнитивный диссонанс, когда «функция» не возвращает результат. Назови «глаголами» как в J или хотя бы «процедурами».

И где-то нужен список допустимых операций. Ведь они, с одной стороны, имеют все признаки глаголов (модифицируемый аргумент слева, один или два константных справа), а с другой «+» и «>» имеют разное число аргументов. Существует ли «&&», «!» и «~»?

monk ★★★★★ ()
Ответ на: комментарий от Kogrom

С термином «глагол» я не встречался, хотя, вероятно, он ещё лучше подходит.

Мне вот это напомнило:

https://www.jsoftware.com/help/primer/verb.htm https://www.jsoftware.com/help/primer/ambivalence.htm

У тебя тоже

a + 3
b = a + 3

имеют разный смысл глагола.

Правда в J пошли дальше в другую сторону и кроме глаголов добавили наречия (которые модифицируют глагол), герундии (когда глагол надо передать вместо существительного) и союзы (которые позволяют компоновать глаголы).

Или не в другую. У тебя тоже есть союзы «?» и «!?».

monk ★★★★★ ()
Ответ на: комментарий от anonymous

Сто один. В честь книжки «Стандарты программирования на С++: 101 правило и рекомендация» Саттера и Александреску.

Вообще, это у меня получился язык мечты. Не в смысле, что хороший, а что от мечтателя. Вот сидишь, думаешь, было бы у меня свободное время, создал бы что-то крутое. И тут бац - 3 месяца локдауна. Вот и проверил себя. Но у меня ещё есть отговорка: «вот была бы такая же вторая волна…». А раз её у меня не было, то ещё можно продолжать фантазировать о серебряной пуле.

Kogrom ()
Ответ на: комментарий от Kogrom

Вот и проверил себя. Но у меня ещё есть отговорка: «вот была бы такая же вторая волна…»

В первой отправке разработал с scratch парсинг оператора struct /до этого flex использовал/.

Парсинг умеет стандартный и расширенный struct.
В расширенном struct можно добавлять данные /любой сложности/ о семантике самого struct и полей.

Конечно при этом формируются метаданные, которые можно использовать для работы с динамическими объектами в run time /то бишь компилятор об этих структурах ни чего не знает/.

Владимир 123

anonymous ()
Ответ на: комментарий от anonymous

Да ничего особенного бы не было. После изучения разнообразных парадигм программирования, после испытания на практике различных методик, становится ясно, что быдлокод рулит, ибо с него всё начинается, и им всё заканчивается.

Соответственно, нужен язык с дубовым синтаксисом для удобного создания тупого кода. Осталось доказать на практике, что этот подход эффективен, написанием большого числа программ. Си и питон это уже доказали, но сомневающиеся говорят, что дело в их возрасте. Ну вот тут возраст меньше года, а язык примитивнее и питона, и си.

Kogrom ()
Ответ на: комментарий от anonymous

Кстати, я пробовал flex и bison. Не понравилось. Если внёс изменения в сгенерированный код, то как потом БНФ править? Это не для средних умов. И вообще я к подобным генераторам с подозрением отношусь. Пусть студенты упражняются. В своём велосипеде больше уверенности.

Kogrom ()
Ответ на: комментарий от Kogrom

Кстати, я пробовал flex и bison. Не понравилось.

bison конечно продвинутей, а в flex ловля и анализ токенов да еще с учетом контекста вынуждает программиста разрабатывать код ИМХО не readable.

Если внёс изменения в сгенерированный код, то как потом БНФ править?

Собственно это был один из доводов почему не стал использовать flex /так как API акцентировано на работу в run-time режиме/.

Кой-какое еще API разработаю и начну все же GUI разрабатывать /с scratch, основанное на использовании метаданных/.

Владимир 123

anonymous ()