Пишем алгоритмы на самом ненужном языке в мире и страдаем

в 16:24, , рубрики: algorithms, whitespace, whitespace tutorial

Привет!

То, что будет происходить ниже – сущий ад. Я буду писать простейшие программы на языке Whitespace (Но не такие простые, как в прошлой статье). Этот язык настолько ненужный, что оригинальный сайт с интерпретатором для этого языка уже давно мертв, все ссылки на оригинальные гайды, соответственно – тоже. Но, слава интернету, у нас есть webarchive, который и дал мне возможность скачать исходники 2х версий этого языка на Haskell, а также парочку бинарников под линух. Также отрыл пару онлайн-интерпретаторов, которые уже использовал в предыдущей статье.

Да, уважаемые читатели, я настоятельно рекомендую вам ознакомится с предыдущей статьей, в которой говорил про основной «скелет» языка. Если вы будете ознакомлены с этой информацией, вам будет легче воспринимать, то, что происходит здесь. Вот ссылка https://habr.com/ru/articles/844450/

Для самых ленивых и отчаянных привожу краткую выжимку:

  • В языке используются 3 символа – пробел, табуляция и перенос строки

  • В начале каждой команды используется особая приставка – IMP, которая характеризует вектор дальнейшей команды (например – IMP взаимодействия со стеком, с вводом-выводом и тд)

  • Язык манипулирует числами, которые хранятся в двоичном представлении: табуляции – единицы, пробелы - нули

  • Символы производятся языком путем сопоставления числа из стека с его ACSII-кодом

  • Язык whitespace – стековый, но также в нем существует и heap, хранящий данные "в долгую"

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

Работать она будет так: первым параметром на вход поступает список чисел.

Вторым параметром поступает число N, на которое мы будем делить сумму введенных элементов массива

Если сумма чисел массива делится нацело на N, программа выведет: Сумма делится

В противном случае: Сумма не делится

Звучит несложно, правда?

Хэээй! Кто это у нас тут?

Я – это ты, автор этой статьи, только из будущего. Я думал, что эта статья получится небольшой и лаконичной. Спустя 3 недели я снова с вами. Эта статья нихрена не лаконичная. Это - 40 тысяч символов моего погружения в никому ненужный стековый язык программирования. Поэтому присаживайтесь по удобнее, наливайте кофейку и наслаждайтесь.

На самом деле я абсолютно без понятия, как провернуть такой трюк. Единственное полезное, что я нашел в исходниках языка, это был файл calc.ws, и в нем была прекрасная программа, которая до бесконечности считывает с консоли числа, пока не придет -1, а затем выводит на консоль их сумму. Мне бы подошла схожая структура: сначала я бы ввел числа массива, затем желаемый делитель, и завершил бы ввод числом -1.

Так, со структурой ввода определились.

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

Пример ввода:

+

7

13

Пример вывода:

20

Давайте посмотрим ее код (условимся на следующие условные обозначения: S – пробел, T – табуляция, L – перенос строки):

SSSTLTLTSSSSTSTTSTLSSSTLTTTTSSTLTSTLSSSTTLSSSTSLSSSTLTLTTTLTTTLTTSSSTSLSSSTTLTTTSLTTTTTSSSTLSTLLLLLLLSSTLSSSTTLSSSTSLSSSTLTLTTTLTTTLTTSSSTTLSSSTSLTTTSLTTTTTSSTTLSTLLL

По меркам языка, эта программа очень небольшая. Давайте вместе с вами разберем ее по кирпичику:

  1. S S STL – поместить число 1 в стек

  2. TL TS – читаем символ с консоли

  3. S S STSTTSTL – помещаем число 45 в стек

  4. S S STL – помещаем число 1 в стек

  5. TTT - Heap retrieve

  6. TS ST – арифметическое вычитание

  7. L TS TL – переход к метке TL, если сверху стека 0

  8. S S STTL – помещаем число 3 в стек

  9. S S STSL – помещаем число 2 в стек

  10. S S STL – помещаем число 1 в стек

  11. TL TT – считываем число с консоли

  12. TL TT – считываем число с консоли 

  13. TL TT – считываем число с консоли

  14. S S STSL – поместить число 2 в стек

  15. S S STTL – поместить число 3 в стек

  16. TTT - Heap retrieve

  17. SLT - Swap 2х верхних элементов

  18. TTT - Heap retrieve

  19. TSSS – сложение

  20. TLST - вывести верхнее число стека

  21. LLL – end program

LSSTL – метка TL

  1. S S STTL – поместить число 3 в стек

  2. SSSTSL - поместить число 2 в стек

  3. SSSTL - поместить число 1 в стек

  4. TL TT – считываем число с консоли и кладем в стек

  5. TL TT – считываем число с консоли и кладем в стек

  6. TL TT – считываем число с консоли и кладем в стек

  7. S S STTL - поместить число 3 в стек

  8. S S STSL – поместить число 2 в стек

  9. TTT - Heap retrieve

  10. SLT - Swap 2х верхних элементов

  11. TTT - Heap retrieve

  12. TSST – вычитание

  13. TLST – вывести верхнее число стека

  14. LLL – конец программы

Честное слово, я очень долго вчитывался в этот код. Он работал, раз за разом, но я не понимал как. Если вы перечитаете команды выше, вы не найдете ни одной команды store(). А команд retrieve() там целых 5. Какого черта мы обращаемся к переменным, которые не создавали?

В этот момент я имел только схематичное представление об этих функциях – store что-то сохраняет, а retrieve – наоборот, вытаскивает сохраненное.

Я начал пытаться экспериментировать с функцией retrieve, создавая сэмплы кода из реальных пробелов, но когда компилятор выдал мне вот это:

Пишем алгоритмы на самом ненужном языке в мире и страдаем - 1

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

Да, извините, но чистокровного whitespace не будет. Вообще то S, T и L это тоже полумеры.

Я считаю, что если и браться за улучшение читаемости языка, то надо делать это основательно. Поэтому я написал обертки для функций whitespace на Kotlin. (выложил на github – если вы псих, то пользуйтесь на здоровье)

Улучшаем читабельность. Очень простая обертка на kotlin

Немного кода:

Сделал класс для IMP:

enum class IMP(val lexicalToken: String) {
    STACK_MANIPULATION(" "),
    ARITHMETIC("t "),
    HEAP_ACCESS("tt"),
    FLOW_CONTROL("n"),
    IO("tn");

    override fun toString(): String {
        return lexicalToken
    }
}

Абстрактный класс команды в whitespace:

abstract class Command(val imp: IMP, val commandValue: String) {
    override fun toString() = imp.lexicalToken+commandValue
}

После этого осталось реализовать лишь сами команды:

class PrintChar: Command(IMP.IO, "  ")
class PrintNumber: Command(IMP.IO," t")
class ReadChar: Command(IMP.IO, "t ")
class ReadNumber: Command(IMP.IO,"tt")

команды ввода/вывода

Благо 16 из 22х команд в whitespace не принимают никаких аргументов, но с остальными пришлось немного заморочиться. Например, команда пуша в стек выглядит следующим образом:

class PushNumber(val number: Int): Command(IMP.STACK_MANIPULATION, " "){
    override fun toString() = super.toString() + decimalToWhitespaceBinary(number)
}

Реализацию метода decimalToWhitespaceBinary можете посмотреть на github

Команды, манипулирующие метками (стандартная концепция goto), реализовал через еще одну абстракцию, которая, по сути, просто переопределяет метод toString:

abstract class CommandWithLabel( command: String, val label: String): Command(IMP.FLOW_CONTROL, command){
    override fun toString() = super.toString()+label
}

Как выглядят команды, работающие с метками (привожу в пример только 2 из 7 методов, вы знаете где найти полный код)

class Label(labelValue:String) : CommandWithLabel("  ", labelValue)

class JumpToLabel(labelValue:String) : CommandWithLabel(" n", labelValue)

Все это я делал лишь ради того, чтобы в методе main красиво написать:

fun main() {
    val whitespaceProgram = listOf(
        
        PushNumber(1),
        PrintNumber(),
        End()

    )
    
    whitespaceProgram.forEach {print(it.toString())}
}

Ну какая красота, разве нет? После запуска, в консоль нам выведется исходный код этих команд на whitespace, который мы уже можем засунуть в компилятор. Я пользовался онлайн-компилятором отсюда.

Пробуем писать простые конструкции

Теперь для того, чтобы не замусоривать эфир, будем рассматривать только ту часть метода main, которая находится внутри listOf

Итак, небольшая демонстрация работы обертки:

PushNumber(65),
PrintNumber(),
End()

Понятно, что это код выведет на экран число 65

PushNumber(65),
PrintChar(),
End()

А этот – уже букву “A”

Давайте для приличия напишем “hello, world!”

Для этого найдем ASCII код каждого символа и засунем его в стек (помним, стек – не очередь, он работает по принципу FILO – First-in-last-out, поэтому засовывать коды символов будем в реверсивном порядке)

h - 104 e - 101 l - 108 l - 108 o - 111 , - 44 -        32 w - 119 o - 111 r - 114 l - 108 d - 100 ! - 33

PushNumber(33),
PushNumber(100),
PushNumber(108),
PushNumber(114),
PushNumber(111),
PushNumber(119),
PushNumber(32),
PushNumber(44),
PushNumber(111),
PushNumber(108),
PushNumber(108),
PushNumber(101),
PushNumber(104),
PrintChar(),
PrintChar(),
PrintChar(),
PrintChar(),
PrintChar(),
PrintChar(),
PrintChar(),
PrintChar(),
PrintChar(),
PrintChar(),
PrintChar(),
PrintChar(),
PrintChar(),
End()

Эта программа, при двойной компиляции, сначала нашего kotlin, а потом уже и whitespace, действительно, выдаст «hello, world!»

Единственное, что мне здесь не нравится, так это 13-кратное повторение команды PrintChar(). Даешь бесконечный цикл!

Label(label),
PrintChar(),
JumpToLabel(label),

Label, или метка – это любая уникальная строка. Пока у нас только одна метка мы можем дать ей самое короткое имя из возможных - 0. Ноль на языке whitespace это пробел. Заканчивается любая метка символом переноса строки ‘n’ (а состоит метка из произвольной комбинации пробелов и табуляций).

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

Однако, как-то это не трушно. Не можем же мы в конце программы оставить бесконечный цикл и уйти.

Нам нужен цикл for. А для этого нам нужно хранить переменную-счетчик. А для этого нам нужно знать, как работает store и retrieve.

Как же работает store и retrieve?

Вот простейший код сохранения числа в переменную, получения этого числа и вывода его на экран:

PushNumber(33),
PushNumber(100),
Store(),
PushNumber(33),
Retrieve(),
PrintNumber(),
End()

Чего???

Давайте рассмотрим инициализацию нашей переменной с момента вызова функции Store() – эта функция берет верхний элемент стека и берет его за ЗНАЧЕНИЕ переменной. Затем она берет следующий элемент из стека, и этот элемент считает АДРЕСОМ по которому мы будем хранить ЗНАЧЕНИЕ.

Как вы понимаете, после сохранения переменной в heap, оба элемента удаляются из нашего стека.

Чтобы получить значение нашей переменной, мы используем функцию Retrieve(), которая в свою очередь берет верхний элемент стека и расценивает его как АДРЕС переменной. По этому адресу извлекается ЗНАЧЕНИЕ и кладется наверх стека (если значение по адресу отсутствует, наверх стека придет 0).

То есть снимаем со стека адрес, и кладем вместо него значение.

Теперь осмыслим код выше. Получается мы сохраняем значение 100 по адресу 33 (можно говорить в терминах переменной с именем 33), а затем кладем в стек число 33, и вызываем Retrieve(), тем самым говоря: положи мне в стек вместо числа 33 значение, которое лежит по адресу 33.

Наверх стека положится число 100, которое мы и выведем на консоль. Класс, разобрались, возвращаемся к циклу For.

Я реализовал его таким образом:

PushNumber(33),//кладем буквы фразы "hello, world!"
PushNumber(100),
PushNumber(108),
PushNumber(114),
PushNumber(111),
PushNumber(119),
PushNumber(32),
PushNumber(44),
PushNumber(111),
PushNumber(108),
PushNumber(108),
PushNumber(101),
PushNumber(104),

PushNumber(666),
PushNumber(-13),
Store(),//Инит переменной 666 со значением -13

Label(startOfCycle),

PushNumber(666),
Retrieve(),//Кладем значение переменной-счетчика 666 наверх стека

PushNumber(1),
Addition(), //Прибавляем к счетчику 1 (Поскольку он отрицателен, мы стремимся к нулю. То же самое, что и отсчет от +13 до 0)

Swap(),
PrintChar(),//Выводим нашу букву, но поскольку наверху стека лежит счетчик, делаем сначала swap

PushNumber(666),//Эти 3 команды сохраняют текущее значение переменной 666 в heap
Swap(),
Store(),

PushNumber(666),//некрасиво, но опять кладем на верх стека значение только что сохраненной переменной,
// потому что оператор if 2мя строчками ниже снимет это значение с верхушки стека
Retrieve(),

JumpToLabelIfTopOfStackIsNegative(startOfCycle),//повторять до тех пор, пока счетчик в 0 не уткнется
End()

Отладка в процессе написания этого кода выглядела примерно так (Я умудрился написать пробел вместо табуляции в одном из IMP):

Подобные процедуры выбивали у меня слезу

Подобные процедуры выбивали у меня слезу
Пишем алгоритмы на самом ненужном языке в мире и страдаем - 3
Пишем алгоритмы на самом ненужном языке в мире и страдаем - 4

Еще немного про Heap: как в него попадают числа

Итак, все-таки: каким образом число может попасть в heap, если мы не вызываем команду store?

На просторах интернета мне удалось найти этот вопрос на StackOverflow: В нем спрашивалось, как можно вывести символ на консоль (на whitespace, конечно) – вопрос пустяковый, но в ответе я увидел следующее:

Пишем алгоритмы на самом ненужном языке в мире и страдаем - 5

Добрый человек накидал трассировку не только стека, но и heap’a – это стало спасательным кругом. Я пошел перечитывать доку – и черт, побери, язык кладет любой ввод с консоли в хип по адресу, который лежит наверху стека:

Read a character and place it in the location given by the top of the stack

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

Долго я ходил вокруг для около, но теперь мы готовы к тому, чтобы:

  1. Понять принцип работы программы из начала статьи

  2. Написать свою собственную программу

Код из начала стати в формате моей котлин-обертки под спойлером:

Код
PushNumber(1),
ReadChar(),
PushNumber(45),
PushNumber(1),
Retrieve(),
Subtraction(),
JumpToLabelIfTopOfStackIsZero(label),

PushNumber(3),
PushNumber(2),
PushNumber(1),
ReadChar(),
ReadNumber(),
ReadNumber(),

PushNumber(2),
PushNumber(3),
Retrieve(),
Swap(),
Retrieve(),
Addition(),
PrintNumber(),
End(),

Label(label),
PushNumber(3),
PushNumber(2),
PushNumber(1),
ReadNumber(),
ReadNumber(),
ReadNumber(),

PushNumber(3),
PushNumber(2),
Retrieve(),
Swap(),
Retrieve(),
Subtraction(),
PrintNumber(),
End()

Давайте, для разбора, поделим программу на 3 части:

  1. первоначальное считывание знака

  2. ветвление для того случая, когда мы ввели “-“

  3. ветвление для того случая, когда мы ввели “+”. (на самом деле, в рамках этой программы, плюсом считается любой знак, кроме минуса)

Первая часть. Считывание знака:

PushNumber(1),
ReadChar(),
PushNumber(45),
PushNumber(1),
Retrieve(),
Subtraction(),
JumpToLabelIfTopOfStackIsZero(label),

 Следующие же две части имеют схожую структуру, в обоих стек заполняется числами от 1 до 3х:

PushNumber(3),
PushNumber(2),
PushNumber(1),

 Затем происходит три считывания с консоли…

ReadChar(),
ReadNumber(),
ReadNumber(),

Так, погодите. Мы же уже считали знак операции. Потом мы должны считать два числа, над которыми эта операция и будет производиться. Но мы видим три операции чтения!

Лирическое отступление: чтение с консоли

Почему считывания с консоли три вместо двух?

Я перезапустил программу, еще раз прогнал. Окно для ввода открывается три раза. Но совокупно в программе четыре операции чтения! Ответа на этот вопрос я ни нашел нигде на просторах интернета, поэтому – ловите эксклюзив.

Такой вот код считает с консоли поочередно 2 числа, и выведет их сумму:

PushNumber(1),
PushNumber(2),
ReadNumber(),
ReadNumber(),

PushNumber(1),
PushNumber(2),
Retrieve(),
Swap(),
Retrieve(),
Addition(),
PrintNumber(),
End(),

Если вы дошли до этого места, то я верю, что прочитать это вам не составит труда.

Теперь поменяем в программе две строчки: вместо начальных ReadNumber, поставим ReadChar. Остальной код оставим прежним:

PushNumber(1),
PushNumber(2),
ReadChar(),
ReadChar(),

PushNumber(1),
PushNumber(2),
Retrieve(),
Swap(),
Retrieve(),
Addition(),
PrintNumber(),
End(),

Как вы думаете, что произойдет после запуска?

Мы помним, что символы в whitespace хранятся, как их ascii-коды, то есть данная программа по логике должна дать мне ввести два символа, и вывести на консоль сумму их ascii-кодов – не угадали.

Данная программа после запуска ждет от тебя ровно один ввод. Если ты вводишь один символ и нажимаешь enter (с числами такое работает прекрасно), то язык считает, что enter – то бишь n – символ новой строки, это вполне себе пользовательский ввод, поэтому нашему enter сопоставляется его ascii код – 10, и складывается с кодом первого введенного символа. Вся смехотворность в том, что не нажать enter при вводе во время рантайма ты просто не можешь – язык не засчитает тебе ввод.

Приведу пример: при вводе от пользователя буквы A во время рантайма, в консоль будет выведено число 75. (код буквы A – 65)

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

Теперь мы можем ответить себе на вопрос – для чего нужно было лишнее чтение с консоли: для того, чтобы скушать символ новой строки, который автоматически навешивается мертвым грузом на любой символьный ввод (Никаких дальнейших манипуляций с этим символом не проводится, это просто костыль, чтобы нормально считать следующие числа).

***Завершение лирического отступления***

Часть 2: ветвление для +

Таким образом, если пользователь ввел не знак “-” (а мы подразумеваем, что пользователь добросовестный и вводит только допустимые символы, то бишь + или -), то мы считываем с консоли два числа, вытаскиваем их из heap’а, складываем их, выводим результат на экран и завершаем программу.

Часть 3: ветвление для -

Если же пользователь ввел знак “-”, то мы прыгаем на метку t, после которой происходит аналогичный код: считываем с консоли два числа, вытаскиваем их из heap’а, вычитаем из первого второе, выводим результат на экран и завершаем программу.

Финальный листинг с комментариями:
PushNumber(1),
ReadChar(),//Считываем символ и кладем его ASCII-код по адресу 1
PushNumber(45), //Кладем в стек число 45: это число - код символа "-"
PushNumber(1),
Retrieve(),//Кладем число по адресу 1 наверх стека
Subtraction(),//Вычитаем из 45 код считанного символа.
JumpToLabelIfTopOfStackIsZero(label),//Если в остатке 0 (пользователь ввел "-"), то перемещаемся к метке label

PushNumber(3),
PushNumber(2),
PushNumber(1),
ReadNumber(),//Считываем с консоли символ переноса строки, оставшийся там с предыдущего ввода
ReadNumber(),
ReadNumber(),//И считываем 2 операнда в heap

PushNumber(2),
PushNumber(3),
Retrieve(),
Swap(),
Retrieve(),//Извлекаем значения операндов в стек

Addition(), //Суммируем и выводим на экран
PrintNumber(),
End(),

Label(label),//Оказываемся здесь, если ввели "-"
PushNumber(3),
PushNumber(2),
PushNumber(1),
ReadNumber(),//Считываем с консоли символ переноса строки, оставшийся там с предыдущего ввода
ReadNumber(),
ReadNumber(),//И считываем 2 операнда в heap

PushNumber(3),
PushNumber(2),
Retrieve(),
Swap(),
Retrieve(),//Извлекаем значения операндов в стек
Subtraction(),//Вычитаем из первого второй и выводим на экран
PrintNumber(),
End()

Осуществляем задуманное

Теперь, когда у нас есть все козыри, давайте напишем алгоритм, который придумали в начале статьи:

Первыми N параметрами на вход поступает массив чисел.

Вторым параметром поступает число M, на которое мы будем делить сумму введенных элементов массива

Если сумма чисел массива делится нацело M, программа выведет: Сумма делится

В противном случае: Сумма не делится

Накидал план работы алгоритма, учитывая специфику whitespace:

  1. Инициализируем счетчик количества введенных чисел.

  1. Инициализируем счетчик суммы введенных чисел.

  2. Инициализируем переменную, в которой лежит последнее введенное число (не считая -1)

Метка начала цикла

  1. Считываем число с консоли. Увеличиваем счетчик введенных чисел.

  2. Если введенное число = -1, то запускаем подпрограмму проверки валидности ввода:

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

Подпрограмма проверки валидности ввода:

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

  1. sumOfInput = sumOfInput – lastInputNumber.

Далее проводим валидацию количества введенных чисел в целом. Нам надо, чтобы чисел было не меньше трех – как минимум одно число для суммы ввода, число-делитель, и -1, завершающая ввод.

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

  2. Если счетчик больше либо равен 3 – идем к метке подсчета результата

Подпрограмма подсчет результата:

  1. Инициализируем переменную результата целочисленного деления

  2. Кладем в эту переменную значение целочисленного деления с первым операндом: счетчик суммы введенных чисел, вторым: последнее введенное число.

  3. Далее результат умножаем на последнее введенное число (наш делитель) и кладем наверх стека.

  4. Из этого числа вычитаем сумму всех введенных чисел.

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

  6. Если нет, то идем к метке плохого исхода

Метка хорошего исхода:

Пишем в консоль: entered amount(X) is divided without remainder by Y

– где X, это сумма введенных чисел, кроме делителя, а Y, это делитель (последнее введенное число)

Метка плохого исхода:

Пишем в консоль: entered amount(X) is not divisible by Y without remainder– где X, это сумма введенных чисел, кроме делителя, а Y, это делитель (последнее введенное число)

 

Метка завершения программы с ошибкой:

Пишем в консоль: error, invalid input.

Адово реализуем простейший алгоритм.

Предупреждение для особо упорных читателей, которые дошли для этого момента: сейчас будут адовые листинги кода с моими комментариями. Если вы читаете это с телефона, то советую кинуть статью в закладки, до тех пор, пока не раздобудете себе экран побольше.

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

 Ссылка на реализацию алгоритма : github

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

  1. счетчик количества введенных чисел – будет лежать в heap по адресу 0

  2. счетчик суммы введенных чисел – адрес 1

  3. переменная, в которой лежит последнее введенное число (не считая -1) – адрес 2

  4. переменная результата целочисленного деления – адрес 3

  5. Метка начала цикла в основном блоке программыпеременная cycleInMain , строка (вайтспесовая, разумеется, из пробелов и табуляций), представляющая число 0 в бинарном представлении

  6. Метка подпрограммы проверки валидности ввода validInputSubroutine – строка, представление числа 1

  7. метка завершения программы с ошибкой failWithErr– строка, представление числа 2

  8. метка подсчета результата calcResultSubroutine – строка, представление числа 3

  9. метка хорошего исхода trueResult  - строка, представление числа 4

  10. метка плохого исхода falseResult – строка, представление числа 5

  11. И еще 6 переменных меток для циклов в блоке вывода на экран вида cycleFor1..cycleFor6

//Переменные:
val countInput = 0
val sumOfInput = 1
val lastInputNumber = 2
val resultOfDivision = 3


//Строковые метки:
val cycleInMain = " "
val validInputSubroutine = "t"
val failWithErr = "t "
val calcResultSubroutine = "tt"
val trueResult = "t  "

val cycleFor1 = "tt "
val cycleFor2 = "ttt"
val cycleFor3 = "t   "
val cycleFor4 = "t  t"
val cycleFor5 = "t t "
val cycleFor6 = "t tt"
val cycleForCounter = 4

Фух, разобрались.

Приступим с основной части, вот этой:

  1. Инициализируем счетчик количества введенных чисел.

  1. Инициализируем счетчик суммы введенных чисел.

  2. Инициализируем переменную, в которой лежит последнее введенное число (не считая -1)

Метка начала цикла

  1. Считываем число с консоли. Увеличиваем счетчик введенных чисел.

  2. Если введенное число = -1, то запускаем подпрограмму проверки валидности ввода:

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

Код:

Код первой части
PushNumber(countInput),//Инициализируем 3 переменные нулями
PushNumber(0),
Store(),
PushNumber(sumOfInput),
PushNumber(0),
Store(),
PushNumber(lastInputNumber),
PushNumber(0),
Store(),

Label(cycleInMain),

PushNumber(777),//Будем класть каждое введенное число в heap по адресу 777
ReadNumber(),

PushNumber(countInput),// 7 команд ниже делают инкремент переменной countInput
Retrieve(),// 7 команд, карл! Ох уж эти стековые языки...
PushNumber(1),
Addition(),
PushNumber(countInput),
Swap(),
Store(),

PushNumber(777), //Проверяем, ввел ли пользователь -1
Retrieve(),
PushNumber(1),
Addition(),
JumpToLabelIfTopOfStackIsZero(validInputSubroutine),//Если ввел, то прыгаем на validInputSubroutine

PushNumber(777),//засовываем в переменную lastInputNumber введенное число (с адреса 777, помним)
Retrieve(),
PushNumber(lastInputNumber),
Swap(),
Store(),

PushNumber(sumOfInput),//прибавляем введенное число к sumOfInput
Retrieve(),
PushNumber(777),
Retrieve(),
Addition(),
PushNumber(sumOfInput),
Swap(),
Store(),

JumpToLabel(cycleInMain),// И идем к началу цикла

Если вам интересно, то в процессе написания данного кода я издавал звуки огромной ехидны. Примерно такие : Ихих фыр фыр шк шк шк

Да, я бы мог пойти еще дальше, и вынести в котлин-функции повторяющиеся блоки инкрементов, инициализаций и тд, но…зачем? Вы хотите читаемый код на whitespace, серьезно?

Далее сама подпрограмма валидности ввода:
// ---------------------------------------------------------------
        //ПОДПРОГРАММА ВАЛИДНОСТИ ВВОДА
        Label(validInputSubroutine),
        /*
            Тут помним, что в сумму всех введенных чисел мы считаем и делитель (последнее введенное число).
            В дальнейшем нам, для правильной калькуляции, надо иметь отдельно сумму всех введенных чисел,
              кроме делителя, и сам делитель. Поэтому из переменной суммы всего инпута нам надо вычесть делитель.
            sumOfInput = sumOfInput – lastInputNumber
         */
        PushNumber(sumOfInput),//Вычли из sumOfInput lastInputNumber
        Retrieve(),
        PushNumber(lastInputNumber),
        Retrieve(),
        Subtraction(),

        PushNumber(sumOfInput),//И сохранили в sumOfInput
        Swap(),
        Store(),

        PushNumber(countInput),//Далее валидация общего количества введенных чисел:
        Retrieve(),
        PushNumber(3),
        Subtraction(),
        JumpToLabelIfTopOfStackIsNegative(failWithErr),//Если счетчик введенных чисел меньше 3х, то идем к метке failWithErr.
        JumpToLabel(calcResultSubroutine),//Иначе – идем к метке подсчета 

Я думаю, идея ясна – проверяем, что ввелось не меньше 3х чисел – как минимум 1 число, представляющее собой сумму, 1 делитель, и -1 в конце для завершения ввода.

Давайте сразу приведу здесь блок кода, в который мы попадаем при некорректном вводе:

Блок некорректного ввода
// ---------------------------------------------------------------
        //ПОДПРОГРАММА НА КЕЙС С НЕКОРРЕКТНЫМ ВВОДОМ
        Label(failWithErr),

        PushNumber(116),//Кладем ascii коды строки "error, invalid input" в инвертированном порядке
        PushNumber(117),
        PushNumber(112),
        PushNumber(110),
        PushNumber(105),
        PushNumber(32),
        PushNumber(100),
        PushNumber(105),
        PushNumber(108),
        PushNumber(97),
        PushNumber(118),
        PushNumber(110),
        PushNumber(105),
        PushNumber(32),
        PushNumber(44),
        PushNumber(114),
        PushNumber(111),
        PushNumber(114),
        PushNumber(114),
        PushNumber(101),

        PushNumber(cycleForCounter),//В цикле For 20 раз подряд выводим символ на консоль
        PushNumber(-20),
        Store(),
        Label(cycleFor1),
        PushNumber(cycleForCounter),
        Retrieve(),
        PushNumber(1),
        Addition(),
        Swap(),
        PrintChar(),
        PushNumber(cycleForCounter),
        Swap(),
        Store(),
        PushNumber(cycleForCounter),
        Retrieve(),
        JumpToLabelIfTopOfStackIsNegative(cycleFor1),
        End(),

Далее – программа подсчета результата (Все очень подробно расписал в комментариях к коду, не вижу смысла рассматривать мои пояснения в отрыве от него, поэтому вот):

Подпрограмма подсчета результата
//ПОДПРОГРАММА ПОДСЧЕТА РЕЗУЛЬТАТА
        /*
        Подпрограмма подсчет результата:
      Инициализируем переменную результата целочисленного деления
      Кладем в эту переменную значение целочисленного деления с первым операндом:
              счетчик суммы введенных чисел, вторым: последнее введенное число.
Далее результат умножаем на последнее введенное число (наш делитель) и кладем наверх стека.
Из этого числа вычитаем сумму всех введенных чисел.
Если после этого наверху стека лежит 0, то значит,
                    сумма без остатка делится на введенный делитель, и мы идем к метке хорошего исхода
Если нет, то идем к метке плохого исхода

         */
        Label(calcResultSubroutine),

        PushNumber(resultOfDivision),//Кладем в стек адрес переменной resultOfDivision
        // Далее кладем значение. Но чтобы значение получить, сначала вычисляем его:
        PushNumber(sumOfInput),//Кладем первый операнд для деления
        Retrieve(),

        PushNumber(lastInputNumber),//Кладем второй операнд для деления
        Retrieve(),

        IntegerDivision(),//Заменяем эти 2 операнда в стеке результатом целочисленного деления

        Store(),//Сохраняем результат по адресу resultOfDivision, который положили в стек вначале подпрограммы

        PushNumber(resultOfDivision),//Опять кладем наверх стека значение переменной resultOfDivision,
        Retrieve(),                   //ибо при сохранении в heap она из стека удалилась

        PushNumber(lastInputNumber),/*Добавляем второй операнд (сейчас будем перемножать
        resultOfDivision с lastInputNumber). Логика следующая: как понять что остатка при делении не было?
        Например, как проверить, 21 делится на 5 с остатком или без него?
        Записываем результат целочисленного деления 21 на 5 (это 4), а затем 4 умножаем на 5.
        Если получили 21 - то поделилось без остатка, если нет - то с остатком. В данном случае 4*5 не равно 21,
        значит нацело не делится
        */
        Retrieve(),

        Multiplication(),//Перемножаем resultOfDivision с lastInputNumber

        PushNumber(sumOfInput), //Кладем наверх стека sumOfInput
        Retrieve(),

        Subtraction(),//Считаем выражение resultOfDivision*lastInputNumber - sumOfInput
        JumpToLabelIfTopOfStackIsZero(trueResult),//Если наверху стека 0, значит делится без остатка. Идем в соответсвующую подпрограмму
        JumpToLabel(falseResult),//Иначе идем к ветвлению, что сумма на делитель без остатка не делится.

Код нашего вывода в консоль, когда сумма ввода делится на делитель без остатка:

Сумма ввода делится на делитель без остатка
//ПОДПРОГРАММА НА СЛУЧАЙ, КОГДА СУММА ДЕЛИТСЯ БЕЗ ОСТАТКА
        Label(trueResult),
/*
Мы хотим получить вывод вида: "entered amount($sumOfInput) is divided without remainder by $lastInputNumber"
Я не нашел элегантного способа вывести число и символы в перемешку в цикле For.
Была идея прибавлять к числам 48, и печатать их ascii коды, но это будет работать только тогда, когда сумма
введенных чисел это число от 0 до 10, т.е. занимает 1 символ.
Поэтому сделаем так. В этой строке 47 символов и 2 числа, которые неоходимо вывести.
Поэтому у нас будет 2 цикла For для печати символов и 2 одиночных вывода чисел.
То есть сначла печатаем "entered amount(", потом число sumOfInput, потом " is divided without remainder by ",
потом число lastInputNumber

 */
//Кладем ascii коды строки "entered amount($sumOfInput) is divided without remainder by $lastInputNumber" в инвертированном порядке
//Прибавляем 48, потому что в цикле For ниже у нас символьная печать, а ascii код каждой цифры это 48+эта цифра.
        PushNumber(lastInputNumber),
        Retrieve(),

        PushNumber(32),
        PushNumber(121),
        PushNumber(98),
        PushNumber(32),
        PushNumber(114),
        PushNumber(101),
        PushNumber(100),
        PushNumber(110),
        PushNumber(105),
        PushNumber(97),
        PushNumber(109),
        PushNumber(101),
        PushNumber(114),
        PushNumber(32),
        PushNumber(116),
        PushNumber(117),
        PushNumber(111),
        PushNumber(104),
        PushNumber(116),
        PushNumber(105),
        PushNumber(119),
        PushNumber(32),
        PushNumber(100),
        PushNumber(101),
        PushNumber(100),
        PushNumber(105),
        PushNumber(118),
        PushNumber(105),
        PushNumber(100),
        PushNumber(32),
        PushNumber(115),
        PushNumber(105),
        PushNumber(32),
        PushNumber(41),

        PushNumber(sumOfInput),
        Retrieve(),
        PushNumber(40),

        PushNumber(116),
        PushNumber(110),
        PushNumber(117),
        PushNumber(111),
        PushNumber(109),
        PushNumber(97),
        PushNumber(32),
        PushNumber(100),
        PushNumber(101),
        PushNumber(114),
        PushNumber(101),
        PushNumber(116),
        PushNumber(110),
        PushNumber(101),

        PushNumber(cycleForCounter),// Первый цикл For - вывод "entered amount("
        PushNumber(-15),
        Store(),
        Label(cycleFor2),
        PushNumber(cycleForCounter),
        Retrieve(),
        PushNumber(1),
        Addition(),
        Swap(),
        PrintChar(),
        PushNumber(cycleForCounter),
        Swap(),
        Store(),
        PushNumber(cycleForCounter),
        Retrieve(),
        JumpToLabelIfTopOfStackIsNegative(cycleFor2),

        PrintNumber(),//Печатаем число - наш sumOfInput

        PushNumber(cycleForCounter),// Второй цикл For - вывод " is divided without remainder by "
        PushNumber(-34),
        Store(),
        Label(cycleFor3),
        PushNumber(cycleForCounter),
        Retrieve(),
        PushNumber(1),
        Addition(),
        Swap(),
        PrintChar(),
        PushNumber(cycleForCounter),
        Swap(),
        Store(),
        PushNumber(cycleForCounter),
        Retrieve(),
        JumpToLabelIfTopOfStackIsNegative(cycleFor3),

        PrintNumber(),//Печатаем число - наш lastInputNumber

        End(),

Код вывода в консоль, когда сумма ввода НЕ делится на делитель без остатка:

 

сумма ввода НЕ делится на делитель без остатка
// ---------------------------------------------------------------
        //ПОДПРОГРАММА НА СЛУЧАЙ, КОГДА СУММА НЕ ДЕЛИТСЯ БЕЗ ОСТАТКА
        Label(falseResult),

        PushNumber(114),//Кладем ascii коды строки "entered amount($sumOfInput) is not divisible by $lastInputNumber without remainder" в инвертированном порядке
        PushNumber(101),
        PushNumber(100),
        PushNumber(110),
        PushNumber(105),
        PushNumber(97),
        PushNumber(109),
        PushNumber(101),
        PushNumber(114),
        PushNumber(32),
        PushNumber(116),
        PushNumber(117),
        PushNumber(111),
        PushNumber(104),
        PushNumber(116),
        PushNumber(105),
        PushNumber(119),
        PushNumber(32),

        PushNumber(lastInputNumber),
        Retrieve(),

        PushNumber(32),
        PushNumber(121),
        PushNumber(98),
        PushNumber(32),
        PushNumber(101),
        PushNumber(108),
        PushNumber(98),
        PushNumber(105),
        PushNumber(115),
        PushNumber(105),
        PushNumber(118),
        PushNumber(105),
        PushNumber(100),
        PushNumber(32),
        PushNumber(116),
        PushNumber(111),
        PushNumber(110),
        PushNumber(32),
        PushNumber(115),
        PushNumber(105),
        PushNumber(32),
        PushNumber(41),

        PushNumber(sumOfInput),
        Retrieve(),

        PushNumber(40),
        PushNumber(116),
        PushNumber(110),
        PushNumber(117),
        PushNumber(111),
        PushNumber(109),
        PushNumber(97),
        PushNumber(32),
        PushNumber(100),
        PushNumber(101),
        PushNumber(114),
        PushNumber(101),
        PushNumber(116),
        PushNumber(110),
        PushNumber(101),



        PushNumber(cycleForCounter),// Первый цикл For - вывод "entered amount("
        PushNumber(-15),
        Store(),
        Label(cycleFor4),
        PushNumber(cycleForCounter),
        Retrieve(),
        PushNumber(1),
        Addition(),
        Swap(),
        PrintChar(),
        PushNumber(cycleForCounter),
        Swap(),
        Store(),
        PushNumber(cycleForCounter),
        Retrieve(),
        JumpToLabelIfTopOfStackIsNegative(cycleFor4),

        PrintNumber(),//Печатаем число - наш sumOfInput

        PushNumber(cycleForCounter),//Второй цикл For - вывод ") is not divisible by "
        PushNumber(-22),
        Store(),
        Label(cycleFor5),
        PushNumber(cycleForCounter),
        Retrieve(),
        PushNumber(1),
        Addition(),
        Swap(),
        PrintChar(),
        PushNumber(cycleForCounter),
        Swap(),
        Store(),
        PushNumber(cycleForCounter),
        Retrieve(),
        JumpToLabelIfTopOfStackIsNegative(cycleFor5),

        PrintNumber(),//Печатаем число - наш lastInputNumber

        PushNumber(cycleForCounter),//Третий цикл For - вывод " without remainder"
        PushNumber(-18),
        Store(),
        Label(cycleFor6),
        PushNumber(cycleForCounter),
        Retrieve(),
        PushNumber(1),
        Addition(),
        Swap(),
        PrintChar(),
        PushNumber(cycleForCounter),
        Swap(),
        Store(),
        PushNumber(cycleForCounter),
        Retrieve(),
        JumpToLabelIfTopOfStackIsNegative(cycleFor6),

        End()

Игра в шпионов. Превращаем отрывок из книги в код.

Итак, предположим - мы написали код на whitespace. Что с ним веселого можно сделать?

Я отвечу: заменить непечатные символы в существующих книжных отрывках и превратить авторский текст в компилируемый код. Для этого я написал программку, которая работает по простому принципу: принимает пути до двух файлов: файлом с текстом, и файлом с whitespace-кодом. Затем она печатает в консоль результат "склейки".

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

Привожу пример работы программы! Изначально у нас был такой текст:

Отрывок из книги "Война и мир"
Гостиная Анны Павловны начала понемногу наполняться. Приехала высшая знать Петербурга, люди самые разнородные по возрастам и характерам, но одинаковые по обществу, в каком все жили; приехала дочь князя Василия, красавица Элен, заехавшая за отцом, чтобы с ним вместе ехать на праздник посланника. Она была в шифре и бальном платье. Приехала и известная, как la femme la plus séduisante de Pétersbourg 1, молодая, маленькая княгиня Болконская, прошлую зиму вышедшая замуж и теперь не выезжавшая в большой свет по причине своей беременности, но ездившая еще на небольшие вечера. Приехал князь Ипполит, сын князя Василия, с Мортемаром, которого он представил; приехал и аббат Морио и многие другие.
— Вы не видали еще, — или: — вы не знакомы с ma tante? 2 — говорила Анна Павловна приезжавшим гостям и весьма серьезно подводила их к маленькой старушке в высоких бантах, выплывшей из другой комнаты, как скоро стали приезжать гости, называла их по имени, медленно переводя глаза с гостя на ma tante, и потом отходила.
Все гости совершали обряд приветствования никому не известной, никому не интересной и не нужной тетушки. Анна Павловна с грустным, торжественным участием следила за их приветствиями, молчаливо одобряя их. Ma tante каждому говорила в одних и тех же выражениях о его здоровье, о своем здоровье и о здоровье ее величества, которое нынче было, слава Богу, лучше. Все подходившие, из приличия не выказывая поспешности, с чувством облегчения исполненной тяжелой обязанности отходили от старушки, чтоб уж весь вечер ни разу не подойти к ней.
Молодая княгиня Болконская приехала с работой в шитом золотом бархатном мешке. Ее хорошенькая, с чуть черневшимися усиками верхняя губка была коротка по зубам, но тем милее она открывалась и тем еще милее вытягивалась иногда и опускалась на нижнюю. Как это бывает у вполне привлекательных женщин, недостаток ее — короткость губы и полуоткрытый рот — казались ее особенною, собственно ее красотой. Всем было весело смотреть на эту полную здоровья и живости хорошенькую будущую мать, так легко переносившую свое положение. Старикам и скучающим, мрачным молодым людям казалось, что они сами делаются похожи на нее, побыв и поговорив несколько времени с ней. Кто говорил с ней и видел при каждом слове ее светлую улыбочку и блестящие белые зубы, которые виднелись беспрестанно, тот думал, что он особенно нынче любезен. И это думал каждый.
Маленькая княгиня, переваливаясь, маленькими быстрыми шажками обошла стол с рабочею сумочкой на руке и, весело оправляя платье, села на диван, около серебряного самовара, как будто все, что она ни делала, было partie de plaisir 3 для нее и для всех ее окружавших.
— J'ai apporté mon ouvrage 4, — сказала она, развертывая свой ридикюль и обращаясь ко всем вместе.
— Смотрите, Annette, ne me jouez pas un mauvais tour, — обратилась она к хозяйке. — Vous m'avez écrit que c'était une toute petite soirée; voyez comme je suis attifée 5.
И она развела руками, чтобы показать свое, в кружевах, серенькое изящное платье, немного ниже грудей опоясанное широкою лентой.
— Soyez tranquille, Lise, vous serez toujours la plus jolie 6, — отвечала Анна Павловна.
— Vous savez, mon mari m'abandonne, — продолжала она тем же тоном, обращаясь к генералу, — il va se faire tuer. Dites-moi, pourquoi cette vilaine guerre 7, — сказала она князю Василию и, не дожидаясь ответа, обратилась к дочери князя Василия, к красивой Элен.
— Quelle délicieuse personne, que cette petite princesse! 8 — сказал князь Василий тихо Анне Павловне.
Вскоре после маленькой княгини вошел массивный, толстый молодой человек с стриженою головой, в очках, светлых панталонах по тогдашней моде, с высоким жабо и в коричневом фраке. Этот толстый молодой человек был незаконный сын знаменитого екатерининского вельможи, графа Безухова, умиравшего теперь в Москве. Он нигде не служил еще, только что приехал из-за границы, где он воспитывался, и был первый раз в обществе. Анна Павловна приветствовала его поклоном, относящимся к людям самой низшей иерархии в ее салоне. Но, несмотря на это низшее по своему сорту приветствие, при виде вошедшего Пьера в лице Анны Павловны изобразилось беспокойство и страх, подобный тому, который выражается при виде чего-нибудь слишком огромного и несвойственного месту. Хотя действительно Пьер был несколько больше других мужчин в комнате, но этот страх мог относиться только к тому умному и вместе робкому, наблюдательному и естественному взгляду, отличавшему его от всех в этой гостиной.
— C'est bien aimable à vous, monsieur Pierre, d'être venu voir une pauvre malade 9, — сказала ему Анна Павловна, испуганно переглядываясь с тетушкой, к которой она подводила его. Пьер пробурлил что-то непонятное и продолжал отыскивать что-то глазами. Он радостно, весело улыбнулся, кланяясь маленький княгине, как близкой знакомой, и подошел к тетушке. Страх Анны Павловны был не напрасен, потому что Пьер, не дослушав речи тетушки о здоровье ее величества, отошел от нее. Анна Павловна испуганно остановила его словами:
— Вы не знаете аббата Морио? Он очень интересный человек... — сказала она.
— Да, я слышал про его план вечного мира, и это очень интересно, но едва ли возможно...
— Вы думаете?.. — сказала Анна Павловна, чтобы сказать что-нибудь и вновь обратиться к своим занятиям хозяйки дома, но Пьер сделал обратную неучтивость. Прежде он, не дослушав слов собеседницы, ушел; теперь он остановил своим разговором собеседницу, которой нужно было от него уйти. Он, нагнув голову и расставив большие ноги, стал доказывать Анне Павловне, почему он полагал, что план аббата был химера.
— Мы после поговорим, — сказала Анна Павловна, улыбаясь.
И, отделавшись от молодого человека, не умеющего жить, она возвратилась к своим занятиям хозяйки дома и продолжала прислушиваться и приглядываться, готовая подать помощь на тот пункт, где ослабевал разговор. Как хозяин прядильной мастерской, посадив работников по местам, прохаживается по заведению, замечая неподвижность или непривычный, скрипящий, слишком громкий звук веретена, торопливо идет, сдерживает или пускает его в надлежащий ход, — так и Анна Павловна, прохаживаясь по своей гостиной, подходила к замолкнувшему или слишком много говорившему кружку и одним словом или перемещением опять заводила равномерную, приличную разговорную машину. Но среди этих забот все виден был в ней особенный страх за Пьера. Она заботливо поглядывала на него в то время, как он подошел послушать то, что говорилось около Мортемара, и отошел к другому кружку, где говорил аббат. Для Пьера, воспитанного за границей, этот вечер Анны Павловны был первый, который он видел в России. Он знал, что тут собрана вся интеллигенция Петербурга, и у него, как у ребенка в игрушечной лавке, разбегались глаза. Он все боялся пропустить умные разговоры, которые он может услыхать. Глядя на уверенные и изящные выражения лиц, собранных здесь, он все ждал чего-нибудь особенно умного. Наконец он подошел к Морио. Разговор показался ему интересен, и он остановился, ожидая случая высказать свои мысли, как это любят молодые люди.
Вечер Анны Павловны был пущен. Веретена с разных сторон равномерно и не умолкая шумели. Кроме ma tante, около которой сидела только одна пожилая дама с исплаканным, худым лицом, несколько чужая в этом блестящем обществе, общество разбилось на три кружка. В одном, более мужском, центром был аббат; в другом, молодом, — красавица княжна Элен, дочь князя Василия, и хорошенькая, румяная, слишком полная по своей молодости, маленькая княгиня Болконская. В третьем — Мортемар и Анна Павловна.
Виконт был миловидный, с мягкими чертами и приемами, молодой человек, очевидно, считавший себя знаменитостью, но, по благовоспитанности, скромно предоставлявший пользоваться собой тому обществу, в котором он находился. Анна Павловна, очевидно, угощала им своих гостей. Как хороший метрдотель подает как нечто сверхъестественно-прекрасное тот кусок говядины, который есть не захочется, если увидать его в грязной кухне, так в нынешний вечер Анна Павловна сервировала своим гостям сначала виконта, потом аббата, как что-то сверхъестественно-утонченное. В кружке Мортемара заговорили тотчас об убиении герцога Энгиенского. Виконт сказал, что герцог Энгиенский погиб от своего великодушия и что были особенные причины озлобления Бонапарта.
— Ah! voyons. Contez-nous cela, vicomte, — сказала Анна Павловна, с радостью чувствуя, как чем-то à la Louis XV отзывалась эта фраза, — contez-nous cela, vicomte 1.
Виконт поклонился в знак покорности и учтиво улыбнулся. Анна Павловна сделала круг около виконта и пригласила всех слушать его рассказ.
— Le vicomte a été personnellement connu de monseigneur 2, — шепнула Анна Павловна одному. — Le vicomte est un parfait conteur 3, — проговорила она другому. — Comme on voit l'homme de la bonne compagnie 4, — сказала она третьему; и виконт был подан обществу в самом изящном и выгодном для него свете, как ростбиф на горячем блюде, посыпанный зеленью.
Виконт хотел уже начать свой рассказ и тонко улыбнулся.
— Переходите сюда, chère Hélène 5, — сказала Анна Павловна красавице княжне, которая сидела поодаль, составляя центр другого кружка.
Княжна Элен улыбалась; она поднялась с той же неизменяющеюся улыбкой вполне красивой женщины, с которою она вошла в гостиную. Слегка шумя своею белою бальною робой, убранною плющом и мохом, и блестя белизной плеч, глянцем волос и бриллиантов, она прошла между расступившимися мужчинами и прямо, не глядя ни на кого, но всем улыбаясь и как бы любезно предоставляя каждому право любоваться красотою своего стана, полных плеч, очень открытой, по тогдашней моде, груди и спины, и как будто внося с собою блеск бала, подошла к Анне Павловне. Элен была так хороша, что не только не было в ней заметно и тени кокетства, но, напротив, ей как будто совестно было за свою несомненную и слишком сильно и победительно действующую красоту. Она как будто желала и не могла умалить действие своей красоты.
— Quelle belle personne! 6 — говорил каждый, кто ее видел. Как будто пораженный чем-то необычайным, виконт пожал плечами и опустил глаза в то время, как она усаживалась пред ним и освещала и его все тою же неизменною улыбкой.
— Madame, je crains pour mes moyens devant un pareil auditoire 7, — сказал он, наклоняя с улыбкой голову.
Княжна облокотила свою открытую полную руку на столик и не нашла нужным что-либо сказать. Она, улыбаясь, ждала. Во все время рассказа она сидела прямо, посматривая изредка то на свою полную красивую руку, легко лежавшую на столе, то на еще более красивую грудь, на которой она поправляла бриллиантовое ожерелье; поправляла несколько раз складки своего платья и, когда рассказ производил впечатление, оглядывалась на Анну Павловну и тотчас же принимала то самое выражение, которое было на лице фрейлины, и потом опять успокоивалась в сияющей улыбке. Вслед за Элен перешла и маленькая княгиня от чайного стола.
— Attendez-moi, je vais prendre mon ouvrage, — проговорила она. — Voyons, à quoi pensez-vous? — обратилась она к князю Ипполиту. — Apportez-moi mon ridicule 8.
Княгиня, улыбаясь и говоря со всеми, вдруг произвела перестановку и, усевшись, весело оправилась.
— Теперь мне хорошо, — приговаривала она и, попросив начинать, принялась за работу.
Князь Ипполит перенес ей ридикюль, перешел за нею и, близко придвинув к ней кресло, сел подле нее.
Le charmant Hippolyte 9 поражал своим необыкновенным сходством с сестрою-красавицею и еще более тем, что, несмотря на сходство, он был поразительно дурен собой. Черты его лица были те же, как и у сестры, но у той все освещалось жизнерадостной, самодовольной, молодой, неизменной улыбкой и необычайной, античной красотой тела; у брата, напротив, то же лицо было отуманено идиотизмом и неизменно выражало самоуверенную брезгливость, а тело было худощаво и слабо. Глаза, нос, рот — все сжималось как будто в одну неопределенную и скучную гримасу, а руки и ноги всегда принимали неестественное положение.
— Ce n'est pas une histoire de revenants? 10 — сказал он, усевшись подле княгини и торопливо пристроив к глазам свой лорнет, как будто без этого инструмента он не мог начать говорить.
— Mais non, mon cher 11, — пожимая плечами, сказал удивленный рассказчик.
— C'est que je déteste les histoires de revenants 12, — сказал князь Ипполит таким тоном, что видно было, — он сказал эти слова, а потом уже понял, что они значили.
Из-за самоуверенности, с которою он говорил, никто не мог понять, очень ли умно или очень глупо то, что он сказал. Он был в темно-зеленом фраке, в панталонах цвета cuisse de nymphe effrayée 13, как он сам говорил, в чулках и башмаках.
Vicomte рассказал очень мило о том ходившем тогда анекдоте, что герцог Энгиенский тайно ездил в Париж для свидания с m-lle George 14 и что там он встретился с Бонапарте, пользовавшимся тоже милостями знаменитой актрисы, и что там, встретившись с герцогом, Наполеон случайно упал в тот обморок, которому он был подвержен, и находился во власти герцога, которою герцог не воспользовался, но что Бонапарте впоследствии за это-то великодушие и отмстил смертью герцогу.
Рассказ был очень мил и интересен, особенно в том месте, где соперники вдруг узнают друг друга, и дамы, казалось, были в волнении.
— Charmant 15, — сказала Анна Павловна, оглядываясь вопросительно на маленькую княгиню.
— Charmant, — прошептала маленькая княгиня, втыкая иголку в работу, как будто в знак того, что интерес и прелесть рассказа мешают ей продолжать работу.
Виконт оценил эту молчаливую похвалу и, благодарно улыбнувшись, стал продолжать; но в это время Анна Павловна, все поглядывавшая на страшного для нее молодого человека, заметила, что он что-то слишком горячо и громко говорит с аббатом, и поспешила на помощь к опасному месту. Действительно, Пьеру удалось завязать с аббатом разговор о политическом равновесии и аббат, видимо, заинтересованный простодушной горячностью молодою человека, развивал перед ним свою любимую идею. Оба слишком оживленно и естественно слушали и говорили, и это-то не понравилось Анне Павловне.
— Средство — европейское равновесие и droit des gens 16, — говорил аббат. — Стоит одному могущественному государству, как Россия, прославленному за варварство, стать бескорыстно во главе союза, имеющего целью равновесие Европы, — и оно спасет мир!
— Как же вы найдете такое равновесие? — начал было Пьер; но в это время подошла Анна Павловна и, строго взглянув на Пьера, спросила итальянца о том, как он переносит здешний климат. Лицо итальянца вдруг изменилось и приняло оскорбительно притворно-сладкое выражение, которое, видимо, было привычно ему в разговоре с женщинами.
— Я так очарован прелестями ума и образования общества, в особенности женского, в которое я имел счастье быть принят, что не успел еще подумать о климате, — сказал он.
Не выпуская уже аббата и Пьера, Анна Павловна

Затем, я запустил алгоритм, передал туда путь до файла с кодом алгоритма, созданного в этой статье и получил:

Исполняемый текст!
Гостиная Анны Павловны начала понемногу
наполняться. Приехала высшая знать Петербурга,
люди	самые	разнородные по возрастам и характерам,	но
одинаковые по обществу, в каком
все	жили;	приехала дочь князя Василия, красавица	Элен, заехавшая
за отцом, чтобы с ним
вместе	ехать	на праздник
посланника. Она была в
шифре и бальном платье.	Приехала	и известная, как la femme	la plus séduisante	de
Pétersbourg	1,
молодая,	маленькая	княгиня Болконская, прошлую зиму вышедшая
замуж	и	теперь	не выезжавшая в большой	свет
по	причине своей беременности, но ездившая еще на небольшие
вечера. Приехал
князь	Ипполит,	сын	князя Василия, с Мортемаром, которого	он	представил; приехал и аббат Морио	и многие другие.	—
Вы	не	видали	еще, — или: —	вы
не	знакомы с ma tante?
2	— говорила	Анна
Павловна приезжавшим гостям и	весьма	серьезно подводила их к маленькой	старушке в высоких	бантах,
выплывшей	из	другой	комнаты, как скоро стали	приезжать гости,
называла их
по	имени,	медленно	переводя глаза с гостя на	ma
tante,	и	потом	отходила. Все гости совершали	обряд	приветствования никому не известной, никому	не интересной и	не
нужной	тетушки.	Анна	Павловна	с грустным, торжественным участием следила за их	приветствиями,
молчаливо одобряя
их.	Ma	tante	каждому говорила
в одних
и тех
же
выражениях о его	здоровье,
о своем здоровье и	о
здоровье	ее	величества,	которое нынче было, слава	Богу, лучше.
Все	подходившие,	из	приличия	не выказывая поспешности,	с чувством облегчения исполненной	тяжелой
обязанности отходили
от	старушки,	чтоб	уж весь вечер ни разу не
подойти	к	ней.	Молодая княгиня Болконская приехала	с	работой
в	шитом золотом бархатном	мешке.
Ее	хорошенькая,	с	чуть черневшимися
усиками
верхняя губка
была	коротка	по
зубам,
но тем милее	она открывалась
и тем еще милее	вытягивалась	иногда	и опускалась	на нижнюю. Как
это бывает у вполне	привлекательных	женщин,	недостаток ее	— короткость	губы
и полуоткрытый рот —	казались	ее	особенною, собственно ее красотой. Всем
было весело смотреть на	эту	полную здоровья	и	живости	хорошенькую будущую
мать, так легко переносившую	свое	положение. Старикам	и скучающим, мрачным	молодым
людям казалось, что они	сами делаются похожи на нее, побыв
и поговорив несколько времени	с	ней. Кто говорил	с ней и
видел при каждом слове	ее	светлую улыбочку	и блестящие белые	зубы,
которые виднелись беспрестанно, тот	думал,	что он	особенно	нынче любезен. И
это думал каждый. Маленькая	княгиня,	переваливаясь, маленькими быстрыми шажками обошла	стол
с рабочею сумочкой на	руке	и,	весело оправляя	платье,	села на
диван, около серебряного самовара,	как	будто все,	что	она	ни делала,
было partie de plaisir	3	для нее	и для всех	ее
окружавших. — J'ai apporté	mon ouvrage 4, — сказала она,
развертывая свой ридикюль и	обращаясь ко	всем	вместе. — Смотрите,
Annette, ne me jouez	pas	un	mauvais tour, —	обратилась она
к хозяйке. — Vous	m'avez	écrit que	c'était	une	toute	petite
soirée; voyez comme je	suis	attifée	5. И она	развела руками,
чтобы показать свое, в	кружевах,	серенькое	изящное платье, немного	ниже грудей
опоясанное широкою лентой. —	Soyez	tranquille, Lise, vous	serez toujours	la
plus jolie 6, —	отвечала Анна Павловна.
— Vous savez,	mon	mari m'abandonne,	— продолжала она
тем	же	тоном, обращаясь
к генералу, —	il	va se
faire tuer. Dites-moi, pourquoi	cette vilaine guerre
7,	—	сказала	она князю Василию и,	не
дожидаясь	ответа, обратилась к дочери князя
Василия,	к	красивой
Элен. — Quelle délicieuse personne, que	cette petite princesse!
8 —
сказал	князь	Василий	тихо Анне Павловне. Вскоре после	маленькой княгини вошел
массивный,	толстый	молодой	человек
с	стриженою	головой,	в	очках, светлых
панталонах
по
тогдашней
моде,
с высоким жабо	и	в
коричневом фраке. Этот толстый	молодой	человек
был незаконный сын знаменитого	екатерининского
вельможи,	графа	Безухова,	умиравшего теперь в Москве.	Он нигде
не	служил	еще,	только	что приехал	из-за границы,	где	он воспитывался, и был первый	раз	в
обществе.	Анна	Павловна	приветствовала его поклоном, относящимся	к людям
самой	низшей	иерархии	в	ее салоне. Но,
несмотря на это низшее	по
своему	сорту	приветствие,	при	виде вошедшего Пьера	в
лице	Анны Павловны	изобразилось беспокойство и
страх,
подобный тому,
который	выражается при	виде
чего-нибудь
слишком огромного и	несвойственного месту. Хотя
действительно Пьер был несколько	больше других
мужчин	в	комнате,	но этот страх мог	относиться только к тому умному и
вместе робкому, наблюдательному и	естественному	взгляду,	отличавшему	его от всех	в
этой гостиной. — C'est	bien	aimable à vous, monsieur	Pierre, d'être
venu voir une pauvre	malade 9, — сказала ему Анна
Павловна, испуганно переглядываясь с	тетушкой,	к	которой она подводила	его. Пьер
пробурлил что-то непонятное и	продолжал	отыскивать что-то глазами.	Он радостно,	весело
улыбнулся, кланяясь маленький княгине,	как	близкой знакомой, и	подошел к тетушке.
Страх Анны Павловны был	не	напрасен, потому	что	Пьер,	не дослушав
речи тетушки о здоровье	ее	величества, отошел	от нее. Анна	Павловна
испуганно остановила его словами:	—	Вы не знаете аббата Морио?	Он
очень интересный человек... —	сказала	она. —	Да,	я слышал	про
его план вечного мира,	и	это очень интересно,	но едва	ли
возможно... — Вы думаете?..	—	сказала	Анна Павловна, чтобы	сказать что-нибудь
и вновь обратиться к	своим занятиям хозяйки дома, но Пьер
сделал обратную неучтивость. Прежде	он,	не	дослушав слов	собеседницы, ушел; теперь
он остановил своим разговором	собеседницу,	которой	нужно было	от него	уйти.
Он, нагнув голову и	расставив	большие ноги,	стал	доказывать	Анне	Павловне,
почему он полагал, что	план	аббата был	химера. — Мы после
поговорим, — сказала Анна	Павловна,	улыбаясь.	И, отделавшись	от молодого человека,
не умеющего жить, она	возвратилась	к своим	занятиям хозяйки дома	и
продолжала прислушиваться и приглядываться,	готовая	подать	помощь на	тот	пункт,	где
ослабевал разговор. Как хозяин	прядильной мастерской, посадив работников по местам,
прохаживается по заведению, замечая	неподвижность	или непривычный, скрипящий,	слишком громкий звук
веретена, торопливо идет, сдерживает	или	пускает его в	надлежащий ход,	—
так и Анна Павловна,	прохаживаясь	по своей гостиной,	подходила к замолкнувшему
или слишком много говорившему	кружку	и одним	словом или перемещением	опять
заводила равномерную, приличную разговорную	машину.	Но	среди этих	забот	все виден
был в ней особенный	страх	за Пьера.	Она заботливо поглядывала	на
него в то время,	как	он подошел послушать	то, что говорилось
около Мортемара, и отошел	к другому кружку, где говорил аббат.
Для Пьера, воспитанного за	границей,	этот	вечер Анны Павловны	был	первый,
который он видел в	России.	Он знал,	что тут собрана	вся
интеллигенция Петербурга, и у	него, как у ребенка в игрушечной
лавке, разбегались глаза. Он	все боялся	пропустить умные разговоры,	которые
он может услыхать. Глядя	на
уверенные	и	изящные	выражения лиц, собранных здесь,	он все	ждал чего-нибудь особенно умного.
Наконец он подошел к	Морио.	Разговор	показался ему	интересен, и он
остановился, ожидая случая высказать	свои	мысли, как	это	любят	молодые люди.
Вечер Анны Павловны был	пущен.	Веретена	с разных	сторон равномерно	и
не умолкая шумели. Кроме	ma	tante, около	которой	сидела	только	одна
пожилая дама с исплаканным,	худым	лицом, несколько	чужая	в этом	блестящем
обществе, общество разбилось на	три	кружка. В одном, более мужском,	центром
был аббат; в другом,	молодом, — красавица княжна Элен, дочь
князя Василия, и хорошенькая,	румяная,	слишком полная по	своей молодости, маленькая
княгиня Болконская. В третьем	—	Мортемар и Анна	Павловна. Виконт	был
миловидный, с мягкими чертами	и	приемами,	молодой человек, очевидно,	считавший себя
знаменитостью, но, по благовоспитанности,	скромно	предоставлявший пользоваться собой	тому обществу,	в
котором он находился. Анна	Павловна,	очевидно,	угощала им	своих гостей. Как
хороший метрдотель подает как	нечто	сверхъестественно-прекрасное тот	кусок	говядины,	который есть
не захочется, если увидать	его	в грязной кухне,	так в	нынешний
вечер Анна Павловна сервировала	своим гостям сначала
виконта, потом аббата,	как	что-то	сверхъестественно-утонченное.	В	кружке
Мортемара	заговорили	тотчас об
убиении герцога Энгиенского.	Виконт	сказал,	что
герцог Энгиенский погиб от	своего великодушия и
что	были	особенные	причины озлобления Бонапарта. —	Ah!
voyons.	Contez-nous cela, vicomte, — сказала
Анна	Павловна,	с
радостью чувствуя, как чем-то à la	Louis XV отзывалась
эта фраза,
—	contez-nous	cela,	vicomte 1. Виконт поклонился в	знак покорности и
учтиво	улыбнулся.	Анна	Павловна
сделала	круг	около	виконта	и	пригласила
всех	слушать
его рассказ.	— Le vicomte a	été personnellement connu
de monseigneur 2,	—	шепнула Анна Павловна одному.	— Le
vicomte	est	un parfait
conteur 3, —	проговорила она другому. —
Comme on voit l'homme	de la bonne
compagnie	4,	—	сказала она третьему; и	виконт
был	подан обществу в самом изящном
и	выгодном	для
него свете, как ростбиф на горячем	блюде, посыпанный зеленью.
Виконт хотел
уже	начать	свой	рассказ и тонко улыбнулся. —	Переходите сюда, chère
Hélène	5,	—	сказала
Анна	Павловна	красавице	княжне, которая сидела поодаль,
составляя	центр
другого кружка.	Княжна
Элен
улыбалась;
она
поднялась с той	же неизменяющеюся	улыбкой
вполне красивой женщины, с	которою	она	вошла в гостиную.	Слегка шумя
своею белою бальною робой,	убранною	плющом и мохом,	и блестя	белизной
плеч, глянцем волос и	бриллиантов,	она прошла между	расступившимися мужчинами и
прямо, не глядя ни	на	кого, но	всем	улыбаясь	и как
бы любезно предоставляя каждому	право	любоваться красотою	своего стана, полных	плеч,
очень открытой, по тогдашней	моде,	груди и спины, и как	будто
внося с собою блеск	бала,	подошла к	Анне	Павловне. Элен	была
так хороша, что не	только	не было в	ней заметно	и
тени кокетства, но, напротив,	ей	как	будто совестно было	за свою
несомненную и слишком сильно	и победительно действующую красоту. Она как
будто желала и не	могла	умалить	действие своей	красоты. — Quelle
belle personne! 6 —	говорил	каждый,	кто ее	видел. Как	будто
пораженный чем-то необычайным, виконт	пожал	плечами и	опустил	глаза	в	то
время, как она усаживалась	пред	ним и	освещала и его все
тою же неизменною улыбкой.	—	Madame,	je crains	pour mes moyens
devant un pareil auditoire	7,	— сказал	он, наклоняя с	улыбкой
голову. Княжна облокотила свою	открытую	полную	руку на	столик	и	не
нашла нужным что-либо сказать.	Она, улыбаясь, ждала. Во все время
рассказа она сидела прямо,	посматривая изредка
то	на	свою	полную красивую руку, легко	лежавшую на столе, то на еще
более красивую грудь, на	которой	она	поправляла	бриллиантовое ожерелье; поправляла	несколько
раз складки своего платья	и,	когда рассказ производил впечатление,	оглядывалась на
Анну Павловну и тотчас	же принимала то самое выражение, которое
было на лице фрейлины,	и	потом опять успокоивалась	в сияющей	улыбке.
Вслед за Элен перешла	и	маленькая княгиня	от	чайного стола. —
Attendez-moi, je vais prendre	mon	ouvrage, — проговорила она.	— Voyons,
à quoi pensez-vous? —	обратилась	она к	князю Ипполиту. —	Apportez-moi
mon ridicule 8. Княгиня,	улыбаясь	и	говоря со всеми,	вдруг	произвела
перестановку и, усевшись, весело	оправилась.	— Теперь	мне хорошо, —	приговаривала
она и, попросив начинать,	принялась	за	работу. Князь	Ипполит	перенес ей
ридикюль, перешел за нею	и,	близко придвинув	к ней кресло,	сел
подле нее. Le charmant	Hippolyte	9 поражал своим	необыкновенным сходством с
сестрою-красавицею и еще более	тем, что, несмотря на сходство, он
был поразительно дурен собой.	Черты	его	лица были	те же, как
и у сестры, но	у	той все	освещалось	жизнерадостной,	самодовольной,	молодой,
неизменной улыбкой и необычайной,	античной	красотой тела;	у	брата,	напротив, то
же лицо было отуманено	идиотизмом и неизменно выражало самоуверенную брезгливость,
а тело было худощаво	и	слабо.	Глаза, нос, рот	—	все
сжималось как будто в	одну	неопределенную и	скучную гримасу, а	руки
и ноги всегда принимали	неестественное положение. — Ce n'est pas
une histoire de revenants?	10 —	сказал он, усевшись	подле
княгини и торопливо пристроив	к
глазам	свой	лорнет,	как будто без этого	инструмента он	не мог начать говорить.
— Mais non, mon	cher	11,	— пожимая	плечами, сказал удивленный
рассказчик. — C'est que	je	déteste les	histoires	de	revenants 12,
— сказал князь Ипполит	таким	тоном,	что видно	было, —	он
сказал эти слова, а	потом	уже понял,	что	они	значили.	Из-за
самоуверенности, с которою он	говорил,	никто не	мог	понять, очень	ли
умно или очень глупо	то,	что он сказал. Он был	в
темно-зеленом фраке, в панталонах	цвета cuisse de nymphe effrayée 13,
как он сам говорил,	в	чулках и башмаках.	Vicomte рассказал очень
мило о том ходившем	тогда	анекдоте, что герцог	Энгиенский тайно	ездил
в Париж для свидания	с	m-lle	George 14 и	что там
он встретился с Бонапарте,	пользовавшимся	тоже милостями знаменитой	актрисы, и	что
там, встретившись с герцогом,	Наполеон	случайно	упал в	тот обморок, которому
он был подвержен, и	находился	во власти	герцога,	которою	герцог не
воспользовался, но что Бонапарте	впоследствии	за это-то великодушие	и отмстил	смертью
герцогу. Рассказ был очень	мил и интересен,
особенно в том	месте,	где	соперники	вдруг	узнают
друг	друга,	и дамы,
казалось, были в	волнении. — Charmant	15,
— сказала Анна Павловна,	оглядываясь вопросительно на
маленькую	княгиню.	—	Charmant, — прошептала маленькая	княгиня,
втыкая	иголку в работу, как будто
в	знак	того,
что интерес и прелесть рассказа мешают	ей продолжать работу.
Виконт оценил
эту	молчаливую	похвалу	и, благодарно улыбнувшись, стал продолжать;	но в это
время	Анна	Павловна,	все
поглядывавшая	на	страшного	для нее молодого	человека,
заметила,	что
он что-то	слишком горячо и громко	говорит с аббатом,
и поспешила на	помощь	к опасному	месту.	Действительно, Пьеру
удалось	завязать	с аббатом
разговор о политическом	равновесии и	аббат, видимо,
заинтересованный простодушной горячностью молодою	человека, развивал перед
ним	свою	любимую	идею. Оба слишком оживленно	и
естественно	слушали и говорили, и это-то
не	понравилось	Анне
Павловне. — Средство — европейское равновесие	и droit des
gens 16,
—	говорил	аббат.	— Стоит одному могущественному государству,	как Россия, прославленному
за	варварство,	стать	бескорыстно
во	главе	союза,	имеющего целью	равновесие Европы,
—	и
оно спасет	мир! — Как же	вы найдете такое
равновесие? — начал	было	Пьер; но в	это время
подошла	Анна	Павловна и,
строго взглянув на	Пьера, спросила	итальянца	о
том, как он переносит	здешний климат. Лицо
итальянца	вдруг	изменилось	и приняло оскорбительно притворно-сладкое	выражение,
которое,	видимо, было привычно ему в
разговоре	с	женщинами.
— Я так очарован прелестями ума	и образования общества,
в особенности
женского,	в	которое	я имел счастье быть принят,	что не успел
еще	подумать	о	климате,
—	сказал	он.	Не выпуская	уже	аббата
и
Пьера,
Анна
Павловна

Если вы забросите этот текст в любой whitespace-компилятор, он начнет выполнять наш алгоритм:

Пишем алгоритмы на самом ненужном языке в мире и страдаем - 6
Пишем алгоритмы на самом ненужном языке в мире и страдаем - 7

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

Не бейте за форматирование и опечатки, я наверняка что-то упустил.

Да, пришлось немного пострадать, но ни о чем не жалею.

К о н е ц.

Источники и ссылки:

Автор: youngmyn

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js