1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors-jakt

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Язык программирования Jakt

Jakt — это безопасный для памяти язык системного программирования.

В настоящее время он транслируется в C++.

Примечание. Язык активно разрабатывается.

Примечание: Если вы клонируете на ПК с Windows (не WSL), убедитесь, что ваш Git-клиент сохраняет окончания строк как \n. Вы можете установить это как глобальную конфигурацию через git config --global core.autocrlf false.

Использование

Для трансляции в C++ требуется clang. Убедитесь, что он установлен.

jakt file.jakt
./build/file

Сборка

См. здесь.

Цели

  1. Безопасность памяти.
  2. Читаемость кода.
  3. Производительность разработчика.
  4. Производительность исполняемого файла.
  5. Удовольствие!

Безопасность памяти

Следующие стратегии используются для обеспечения безопасности памяти:

  • автоматический подсчёт ссылок;
  • строгая типизация;
  • проверка границ;
  • отсутствие необработанных указателей в безопасном режиме.

В Jakt есть три типа указателей:

  • T (Сильный указатель на класс T с подсчётом ссылок.)
  • weak T (Слабый указатель на класс T с подсчётом ссылок. Становится пустым при уничтожении объекта.)
  • raw T (Необработанный указатель на произвольный тип T. Можно использовать только в блоках unsafe.)

Нулевые указатели невозможны в безопасном режиме, но указатели можно обернуть в Optional, то есть Optional<T> или T? для краткости.

Математическая безопасность

  • Целочисленное переполнение (как знаковое, так и беззнаковое) является ошибкой времени выполнения.
  • Числовые значения не преобразуются автоматически в int. Все преобразования должны быть явными.

Для случаев, когда желательно бесшумное целочисленное переполнение, существуют явные функции, которые предоставляют эту функциональность.

Читаемость кода

На чтение кода тратится гораздо больше времени, чем на его написание. По этой причине Jakt уделяет большое внимание читаемости.

Некоторые из функций, способствующих более читаемым программам:

  • Неизменяемость по умолчанию.
  • Метки аргументов в выражениях вызова (object.function(width: 10, height: 5)).
  • Выведенный объём enum (можно сказать Foo вместо MyEnum::Foo).
  • Сопоставление с образцом с помощью match.
  • Дополнительная цепочка (foo?.bar?.baz (с возможностью сбоя) и foo!.bar!.baz (безошибочная)).
  • Объединение None для дополнительных параметров (foo ?? bar даёт foo, если у foo есть значение, иначе bar).
  • Операторы defer.
  • Указатели всегда разыменовываются с . (никогда ->).
  • Параметры замыкающего замыкания можно передать вне круглых скобок вызова.
  • Распространение ошибок с типом возврата ErrorOr<T> и выделенными ключевыми словами try / must.

Повторное использование кода

Jakt гибок в том, как проект может быть структурирован с помощью встроенной системы модулей.

import a                                // (1)
import a { use_cool_things }            // (2)
import fn()                             // (3)
import relative foo::bar                // (4)
import relative parent::foo::baz        // (5)
import relative parent(3)::foo::baz     // (6)
  1. Импортировать модуль из того же каталога, что и файл.
  2. Импорт только use_cool_things() из модуля a.
  3. Импорты могут быть рассчитаны во время компиляции. См. Компиляторные импорты
  4. Импортируйте модуль с использованием ключевого слова relative, когда модуль является подпутью каталога, содержащего файл.
  5. Импортируйте модуль в родительском пути на один каталог выше файла.
  6. Синтаксический сахар для импорта модуля тремя родительскими путями вверх от файла.

Стандартная библиотека Jakt

У Jakt есть стандартная библиотека, доступ к которой осуществляется через пространство имён jakt:::

import jakt::arguments
import jakt::libc::io { system }

Стандартная библиотека Jakt находится в зачаточном состоянии, поэтому рассмотрите возможность внести свой вклад!

Вызовы функций

При вызове функции необходимо указать имя каждого аргумента при передаче:

rect.set_size(width: 640, height: 480)

Есть два исключения из этого правила:

  • Если параметр в объявлении функции объявлен как anon, допускается пропуск метки аргумента.
  • При передаче переменной с тем же именем, что и параметр.

Структуры и классы

Существует два основных способа объявления структуры в Jakt: struct и class.

struct

Основной синтаксис:

struct Point {
    x: i64
}
``` **Структуры в Jakt имеют семантику значений:**
* Переменные, содержащие структуру, всегда имеют уникальный экземпляр структуры.
* Копирование экземпляра структуры всегда создаёт глубокую копию.

**Jakt генерирует конструктор по умолчанию для структур.** Он принимает все поля по имени. Для структуры Point это выглядит так:
```jakt
Point(x: i64, y: i64)

Члены структуры по умолчанию являются публичными.

Класс

  • базовая поддержка классов;
  • члены класса по умолчанию являются приватными;
  • наследование;
  • класс-основанный полиморфизм (присвоение дочернего экземпляра вещам, требующим родительского типа);
  • тип Super;
  • тип Self.

Тот же базовый синтаксис, что и у struct:

class Size {
    width: i64
    height: i64

    public fn area(this) => .width * .height
}

Классы в Jakt имеют ссылочную семантику:

  • копирование экземпляра класса (или «объекта») копирует ссылку на объект;
  • все объекты по умолчанию подсчитываются по ссылкам. Это гарантирует, что доступ к объектам не будет осуществляться после их удаления.

Члены класса по умолчанию являются частными.

Функции-члены

И структуры, и классы могут иметь функции-члены.

Существует три вида функций-членов:

  1. Статические функции-члены не требуют объекта для вызова. У них нет параметра this.
class Foo {
    fn func() => println("Hello!")
}

// Foo::func() можно вызвать без объекта.
Foo::func()
  1. Неизменяющие функции-члены требуют вызова объекта, но не могут изменять объект. Первый параметр — это this.
class Foo {
    fn func(this) => println("Hello!")
}

// Foo::func() может быть вызван только для экземпляра Foo.
let x = Foo()
x.func()
  1. Изменяющие функции-члены требуют вызова объекта и могут модифицировать объект. Первым параметром является mut this.
class Foo {
    x: i64

    fn set(mut this, anon x: i64) {
        this.x = x
    }
}

// Foo::set() может вызываться только для изменяемого Foo:
mut foo = Foo(x: 3)
foo.set(9)

Сокращение для доступа к переменным-членам

Чтобы уменьшить повторяющийся спам this в методах, сокращение .foo расширяется до this.foo.

Строки

Строки предоставляются в языке в основном как тип String, который является подсчитываемым по ссылкам (и размещённым в куче) строковым типом. Литералы строк записываются двойными кавычками, например, «Hello, world!».

Перегруженные строковые литералы

По умолчанию строковые литералы имеют тип String; однако они могут использоваться для неявного создания любого типа, реализующего признак FromStringLiteral (или ThrowingFromStringLiteral). В настоящее время в прелюдии языка только StringView реализует этот признак, который можно использовать только для ссылки на строки со статическим временем жизни:

let foo: StringView = "foo" // Эта строка не размещается в куче, а foo — всего лишь толстый указатель на статическую строку.

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

struct NotString implements(FromStringLiteral) {
    fn from_string_literal(anon string: StringView) -> NotString => NotString()
}

fn test(x: NotString) {}

fn main() {
    let foo: NotString = "foo"
    test(x: "Some string literal")
}

Массивы

Динамические массивы предоставляются через встроенный тип Array. Они могут расти и уменьшаться во время выполнения.

Array безопасен для памяти:

  • выход за границы приведёт к панике программы с ошибкой времени выполнения;
  • срезы массива сохраняют базовые данные живыми посредством автоматического подсчёта ссылок.

Объявление массивов

// Функция, которая принимает Array<i64> и возвращает Array<String>
fn foo(numbers: [i64]) -> [String] {
    ...
}

Сокращение для создания массивов

// Array<i64> с 256 элементами, все инициализированы нулём.
let values = [0; 256]

// Array<String> с 3 элементами: «foo», «bar» и «baz».
let values = ["foo", "bar", "baz"]

Словари

  • создание словарей;
  • индексация словарей;
  • присвоение в индексы. Дакт

Декларирование словарей

fn main() {
    let dict = ["a": 1, "b": 2]

    println("{}", dict["a"])
}
// Функция, которая принимает Dictionary<i64, String> и возвращает Dictionary<String, bool>
fn foo(numbers: [i64: String]) -> [String: bool] {
    ...
}

Сокращённая запись для создания словарей

// Словарь <String, i64> с 3 элементами.
let values = ["foo": 500, "bar": 600, "baz": 700]

Наборы

  • Создание наборов;
  • Ссылочная семантика.
fn main() {
    let set = {1, 2, 3}

    println("{}", set.contains(1))
    println("{}", set.contains(5))
}

Кортежи

  • Создание кортежей;
  • Индексирование кортежей;
  • Типы кортежей.
fn main() {
    let x = ("a", 2, true)

    println("{}", x.1)
}

Перечисления и сопоставление с образцом

  • Перечисления как типы сумм;
  • Общие перечисления;
  • Перечисления в качестве имён для значений базового типа;
  • Выражения match;
  • Вывод области видимости перечисления в блоках match;
  • Возврат значений из блоков match.
enum MyOptional<T> {
    Some(T)
    None
}

fn value_or_default<T>(anon x: MyOptional<T>, default: T) -> T {
    return match x {
        Some(value) => {
            let stuff = maybe_do_stuff_with(value)
            let more_stuff = stuff.do_some_more_processing()
            yield more_stuff
        }
        None => default
    }
}

enum Foo {
    StructLikeThingy (
        field_a: i32
        field_b: i32
    )
}

fn look_at_foo(anon x: Foo) -> i32 {
    match x {
        StructLikeThingy(field_a: a, field_b) => {
            return a + field_b
        }
    }
}

enum AlertDescription: i8 {
    CloseNotify = 0
    UnexpectedMessage = 10
    BadRecordMAC = 20
    // etc
}

// Использование в match:
fn do_nothing_in_particular() => match AlertDescription::CloseNotify {
    CloseNotify => { ... }
    UnexpectedMessage => { ... }
    BadRecordMAC => { ... }
}

Универсальные шаблоны

  • Универсальные типы;
  • Постоянные универсальные шаблоны (минимальная поддержка);
  • Постоянные универсальные шаблоны (полная поддержка);
  • Универсальный вывод типов;
  • Черты.

Дакт поддерживает универсальные структуры и универсальные функции.

fn id<T>(anon x: T) -> T {
    return x
}

fn main() {
    let y = id(3)

    println("{}", y + 1000)
}
struct Foo<T> {
    x: T
}

fn main() {
    let f = Foo(x: 100)

    println("{}", f.x)
}
struct MyArray<T, comptime U> {
    // NOTE: В настоящее время нет способа получить доступ к значению 'U', обращение к 'U' допустимо только как к типу.
    data: [T]
}

Пространства имён

  • Поддержка пространств имён для функций и структур/классов/перечислений;
  • Глубокая поддержка пространств имён.
namespace Greeters {
    fn greet() {
        println("Well, hello friends")
    }
}

fn main() {
    Greeters::greet()
}

Приведение типов

В Дакте есть два встроенных оператора приведения типов.

  • as? T: Возвращает Optional<T>, пустое, если исходное значение не может быть преобразовано в T.
  • as! T: Возвращает значение T, прерывает работу программы, если исходное значение не может быть преобразовано в T.

Оператор as может выполнять следующие действия (обратите внимание, что реализация может не совпадать):

  • Приведение к одному и тому же типу является безотказным и бессмысленным, поэтому в будущем оно может быть запрещено.
  • Если тип источника неизвестен, приведение считается допустимым как утверждение типа.
  • Если оба типа являются примитивными, выполняется безопасное преобразование.
    • Преобразование целых чисел завершится неудачно, если значение выходит за пределы диапазона. Это означает, что повышающие преобразования, такие как i32 → i64, безотказны.
    • Float → Integer преобразует значения путём усечения десятичной точки (?)
    • Integer → Float преобразует значения в ближайшее целое число, представимое типом с плавающей точкой (?). Если целочисленное значение слишком велико, результатом будет бесконечность (?)
    • Любой примитивный тип → bool создаст true для любого значения, кроме 0, которое равно false.
    • bool → любой примитивный тип будет выполнять false → 0 и true → 1, даже для чисел с плавающей запятой.
  • Если типы являются двумя разными типами указателей (см. выше), приведение по существу является пустым действием. Приведение к T будет увеличивать значение на... Сильная и слабая ссылки. Приведение типов

reference count as expected; that's the preferred way of creating a strong reference from a weak reference. A cast from and to raw T is unsafe.

  • Если типы являются частью одной иерархии типов (то есть один является дочерним типом другого):
    • Дочерний тип можно безопасно привести к родительскому типу.
    • Родительский тип можно привести к дочернему типу, но это приведёт к проверке типа во время выполнения, и если объект не был дочернего типа или одного из его подтипов, то будет ошибка.
  • Если типы несовместимы, будет предпринята попытка использовать пользовательское приведение. Детали здесь ещё не определены.
  • В противном случае приведение даже не скомпилируется.

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

Traits

Чтобы сделать дженерики более мощными и выразительными, вы можете добавить дополнительную информацию к ним:

trait Hashable<Output> {
    fn hash(self) -> Output
}

class Foo implements(Hashable<i64>) {
    fn hash(self) => 42
}

Traits могут использоваться для добавления ограничений к универсальным типам, а также предоставляют реализации по умолчанию на основе минимального набора требований — например:

trait Fancy {
    fn do_something(this) -> void
    fn do_something_twice(this) -> void {
        .do_something()
        .do_something()
    }
}

struct Boring implements(Fancy) {
    fn do_something(this) -> void {
        println("I'm so boring")
    }

    // Note that we don't have to implement `do_something_twice` here, because it has a default implementation.
}

struct Better implements(Fancy) {
    fn do_something(this) -> void {
        println("I'm not boring")
    }

    // However, a custom implementation is still valid.
    fn do_something_twice(this) -> void {
        println("I'm not boring, but I'm doing it twice")
    }
}

У traits могут быть методы, которые ссылаются на другие traits как типы, что можно использовать для описания иерархии traits:

trait ConstIterable<T> {
    fn next(this) -> T?
}

trait IntoIterator<T> {
    // Note how the return type is a reference to the ConstIterable trait (and not a concrete type)
    fn iterator(this) -> ConstIterable<T>
}

Перегрузка операторов и Traits

Операторы реализованы как traits и могут быть перегружены путём их реализации для данного типа:

struct Foo implements(Add<Foo, Foo>) {
    x: i32

    fn add(this, anon rhs: Foo) -> Foo {
        return Foo(x: .x + other.x)
    }
}

Связь между операторами и traits следующая (обратите внимание, что @ используется в качестве заполнителя для имени или символа любого бинарного оператора):

Оператор Trait Метод Name Derived From Method
+ Add add -
- Subtract subtract -
* Multiply multiply -
/ Divide divide -
% Modulo modulo -
< Compare less_than compare
> Compare greater_than compare
<= Compare less_than_or_equal compare
>= Compare greater_than_or_equal compare
== Equal equals -
!= Equal not_equals equals
@= @Assignment @_assign -

Другие операторы ещё не были преобразованы в traits, не решены или реализованы:

Operator Описание Статус
& Побитовое И Не решено
| Побитовое Или Не решено
^ Побитовое Xor Не решено
~ Побитовое Не Не решено
<< Побитовый Сдвиг влево Не решено
>> Побитовый сдвиг вправо Не решено
and Логическое И Не решено
or Логическое Или Не решено
not Логическое Не Не решено
= Присвоение Не решено

Анализ безопасности

(Ещё не реализован)

Для обеспечения безопасности необходимо провести несколько видов анализа (не исчерпывающий список):

  • Предотвращение перекрытия вызовов методов, которые будут конфликтовать друг с другом. Например, создание итератора над контейнером, и пока он активен, Обработка ошибок

Функции, которые могут завершиться с ошибкой вместо нормального возврата, помечаются ключевым словом throws:

fn task_that_might_fail() throws -> usize {
    if problem {
        throw Error::from_errno(EPROBLEM)
    }
    ...
    return result
}

fn task_cannot_fail() -> usize {
    ...
    return result
}

В отличие от таких языков, как C++ и Java, ошибки не разворачивают стек вызовов автоматически. Вместо этого они всплывают к ближайшему вызывающему объекту.

Если ничего не указано, вызов функции, которая throws, из функции, которая также throws, будет неявно распространять ошибки.

Синтаксис для перехвата ошибок

Чтобы перехватить ошибки локально, а не позволить им всплывать к вызывающей стороне, используйте конструкцию try/catch, например:

try {
    task_that_might_fail()
} catch error {
    println("Caught error: {}", error)
}

Существует также более короткая форма:

try task_that_might_fail() catch error {
    println("Caught error: {}", error)
}

Перебрасывание ошибок

(Не реализовано)

Встроенный C++

Для лучшей совместимости с существующим кодом на C++, а также в ситуациях, когда возможностей Jakt в блоках unsafe недостаточно, существует возможность встраивания встроенного кода на C++ в программу в виде блоков cpp:

mut x = 0
unsafe {
    cpp {
        "x = (i64)&x;"
    }
}
println("{}", x)

Ссылки

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

Ссылка может быть либо неизменной (по умолчанию), либо изменяемой.

Синтаксис ссылочного типа

  • &T — неизменяемая ссылка на значение типа T.
  • &mut T — изменяемая ссылка на значение типа T.

Синтаксис выражения ссылки

  • &foo создаёт неизменяемую ссылку на переменную foo.
  • &mut foo создаёт изменяемую ссылку на переменную foo.

Разыменование ссылки

Чтобы «получить значение» из ссылки, её нужно разыменовать с помощью оператора *, однако компилятор автоматически разыменует ссылки, если разыменование является единственным однозначным правильным использованием ссылки (на практике ручное разыменование требуется только там, где ссылка хранится или передаётся функциям).

fn sum(a: &i64, b: &i64) -> i64 {
    return a + b
    // Или с ручным разыменованием:
    return *a + *b
}

fn test() {
    let a = 1
    let b = 2
    let c = sum(&a, &b)
}

Для изменяемых ссылок на структуры необходимо заключить разыменование в круглые скобки, чтобы получить доступ к полю:

struct Foo {
    x: i64
}
fn zero_out(foo: &mut Foo) {
    foo.x = 0
    // Или с ручным разыменованием:
    (*foo).x = 0
}

Список функций ссылок (первая версия):

— ссылочные типы; — параметры ссылочных функций; — локальные переменные ссылок с базовым анализом времени жизни; — отсутствие ссылок в структурах; — отсутствие ссылок в типах возвращаемых значений; — невозможность изменения ссылок на неизменяемые значения; — разрешение &foo и &mut foo без метки аргумента для параметров с именем foo; — автоматическое разыменование ссылок там, где применимо.

Что нужно сделать:

— ссылки (unsafe) и необработанные указатели, взаимно конвертируемые; — захват по ссылке в постоянных замыканиях отсутствует.

Замыкания (список функций первой версии):

— функция как параметр функции; — функции как переменные; — отсутствие возвращающих функций из функций; — лямбды могут вызывать исключения; — явные захваты.

Что нужно сделать:

— возврат функции из функции.

Выполнение во время компиляции

Выполнение функции во время компиляции (или CTFE) в Jakt позволяет выполнять любую функцию jakt во время компиляции при условии, что результат значения может быть синтезирован с использованием его полей — в настоящее время это запрещает только несколько объектов прелюдии, которые нельзя создать с помощью их полей (например, объекты Iterator и StringBuilders).

Любую обычную функцию Jakt можно превратить в функцию времени компиляции, заменив ключевое слово function в её определении на compiletime. Объявление с ключевым словом comptime заставит все вызовы этой конкретной функции выполняться во время компиляции.

Ограничения вызова

Функции comptime могут вызываться только постоянными выражениями; это ограничение включает объект this методов.

Бросок в контексте comptime

Бросок ведёт себя так же, как и обычный поток управления ошибками, если ошибка выходит из контекста comptime (достигая исходного места вызова), она будет преобразована в ошибку компиляции.

Побочные эффекты

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

Импорт comptime

Можно разработать собственную обработку импорта на основе данных, доступных во время компиляции. Отличным примером этого в компиляторе Jakt является модуль платформы.

См. меньший пример в образце импорта comptime.

Comptime TODO

  • Реализовать выполнение всех выражений Jakt.

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

Вы можете оставить комментарий после Вход в систему

Введение

Джакт — это системный язык программирования, обеспечивающий безопасность памяти, который активно развивается. В настоящее время его можно транслировать в C++. Развернуть Свернуть
C++ и 6 других языков
BSD-2-Clause
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/mirrors-jakt.git
git@api.gitlife.ru:oschina-mirror/mirrors-jakt.git
oschina-mirror
mirrors-jakt
mirrors-jakt
main