Introduction

vProto - is an advanced description of regular expressions, allowing for the specification of a Finite State Machine, including states, behaviors, and transition conditions. This description serves as the basis for generating C++ code, tailored for data parsing purposes.

State Machine descriptions are done using tokens. Tokens written on a single line denote sequential traversal (once one is completed, it moves to the next). Branching occurs when transitioning to a new line with an increase in depth (increase in indentation). Lines at the same depth correspond to branches. At the end of a line, there may be the presence of the symbol \ which symbolizes continuation of the line (including possible branching), and it will be used during transition in case branching is terminated.

Example:
    token_1_1 token_1_2 token_1_3 \
        token_2_1 token_2_2
        token_3_1 token_3_2 \
            token_4_1 token_4_2
            token_5_1 token_5_2
        token_6_1 token_6_2
    token_7_1 token_7_2 token_7_3
        token_8_1 token_8_2
        token_9_1 token_9_2

The token's name begins with the line number, followed by the token number within the line. Essentially, at the beginning of any description, the first token represents branching. In the example, this corresponds to either token_1_1 or token_7_1 (because they are at the same depth or indentation level). Let's assume a transition to token_1_1; then token_1_2 and token_1_3 will be executed sequentially, one after the other, since they are on the same line. After token_1_3, branching occurs to token_2_1, token_3_1, and token_6_1 because they are at the same depth. Let's say a transition occurs to token_3_1; then after its successful completion, there will be a transition to token_3_2, and then branching occurs again between token_4_1 and token_5_1 (as they are at the same depth). Assuming a transition to token_4_1, then next will be token_4_2, which marks the end of the branch. After token_4_2, a return back occurs. The return back continues until a \ symbol is found at the end of the branch. The presence of this symbol on line 3 indicates that the transition will be made back to line 1, and the presence of \ on line 1 returns to the beginning. So, upon completion of token_4_2, the transition will be to the beginning, to the branching of token_1_1 and token_7_1. Another behavior occurs upon completion of token_8_2 or token_9_2; for them, branching occurs on line 7, where \ is absent. This means that upon completion of lines 8 or 9, the transition will be made to the branching in line 7, i.e., between token_8_1 and token_9_1, creating a cyclic transition through branching.

The successful passage of a token (and consequently, transition to the next) depends on the incoming data. Sometimes tokens can be optional or have certain requirements for their repetition. It's possible to modify token traversal properties by adding additional options using parentheses, for example: (min=5, max=10, init=100).

Shorter forms can also be used: ? (equivalent to min=0, max=1), * (equivalent to min=0, max=infinity), + (equivalent to min=1, max=infinity).

If incoming characters do not match the token, an exception will occur (preventing a transition to the next token), which can be caught; otherwise, parsing will exit. To catch the exception, you need to add a branch with 'catch:' specified. If a '\' character exists in the branch, it will be analyzed as part of the parent branch catch.

The demonstration of using examples can be found at the following URL

Range [a-zA-Z\x20\r\n]

Describes the range of permissible values for the incoming byte. The expected format is equivalent to standard range in regular expressions. This token supports:

Also, this token supports redirection to a variable, meaning that while we are within this token, all data will be redirected to the specified variable (equivalent min=1, max=infinity). It is possible to use modifiers (string/uint/hex/bool/bin(binLe)/binBe) that will affect the variable initialization and assignment.

Variables u8-u64, h8-h64, b8-h64, string, bool, enum

As mentioned earlier, when initializing variables, the main type is a range, which can describe all variables. However, there are also shorter forms of variable notation:

If you do not specify the ':', redirection to the variable will not occur, but the data will be ignored, equivalent to range without redirection.
Example of TLV parsing: b8:type b32:length data:value(max=length)
Example of UDP header parsing: bBE16:srcPort bBE16:dstPort bBE16:dataLength bBE16:checksum data:udpPayload(max=dataLength)

"casesensitive text" or 'no-casesensitive'

"casesensitive" or 'no-casesensitive' - describes constant words or binary values. For example, in the HTTP protocol, words like "GET" or "HTTP/1.1" are case-sensitive, while headers like 'Content-Length' or 'Content-type' can be written using uppercase or lowercase characters. You can also describe the binary value of a character, like in ranges.

{ c++ user code } notify:userFunction

{ c++ code } - calling user's C++ code, this token doesn't use incoming bytes but can be used for modifying variables, invoking callbacks, determining branching conditions, or during debugging stages. This code is called as a function, and inside it, the user can "return false" (by default, it automatically returns true) - indicating successful or unsuccessful token processing. Examples:
b8:type { printf("Read Type: %u ", type); }
    { return type == 1; } { userFunction1(); }
    { return type == 2; } { userFunction2(); }
    { return type == 3; } { userFunction3(); }
C++ insertion is used for debugging\printf function (automatically setting successful token processing return true), as well as for branching: the "return type == ..." returns true or false, thereby selecting a branch transition.

If a user wishes to add their own functions or repositories, they need to define the "OutputClassName". The parser will inherit from this class and access its variables and functions. It is recommended to inherit OutputClassName from the demoResult state structure.

notify:userFunction - essentially replaces the C++ insert { userFunction(); }, but shorter and more efficient in terms of performance.
Example TLV data struct: b8:type b32:length string:value(max=length) notify:gotValue

begin:userFunction end:userFunction

begin:userFunction end:userFunction - transfers to the user all data between the beginning and the end.
Example(will return to the user all the data that was used in the tokens between begin and end):
"GET" [ \t]+ begin:userFunction [^ \t]+ end:userFunction [ \t]+ "HTTP/" [0-9] "." [0-9] "\r"? "\n"

label:name, jmp:name, call:name, return

This tokens are used for transitions within the state tree:

Example:
call:readRequestType call:readUrl call:readHeaders

label:readRequestType
    "GET" return
    "POST" return

label:readUrl [ \t]+ [^ \t]->string:url [ \t]+ "HTTP/" [0-9] "." [0-9] "\r"? "\n" return

label:readHeaders ...

reset, break, bang, back:depthNumber

Example:
'A' // depth == 0
    'B' // depth == 1
        'C' back:1 // depth == 2, back:1 go to depth == 1, between 'B' and 'D'.
    'D' // depth == 1
        'E' back:0 // depth == 2, back:0 go to depth == 0, between 'A' and 'F'.
'F' // depth == 0

<tokensCase1, tokensCase2, tokensCase3>

<listTokensCase1, listTokensCase2,..> - this token allows creating nested branching variations within itself, describing a sequence of regular tokens and comma-separated alternative options. Its presence changes the logic of the entire state machine by creating parallel states, enabling simultaneous processing across multiple states. Completion of any sequence leads to the termination of other parallel states, essentially, the 'bang' token is automatically set after this token. This token significantly simplifies understanding of the finite state machine, but working in parallel states is always slower than working in a single state. It is recommended to minimize the description of variations in this token so that options are discarded as quickly as possible.
Example:
<"GET", "POST", "PUT"> - All three states are processed in parallel until one of them wins. If the byte G is received, then only the GET branch has a chance of passing; POST and PUT automatically fail. But if the byte P is received, then we will be simultaneously in the POST and PUT states, waiting for other symbols to determine the state.
Example (parallel states):
"GET" [ \t]+ \
    <'url-1'> [ \t]+
    <'url-2'> [ \t]+
    <'url-3'> [ \t]+
All three states url-1, url-2, url-3 are considered in parallel as data arrives (which can come byte by byte). Decisions are made about which states to exclude gradually.
Example (no parallel states):
"GET" [ \t]+ \
    'url-1' [ \t]+
    'url-2' [ \t]+
    'url-3' [ \t]+
Without using <>, branching will not be considered as parallel states, and transitions to url-2 and url-3 will not be possible because upon the arrival of the symbol 'u', the transition to the first, more prioritized branch url-1 (because it first) will be chosen, and if, for example, a url-2 comes next, the transition to url-2 will not occur. An exception will occur at the utl-1 branch.

optimization

Recommendations for achieving maximum performance:

Author: Shchekoldin Sergey (Щеколдин Сергей)
shchekoldin@gmail.com