Язык программирования Кедр

90f639ab64c7df025d652be17e60229a

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

Система типов

Программист может объявить типы следующих видов:

Объект

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

Упорядоченный код

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

Цель языка

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

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

Ход разработки

Есть компилятор в С и редактор кода, которые я переписываю на Кедр, после чего выйдет пробная версия. Здесь можно следить за процессом.

© Habrahabr.ru