Micro, v2.0, описание
Posted on

Micro — интерпретируемый высокоуровневый язык программирования. Поддерживает функциональную парадигму, обладает слабой полустатической типизацией и сборкой мусора.
Общее описание
Вызовы функций
Программы на данном языке представляют собой последовательность вызовов функций. Вызов функции записывается следующим образом:
function, {parameter}
При этом и function, и parameter могут быть либо константами (числовыми или строковыми), либо точно такими же вызовами функций.
При исполнении сначала вычисляется выражение для самой функции. По результату определяется арность функции. Затем вычисляется нужное число аргументов. И наконец они передаются функции.
Например, имеем следующее выражение:
+ 2 * 5 12
Первой интерпретатор встречает функцию +. Она имеет арность 2. Интерпретатор начинает вычислять 2 аргумента для неё. Первый равен 2. При вычислении второго же обнаруживается новая функция — *, которая также имеет арность 2. Поэтому начинается расчёт 2 аргументов уже для функции *. Это 5 и 12. После этого интерпретатор может вычислить результат выражения * 5 12 (это 60), который и будет вторым аргументом для функции +. Затем интерпретатор может вычислить результат всего выражения (+ 2 60) — 62.
Разбиение на токены
Константы:
- числа —
/\d+(\.\d+)?(e-?\d+)?/, например:23,4.2; - символы —
/'(\\['\\tn]|[^'\n])'/, например:'t','\''; - строки —
/"(\\["\\tn]|(?!\\)[^"])*"/, например:"","test","Say \"Hello!\".".
Идентификаторы:
- буквенные —
/[a-z_]+/i - "fn"; - пунктуационные —
/[[:punct:]]+/ - /['"_():;]/.
Идентификаторы разных типов и любые константы можно чередовать без разделения их пробелами. Например, код ~sin2.3 будет преобразован в следующую последовательность токенов: ~, sin и 2.3.
Пользовательские функции
Пользователь может объявлять собственные функции:
"fn", [name], "(", {argument, type}, ")", type,
{entity},
";"
name и argument — любые идентификаторы. Имя функции указывать необязательно.
Если имя функции указано, то данная функция доступна для вызова посредством этого имени ниже в текущей области видимости, а также внутри себя самой. Анонимные функции доступны для вызова только после передачи их в другие функции в качестве аргументов.
Функции можно переопределять.
Результатом выполнения функции является последнее вычисленное внутри неё значение.
Замыкания
Внутри функции доступны все идентификаторы из внешних для функции областей видимости. Если параметры функции имеют имя, совпадающее с одним из этих идентификаторов, то параметр перекрывает этот идентификатор.
Переменные
В качестве переменных используются функции вида fn name() value;. Они не требуют аргументов и при вызове просто возвращают сохранённое в них значение.
Для изменения значения переменных используется возможность переопределять функции.
Полустатическая типизация
Все значения в языке разделяются на простые и вызываемые. К вызываемым относятся функции арности больше 0, к простым — все остальные типы и функции арности 0.
Типы для простых значений не указываются. Для вызываемых значений требуется указание типа для аргументов и результата функций. Тип представляет собой список из арности функции и аналогичного типа для результата функции, начинающийся и разделяемый символом :.
Например, тип :5:12:23 обозначает функцию арности 5, которая возвращает функцию арности 12, которая возвращает функцию арности 23.
Циклический вызов функций
В языке функции арности 0 эквивалентны значению своего результата.
Это означает, что когда интерпретатор встречает функцию арности 0, он начинает циклически её вызывать до тех пор, пока результат вызова не перестанет быть функцией арности 0.
Допустим, имеется определение:
fn answer()
fn()
fn()
42
;
;
;
Тогда, вызов функции answer будет эквивалентен значению 42.
Циклический вызов может остановиться не только на простом значении, но и на вызываемом. Допустим, имеется определение:
fn add():0:0:2
fn():0:2
fn():2
fn(x y)
+ x y
;
;
;
;
Тогда, вызов функции add 2 3 вернёт значение 5.
Рекурсия
Простой рекурсивный вызов невозможен — интерпретатор имеет низкий предел на глубину рекурсии. Чтобы обойти его, используется упаковка вызовов.
В рантайме имеется функция >@, которая упаковывает свой аргумент, не вызывая его. Так как упаковываемый вызов не вычисляется, а результат функции не является функцией, циклического вызова не происходит.
Так же в рантайме имеется функция <<@, которая производит циклическую распаковку своего аргумента — она циклически распаковывает свой аргумент до тех пор, пока результат вызова не перестанет быть упакованным.
Это позволяет заменить рекурсию на циклический вызов. Допустим, нам надо узнать длину списка. Рекурсивный вариант функции:
fn size(list)
fn _size(list result)
if != list $
fn() _size tail list + result 1;
fn() result;
;
_size list 0
;
Данный вариант не сможет обрабатывать слишком длинные списки (упрётся в максимальную глубину рекурсии). А вариант функции с упаковкой сможет:
fn size(list)
fn _size(list result)
if != list $
nb рекурсия здесь остановится...
>@fn() _size tail list + result 1;
fn() result;
;
nb ...а здесь она будет заменена на циклический вызов
<<@_size list 0
;
Исходники
Файлы исходного кода должны быть в кодировке ASCII.
Ключевые слова
1 ключевое слово: fn.
Типы
Типизация: слабая полустатическая.
Менеджмент памяти: сборка мусора.
Копирование: по ссылке.
Хранение:
- ссылки — на стеке;
- значения — в куче.
Нулевой тип
Название: nil.
Тип: фиктивный тип; не хранит значение, а служит маркером его отсутствия; существует единственное значение данного типа.
Определение: значение данного типа доступно через вызов функции рантайма nil.
Целые числа
Название: int.
Тип: знаковые целые, 8 байт.
Определение:
/\d+/
/'(\\['\\tn]|[^'\n])'/
Вещественные числа
Название: num.
Тип: с плавающей запятой, 8 байт.
Определение: /\d+(((\.\d+)(e-?\d+))|(\.\d+)|(e-?\d+))/.
Списки
Название: list.
Тип: LISP-подобный список — или пустой список, или пара из головы и хвоста, где голова может иметь любой тип (и является хранимым элементом), а хвост является аналогичным списком.
Определение:
- пустого списка — через вызов функции рантайма
$; - списка из головы и хвоста — через вызов функции рантайма
,.
Доступ к элементам:
- к голове списка — через вызов функции рантайма
head; - к хвосту списка — через вызов функции рантайма
tail.
Строки
Строками являются списки, хранящие коды символов. Используется кодировка ASCII.
Определение: /"(\\["\\tn]|(?!\\)[^"])*"/.
Паки
Название: pack.
Тип: обёртка над значением; единственное предназначение — остановка автоматического вызова замыканий нулевой арности.
Определение: оборачивание значения в пак доступно через вызов функции рантайма >@.
Доступ к обёрнутому значению:
- однократный доступ (на один уровень вглубь) — через вызов функции рантайма
<@; - итеративный доступ (на максимальный уровень вглубь) — через вызов функции рантайма
<<@; функция итеративно разворачивает значение до тех пор, пока результат является обёрнутым значением.
Замыкания
Название: closure.
Объявление:
"fn", [identifier], "(", {identifier, {":", INTEGRAL NUMBER}}, ")", {":", INTEGRAL NUMBER},
{entity},
";"
Здесь первый identifier — имя замыкания, по которому оно будет доступно ниже в текущей области видимости, а также внутри самого себя, а все последующие identifier — имена аргументов замыкания.
{":", INTEGRAL NUMBER} — определение типа аргументов замыкания и результата замыкания (в последнем случае). Тип указывается только для замыканий (полустатическая типизация) и является списком арностей этих замыканий и их результатов.
Перегрузка замыканий не поддерживается, но поддерживается их переопределение.
Вызов замыкания: entity, entity, {entity}.
Здесь первая entity — идентификатор, являющийся замыканием, или аналогичный вызов замыкания (вложенный вызов), возвращающий замыкание, а все последующие entity — аргументы.
Логический тип
В качестве значений логического типа используются значения других типов:
- ложным логическим значением являются:
- значение
nil; - число 0 (целое или вещественное);
- пустой список;
- значение
- истинным логическим значением являются:
- числа, отличные от 0 (целые или вещественные);
- непустые списки;
- любые паки;
- любые замыкания.
Операции
Отсутствуют.
Переменные
В качестве переменных используются замыкания вида fn name() value;.
Присваивание осуществляется через переопределение замыканий.
Управляющие конструкции
Условие
В качестве условного оператора используется функция рантайма if:
"if", condition,
true value,
false value
Здесь:
condition— это вызов замыкания; его результат трактуется как значение логического типа;true value— это вызов замыкания; его значение возвращается, если результатconditionявляется истинным логическим значением;false value— это вызов замыкания; его значение возвращается, если результатconditionявляется ложным логическим значением.
Стоит учесть, что и true value, и false value вычисляются до выбора значения результата. Поэтому, если их вычисление производит побочные эффекты, нужно обернуть их в замыкания:
if condition
fn() true value;
fn() false value;
Цикл
Отсутствует.
Точка входа
Отсутствует. Интерпретатор исполняет весь предоставленный код в порядке его следования.
Комментарии
Однострочные: /(?<![a-z_])nb(?![:a-z_]).*/i.
Многострочные: /(?<![a-z_])nb:.*?(?<![a-z_])nb;/is.
Модульность
Отсутствует.
Полная грамматика
program = entity list;
entity list = {entity};
entity = INTEGRAL NUMBER
| REAL NUMBER
| CHARACTER
| STRING
| identifier
| function definition
| function call;
identifier = (ALPHABETIC IDENTIFIER - "fn") | PUNCTUATION IDENTIFIER;
function definition = "fn", [identifier], "(", {identifier, type}, ")", type,
entity list,
";";
type = {":", INTEGRAL NUMBER};
function call = entity, entity, {entity};
SINGLE-LINE COMMENT = ? /(?<![a-z_])nb(?![:a-z_]).*/i ?;
MULTILINE COMMENT = ? /(?<![a-z_])nb:.*?(?<![a-z_])nb;/is ?;
INTEGRAL NUMBER = ? /\d+/ ?;
REAL NUMBER = ? /\d+(((\.\d+)(e-?\d+))|(\.\d+)|(e-?\d+))/ ?;
CHARACTER = ? /'(\\['\\tn]|[^'\n])'/ ?;
STRING = ? /"(\\["\\tn]|(?!\\)[^"])*"/ ?;
ALPHABETIC IDENTIFIER = ? /[a-z_]+/i ?;
PUNCTUATION IDENTIFIER = ? /[!#$%&*+,\-.\/<=>?@[\\\]^`{|}~]+/ ?;
Рантайм
- общие функции:
==(x: any, y: any): bool— равенство;!=(x: any, y: any): bool— неравенство;!(x: any): bool— логическое отрицание;&&(x: any, y: any): bool— конъюнкция;||(x: any, y: any): bool— дизъюнкция;if(condition: bool, true_value: any, false_value: any): bool— выбор: еслиconditionистинно, возвращаетtrue_value, иначе —false_value;- функции для работы с типами:
nil(): nil— возвращает значение нулевого типа;num(x: str): num— парсит вещественное число из строки;type(x: any): str— возвращает имя типа переданного значения;arity(x: closure): list<int>— возвращает список арностей переданного замыкания (голова списка — арность самого замыкания, хвост — аналогичный список для результата замыкания);str(x: any): str— преобразует переданное значение в строку;
- числовые функции:
<(x: int|num, y: int|num): bool— меньше;<=(x: int|num, y: int|num): bool— меньше или равно;>(x: int|num, y: int|num): bool— больше;>=(x: int|num, y: int|num): bool— больше или равно;~(x: int|num): int|num— унарный минус;+(x: int|num, y: int|num): int|num— сложение;-(x: int|num, y: int|num): int|num— вычитание;*(x: int|num, y: int|num): int|num— умножение;/(x: int|num, y: int|num): int|num— деление (для целых чисел — целочисленное деление, для вещественных — вещественное);%(x: int|num, y: int|num): int|num— остаток от деления (для вещественных чисел — вещественный остаток);- математические функции:
floor(x: num): num;ceil(x: num): num;trunc(x: num): int;sin(x: num): num;cos(x: num): num;tn(x: num): num;arcsin(x: num): num;arccos(x: num): num;arctn(x: num): num;angle(y: num, x: num): num— atan2;pow(base: num, exponent: num): num;sqrt(x: num): num;exp(x: num): num;ln(x: num): num;lg(x: num): num;abs(x: num): num;random(): num— возвращает случайное вещественное число в диапазоне [0; 1);
- функции для работы со списками:
$(): list<any>— возвращает пустой список;,(head: any, tail: list<any>): list<any>— конструирует новый список из переданных головы и хвоста;head(list: list<any>): any— возвращает голову списка;tail(list: list<any>): list<any>— возвращает хвост списка;
- функции для упаковки:
>@(x: any): pack<any>— упаковывает переданное значение без его предварительного вычисления;<@(x: pack<any>): any— распаковывает переданное значение;<<@(x: pack<any>): any— распаковывает переданное значение в цикле до тех пор, пока результат является упакованным значением;
- системные функции:
exit(code: int): nil— завершает программу с переданным кодом возврата;- функции для ввода/вывода:
in(): int— считывает один символ из stdin и возвращает его код;out(string: str): nil— выводит переданную строку в stdout;err(string: str): nil— выводит переданную строку в stderr.