Skip to content

Язык программирования, вдохновляемый Swift, JS, TS, Groovy, Kotlin, C++ и SmallTalk.

License

Notifications You must be signed in to change notification settings

sziberov/Root-Language

Repository files navigation

Logo

RoadMap

Назначение

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

Некоторые языки, такие как Swift, Groovy, TS или JS (в частности NW.JS), отвечают этим требованиям, но либо не в полном объёме, либо с оговорками. К примеру, Swift неспособен загружать и выполнять исходный код на лету, в Groovy и TS с этим тоже есть трудности, JS в принципе не имеет полноценных типизации и параллелизации. Это малая, но тем не менее существенная часть проблем, выявленных в ходе написания Poppy DE (проекта, который изначально даже и не задумывался, как нечто грандиозное и требующее особого отношения).

Этапы разработки

  • Теория -> В процессе
  • Лексер -> Относительно готов
  • Парсер -> Относительно готов
  • Интерпретатор -> В процессе
  • Перевод на компилируемый ЯП (C++) -> В процессе (2/3)

Чего нет в Swift, что есть/будет в Root:

  • Многофункциональный интерпретатор
    • Локальные экземпляры (процессы) внутри кода (песочницы), с возможной передачей контекста
    • Кастомные определения глобальных мемберов в песочницах, таких как import(), print() или process...
    • Исполнение по предельному уровню ошибок синтаксиса
    • Мультипоточность (любая функция может быть вызвана асинхронно, включая вложенные вызовы, и обождена синхронно)
    • Автоматический выбор перегруженной функции при вызове (примитивного) словаря, который её хранит
    • Необязательнные метки параметров в объявлениях функций
    • Статичные мемберы у функций
    • Предельно точные десятичные числа, основанные на хранении и вычислении оных в виде строк
  • Более удобное и безопасное устройство памяти
    • Автоматическое остлеживание доступности композитов и циклов удержания
    • Использование родительской области видимости в моменты вызова супер-функции по умолчанию
    • Доступ к переменным, объявленным внутри функции, из вложенного в неё объявления композита
    • Оповещение наблюдателей об уничтожении композитов
    • Использование наблюдателей одного мембера без ограничений по их разновидности (willSet + get и т.д...)
    • Динамический поиск мемберов в ссылочном значении переменной вне зависимости от заданного ей типа
    • Сохранение и загрузка текущего состояния из файла
    • Отладочная консоль (в виде окна с иерархией композитов и строкой ввода)
  • Метапрограммирование (расширенное взаимодействие с композитами во время исполнения вне отладки)
    • Чтение и изменение внутренних свойств, описывающих композит
    • Получение вызывающего из функции
    • Получение списка наследующих композитов
    • Получение исходного кода
    • Создание и вызов функции с переопределённым self через bind() и apply()
    • Выполнение кода в определённом namespace
    • Уровни доступа: полное редактирование, частичное редактирование, частичное чтение, нет
  • Расширенная типизация
    • По-настоящему опциональные типы вместо обёрточного Optional
    • Явные примитивные типы
    • Явный композитный тип namespace для разграничения и группирования пространств имён
    • Объединённые типы
    • Разделение вложенных типов на статические и объектные
    • Все композиты и обобщённые типы - объекты первого класса
    • Любые типы в ограничениях обобщённых параметров
    • Анонимные inline протоколы
    • Ссылки на типы из сигнатур функций
    • Значения по умолчанию, в т.ч. в протоколах
    • Сравнение композитных типов по определению или содержимому в случаях, когда по ссылке - неоптимально (песочницы, мультипоточность)
    • inout-переменные (не параметры)
  • Новые операторы и ключевые слова
    • chain для объявления наблюдателей мемберов на уровне композита
    • delete для раздекларирования мемберов или уборки элементов из коллекций
    • final (вместо let) для объявления констант более широкого спектра
    • friend для предоставления доступа к непубличным мемберам ограниченному кругу композитов
    • in для проверки наличия мембера в композите
    • protected для приватных, но всё ещё наследуемых, мемберов
    • virtual для объявления ожидаемо перегружаемых мемберов
    • when (вместо switch) для переключения потока кода
    • with для смены области видимости
    • ... (rest) для раскладывания коллекций на аргументы вызовов

Спецификация

Типы

Примитивы

Явные: логическое значение, словарь, строка, тип, целое число, число с плавающей точкой. Неявные: ссылка, узел, указатель.

Неявные примитивы существуют для поддержки композитных типов и доступны для взаимодействия лишь косвенно.

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

Композиты

Композит Мемберы Родители Инстанцируемость
Класс Любые Класс, протоколы Да
Объект Любые Класс/структура Нет
Перечисление Случаи, перечисления Протоколы Нет
Пространство имён Любые Нет Нет
Протокол Определения Протоколы Нет
Структура Любые Протоколы Да
Функция Статические Нет Нет

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

Доступ к сырому представлению композитов в целях метапрограммирования осуществим посредством мембера metaSelf.

Синтаксис

Объявления

Объявления подразделяются на 4 группы: импорты, операторы, мемберы и наблюдатели.

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

У объявлений могут быть модификаторы, влияющие на внешний доступ к ним, или несущие другие эффекты.

Операторы

Операторы состоят из двух объявлений с одним общим идентификатором: структуры, отвечающей за восприятие, и функции, определяющей поведение.

postfix operator ++

private infix operator == {
    associativity: left
    precedence: 64
}

struct Integer {
    private var value: int

    init(v: int) {
        value = v
    }

    postfix func ++(v: inout Self) {
        v = v+1
    }

    func ==(lhs: Self, rhs: Self) {
        return lhs.value == rhs.value
    }
}
Классы

...

Перечисления

...

Пространства имён

Пространства имён, в отличии от других композитов, могут содержать объявления импорта и операторов. Разрешается как прямое их объявление внутри тела, так и посредством функций (будет использовано первое ПИ в цепочке областей видимости).

...

Протоколы

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

...

Структуры

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

...

Функции

...

Переменные

Переменные объявляются ключевым словом var и служат для хранения примитивов.

'var' <Переменная>[, <Переменная>]

<Переменная> = <Идентификатор>[':' <Тип>][ '=' <Выражение>]

Любая переменная также может "содержать" nil (ничего), если её тип или один из типов указан со знаком ? на конце. В противном случае она обязана содержать значение на момент обращения (при ленивой инициализации) или выполнения объявления. Это относится и к функциям - нельзя передать аргумент nil в обязательный параметр, и нельзя отдать nil как обязательное возвратное значение. Аналогичным образом запрещается вставка nil-значений и записей, содержащих nil-ключи и/или значения, в массивы и словари с обязательными типами содержания соответственно. При этом, полное отсутствие значений и записей в таких массивах и словарях не запрещается.

var emptyVariable             Пустая переменная с необязательным значением любого типа
var testingVariable = 1,      Переменная с необязательным значением любого типа, инициализируемая со значением Integer(1)
    nillableVariable: _? = 1

По тому же принципу переменные могут содержать значения по умолчанию, которые будут автоматически установлены в момент обращения или выполнения объявления. Для этого вместо ? следует использовать !. Существует два правила использования умолчаний: тип должен содержать публичный неопциональный конструктор без параметров, а переменная не должна иметь более одного типа по умолчанию. Уточнение: переменные, которым было автоматически установлено значение, не становятся константами, им всё ещё можно задать nil, впоследствии переписав умолчание на новое. Последнее может быть полезно, если конструктор типа учитывает какие-либо условия даже без передачи аргументов.

var defaultVariable: Integer!  Переменная, инициализируемая по обращению со значением Integer(0)

Явные примитивные типы тоже поддерживают умолчания: bool - false, dict - [:], float - 0.0, int - 0, string - '', type - _?.

Ленивая инициализация распространяется на все статические переменные, и переменные объектов с модификатором lazy.

Наблюдатели

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

Всего есть 9 идентификаторов состояний, для перехвата наблюдателями: willGet, get, didGet, willSet, set, didSet, willDelete, delete и didDelete. Также, верхнеуровневые наблюдатели подразделяются на два типа: chain и subscript.

class StepCounter {
    var steps: Integer = 0 {
        willSet {
            print('About to set steps to \(newValue)')
        }

        didSet {
            if steps > oldValue {
                print('Added \(steps-oldValue) steps')
            }
        }
    }
}

var stepCounter = StepCounter()

stepCounter.steps = 200
// About to set steps to 200
// Added 200 steps

stepCounter.steps = 360
// About to set steps to 360
// Added 160 steps

stepCounter.steps = 896
// About to set steps to 896
// Added 536 steps

Любой мембер, имеющий наблюдателей состояний get, set и delete, не теряет своего внутреннего хранилища данных, но во избежание рекурсии при его записи и чтении из области видимости наблюдателя необходимо пользоваться переменной value. Эта переменная чуть менее важна, но всё ещё несёт своё предназначение и в других состояниях.

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

Модификаторы

...

Указание типов

                                  Крайности
_                                 Любой
void                              Никакой

                                  Примитивы
any                               Любой
bool                              Логическое значение
dict                              Словарь
float                             Число с плавающей точкой
int                               Целое число
string                            Строка
type                              Тип

                                  Композиты
Any                               Любой
Class                             Класс
Enumeration                       Перечисление
Function                          Функция
Namespace                         Пространство имён
Object                            Объект
Protocol                          Протокол
Structure                         Структура

                                  Функции
<...>(...) awaits? throws? -> _?  Любая (композит типа Function)
<T>(T, T) -> void                 Функция с обобщённым параметром любого типа и обычными параметрами того же обобщённого типа без возвратного значения
(Integer, Number) -> Boolean      Функция с двумя параметрами типа Integer и Number соответственно и возвратным значением типа Boolean
([]..., ...) awaits throws -> _   Ожидающая, кидающая исключения функция, с любым количеством параметров типа Array в начале, и любыми параметрами после них, с возвратным значением любого типа
(() -> _)?                        Необязательная функция без параметров с возвратным значением любого типа

                                  Массивы
[]                                Любой (объект типа Array)
[_?]
Array
Array<_?>
[_]                               Массив со значениями любого типа
Array<_>

                                  Словари
[:]                               Любой (объект типа Dictionary)
[_?: _?]
Dictionary
Dictionary<_?, _?>
[_: _]                            Словарь с ключами и значениями любого типа
Dictionary<_, _>
[String: _]                       Словарь с ключами типа String и значениями любого типа
Dictionary<String, _>

                                  Прочие
Custom.Self                       Композит, в родительском древе которого находится тип Custom, или который сам им является
Custom                            Объект, в родительском древе которого находится тип Custom
Super                             Верхний (относительно текущего) композит в цепочке наследования композитов или объектов
Self                              Текущий композит
Sub                               Нижний (относительно текущего) композит в цепочке наследования объектов
Объединённые типы

Если переменная должна принимать один из нескольких типов, следует использовать объединённый тип. Оный можно определить с помощью знака |. Объединённый тип имеет приоритет над пересекающимися.

var unionVariable: Integer | String | Boolean  Переменная, принимающая значения типа Integer, String и Boolean
Пересекающиеся типы

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

class CustomCompositeA {}
class CustomCompositeB: CustomCompositeA, CustomProtocolA {}
class CustomCompositeC: CustomCompositeA, CustomProtocolA, CustomProtocolB {}

var intersectionVariable: CustomCompositeA & CustomProtocolB  Переменная, принимающая значения типа CustomCompositeA, соответствующего протоколу CustomProtocolB

Удаление

Ключевое слово delete производит попытку удаления мембера из композита.

'delete' <Идентификатор>[<Путь к мемберу>]

<Путь к мемберу> = ['?']'.'<Идентификатор>[<Путь к мемберу>]
<Путь к мемберу> = '['<Аргументы>']'[<Путь к мемберу>]

Если композит не поддерживает этот оператор, определяя наблюдателей willDelete, delete и didDelete, возвращаемым значением будет Boolean(false).

delete variable           Удаление из композита области видимости
delete instance.variable  Удаление из другого композита
delete array[0]           Удаление из массива

Внутреннее устройство

Сборка мусора

Для управления временем жизни композитов в Root применяется развитый Автоматический Подсчёт Ссылок (Automatic Reference Counting).

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

Композит А попадает в список удерживающих композита Б, если он действительно удерживает его. Действительное удержание означает прямую ссылаемость композита А на композит Б в своих идентификаторах (не считая собственного и списка удерживающих), типе, импортах, мемберах или наблюдателях. То же самое условие, только наоборот (отсутствие ссылок), ведёт к выпадению из списка.

Автоматическое (не принудительное) решение об уничтожении композита принимается только в том случае, если тот неудерживаем значимо. Значимое удержание означает формальное удержание глобальным пространством имён, композитом текущей области видимости или значением текущей передачи управления. В свою очередь композит А считается формально удерживаемым композитом Б, если он или хотя бы один из его списка удерживающих (рекурсивно) может быть распознан как композит Б.

Попытки очистки памяти происходят в четырёх случаях:

  • После полного уничтожения удерживающего - все ранее удерживаемые им композиты.
  • После удаления конкретной ссылки - ссылаемый композит.
  • После выполнения инструкции - все созданные за момент её выполнения композиты.
  • В момент возврата из тела функции (им.вв. синтаксический конструкт) - созданное для его выполнения пространство имён.

Вызовы, области видимости и передачи контроля

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

Под воплощениями подразумеваются следующие структуры:

  • Вызов: функция (композит), расположение (токена, с которого начинается синтаксический конструкт).
  • Область видимости (композит).
  • Передача контроля: значение (примитивное значение), тип (строка). Оба свойства могут быть незаполнены.

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

"Области видимости" позволяют определять, над каким композитом в данный момент производятся действия. От этого отталкиваются многие правила интерпретации Абстрактного Синтаксического Дерева (Abstract Syntax Tree) и АПС (ARC). Часто продолжительность жизни "области видимости" пересекается с оной у композита, на который она ссылается. Например, для инструкции if создаётся пространство имён и добавляется как "область видимости", после чего обое существуют до момента выполнения последней инструкции в теле then. С другой стороны, при создании или редактировании композита, последняя выполненная в его теле инструкция не обязательно сопровождается попыткой уничтожения оного после уборки текущей "области видимости", так как результат может быть заведомо использован позднее, в том числе в "передаче управления".

"Передачи управления" позволяют реализовать переход от одной инструкции к другой. Они тоже задействуются правилами интерпретации АСД (AST) и АПС (ARC). В "передачи" входят явный и не- возврат значения из одной "области видимости" в другую (из вызванной функции в вызывающую, из дочернего блока в родительский и т.д.) по return или последней инструкции в теле, и прерывания циклов по break или continue, а также выбросы исключений по throw (автоматические или ручные). Передача может производиться как в пределах одной "области видимости" (break), так и ограничиваться их суммарным количеством (throw). Как правило, в простых языках программирования достаточно одной "передачи управления" на всё время выполнения, но в Root, в отличии от них, присутствуют косвенные вызовы (не путать с асинхронными, где параллелизации подвергаются как минимум все три списка), в число которых входит вызов деинициализаторов, поэтому каждый косвенный вызов сопровождается своей собственной "передачей управления", во избежание перезаписи основной.

Наследование

Каждый композит имеет список идентификаторов, указывающих на свои уровни наследования и область видимости. Эти идентификаторы могут использоваться вручную, или автоматически для поиска мемберов. Как правило, они устанавливаются динамически в моменты вызова (де-)инициализаторов (для переключения между статическим контекстом и контекстом объекта), и назначаются по одному разу для других композитов в моменты создания.

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

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

Пример кода и неформальное описание выходной структуры памяти
class A {
    var a = 1

    init {}

    func b() { 2 }

    virtual func c() { 3 }
}

class B: A {
    var a = 4,
        b = 5

    init { super() }

    func c() { 6 }

    var d = 7
}

var b = B()
Класс А
    Идентификаторы
        сам == Класс А
        Сам == Класс А
        Область видимости == Пространство имён Глобал
    Тип == Класс
    Инструкции
        а = 1
        б = Функция { 2 }
        в = функция (виртуально) { 3 }
    Мемберы
        инит == Функция #3

Функция инит
    Идентификаторы
        Собственный = 3
        сам == Класс А
        Сам == Класс А
        Область видимости == Класс А
    Тип == Функция -> Объект А
    Инструкции
        вернуть сам

Класс Б
    Идентификаторы
        над == Класс А
        Над == Класс А
        сам == Класс Б
        Сам == Класс Б
        Область видимости == Пространство имён Глобал
    Тип == Класс: Класс А
    Инструкции
        а = 4
        б = 5
        в = Функция { 6 }
        г = 7
    Мемберы
        инит == Функция #5

Функция инит
    Идентификаторы
        Собственный = 5
        над == Класс А
        Над == Класс А
        сам == Класс Б
        Сам == Класс Б
        Область видимости == Класс Б
    Тип == Функция -> Объект Б
    Инструкции
        над()
        вернуть сам

Объект Б
    Идентификаторы
        над == Объект А
        Над == Класс А
        сам == Объект Б
        Сам == Класс Б
        Область видимости == Класс Б
    Тип == Объект класса Б
    Мемберы
        а == 4
        б == 5
        в == Функция 9
        г == 7

Объект А
    Идентификаторы
        сам == Объект А
        Сам == Класс А
        под == Объект Б
        Под == Класс Б
        Область видимости == Класс А
    Тип == Объект класса А
    Мемберы
        а == 1
        б == Функция #11
        в == Функция (виртуально) #12

Пространство имён Вызов<инит #5>  (существует временно)
    Идентификаторы
        над == Объект А
        Над == Класс А
        сам == Объект Б
        Сам == Класс Б
        Область видимости == Класс Б

Функция в
    Идентификаторы
        Собственный = 9
        над = Объект А
        Над = Класс А
        сам == Объект Б
        Сам == Класс Б
        Область видимости == Объект Б
    Тип == Функция
    Инструкции
        6

Пространство имён Вызов<инит #3>  (существует временно)
    Идентификаторы
        сам == Объект А
        Сам == Класс А
        под == Объект Б
        Под == Класс Б
        Область видимости == Класс А

Функция б
    Идентификаторы
        Собственный = 11
        сам == Объект А
        Сам == Класс А
        под == Объект Б
        Под == Класс Б
        Область видимости == Объект А
    Тип == Функция
    Инструкции
        2

Функция в
    Идентификаторы
        Собственный = 12
        сам == Объект А
        Сам == Класс А
        под == Объект Б
        Под == Класс Б
        Область видимости == Объект А
    Тип == Функция
    Инструкции
        3

About

Язык программирования, вдохновляемый Swift, JS, TS, Groovy, Kotlin, C++ и SmallTalk.

Topics

Resources

License

Stars

Watchers

Forks

Languages