diff --git a/data/core.telegram.org/mtproto/TL-formal.html b/data/core.telegram.org/mtproto/TL-formal.html new file mode 100644 index 0000000000..5c7e52adea --- /dev/null +++ b/data/core.telegram.org/mtproto/TL-formal.html @@ -0,0 +1,319 @@ + + +
+ +See also TL Language. +For the syntax of declaring combinators, see in article Formal declaration of TL combinators. +For the syntax of patterns, see in article Formal declaration of TL patterns.
+Comments are the same as in C/C++. They are removed by a lexical parser (for example, being replaced by a single space). Whitespace separates tokens. Except for string constants, tokens cannot contain spaces.
+Character classes:
+lc-letter ::= a
| b
| ... | z
+uc-letter ::= A
| B
| ... | Z
+digit ::= 0
| 1
| ... | 9
+hex-digit ::= digit | a
| b
| c
| d
| e
| f
+underscore ::= _
+letter ::= lc-letter | uc-letter
+ident-char ::= letter | digit | underscore
Simple identifiers and keywords:
+lc-ident ::= lc-letter { ident-char }
+uc-ident ::= uc-letter { ident-char }
+namespace-ident ::= lc-ident
+lc-ident-ns ::= [ namespace-ident .
] lc-ident
+uc-ident-ns ::= [ namespace-ident .
] uc-ident
+lc-ident-full ::= lc-ident-ns [ #
hex-digit *8 ]
Tokens:
+underscore ::= _
+colon ::= :
+semicolon ::= ;
+open-par ::= (
+close-par ::= )
+open-bracket ::= [
+close-bracket ::= ]
+open-brace ::= {
+close-brace ::= }
+triple-minus ::= ---
+nat-const ::= digit { digit }
+lc-ident-full
+lc-ident
+uc-ident-ns
+equals ::= =
+hash ::= #
+question-mark ::= ?
+percent ::= %
+plus ::= +
+langle ::= <
+rangle ::= >
+comma ::= ,
+dot ::= .
+asterisk ::= *
+excl-mark ::= !
+Final-kw ::= Final
+New-kw ::= New
+Empty-kw ::= Empty
Final
is a reserved keyword, e.g. a special token. Words like Type
are not keywords, rather they are identifiers with preset values.
Tokens consisting of one or more constant symbols shall be hereafter denoted using terms in quotation marks (for example, ---
replaces triple-minus).
Syntactically, a TL program consists of a stream of tokens (separated by spaces, which are ignored at this stage). General program structure:
+TL-program ::= constr-declarations { ---
functions
---
fun-declarations | ---
types
---
constr-declarations }
Here the constructor- and function declarations are nearly identical in their syntax (they are both combinators):
+constr-declarations ::= { declaration }
+fun-declarations ::= { declaration }
There are various declarations:
+declaration ::= combinator-decl | partial-app-decl | final-decl
+Before explaining how declarations of combinators, partial applications, and type finalization
are given, we will introduce additional syntactical categories:
The concept of an expression (expr) is important. There are type expressions (type-expr) and numeric expressions (nat-expr). However, they are defined the same way. Their correctness as type- or numeric expressions is checked when the type of the analyzed expression is checked.
+type-expr ::= expr
+nat-expr ::= expr
+expr ::= { subexpr }
+subexpr ::= term | nat-const +
subexpr | subexpr +
nat-const
+term ::= (
expr )
| type-ident | var-ident | nat-const | %
term | type-ident <
expr { ,
expr } >
+type-ident ::= boxed-type-ident | lc-ident-ns | #
+boxed-type-ident ::= uc-ident-ns
+var-ident ::= lc-ident | uc-ident
+type-term ::= term
+nat-term ::= term
Note that writing E = E_1 E_2 ... E_n
in the expression for expr means applying the function E_1 to the argument E_2, applying the result to E_3, etc. Specifically, E_1 E_2 E_3 = (E_1 E_2) E_3
. A solitary #
is included in type-ident, because it is actually the identifier for a built-in type (#
alias nat
).
The expression E<E_1,...,E_n>
is syntactic sugar for (E (E_1) ... (E_n))
, i.e. both expressions are transformed into the same internal representation.
combinator-decl ::= full-combinator-id { opt-args } { args } =
result-type ;
+full-combinator-id ::= lc-ident-full | _
+combinator-id ::= lc-ident-ns | _
+opt-args ::= {
var-ident { var-ident } : [excl-mark] type-expr }
+args ::= var-ident-opt :
[ conditional-def ] [ !
] type-term
+args ::= [ var-ident-opt :
] [ multiplicity *
] [
{ args } ]
+args ::= (
var-ident-opt { var-ident-opt } :
[!
] type-term )
+args ::= [ !
] type-term
+multiplicity ::= nat-term
+var-ident-opt ::= var-ident | _
+conditional-def ::= var-ident [ .
nat-const ] ?
+result-type ::= boxed-type-ident { subexpr }
+result-type ::= boxed-type-ident <
subexpr { ,
subexpr } >
See Formal declaration of TL combinators for a description of what exactly this means. Here we will only note that when declaring the type of a combinator’s next argument, only the names of previously arranged (more to the left) arguments of the same combinator may be used as variables, but when declaring the result type you can use all of its parameters (of type Type
and #
).
Note that the names of combinators declared in this way may be used in TL itself only as the corresponding bare types. The only combinators that appear in declarations are built-in: O : #
and S : # -> #
.
There are also “pseudo-declarations” that are allowed only to declare built-in types (such as int ? = Int;
):
builtin-combinator-decl ::= full-combinator-id ?
=
boxed-type-ident ;
partial-app-decl ::= partial-type-app-decl | partial-comb-app-decl
+partial-type-app-decl ::= boxed-type-ident subexpr { subexpr } ;
| boxed-type-ident <
expr { ,
expr } >
;
+partial-comb-app-decl ::= combinator-id subexpr { subexpr } ;
See Formal declaration of TL patterns.
+final-decl ::= New
boxed-type-ident ;
| Final
boxed-type-ident ;
| Empty
boxed-type-ident ;
This type of declaration means that there must not be any constructor for the indicated type: before the declaration for New
and after the declaration for Final
. The keyword Empty
enables both effects.
Nearly all predefined identifiers may be given using the following schema (usually located in common.tl
):
/////
+//
+// Common Types
+//
+/////
// Built-in types
+int ? = Int;
+long ? = Long;
+double ? = Double;
+string ? = String;
// Boolean emulation
+boolFalse = Bool;
+boolTrue = Bool;
// Boolean for diagonal queries
+boolStat statTrue:int statFalse:int statUnknown:int = BoolStat;
// Vector
+vector {t:Type} # [t] = Vector t;
+tuple {t:Type} {n:#} [t] = Tuple t n;
+vectorTotal {t:Type} total_count:int vector:%(Vector t) = VectorTotal t;
/////
+//
+// Result- (Maybe-) types
+//
+/////
resultFalse {t:Type} = Maybe t;
+resultTrue {t:Type} result:t = Maybe t;
pair {X:Type} {Y:Type} a:X b:Y = Pair X Y;
+map {X:Type} {Y:Type} key:X value:Y = Map X Y;
Empty False;
+true = True;
unit = Unit;
+Predefined identifier Type
: This type signifies the type of all types. It is usually used to specify the types of optional parameters in the constructors of polymorphic types. If strongly desired, it can be used in its own right, but this is very rarely needed in practice.
Identifier #
: This type is used to specify a special type of nonnegative integers in the range from 0 to 2^31-1; its main purpose is the same as that of Type
. There are two built-in constructors: O
: # and S
: # -> # (“null” and “next number”, respectively), which work as if #
was defined using the schema
O = #;
+S # = #;
Identifier Tuple
: Type -> # -> Type denotes a set of the specified number of values of the indicated type. In other words, Tuple X n means “a set of n values of type X".
The typeBool
, with two constructors boolTrue
and boolFalse
, is used to transmit Boolean values.
The constructor-less type False
may be used instead of undeclared or invalid types in the construction of a TL schema, because any attempt to (de)serialize a value of type False
will produce an error. Usage Example:
user {flags:#} id:flags.0?string first_name:flags.1?string last_name:flags.2?string reserved3:flags.3?False reserved4:flags.4?False = User flags;
+user_present {flags:#} info:%(User flags) = UserInfo flags;
+user_absent {flags:#} = UserInfo flags;
+getUser flags:# id:int = !UserInfo flags;
In the future, bits 3 and 4 in the flags
field may be used to transmit new fields after changing the names and types of the reserved3
and reserved4
fields. This will change the user
constructor’s number, but this isn’t too important, since the User flags
type is only used as a bare type. Transmitting bits 3 or 4 in the flags
field in a getUser
query before these fields have actually been defined will lead to an error in the (de)serialization of the request.
True
with a single null constructor true
plays a role similar to the void type in C/C++. It is especially useful as a bare type %True
, alias true
, because its serialization has zero length. For example, the first_name:flags.1?string
constructor used above is in fact shorthand for (the as-yet unsupported) alternative-type general constructor first_name:(flags.1?string:true)
.When directly used in a conditional field it may simply indicate the presence (absence) of a certain parameter with void type. +If the conditional field exists, the associated parameter will not be populated; the conditional field simply exists and the existance value can be used to perform certain operations, example:
+user {flags:#} id:flags.0?string first_name:flags.1?string last_name:flags.2?string bot:flags.3?true reserved4:flags.4?False = User flags;
+If bit 3 of the flags
parameter isn't set, the user is a normal user.
+If bit 3 of the flags
parameter is set, this indicates that the specified user is a bot: however, during deserialization, the bot
parameter must not be assigned any value, since true
is actually a void
type.
Unit
with a single null constructor Unit
is similar to the previous type.TDLib is a fully functional Telegram client which takes care of all networking, local storage and data consistency details. In this tutorial we describe the main concepts understanding of which is required for effecient TDLib usage.
-In this text, Client means an interface for interaction with a TDLib instance and Application means the program that uses TDLib to interact with Telegram.
-The main TDLib API is fully-asyncronous. An Application can send a request to TDLib through Client.send
method and receive a response asynchronously through the Client.receive
method when it becomes available. The exact naming of these methods and the way in which requests are matched with responses is different for different TDLib interfaces, but the concept as a whole remains the same. For example, in TDLib JSON interface these methods are called td_json_client_send
and td_json_client_receive
, and their @extra field should be used to match requests with the corresponding responses.
In a high-level interface used by an Application the matching of responses with corresponding requests is often automated and transformed by some wrapper into a call to a continuation, a callback, a Promise or a Future to simplify the handling of responses.
-Aside from responses to requests, an Application receives a lot of important data through incoming updates. Updates are used to pass new data from TDLib to the Application and often control the behavior of the Application, leaving no chance to implement something wrong. The correct handling of updates is crucial for creating an Application that is efficient and works correctly.
---You can find a list of all available TDLib API methods in our web-documentation. You can also find the descriptions of all available TDLib methods and classes in the TDLIB API scheme.
-TDLib can be used from any programming language. You can find a lot of examples of TDLib-based frameworks in various programming languages in our examples section.
-
This section describes the basic notions required for understanding the TDLib API. If you have used the TDLib-based Telegram Bot API most of them should be already familiar to you.
-Telegram is a messenger, so the main object is a message. Each message belongs to some chat and has a unique identifier within that chat. Messages inside a chat should be sorted by that identifier. Telegram supports many different kinds of messages, so a message can have many different kinds of message content. Currently there are more than 40 different kinds of message content, for example messageText for text messages, messagePhoto for photos, or messageScreenshotTaken for notifications about screenshots taken by the other party.
-A Telegram user is called user. Each user has a unique identifier and a first name, and can also have an optional last name, username and profile photo among other useful fields. Bot is a special type of user which can be controlled through the Telegram Bot API.
-Each chat has members, i.e. users that immediately receive all messages sent to the chat. Currently there are 6 possible chat member statuses which describe different rights and restrictions a user can have in a chat, ranging from the owner of the chat who has more rights in the chat than any other user, to a user banned in the chat who is banned in the chat and can't return to it by self or even view chat messages, even if the chat is public.
-As noted earlier, each message belongs to a chat. Currently there are 4 different types of chats on Telegram:
-Each chat has a unique identifier, a title and an optional chat photo. Chats comprise sorted lists shown to the user, position in which is determined, roughly speaking, by the time of latest activity. The correct order of chats in chat lists is maintained by TDLib, so the Application only needs to listen to updates that change the chat.positions
field and sort all chats by the pair (position.order, chat.id)
in a given position.list
.
Messages, chat photos and many other objects can have a file inside of them. Each file has an identifier and may be available locally on a hard drive or remotely on a cloud server. A file can be usually downloaded to the local hard drive or uploaded to Telegram cloud servers.
-Messages with media content like photos or videos can have a short accompanying text called caption. The texts of text messages and media captions can contain fragments, which should be formatted in some unusual way. These fragments are called text entities and the combination of a text and its entities are referred together as a formatted text.
-TDLib sends a lot of important data to the Application through updates. For example, if there is a user unknown to the Application, or some data about a user has changed, then TDLib immediately sends an updateUser to the Application.
---You can find list of all currently available updates here »
-
Authorization is an example of a behavior, which is controlled by TDLib through updates. Whenever an action is required to proceed with user authorization, the Application receives an updateAuthorizationState with the description of the current AuthorizationState. The Application only needs to handle this update appropriately to correctly implement user authorization.
-The first authorization state received by the Application is always of the type authorizationStateWaitTdlibParameters
. When it is received, the Application should provide parameters for TDLib initialization by calling the setTdlibParameters method. In this method the Application will need to specify, among other parameters:
After call to setTdlibParameters
in case of success Application will receive updateAuthorizationState
with new state and just needs to handle that update, nothing should be done explicitly. If setTdlibParameters
fails, then authorization state is not changed and the Application should try to handle the current authorization state again.
The second received authorization state is always authorizationStateWaitEncryptionKey
. When it is received, the database encryption key should be provided through a call to checkDatabaseEncryptionKey
. For most mobile apps, you can provide an empty database encryption key here (more info). If user isn't authorized yet, then some of authorizationStateWaitPhoneNumber
, authorizationStateWaitCode
, authorizationStateWaitRegistration
and authorizationStateWaitPassword
authorization states may be received. After completing these authorization steps, the Application will receive authorizationStateReady
, meaning that authorization was successful and ordinary requests can be sent now.
--You can find complete examples of user authorization in our Java and C# examples.
-
To send any kind of message, the Application needs to call the method sendMessage providing a chat identifier
and the content of the message to be sent. For example, the Application can send a text message using inputMessageText class as input message content, a photo using inputMessagePhoto or a location using inputMessageLocation. The Application can use inputFileLocal as InputFile in these objects to send a local file from the hard drive.
--You can find examples of sending a text message in our Java and C# examples.
-
All updates and responses to requests should be handled in the order they are received. Here is a list of the most important updates and how they should be handled:
-chat_id
, it never needs to use a getChat
request to receive the chat object. Instead it should maintain a cache of chats received through this update and take all the necessary data about chats from this cache.user_id
, it never needs to use the getUser
request to receive the user object. Instead it should maintain a cache of users received through this update and take all the necessary data about users from this cache.basic_group_id
, it never needs to use the getBasicGroup
request to receive the basicGroup
object. Instead it should maintain a cache of basic groups received through this update and take all the necessary data about basic groups from this cache.supergroup_id
, it never needs to use the getSupergroup
request to receive the supergroup object. Instead it should maintain a cache of supergroups received through this update and take all the necessary data about supergroups from this cache.secret_chat_id
, it never needs to use the getSecretChat
request to receive the secret chat object. Instead it should maintain a cache of secret chats received through this update and take all the necessary data about secret chats from this cache.--For a full list of currently available updates see the documentation for the Update class.
-You can find an example of correct handling of some updates in our Java example.
-
Currently there are 3 different types of chat lists:
- Main chat list.
- Archive chat list.
- A folder chat list.
The positions of chats in chat lists are managed by TDLib, so the Application only needs to listen to updates that change the chat.positions
field, maintain the list of all chats, sorted by the pair (position.order, chat.id)
in descending order, and call getChats
only if there are not enough known chats. Responses to getChats
can be often safely ignored, because if all updates changing chat.positions
are processed correctly, then the chat list should already be up to date. Because chats are sorted in descending order of position.order
, the first request to getChats
should have offset_order == 2^63 - 1 == 9223372036854775807
— the maximum possible value that a signed 64-bit integer can have. For optimal performance, the number of returned chats is chosen by TDLib and can be smaller than the specified limit. If the Application needs more chats, it should repeat the request with adjusted offset_order
and offset_chat_id
parameters.
--You can find an example of retrieving the Main chat list in our Java example.
-
The Application can use the getChatHistory
method to get messages in a chat. The messages will be returned in the reverse chronological order (i.e., in descending order of message_id
). The Application can pass from_message_id == 0
to get messages from the last message. To get more messages than can be returned in one response, the Application needs to pass the identifier of the last message it has received as from_message_id
to next request. For optimal performance, the number of the returned messages is chosen by TDLib and can be smaller than the specified limit. If the Application needs more messages, it needs to adjust the from_message_id
parameter and repeat the request.