Рассмотрим синтаксис, обработку ошибок, взаимодействие с языком С и возможности реактивного программирования.

Предыдущая статья здесь.

Особенности синтаксиса

val identity = Matrix4 1 0 0 0
                       0 1 0 0
                       0 0 1 0
                       0 0 0 1

Позиционный способ инициализации полей хорошо подходит для матриц. Целочисленный литерал применяется также для вещественных типов чисел.

control.set_width width
        set_height control.measure_height
        arrange

import num::vector_f32::Vector3
                        Vector4
            vector_i32::Vector2i

Методы set_width, set_height и arrange вызываются на одной и той же привязке control.

let enabled_extension_names = ["VK_EXT_debug_report"
                               "VK_EXT_debug_utils"
                               "VK_KHR_surface"
                               "VK_KHR_xcb_surface"
                               "VK_KHR_xlib_surface"]

Запятые между элементами не нужны, если каждый находится на отдельной строке или строках. Указатель на массив будет передан в С функцию, поэтому все строковые литералы получат тип CString.

text
|> lines
|> filter { line -> line.starts_with "layout" }
|> map { remove_comment _ }

Операторы |> удобно располагать в вертикальный ряд. Замыкания создаются с помощью фигурных скобок. Каждое использование подчёркивания возвращает следующий параметр.

let up_right = Vector3 left.as_f32 0 border_z
let adjusted_size = size as f32 * 1.5 |> as_u32

Преобразование типа можно записать как вызов метода.

let thickness = (left = border.left
                 right = border.right
                 up = border.up
                 down = border.down)

let sizes = List<(u32, u32) width height>.new

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

Внутристрочное объявление типов

type Entry =
    path : String
    created : DateTime
    
    | File size : u32
    | Directory entries : Slice<Entry>
                | Local
                | Remote url : String

Entry и Directory это типы-суммы, Local и Remote — структуры. Поля path и created доступны как для Entry, так и для каждого из подтипов.

type Entry @abstract = struct
    path : String
    created : DateTime
    is_directory : bool

    | File size : u32
    | Directory entries : Slice<mut ref Entry>
                is_remote : bool
                | Remote url : String

Структуры File и Directory наследуют поля структуры Entry. Ссылка типа mut ref Entry указывает на File, Directory или Remote.

Классы типов

type Comparable = class
    def (>) (rhs : Self) : bool
    def (<) (rhs : Self) : bool
    def (>=) (rhs : Self) : bool
    def (<=) (rhs : Self) : bool

type Compare @virtual =
    def compare (other : Self) : Ordering

    def (>) (rhs : Self) =
        let result = compare rhs
        result == Ordering::Greater

    ...

    is Comparable

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

Обработка ошибок

def try_read_file (path : String) : Result<String, String> =
    ...

object i32
    def try_parse (s : String) : Option<i32> =
        ...

Result<String, String> содержит либо прочитанный из файла текст в Ok<String>, либо Error<String> с сообщением об ошибке.

def try_add (lhs_path : String) (rhs : i32) =
    let lhs =
        let! text = try_read_file lhs_path
        let! number = i32.try_parse text
        number

    lhs + rhs |> Some

Привязки text и number имеют типы String и i32, учитывающие только случаи успешного выполнения функций try_read_file и try_parse.

Ниже показан аналогичный код без let!.

def try_add (lhs_path : String) (rhs : i32) =
    let lhs =
        let text = case try_read_file lhs_path of
            Result::Ok text -> text
            Result::Error _ -> return None

        let number = case i32.try_parse text of
            Some number -> number
            None -> return None

        number

    lhs + rhs |> Some

Полученное от try_read_file сообщение об ошибке можно передать выше по стеку вызовов, вернув Result<i32, String> из try_add вместо Option<i32>.

def try_add (lhs_path : String) (rhs : i32) =
    let lhs =
        let! text = try_read_file lhs_path
        let! number = i32.try_parse text |> to_result "cannot parse $text"
        number

    lhs + rhs |> Result::Ok

Option конвертируется в Result с помощью метода to_result.

Взаимодействие с языком С

Типы

type VkInstance = MutPtr
type CString = Ptr<c_char>

Именованный кортеж с единственным элементом в С коде представлен как тип элемента, поэтому в объявлениях внешних функций VkInstance и CString можно использовать вместо void* и const char*.

typealias DebugUtilsMessengerCallbackExt = fn
    (message_severity : DebugUtilsMessageSeverityFlagsExt)
    * (message_types : DebugUtilsMessageTypeFlagsExt)
    * (callback_data : ptr DebugUtilsMessengerCallbackDataExt)
    * (user_data : MutPtr) -> u32

typealias CharFun = fn GlfwWindow * u32 -> Unit

Псевдонимы для типов указателей на функцию.

type SampleCountFlags = flags
    | 1
    | 2
    | 4
    | 8
    | 16
    | 32
    | 64

Символы флагов могут быть числами или начинаться с цифры.

type InstanceCreateInfo = struct
    type : StructureType
    next : Ptr
    flags : InstanceCreateFlags
    application_info : ptr ApplicationInfo
    enabled_layer_count : u32
    enabled_layer_names : ptr CString
    enabled_extension_count : u32
    enabled_extension_names : ptr CString

Структуры переводятся на С дословно.

Значения

object f64
    val min "DBL_MIN" : f64
    val max "DBL_MAX" : f64
    val epsilon "DBL_EPSILON" : f64

Внешние константы DBL_MIN, DBL_MAX и DBL_EPSILON представлены полями объекта f64.

Функции

module vk

def create_semaphore "vkCreateSemaphore"
    (device : VkDevice)
    (create_info : ptr SemaphoreCreateInfo)
    (allocator : Allocator)
    (semaphore : mut ptr VkSemaphore) : Result

endmodule

Библиотечная функция vkCreateSemaphore доступна под именем vk::create_semaphore.

def create_semaphore (device : VkDevice) =
    let semaphore_create_info = SemaphoreCreateInfo
        type = StructureType::SemaphoreCreateInfo
        next = null
        flags = SemaphoreCreateFlags@zero

    let mut semaphore = null
    let result = vk::create_semaphore device semaphore_create_info@ptr null
                                      semaphore@mut_ptr
    assert result == Result::Success

    semaphore

Модификаторы @ptr и @mut_ptr возвращают указатель на значение, @fn — указатель на функцию, @zero — обнулённый блок памяти.

type f32
    def Real.sin "sinf" : f32
    def Real.cos "cosf" : f32
    def Real.floor "floorf" : f32
    def Real.sqrt "sqrtf" : f32

Внешние функции могут быть не только свободными, но и методами типа, в том числе служить определением абстрактных.

type f64
    def Real.sin : f64
    def Real.cos : f64
    def Real.floor : f64
    def Real.sqrt : f64

Функции для f64 уже имеют правильные имена.

Код

В опубликованном коде есть ящики для работы с vulkan и glfw.

Реактивное программирование

type Border =
    inherit Control

    var obs left : u32 = 0
    var obs right : u32 = 0
    var obs up : u32 = 0
    var obs down : u32 = 0
    var obs content : Option<Control> = None

Модификатор obs создаёт контейнер типа mut Atom<T>, поддерживающий публикацию оповещений об изменении значения.

let content_width = content@atom.map
    { case _ of
          None -> 0
          Some control -> control.width }

content@atom вместо значения поля content возвращает контейнер, на котором определена функция map.

let thickness = (border.left@atom
                 border.right@atom
                 border.up@atom
                 border.down@atom)
                |> to_atom

Можно преобразовать кортеж атомов в атом кортежей, thickness имеет тип Atom<(u32, u32, u32, u32)>.

val memory@atom = (content_width, content_height, thickness)
                  |> to_atom
                  |> map { width, height, thickness ->
                      if width == 0 || height == 0
                      then None
                      else Some (width, height, thickness) }
                  |> option_map { width, height, (left, right, up, down) ->
                      ...
                      MeshMemory.from_quads quads.as_slice }

Из-за модификатора @atom привязка memory имеет тип Option<MeshMemory>, а не Atom<Option<MeshMemory>>.

Комментарии (7)


  1. voidinvader
    07.06.2024 13:04
    +1

    "Товарищ! Увидишь мутного кедра - засунь..."


  1. a-tk
    07.06.2024 13:04

    А оно уже чем-нибудь компилируется куда-нибудь?


    1. denismarkelov Автор
      07.06.2024 13:04

      Да, компилятор есть, будет опубликован когда перепишу его на Кедр.


  1. DungeonLords
    07.06.2024 13:04
    +1

    "Структуры переводятся на С дословно."
    А как узнать размер структуры, если бы она была упакована в памяти как массив, то есть без gap'ов? К сожалению sizeof(mystruct) покажет размер не упакованной структуры... А как узнать размер такой же, но если бы она была упакованной?


    1. denismarkelov Автор
      07.06.2024 13:04

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


      1. DungeonLords
        07.06.2024 13:04

        Я хотел бы написать что-то в стиле

        if(sizeof(int)!=2) WarningExit();
        

        Только мне надо варнинг если размер упакованной структуры не равен размеру упакованной..


  1. kozlov_de
    07.06.2024 13:04

    Как происходит создание объектов и освобождение памяти?

    Очевидно, как в ++

    Потому что как в раст специфично, а свою среду выполнения вы не потянете