# Slime ![](https://i.imgur.com/lepTKFG.png) <!-- ![](https://i.imgur.com/pLVexp9.png) --> Slime - эзотерический язык программирования нового поколения, имеющий строгую статическую типизацию и динамическую типизацию одновременно. Он содержит все современные языковые средства*: внутриблочные описания, автоопределение типа, кортежи, срезы, классы, перегрузку операций, интерфейсы, обработку исключений, обобщенные классы и подпрограммы, лямбда-выражения, средства параллельного программирования. *некоторые из перечисленных средств требуют реализации собственного парсера и интерпретатора текстового содержания функций ## Спецификация ### Типы данных - Массив - последовательный контейнер произвольного доступа, элементами которого могут быть данные любого типа. Массив имеет заранее определённую длину - целое число (неограниченного диапазона). Модуль длины массива определяет количество содержащихся в нём элементов, а знак - порядок индексации - массивы положительной длины - индексируются с нуля ```slime array = ["a", "b", "c"]; print(array[0]); print(array[1]); print(array[2]); ``` Вывод: ```bash abc ``` - массивы отрицательной длины - индексируются также с нуля, но уже в отрицательном направлении ```slime array = -["a", "b", "c"]; print(array[0]); print(array[-1]); print(array[-2]); ``` Вывод: ```bash abc ``` Обращение к элементу массива за пределами его диапазона возвращает пустой массив, а запись по такому индексу не производит никакого эффекта (такая операция записи также возвращает `[]`). ### Комментарии Комментарии как в питоне `#` ### Способы определить массив ```slime # Просто как массив x = [[], [[], [], []], [[[]]]]; # Числовые литералы # Определяют массив, длина которого соответствует значению литерала, # заполненный пустыми массивами x = 4; # эквивалентно [[], [], [], []] # Также поддерживаются двоичные, восьмеричные и шестнадцатеричные числовые литералы bin = 0b001001; oct = 010; # Это 8 hex = 0xC0FFEE; # символьные литералы # Эквивалентны числовым литералам, определяющим соответствующую кодовую точку Unicode x = 'x'; # эквивалентно 120 # Строковые литералы # Эквивалентны определению массива символьных литералов, # составляющих данную строку str = "Hello Slime!"; # эквивалентно ['H', 'e', 'l', 'l', 'o', ' ', 'S', 'l', 'i', 'm', 'e', '!'] # Строковые и символьные литералы поддерживают escape последовательности, # аналогично языку Си str = "\t***\n\t*x*\n\t***" # Объявление функции fib = { @[0] <= 0 || (fib(@[0] - 1) + fib(@[0] - 2)) }; # Объявление функции всегда создаёт массив из двух элементов, # первый из которых число (на самом деле массив, длина которого равна этому числу), # равное индексу фрейма стека, в котором объявлена функция, # а второй - исходный текст тела функции # Объявление функции fib в глобальном пространстве эквивалентно следующей записи fib = [0, "\n @[0] <= 0 || (fib(@[0] - 1) + fib(@[0] - 2))\n"]; ``` ### Операторы #### Унарные операторы - `+` — ничего не делает ```slime simple_x = [1, 2, 3]; added_x = +[1, 2, 3]; if {simple_x == added_x} { print("simple_x == added_x"); } ``` Вывод: ``` simple_x == added_x ``` - `-` — создаёт массив с таким же числом элементов, но инвертированным знаком длины. Элемент с индексом `i` исходного массива равен элементу с индексом `-i` результирующего ```slime x = -[1, 2, 3]; # x[0] == 1, x[-1] == 2, x[-2] == 3 ``` - `!` - пустой массив превращает в массив, содержащий один пустой массив, любой другой - в пустой массив ```slime x = ![]; # [[]] x = ![1, 2, 3] # [] ``` #### Бинарные операторы - Операции сравнения `>`, `<`, `>=`, `<=`, `==`, `!=` - реализуют полное глубокое лексикографическое сравнение. Массив отрицательной длины всегда считается меньше массивов положительной или нулевой длины. Сравнение двух массивов отрицательной длины на `>`, `<`, `>=`, `<=`, `==`, `!=` эквивалентно сравнению обратным им массивов (взятых со знаком `-`) на `<`, `>`, `<=`, `>=`, `==`, `!=` соответственно. ```slime # (a > b) эквивалентно (-a > -b) # (a <= b) эквивалентно (-a <= -b) ``` Операции сравнения возвращают `[]` если результат сравнения ложь, и `[[]]`, если истина - `+` — конкатенирует массивы. ```slime x = [1, 2, 3] + ["hello", "slime"]; # [1, 2, 3, "hello", "slime"] y = -[1, 2, 3] + -[4, 5]; # -[1, 2, 3, 4, 5] ? # Отрицательные массив при конкатенации с положительным # уменьшает его размер на собственную длину a = [1, 2, 3] + -["hello", "slime"]; # [1] b = [1, 2, 3] + -["hello", "slime", "!", "!!!"]; # -["!!!"] c = [1] + -["hello", "slime", "!", "!!!"]; # -["slime", "!", "!!!"] # Для конкатенации применимо следующее правило # -(a + b) == (-b + -a) p = -[1] + [a, b, c] # -([1] + -[a, b, c]) => [b, c] q = -[1, 2, 3] + -["hello", "slime"]; # -[1, 2, 3, "hello", "slime"] ``` - `-` — эквивалентен сложению с отрицательным массивом ```slime # x - y == x + -y ``` - `*` — умножение, конкатенирует первый массив сам с собой столько раз, каков модуль длина второго. Если второй массив отрицательный, от результата берётся отрицание ```slime x = "Slime!" * 3; # "Slime!Slime!Slime!" y = "Slime!" * -3; # -"Slime!Slime!Slime!" z = -"Slime!" * 3; # -"Slime!Slime!Slime!" t = -"Slime!" * -3; # "Slime!Slime!Slime!" ``` - `/` и `%` деление и остаток от деления. Если длины массивов положительны, то длина первого массива целочисленно делится на длину второго, в результате оператор `/` возвращает первые p элементов первого массива, где p - результат деления, а операция `%` возвращает последние q элементов, где q - остаток от деления. Если один из массивов отрицательный, результат берётся обратный; если оба отрицательные, знаки отбрасываются. ```slime x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; x / 3; # [1, 2, 3] x % 3; # [10] -x / 3; # -[1, 2, 3] x / -3; # -[1, 2, 3] -x / -3; # [1, 2, 3] ``` - `&&` и `||` работают примерно как в js. + `&&` - возвращает левый операнд если он `[]` и второй операнд в противном случае + `||` - возвращает левый операнд если он не`[]` и второй операнд в противном случае если операция возвращает левый операнд, значение правого не подсчитывается - Оператор добавления `.` — `a.b` эквивалентно `a + [b]` (TODO: возможно, стоит выбрать другой символ для этого оператора, например, `^`) - Оператор `,` — выполняет левую часть, затем правую, возвращает результат правой. - `=` — оператор присваивания. Левый операнд этого оператора обязан быть lvalue (в ином случае операция не производит никаких действий, и возвращает `[]`). Lvalue в Slime являются идентификаторы(включая @), и результат операции взятия элемента по индексу `[]`. #### Операторы скобок - `()` - вызов функции. Параметры, перечисленные внутри скобок через запятую, превращаются в массив - входной параметр функции `@` ```slime fun = { print(@[0]); print(@[1]); print(@[2]); }; fun("Hello ", "Slime", "!!!") ``` Вывод: ```bash Hello Slime!!! ``` - `{}` - вызов функции с передачей функционального параметра. Трактует содержимое скобок как тело функции, и передаёт в `@` как функцию, задавая в качестве индекса стека индекс стека вызывающего контекста ```slime if = { @() && { @() }} # реализация if из стандартной библиотеки if { 3 > 0 } { print("3 > 0"); } ``` Вывод: ```bash 3 > 0 ``` - `[]` - оператор доступа по индексу. Возвращает элемент массива. Результат этой операции является lvalue. #### Приоритет операторов Как в js. Можно пользоваться круглыми скобками для изменения приоритета. Вызов функции фигурными скобками имеет такой же приоритет, как и вызов обычными круглыми. #### Идентификаторы Идентификатор в Slime определяется так же, как и в языке Си. Помимо этого существует специальный идентификатор `@`, для доступа ко входным параметрам функции. ### Функции Функция — массив вида: `[<frame_index>, <code>, <p_0>, <p_1>, ...]` - `<frame_index>` — неотрицательное slime-число (массив, длина которого трактуется как число), определяющее индекс кадра стека, в котором была создана функция - `<code>` — slime-строка, содержащая исходный код функции - `<p_i>` — необязательные предварительно связанные параметры функции При попытке вызвать функцию, не удовлетворяющую этому формату, или если `<code>` не является валидным кодом на slime, возвращается `[]`. #### Программный стек При вызове функции создаётся новый кадр программного стека, хранящий информацию о локальных переменных. Индекс кадра стека всегда на 1 больше индекса кадра, вызвавшего функцию. Индекс кадра глобального контекста 0. Каждая выполняемая функция имеет возможность обращаться в текущий, её собственный, кадр; кадр контекста, в котором она была создана; кадр глобального контекста. Если внутри функции происходит обращение к переменной, она ищется в порядке: текущий контекст, контекст создания; глобальный контекст. Если переменная не найдена, она создаётся в текущем кадре. #### Содержимое функции Функция — набор строк кода, отделённых символом `;`. Внутри функции можно использовать обращение к идентификаторам (переменным), операторами языка и круглыми скобками. В текущем кадре функции всегда определён идентификатор `@`, содержащий ещё входные параметры. При вызове функции через круглые скобки `@` заполняется набором предварительно связанных параметров, и и набором переданных через запятую входных параметров в скобках. ```slime foo = { print(@[0]); print(@[1]); print(@[2]); print(@[3]); print(@[4]); }; bar = foo."Slime "."is "; bar("the best ", "language", "in the world!!!"); ``` Вывод ```bash Slime is the best language in the world!!! ``` При вызове через фигурные скобки, предварительно связанные переменные игнорируются, а в `@` попадает новая созданная функция. ```slime foo = { @(); }; bar = foo.{ print("Slime is the worst language in the world") }; bar{ print("Slime is the best language in the world!!!") }; ``` Вывод ```bash Slime is the best language in the world!!! ``` Результатом выполнения функции является результат её последней непустой строки. Так, любая программа на Slime является slime-функцией, в которой `@` инициализирован набором входных параметров, а её результат — результат выполнения всей программы. ## Стандартная библиотека (TODO) ```slime null = []; false = []; true = [[]]; if = { @() && { @() }}; while = # в разработке ... ```