Интерпретатор из подворотен

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

    Ну а если серьёзно, как-то раз у нас на работе кто-то в шутку предложил написать гоп-язык, чтобы программистом мог себя почувствовать себя любой. Начинать конструкции со слова «чо» и всё такое. Тут надо заметить, что, не встретив на своём жизненном пути образования в области computer science, я пропустил все те интересные курсы по построению компиляторов, формальным грамматикам и прочим вкусностям, которые вкушают нормальные студенты на втором-третьем курсе. Книга Вирта по построению компиляторов хотя и добавила мне знания всяких умных терминов типа БНФ, но практической пользы не принесла ­— ни одного компилятора я так и не написал. Поэтому задача оказалась для меня довольно интересной.
    Если вы старше 18 лет, адекватно воспринимаете обсценную лексику нашего родного языка и вам интересно, с чего начать, добро пожаловать под кат.

    Из всех средств для работы с грамматикой я встречал упоминания всего нескольких. Во-первых, это конечно же lex, flex, yacc, bison и antlr, которые используются преимущественно для компиляторов на C/C++. Во-вторых, это ocamllex и ocamlyacc — которые предназначены для разработки компилятора, как несложно догадаться, на окамле. И в-третьих, это fslexx и fsyacc — то же самое, только для языка F#, который скопирован с ocaml чуть менее, чем полностью, зато впоследствии оброс немаленьким количеством плюшек. F# я отмёл, как отсутствующий в моей системе, а все плюсовые средства — по причине их многочисленности. Да, иногда богатый выбор — это тоже плохо, я побоялся запутаться во всех этих лексерах и парсерах.

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

    Типы языка

    Не буду врать, я не сразу продумал всю архитектуру языка и допиливал фичи постепенно. Но для сокращения размера материала (и чтобы быстро пройти тот путь, который у меня занял шесть прекрасных утренних воскресных часов) мы сделаем вид, будто всё было спланировано заранее. Каждая инструкция нашего языка является, скажем так, объектом. В C++ его можно было бы представить в виде одного из потомков некого базового класса Instruction, или в виде сложной структуры, или вообще в виде union. К счастью, окамл позволяет нам всё сделать максимально просто. Первый наш файл, yobaType.ml, который описывает все возможные виды инструкций, устроен максимально просто:
    type action =
            DoNothing
          | AddFunction of string * action list
          | CallFunction of string
          | Stats
          | Create of string
          | Conditional of int * string * action * action
          | Decrement of int * string
          | Increment of int * string;;

    Каждая конструкция языка будет приводиться к одному из этих типов. DoNothing — это просто оператор NOP, он не делает ровным счётом ничего. Create создаёт переменную (у нас они всегда целочисленны), Decrement и Increment соответственно уменьшают и увеличивают заданную переменную на какое-то число. Кроме этого есть Stats для вывода статистики по всем созданным переменным и Conditional — наша реализация if, которая умеет проверять, есть ли в заданной переменной требуемая величина (или большая). В самом конце я добавил AddFunction и CallFunction — возможность создавать и вызывать собственные функции, которые на самом деле очень даже процедуры.

    Грамматика языка

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

    Перед показом кода я расскажу, как устроен наш язык. Любая конструкция, кроме запроса статистики (это у нас как бы служебная команда) и создания функции начинается и заканчивается ключевыми словами. Благодаря этому мы можем смело расставлять как угодно переносы строк и отступы. Кроме этого (мы же работаем с русским языком) я специально создал по паре инструкций для случаев, когда надо передавать и переменную, и значение. Позже увидите, зачем это было нужно. Итак, наш файл yobaParser.mly:
    %{
            open YobaType
    %}

    %token <string> ID
    %token <int> INT

    %token RULEZ
    %token GIVE TAKE
    %token WASSUP DAMN
    %token CONTAINS THEN ELSE
    %token FUCKOFF
    %token STATS
    %token MEMORIZE IS
    %token CALL

    %start main
    %type <YobaType.action> main

    %%

    main:
            expr                                          { $1 }
    expr:
            fullcommand                                   { $1 }
          | MEMORIZE ID IS fullcommandlist DAMN           { AddFunction($2, $4) }
    fullcommandlist:
            fullcommand                                   { $1 :: [] }
          | fullcommand fullcommandlist                   { $1 :: $2 }
    fullcommand:
            WASSUP command DAMN                           { $2 }
          | STATS                                         { Stats }
    command:
            FUCKOFF                                       { DoNothing }
          | GIVE ID INT                                   { Increment($3, $2) }
          | GIVE INT ID                                   { Increment($2, $3) }
          | TAKE ID INT                                   { Decrement($3, $2) }
          | TAKE INT ID                                   { Decrement($2, $3) }
          | RULEZ ID                                      { Create($2) }
          | CALL ID                                       { CallFunction($2) }
          | CONTAINS ID INT THEN command ELSE command     { Conditional($3, $2, $5, $7) }
          | CONTAINS INT ID THEN command ELSE command     { Conditional($2, $3, $5, $7) }
    %%

    Первым делом мы вставляем заголовок — открытие модуля YobaType, который содержит наш тип action, описанный в самом начале. Для чисел и строк, не являющихся ключевыми словами языка (переменных) мы объявляем два специальных типа, которым указываем, что именно они в себе содержат. Для каждого из ключевых слов с помощью директивы %token мы создаём тоже свой тип, который будет идентифицировать это слово в грамматике. Можно было бы указать их все хоть в одну строчку, просто такая запись группирует всё по видам инструкций. Имейте в виду, что все созданные нами токены — это именно подстановочные типы, по которым парсер грамматики определяет, что ему делать. Обозвать их можно как угодно, то, как они будут выглядеть в самом языке, мы опишем позже. Указываем, что входной точкой для грамматики является main, и что возвращать он всегда должен объект типа action — инструкцию для интерпретатора. Наконец, после двух знаков %% мы описываем саму грамматику:
    • Инструкция состоит либо из команды (fullcommand), либо из создания функции.
    • Функция, в свою очередь, состоит из списка команд (fullcommandlist).
    • Команда бывает либо служебной (STATS), либо обычной (command), в таком случае она должна быть обёрнута в ключевые слова.
    • С обычной командой всё просто, даже расписывать не буду.

    В фигурных скобках мы указываем, что делать при совпадении строки с данным вариантом, при этом $N обозначает N-ный член конструкции. Например, если мы встречаем «CALL ID» (ID — это строка, не забываем), то мы создаём инструкцию CallFunction, которой в качестве параметра передаём $2 (как раз ID) — имя вызываемой функции.

    Лексер — превращаем язык в ключевые слова

    Мы дошли одновременно до практически самой простой и самой муторной части. Простая она, потому что всего лишь описывает превращение слов языка в наши токены. А муторная, потому что лексеры (а может, только окамловский лексер) плохо рассчитаны на работу с русским языком, поэтому работать с русскими символами можно только как со строками. Так как я хотел сделать ключевые слова языка регистро-независимыми, это добавило кучу геморроя — вместо простого написания «дай» надо было расписывать вариант написания каждой буквы. В общем, смотрите сами, файл yobaLexer.mll:
    1. {
    2.         open YobaParser
    3.         exception Eof
    4. }
    5. rule token = parse
    6.         ("и"|"И") ("д"|"Д") ("и"|"И") (' ')+
    7.         ("н"|"Н") ("а"|"А") ("х"|"Х") ("у"|"У") ("й"|"Й") { FUCKOFF }
    8.       | ("б"|"Б") ("а"|"А") ("л"|"Л") ("а"|"А")
    9.         ("н"|"Н") ("с"|"С") (' ')+
    10.         ("н"|"Н") ("а"|"А") ("х"|"Х")                     { STATS }
    11.       | [' ' '\t' '\n' '\r']                              { token lexbuf }
    12.       | ['0'-'9']+                                        { INT(int_of_string(Lexing.lexeme lexbuf)) }
    13.       | ("д"|"Д") ("а"|"А") ("й"|"Й")                     { GIVE }
    14.       | ("н"|"Н") ("а"|"А")                               { TAKE }
    15.       | ("ч"|"Ч") ("о"|"О")                               { WASSUP }
    16.       | ("й"|"Й") ("о"|"О") ("б"|"Б") ("а"|"А")           { DAMN }
    17.       | ("л"|"Л") ("ю"|"Ю") ("б"|"Б") ("л"|"Л") ("ю"|"Ю") { RULEZ }
    18.       | ("е"|"Е") ("с"|"С") ("т"|"Т") ("ь"|"Ь")           { CONTAINS }
    19.       | ("т"|"Т") ("а"|"А") ("д"|"Д") ("а"|"А")           { THEN }
    20.       | ("и"|"И") ("л"|"Л") ("и"|"И")                     { ELSE }
    21.       | ("у"|"У") ("с"|"С") ("е"|"Е") ("к"|"К") ("и"|"И") { MEMORIZE }
    22.       | ("э"|"Э") ("т"|"Т") ("о"|"О")                     { IS }
    23.       | ("х"|"Х") ("у"|"У") ("й"|"Й") ("н"|"Н") ("и"|"И") { CALL }
    24.       |
    25.       ("а"|"б"|"в"|"г"|"д"|"е"|"ё"|"ж"
    26.        |"з"|"и"|"й"|"к"|"л"|"м"|"н"|"о"
    27.        |"п"|"р"|"с"|"т"|"у"|"ф"|"х"|"ц"
    28.        |"ч"|"ш"|"щ"|"ъ"|"ы"|"ь"|"э"|"ю"|"я")+             { ID(Lexing.lexeme lexbuf) }
    29.       | eof                                               { raise Eof }

    Я только отмечу, что этот код нельзя показывать детям и людям с хрупкой душевной организацией, потому что вся обсценная лексика языка описана именно здесь. Кроме того, в начале мы открываем модуль нашего парсера, в котором определены все типы (STATS, GIVE, TAKE и т.п.) и создаём исключение, которое будет кидаться, когда ввод закончится. Обратите внимание на 11-ую строку, в ней указывается, что пробелы и прочий мусор нужно игноировать, при этом она специально идёт после тех инструкций, которые содержат в себе пробелы. 12-ая и 25-28-ая строки обрабатывают числа и названия переменных, а 29-ая кидает то самое исключение, обозначающее конец файла.

    Интерпретатор

    Осталась последняя часть — сам интерпретатор, который обрабатывает наши конструкции языка. Сначала код, потом объяснение:
    1. open YobaType
    2.  
    3. let identifiers = Hashtbl.create 10;;
    4. let funcs = Hashtbl.create 10;;
    5.  
    6. let print_stats () =
    7.         let print_item id amount =
    8.                 Printf.printf ">> Йо! У тебя есть %s: %d" id amount;
    9.                 print_newline ();
    10.                 flush stdout in
    11.         Hashtbl.iter print_item identifiers;;
    12.  
    13. let arithm id op value () =
    14.         try
    15.                 Hashtbl.replace identifiers id (op (Hashtbl.find identifiers id) value);
    16.                 Printf.printf ">> Гавно вопрос\n"; flush stdout
    17.         with Not_found -> Printf.printf ">> Х@#на, ты %s не любишь\n" id; flush stdout;;
    18.  
    19. let rec cond amount id act1 act2 () =
    20.         try
    21.                 if Hashtbl.find identifiers id >= amount then process_action act1 () else process_action act2 ()
    22.         with Not_found ->
    23.                 Printf.printf ">> Човаще?!\n";
    24.                 flush stdout
    25. and process_action = function
    26.         | Create(id) -> (function () -> Hashtbl.add identifiers id 0)
    27.         | Decrement(amount, id) -> arithm id (-) amount
    28.         | Increment(amount, id) -> arithm id (+) amount
    29.         | Conditional(amount, id, act1, act2) -> cond amount id act1 act2
    30.         | DoNothing -> (function () -> ())
    31.         | Stats -> print_stats
    32.         | AddFunction(id, funclist) -> (function () -> Hashtbl.add funcs id funclist)
    33.         | CallFunction(id) -> callfun id
    34. and callfun id () =
    35.         let f: YobaType.action list = Hashtbl.find funcs id in
    36.         List.iter (function x -> process_action x ()) f
    37. ;;
    38.  
    39. while true do
    40.         try
    41.                 let lexbuf = Lexing.from_channel stdin in
    42.                 process_action (YobaParser.main YobaLexer.token lexbuf) ()
    43.         with
    44.                 YobaLexer.Eof ->
    45.                         print_stats ();
    46.                         exit 0
    47.               | Parsing.Parse_error ->
    48.                         Printf.printf ">> Ни@#я не понял б@#!\n";
    49.                         flush stdout
    50.               | Failure(_) ->
    51.                         Printf.printf ">> Ни@#я не понял б@#!\n";
    52.                         flush stdout
    53. done

    Первым делом мы создадим две хэштаблицы — для переменных и для функций. Начальный размер 10 взят от фонаря, у нас же тренировочный язык, зачем нам сразу много функций.
    Затем объявим две небольших функции: одна — для вывода статистики, вторая — для инкремента/декремента переменных.

    Дальше идёт группа из сразу трёх функций: cond обрабатывает условные конструкции (наш if), callfun отвечает за вызов функций, а process_action отвечает за обработку пришедшей на вход инструкции как таковой. Надеюсь, почему все три функции зависят друг от друга, объяснять не надо.

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

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

    Добавим к этому Makefile и можно играться:
    all:
       ocamlc -c yobaType.ml
       ocamllex yobaLexer.mll
       ocamlyacc yobaParser.mly
       ocamlc -c yobaParser.mli
       ocamlc -c yobaLexer.ml
       ocamlc -c yobaParser.ml
       ocamlc -c yoba.ml
       ocamlc -o yoba yobaLexer.cmo yobaParser.cmo yoba.cmo

    clean:
       rm -f *.cmo *.cmi *.mli yoba yobaLexer.ml yobaParser.ml

    Проверка языка

    А теперь внимание, зачем я делал поддержку разного порядка значений и переменных? Дело в том, что язык получился очень, гм, натуральным, интерпретатор буквально разговаривает с вами. Поэтому я сам постоянно путал, в каком порядке что писать:
    $ ./yoba
    чо люблю сэмки йоба
    чо люблю пиво йоба
    усеки сэмкораздача это
    чо дай 3 сэмки йоба
    чо дай 4 сэмки йоба
    чо на пиво 2 йоба
    йоба
    чо х@#ни сэмкораздача йоба
    >> Гавно вопрос
    >> Гавно вопрос
    >> Гавно вопрос
    баланс нах
    >> Йо! У тебя есть сэмки: 7
    >> Йо! У тебя есть пиво: -2
    чо есть 4 сэмки тада дай 1 пиво или иди на@#й йоба
    >> Гавно вопрос
    баланс нах
    >> Йо! У тебя есть сэмки: 7
    >> Йо! У тебя есть пиво: -1
    чо есть 4 сэмки тада х@#ни сэмкораздача или х@#ни сэмкораздача йоба
    >> Гавно вопрос
    >> Гавно вопрос
    >> Гавно вопрос
    баланс нах
    >> Йо! У тебя есть сэмки: 14
    >> Йо! У тебя есть пиво: -3

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

    Надеюсь, было интересно. Весь код интерпретатора можно скачать тут (2кб).
    Если более опытные люди поделятся замечаниями по поводу кода, буду благодарен.
    Share post

    Comments 28

      +15
      а где воркеры? ну т.е. «лохи»?
        +60
        Можно добавить пару операторов?

        «if» — «Есть чё?»
        «else» — «А если найду?»
          +14
          «А если найду?» — это elif
          • UFO just landed and posted this here
            0
            «while» — «хоп, давай-давай!»
            +1
            Очень!
              +67
              «try» — «кинул пацана?»
              «catch» — «по #балу на!»
                +1
                try = зацени

                catch = опа!

                finally = палюбому
                +2
                Сразу вспомнился йобибайт. Если компилятор обработает такое количество информации — снимаю шляпу!
                  +31
                  Надо срочно двигать в массы! У меня тут под окнами периодически очень перспективные программисты собираются!
                    +26
                    Так сказать, придадим альтернативный смысл термину быдлокодер!
                      0
                      быдлокодер, в хорошем смысле этого слова
                    +2
                    Чо пацан, ANTLR позволяет лексер и парсер в одном файле делать, вообще удобный парсер
                    А ваще — зачОт!
                      +38
                      Даёшь SCM для риальных пацанов!

                      $ сука замути ; init
                      $ сука взял код.йоба ; add
                      $ сука понял ; commit
                      $ сука сныкал на районе ; push
                      $ сука чё нового; pull
                      $ сука чё там ; diff
                      $ сука чёзакаракули ; gui
                        +16
                        — Микрософт приняла решение переписать Windows N на языке Йоба#. Для это были наняты четкие пацанчики из Подмосковья, во главе с профессиональным кодером Сявой. Как сообщает пресс-центр Микрософта это будет первая система по понятиям.
                        • UFO just landed and posted this here
                            +14
                            Я бы выдал премию «золотой велосипед»! :)
                            Практической пользы — ровно ноль. Но, БЛИН!, как же это чертовски интересно! Вот ради таких вещей можно надо было уходить в программисты в своё время. Это же творчество в чистом виде!
                              +3
                              Забавно получилось! Предлагаю ещё пару команд ввода-вывода

                              «Отвечаешь?» — input
                              «Нан#х» — print
                                +80
                                Почему еще никто не запостил это:
                                #define подъёбку setlocale
                                #define чуть_чуть 7
                                #define так_себе 12
                                #define пошло_оно_всё 120
                                #define срака double
                                #define волосатая unsigned long
                                #define фигню фигня
                                #define кидай cin >> 
                                #define кончил }
                                #define начал {
                                #define конкретно *
                                #define ну )
                                #define в_общем (
                                #define кагбэ [ 
                                #define ХУЙ 0
                                #define да ] 
                                #define какая_то int
                                #define какой_то int
                                #define какое_то int
                                #define какие_то int
                                #define давай void
                                #define туды_сюды for
                                #define Слышь_это cout <<
                                #define эээ <<
                                #define и_ещё_больше ++
                                #define хуякс /
                                #define Подрыхнуть Sleep
                                #define подвинь_жопу new
                                #define бля endl
                                #define шнягу шняга
                                #define стал =
                                #define стала =
                                #define стало =
                                #define стали =
                                #define взад return
                                #define ну_если_уж if
                                #define убрать_нахуй delete
                                #define Закрой_Пасть CloseHandle
                                #define УЁБИЩЕ HANDLE
                                #define стало_похоже_на ==
                                #define говно NULL
                                #define присобачить +=
                                #define тогда /*WTF*/
                                #define Жди_Хрен_Дождёшься WaitForSingleObject
                                #define вантуз GetLastError
                                #define ХУИТА main // sic!
                                #define поехали CreateThread
                                #define въёбывай LPTHREAD_START_ROUTINE
                                #define почти <
                                #define норма 1
                                #define ДОХУЯ INFINITE
                                #include <windows.h>
                                #include <iostream>
                                #pragma warning в_общем disable: 4244 ну 
                                using namespace std;
                                 
                                какая_то фигня;
                                какие_то маленькое, ОГРОМНОЕ;
                                какие_то Ленин, ЕБАНУТОСТЬ;
                                 
                                давай поработай в_общем какая_то конкретно шняга ну 
                                начал 
                                	маленькое стало шняга кагбэ ХУЙ да;
                                	какой_то козёл, говнистость;
                                	туды_сюды в_общем козёл стал норма; козёл почти фигня; козёл и_ещё_больше ну 
                                	 начал 
                                		ну_если_уж в_общем шняга кагбэ козёл да почти маленькое ну 
                                		 начал 
                                			маленькое стало шняга кагбэ козёл да;
                                			Ленин стал козёл;
                                			Подрыхнуть в_общем так_себе ну;
                                		 кончил 
                                	 кончил 
                                 
                                	Слышь_это "\n\nМинимальный элемент массива: " эээ маленькое эээ бля;
                                 
                                	ОГРОМНОЕ стало шняга кагбэ ХУЙ да;
                                	туды_сюды в_общем говнистость стало норма; говнистость почти фигня; говнистость и_ещё_больше ну 
                                	 начал 
                                		ну_если_уж в_общем шняга кагбэ говнистость да > ОГРОМНОЕ ну 
                                		 начал 
                                			ОГРОМНОЕ стало шняга кагбэ говнистость да;
                                			ЕБАНУТОСТЬ стала говнистость;
                                			Подрыхнуть в_общем пошло_оно_всё ну;
                                		 кончил кончил 
                                 
                                	Слышь_это "\n\nМаксимальный элемент массива: " эээ ОГРОМНОЕ эээ бля; кончил 
                                	 какая_то ХУИТА в_общем ну начал подъёбку в_общем ХУЙ, ".1251" ну;
                                 
                                	Слышь_это "\nВведите размерность массива: \n"; кидай фигню;
                                	какая_то конкретно шняга стал подвинь_жопу какая_то кагбэ фигня да;
                                	Слышь_это "\nВведите элементы массива: \n";
                                 
                                	туды_сюды в_общем какой_то козёл стал говно; 
                                	козёл почти фигня; козёл и_ещё_больше ну кидай шнягу кагбэ козёл да;
                                	волосатая пизда;
                                 
                                	УЁБИЩЕ быдло стало поехали в_общем говно, говно, в_общем въёбывай ну поработай, в_общем давай конкретно ну шняга, ХУЙ, &пизда ну;
                                 
                                	ну_если_уж в_общем быдло стало_похоже_на говно ну тогда взад вантуз в_общем ну; // Если ошибка
                                 
                                	// Находим среднее арифметическое
                                	срака посередине стало шняга кагбэ ХУЙ да;
                                	туды_сюды в_общем какая_то козёл стал норма; козёл почти фигня; козёл и_ещё_больше ну 
                                	 начал 
                                		посередине присобачить шнягу кагбэ козёл да;
                                		Подрыхнуть в_общем чуть_чуть ну;
                                	 кончил 
                                	посередине стало посередине хуякс фигня;
                                	Слышь_это "\n\nСреднее арифметическое элементов массива: " эээ посередине эээ бля;
                                 
                                	Жди_Хрен_Дождёшься в_общем быдло, ДОХУЯ ну; // Ждём, пока поток поработай закончит работу
                                 
                                	какая_то писька стала посередине; // Целая часть
                                	шняга кагбэ Ленин да стал писька; // Замена минимума
                                	шняга кагбэ ЕБАНУТОСТЬ да стала писька; // Замена максимума
                                 
                                	Слышь_это "\nМассив с заменой минимума и максимума на целую часть среднего арифметического: \n" эээ бля;
                                	туды_сюды в_общем какая_то какашка стала говно; какашка почти фигня; какашка и_ещё_больше ну 
                                		начал Слышь_это шняга кагбэ какашка да эээ " "; кончил 
                                	Слышь_это бля эээ бля;
                                	Закрой_Пасть в_общем быдло ну; // Закрываем дескриптор потока
                                	убрать_нахуй шнягу;
                                	взад ХУЙ; кончил
                                
                                  +15
                                  Последняя строка очень красноречива )
                                    +19
                                    Курсовую что ли так написать
                                      0
                                      Не хватает строчки:
                                      #define пижже_чем >
                                      +4
                                      А как можно было забыть о таком замечательном языке, как LOLCODE, тем более что о нём знает даже Википедия?
                                        +3
                                        Во времена пошли, гопники уже свои языки программирования пишут, на отнятом у очкозавра ноуте.
                                          –1
                                          class РовныйПацан: Район {}

                                          public interface Понятия {}

                                          public interface Рамсы: Понятия {}

                                          public interface Берега: Рамсы {}
                                            +1
                                            Надо срочно делать диалект SQL, чтобы гопники могли запрашивать семки, на пиво и позванить по чётким понятиям.
                                              0
                                              SELECT `мелочь`, `семки` from `лошара` =>`лошара`, `мелочь`, `семки` есть?
                                              +3
                                              > кидает то самое исключение, обозначающее конец файла.

                                              Уже в середине вашего текста прочитал как «кидает то самое заключение, обозначающее конец фуфла».

                                              Only users with full accounts can post comments. Log in, please.