Язык программирования, обладающий высокой динамичностью, безопасностью во время написания и выполнения, простым удобочитаемым синтаксисом и поддержкой ряда низкоуровневых возможностей.
Некоторые языки, такие как Swift, Groovy, TS или JS (в частности NW.JS), отвечают этим требованиям, но либо не в полном объёме, либо с оговорками. К примеру, Swift неспособен загружать и выполнять исходный код на лету, в Groovy и TS с этим тоже есть трудности, JS в принципе не имеет полноценных типизации и параллелизации. Это малая, но тем не менее существенная часть проблем, выявленных в ходе написания Poppy DE (проекта, который изначально даже и не задумывался, как нечто грандиозное и требующее особого отношения).
- Теория -> В процессе
- Лексер -> Относительно готов
- Парсер -> Относительно готов
- Интерпретатор -> В процессе
- Перевод на компилируемый ЯП (C++) -> В процессе (2/3)
- Многофункциональный интерпретатор
- Локальные экземпляры (процессы) внутри кода (песочницы), с возможной передачей контекста
- Кастомные определения глобальных мемберов в песочницах, таких как
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