Язык TL

Язык TL (Type Language или Time Limit) служит для описания используемой системы типов, конструкторов и существующих функций. Фактически используется формат описания комбинаторов, приведенный в статье двоичная сериализация данных.

См. также:

Продвинутые темы:

Обзор

Программа на TL обычно состоит из двух секций, разделенных ключевым словом «---functions---». Первая секция состоит из описаний встроенных и составных типов (т.е. их конструкторов). Вторая секция состоит из объявлений функций — т.е. функциональных комбинаторов.

Фактически и первая, и вторая секция состоят из описаний комбинаторов, каждое из которых завершается точкой с запятой, однако в первой секции участвуют только конструкторы, а во второй — только функции. Каждый комбинатор описывается с помощью ‘’описания комбинатора‘’ в формате, объясненном выше. Однако есть возможность явно задавать номер комбинатора, а также названия полей.

Если нужно после объявлений функций вернуться к объявлению типов, используется ключевое слово (разделитель секций) «---types---». Кроме того, в разделе типов можно объявить и функциональный комбинатор, если тип его результата предварить восклицательным знаком (фактически раздел функций — это такой раздел, при интерпретации которого этот восклицательный знак добавляется автоматически).

Для явного задания 32-битных имен комбинаторов сразу после имени комбинатора дописывается диез (#), за которым следует 8 шестнадцатеричных цифр.

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

В качестве идентификатора конструктора или типа можно также использовать составные конструкции вида <namespace_identifier>.<constructor_identifier> и <namespace_identifier>.<Type_identifier>.

При этом идентификатор, стоящий слева от точки, называется пространством имен (namespace). Правило про первую заглавную букву в идентификаторах типов и строчную букву в идентификаторах конструкторов распространяется при этом на часть конструкции после точки. Например, auth.Message может быть типом, а auth.std_message может быть конструктором.

Пространства имен не нуждаются в специальном объявлении.

Комментарии

Комментарии такие же, как в C++.

Пример

// built-in types
int#a8509bda ? = Int;
long ? = Long;
double ? = Double;
string ? = String;
null = Null;

vector {t:Type} # [ t ] = Vector t;
coupleInt {alpha:Type} int alpha = CoupleInt<alpha>;
coupleStr {gamma:Type} string gamma = CoupleStr gamma;  
/* name of type variable is irrelevant: might replace 'gamma' with 'alpha'; 
   however, combinator number will depend on the specific choice */

intHash {alpha:Type} vector<coupleInt<alpha>> = IntHash<alpha>;
strHash {alpha:Type} (vector (coupleStr alpha)) = StrHash alpha;
intSortedHash {alpha:Type} intHash<alpha> = IntSortedHash<alpha>;
strSortedHash {alpha:Type} (strHash alpha) = StrSortedHash alpha;

// instantiating polymorphic types ('templates')
// outdated, ignored altogether
Vector int;
// with syntactic sugar as well:
Vector<string>;
Vector Object;
IntHash int;
IntHash string;
IntHash Object;
StrHash int;
StrHash string;
StrHash Object;

// custom types
pair x:Object y:Object = Pair;
triple x:Object y:Object z:Object = Triple;

user#d23c81a3 id:int first_name:string last_name:string = User;
no_user#c67599d1 id:int = User;
group id:int title:string last_name:string = Group;
no_group = Group;

---functions---

// maybe some built-in arithmetic functions; inverse quotes make 'identifiers' 
// from arbitrary non-alphanumeric strings
`+` Int Int = Int;
`-` Int Int = Int;
`+` Double Double = Double;
// ...

// API functions (aka RPC functions)
getUser#b0f732d5 int = User;
getUsers#2d84d5f5 (Vector int) = Vector User;

В данном случае для конструктора user явно задан номер (0xD23C81A3); в действительности можно было бы этого не делать, так как это значение есть CRC32 от строки 'user id:int first_name:string last_name:string = User', которое было бы использовано по умолчанию.

Специальные конструкторы для Vector int, Vector User, Vector Object и т.д. не нужны — везде можно использовать один универсальный конструктор:

vector#1cb5c415 {t:Type} # [ t ] = Vector t;

Обратите внимание, что при вычислении номера конструктора getUsers (Vector int) = Vector User; вычисляется CRC32 от строки getUsers Vector int = Vector User, из которой удалены все скобки.

Запись T0<T1,T2,...,Tn> является синтаксическим сахаром для (T0 T1 T2 ... Tn). Например, Vector<User> и (Vector User) полностью равнозначны.

Пример RPC-запроса

Предположим, мы хотим запросить getUsers([2,3,4]). Этот запрос будет сериализован следующим образом:

0x2d84d5f5 0x1cb5c415 0x3 0x2 0x3 0x4

Ответ на него может выглядеть примерно так (для наглядности добавлены переносы строк):

0x1cb5c415 0x3
    0xd23c81a3 
        0x2 0x76615005 0x6c65 
        0x72754405 0x766f
    0xc67599d1 0x3
    0xd23c81a3
        0x4 0x6b694e07 0x79616c6f
        0x72754405 0x766f

Это примерно соответствует

[
   {"id":2,"first_name":"Peter", "last_name":"Parker"},
   {},
   {"id":4,"first_name":"John","last_name":"Doe"}
]

Обратите внимание, что в обоих случаях используется один и тот же универсальный конструктор vector#1cb5c415, в запросе для сериализации значения типа Vector int, в ответе — типа Vector User. Путаницы не происходит, так как в обоих случаях тип (де)сериализуемого значения известен до начала его десериализации. Например, сервер, получив запрос, видит, что первая его компонента — это 0x2D84D5F5, что соответствует комбинатору getUsers#2d84d5f5 (Vector int) = Vector User, поэтому понятно, что дальше будет следовать значение именно типа Vector int. Клиент же, получив ответ на этот запрос, знает, что ему должны вернуть значение типа Vector User, и десериализует его соответственно.