Язык программирования Кедр
Кедр это универсальный язык программирования, предназначенный как для пользователей с минимальной подготовкой, так и для разработки операционных систем, драйверов устройств и игр.
Система типов
Программист может объявить типы следующих видов:
Объект
type Buffer =
val type : BufferType
var object : VkBuffer
var size : u32
val offset : u64
val usage : BufferUsageFlags
У типа Buffer
пять полей, каждое из которых также является параметром конструктора.
type StorageBuffer =
inherit Buffer type = BufferType::Storage
offset = 0
let device : VkDevice
let memory : VkMemory
def discard =
vk::destroy_buffer device object null
vk::free_memory device memory null
Во время наследования мы передали значения полей type
и offset
, оставшиеся три поля становятся параметрами конструктора StorageBuffer
. К ним добавляются еще два параметра/поля device
и memory
. Видимость let
привязок и функций ограничена текущим файлом.
type List @abstract =
...
type MutList @[mut_of List] =
inherit List
MutList
это изменяемая версия типа List
, доступен также под именем mut List
. List
является абстрактным, поэтому List.new
создает MutList
. new
используется для типов без параметров конструктора.
import collection::List@mrc
let list @owner = List.new
Модификатор @mrc
, расшифровывается как manual reference counting, дает доступ к версии типа без автоматического подсчета ссылок. Атрибут @owner
гарантирует освобождение памяти после выхода привязки из зоны видимости, также доступен для полей объектов.
По окончании файла тип привязки list
меняется с MutList
на List
(с атрибутом @mut
тип останется прежним). var
привязки без @mut
превращаются в val
.
type Array @byval =
val size : u32
let ptr @param = kd_alloc (sizeof T * size) as MutPtr
Объекты передаются по значению если задан атрибут @byval
. Код непосредственно внутри типа идёт в конструктор. Атрибут @param
позволяет передать значение привязки как именованный параметр конструктора, может также использоваться в функциях.
К типу можно добавлять поля и код конструктора до его наследования или создания первого объекта типа. Таким образом конструктор может быть распределен среди нескольких файлов.
Поддерживается множественное наследование, но каждый тип может быть унаследован только один раз.
Явное преобразование типов окончательно, из ссылки на объект типа List
нельзя снова получить MutList
.
Структура
type Vector3 = struct
x : T
y : T
z : T
let vector = Vector3 x = 1
y = 3
z = 8
let vector_ref = Vector3@ref 1
y = 3
..vector
Задавать значения полей можно позиционно, по имени и с помощью заполнителя. Тип заполнителя не имеет значения. Синтаксис работает одинаково для создания объектов, инициализации структур, наследования типов и вызова функций. Сначала идут позиционные аргументы, потом именованные, по одному на строку, и в конце заполнитель.
Структуру можно инициализировать в куче, если добавить к имени типа суффикс @ref
. Тип полученной привязки будет mut ref Vector3
, что также можно записать как MutRef
. Ссылки такого рода по сути являются указателями, которые вместо традиционных для указателей операций дают доступ к полям и методам типа.
type Vector3 where T : Real
def norm = x * x + y * y + z * z |> sqrt
def normalized = self / norm
def normalize @mut =
let l = norm
x /= l
y /= l
z /= l
В заголовке нет знака =
, значит это расширение уже существующего типа Vector3
. Оператор |>
передает левую часть в указанную справа функцию или вызывает метод. Метод normalize
имеет атрибут @mut
, что позволяет ему изменять значения полей структуры. Вызываться такой метод может только на модифицируемых структурах, т.е. на привязках let mut
и var
и значениях типа mut ref Vector3
.
Объединение
type ClearColorValue = union
float_32 : [f32; 4]
int_32 : [i32; 4]
uint_32 : [u32; 4]
Все поля указывают на один адрес в памяти. При создании нужно задать значение одного поля.
Именованный кортеж
type VkInstance = MutPtr
type CString = Ptr
type X = i32
type Some = T
type CString ptr
fun get (i : u32) = ptr[i]
Может иметь любое количество элементов, но чаще всего элемент один, так как иначе структура часто будет лучшим выбором.
Особые функции задаются через ключевое слово fun
, fun get
это индексатор.
Символ
Также известен как единичный тип. По сути является именованным кортежем с нулём элементов.
type None = symbol
Используется в типах-суммах.
Тип-сумма
Отличается от объединения наличием информации об активном поле.
type BufferType =
| Vertex
| Index
| Storage
| Image
| Uniform
Перечисление это тип-сумма единичных типов.
type Option =
| None
| Some T
type Result =
| Ok T
| Error E
Option
позволяет представить отсутствие значения. Функция возвращает Result
чтобы передать информацию об ошибке в случае неудачи.
type Metal @open = enum
Iron | Copper | Nickel
type Metal
Silver | Gold
type Platinum = symbol
inherit Metal
К открытым типам-суммам поля можно добавлять в любой точке кода. Модификатор enum
ограничивает типы полей единичными.
type Target =
| Buffer VkBuffer
| Image image : VkImage
width : u32
height : u32
Символы, именованные кортежи и структуры, определенные внутри типа-суммы, могут быть использованы отдельно как полноценные типы.
let value = Some 5
let metal = Metal::Silver
Привязки value
и metal
имеют типы Some
и Silver
, вместо Option
и Metal
, как можно было бы предположить.
Флаги
type QueueFlags = flags
| Graphics
| Compute
| Transfer
| SparseBinding
| Protected
let queue_flags = QueueFlags::Graphics | QueueFlags::Transfer
assert queue_flags.contains QueueFlags::Graphics
Упорядоченный код
Код может ссылаться только на элементы, которые определены выше (исключение — идущие подряд функции видят друг друга). Таким образом устранена несогласованность, когда код внутри функций последователен, а снаружи нет. Также не нужна входная точка в приложение, весь код верхнего уровня выполняется после запуска. Элементы можно импортировать для модуля, а не отдельно для каждого файла. Но главное следствие — такой код можно прочитать от начала до конца, с ним легко работать.
Цель языка
Мы хотели бы иметь один язык, оптимальный для каждой из задач программирования. Вопрос освобождения памяти нас в этом ограничивает — одно приложение требует ручного управления, другое может воспользоваться всеми преимуществами сборщика мусора. Здесь нужен компромисс.
В остальных вопросах компромисс не нужен, язык должен быть настолько хорош, насколько позволяет наше текущее понимание математики вычислений, чтобы любая задача на нем решалась так, как будто язык был специально для неё создан.
Ход разработки
Есть компилятор в С и редактор кода, которые я переписываю на Кедр, после чего выйдет пробная версия. Здесь можно следить за процессом.