Trust (trash + Rust) - the only programming language you can trust.
Trust - это интерпретируемый язык программирования, синтаксис которого заимствован из Rust. В нём есть:
- базовые типы
- условные конструкции
- циклы for и while
- функции
В Trust есть следующие типы:
bool
- логический типi32
- целое 32 битное знаковое числоf64
- 64 битное число с плавающей точкойusize
- беззнаковое целое число, размер которого равен размеру машинного словаchar
- беззнаковое 8 битное число, используемое для представления символовString
- строка, состоящая из символов, представимых типомchar
Vec<type>
- динамический массив, способный хранить примитивные типы (все, кромеString
иVec<T>
)
Пример объявления переменных:
let a: i32 = 100; // иммутабельная переменная a
let mut b: i32 = 100; // мутабельная переменная b
let mut c: i32; // мутабельная неинициализированная переменная c
let sum = a + c; // ошибка, использование неинициализированных переменных (с) запрещено
let d: i32; // ошибка, иммутабельные переменные должны быть инициализированы сразу при объявлении
let e = 100; // при объявлении переменных можно не указывать явно тип, в данном случае e имеет тип i32 (type inference)
let mut f; // ошибка, при использовании type inference переменная должна быть инициализирована сразу при объявлении
Особенностью языка, также заимствованной из Rust, являются довольно строгие правила приведения типов:
- Типы операндов арифметических операций (+, -, *, /, %) должны совпадать
- bool - единственный допустимый тип операндов логических операций (&&, ||)
- Условия в
if
иwhile
должны иметь типbool
- Неявные преобразования типов не совершаются ни в каком случае
- Для явного преобразования типов используется оператор
as
:<expression> as <type>
. Если запрашиваемое преобразование допустимо, оно будет сделано, иначе - будет брошено исключение - Целочисленные литералы отвечают типу
i32
, литералы с суффиксомusize
- типуusize
(например, 0 и 0usize)
Условия в if
и while
не обязательно должны быть в круглых скобках. Цикл for
имеет 3 вида:
for <variable> in <expr1>..<expr2>
-expr1
иexpr2
должны иметь одинаковые типы:i32
илиusize
.variable
пройдет все значения отexpr1
доexpr2
невключительно с шагом 1for <variable> in <expr1>..=<expr2>
-expr1
иexpr2
должны иметь одинаковые типы:i32
илиusize
.variable
пройдет все значения отexpr1
доexpr2
включительно с шагом 1for <variable> in <expression>
-expression
должно иметь типString
илиVec<T>
.variable
пройдет по значениям итерируемого объекта и будет иметь типChar
илиT
соответственно. Еслиexpression
- это переменная, то на время этого цикла она станет иммутабельной Внутри циклов можно использоватьbreak
иcontinue
.
Функции должны быть объявлены на самом внешнем уровне вложенности, среди всех функций должна быть ровно 1 функция с названием main. Она должна ничего не принимать и ничего не возвращать. Имена всех функций должны быть различны.
Функции могут принимать аргументы, типы аргументов должны быть явно указаны при объявлении. При вызове функции имена аргументов станут переменными, доступными внутри данной функции. Их можно сделать изменяемыми, добавив при объявлении mut
: fn foo(a: i32)
-> fn foo(mut a: i32)
.
Функции могут возвращать значения, тип возвращаемого значения должен быть явно указан при объявлении: fn void_function()
, fn non_void() -> i32
. В теле функции значение может быть возвращено с помощью конструкции return <expression>
; в случае функции без возвращаемого значения можно досрочно завершить её исполнение: return;
. Также функция может заканчиваться строкой, содержащей выражение без ;
в конце, тогда это выражение будет вычислено и возвращено, использование слова return
в данном случае необязательно. Пример:
fn sum(a: i32, b: i32) -> i32 {
a + b
}
При вызове функции все аргументы будут скопированы, возвращаемое значения так же будет скопировано.
В языке есть 2 непримитивных типа: String
и Vec<T>
. Они обладают некоторыми качествами, не присущими остальным, фундаментальным типам
Для создания экземляра типа String
есть строковые литералы: let foo: String = "bar";
. Для создания экземпляра типа Vec<T>
есть две конструкции: vec![expr1, expr2, ..., expr_n]
и vec![expr1; expr2]
. В первом случае будет создан массив из переданных ему элементов; все они должны иметь тип T
. Во втором случае будет создан массив из expr2
(должно иметь тип usize
) элементов expr1
(должно иметь тип T
).
Compound типы позволяют получить доступ по индексу к своим элементам: expr1[expr2]
, expr1
- compound type, expr2
должен иметь тип usize
. Однако в такой формулировке элементы будут доступны только на чтение. Для изменения элементов compound типов есть следующая конструкция: identifier[expr1] = expr2
. В переменной identifier
должен быть compound тип, хранящий элементы типа U
; expr1
должно иметь тип usize
, expr2
должно иметь тип U
.
У String
есть единственный метод .len() -> usize
, который возвращает количество символов в строке. У Vec<T>
есть 3 метода: .len() -> usize
, .push(T)
и .pop()
.
Методы можно вызывать только от значений, хранящихся в переменных.
В языке есть функции print!
и println!
, позволяющие выводить значения выражений на экран. Есть 3 варианта использования:
println!();
println!("string");
- вывод строки на экранprintln!("{}, {}!", expr1, expr2);
- форматированный вывод
Trust имеет области видимости переменных и допускает, например, такие конструкции:
fn foo() {
let bar = 0;
let bar = 0;
{
let bar = 1;
println!("{}", bar); // будет выведено 1
}
println!("{}", bar); // будет выведено 0
}
Изначально я хотел придерживаться стратегии, что любая программа, успешно интерпретируемая Trust, должна так же работать и на Rust, но вскоре пришлось отказаться от этого требования. Однако я старался допускать минимальное количество ситуаций, когда это "правило" бы нарушалось, стараясь принимать во внимание здравый смысл. Ниже представлен неполный список случаев, когда программа работает на Trust, но не на Rust:
let s: String = "foo";
Эта программа корректна на языке Trust, так как выражение "foo"
имеет тип String
. В Rust же "foo"
имеет тип &str
, и чтобы добиться желаемого результата, нужно сделать так:
let s: String = "foo".to_string();
let s: String = ...;
println!("{}", s[0]);
Trust выведет первый символ строки s
. На Rust же такая программа не скомпилируется. Вот исправленная версия:
let s: String = ...;
println!("{}", s.chars().nth(0).unwrap());
let mut var: i32;
if true {
var = 0;
}
println!("{}", var);
Эта программа на Trust выводит 0, но не компилируется на Rust с ошибкой "used binding var
is possibly-uninitialized"
Следующие программы работают на Trust, но не работают на Rust. Это происходит из-за того, что в тех местах, где в Rust происходит передача владения, в Trust делается копия объекта.
В сущности, эти 3 примера показывают одно и то же явление, но для наглядности приведены они все. Пример 1:
let foo = vec![1, 2];
for element in foo {
println!("{}", element);
}
for element in foo {
println!("{}", element);
}
Пример 2:
let foo = vec![1, 2];
let x = foo;
let y = foo;
Пример 3:
fn take_ownership(v: String) {
;
}
fn main() {
let foo: String = "bar"; // .to_string();
take_ownership(foo);
take_ownership(foo);
}
- Склонировать репозиторий
mkdir build
cd build
cmake ..
make
./TrustLangInterpreter input.in