Язык программирования Кедр. Продолжение
Рассмотрим синтаксис, обработку ошибок, взаимодействие с языком С и возможности реактивного программирования.
Предыдущая статья здесь.
Особенности синтаксиса
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
| 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
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 =
...
object i32
def try_parse (s : String) : Option =
...
Result
содержит либо прочитанный из файла текст в Ok
, либо Error
с сообщением об ошибке.
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
из try_add
вместо Option
.
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
Именованный кортеж с единственным элементом в С коде представлен как тип элемента, поэтому в объявлениях внешних функций 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 = None
Модификатор obs
создаёт контейнер типа mut Atom
, поддерживающий публикацию оповещений об изменении значения.
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
, а не Atom
.