mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-11-01 12:47:16 +00:00
doc: document CLI BNF grammar, add DFA figures
Technical details on CLI implementation. Signed-off-by: Quentin Young <qlyoung@cumulusnetworks.com>
This commit is contained in:
parent
79120ae8d6
commit
e53d58537c
@ -66,10 +66,58 @@ parser is implemented in Bison and the lexer in Flex. These may be found in
|
||||
to look. Bison is very stable and if it detects a syntax error, 99% of
|
||||
the time it will be a syntax error in your definition.
|
||||
|
||||
The formal grammar in BNF is given below. This is the grammar implemented in
|
||||
the Bison parser. At runtime, the Bison parser reads all of the CLI strings and
|
||||
builds a combined directed graph that is used to match and interpret user
|
||||
input.
|
||||
|
||||
Human-friendly explanations of how to use this grammar are given a bit later in
|
||||
this section alongside information on the :ref:`cli-data-structures` constructed
|
||||
by the parser.
|
||||
|
||||
.. productionlist::
|
||||
command: `cmd_token_seq`
|
||||
: `cmd_token_seq` `placeholder_token` "..."
|
||||
cmd_token_seq: *empty*
|
||||
: `cmd_token_seq` `cmd_token`
|
||||
cmd_token: `simple_token`
|
||||
: `selector`
|
||||
simple_token: `literal_token`
|
||||
: `placeholder_token`
|
||||
literal_token: WORD `varname_token`
|
||||
varname_token: "$" WORD
|
||||
placeholder_token: `placeholder_token_real` `varname_token`
|
||||
placeholder_token_real: IPV4
|
||||
: IPV4_PREFIX
|
||||
: IPV6
|
||||
: IPV6_PREFIX
|
||||
: VARIABLE
|
||||
: RANGE
|
||||
: MAC
|
||||
: MAC_PREFIX
|
||||
selector: "<" `selector_seq_seq` ">" `varname_token`
|
||||
: "{" `selector_seq_seq` "}" `varname_token`
|
||||
: "[" `selector_seq_seq` "]" `varname_token`
|
||||
selector_seq_seq: `selector_seq_seq` "|" `selector_token_seq`
|
||||
: `selector_token_seq`
|
||||
selector_token_seq: `selector_token_seq` `selector_token`
|
||||
: `selector_token`
|
||||
selector_token: `selector`
|
||||
: `simple_token`
|
||||
|
||||
Tokens
|
||||
~~~~~~
|
||||
The various capitalized tokens in the BNF above are in fact themselves
|
||||
placeholders, but not defined as such in the formal grammar; the grammar
|
||||
provides the structure, and the tokens are actually more like a type system for
|
||||
the strings you write in your CLI definitions. A CLI definition string is
|
||||
broken apart and each piece is assigned a type by the lexer based on a set of
|
||||
regular expressions. The parser uses the type information to verify the string
|
||||
and determine the structure of the CLI graph; additional metadata (such as the
|
||||
raw text of each token) is encoded into the graph as it is constructed by the
|
||||
parser, but this is merely a dumb copy job.
|
||||
|
||||
Each element in a command definition is assigned a type by the parser based on a set of regular expression rules.
|
||||
Here is a brief summary of the various token types along with examples.
|
||||
|
||||
+-----------------+-----------------+-------------------------------------------------------------+
|
||||
| Token type | Syntax | Description |
|
||||
@ -382,6 +430,8 @@ In the examples below, each arrowed token needs a doc string.
|
||||
"command <foo|bar> [example]"
|
||||
^ ^ ^ ^
|
||||
|
||||
.. _cli-data-structures:
|
||||
|
||||
Data Structures
|
||||
---------------
|
||||
|
||||
@ -401,76 +451,65 @@ self-contained 'subgraphs'. Each subgraph is a tree except that all of
|
||||
the 'leaves' actually share a child node. This helps with minimizing
|
||||
graph size and debugging.
|
||||
|
||||
As an example, the subgraph generated by looks like this:
|
||||
As a working example, here is the graph of the following command: ::
|
||||
|
||||
::
|
||||
show [ip] bgp neighbors [<A.B.C.D|X:X::X:X|WORD>] [json]
|
||||
|
||||
.
|
||||
.
|
||||
|
|
||||
+----+---+
|
||||
+--- -+ FORK +----+
|
||||
| +--------+ |
|
||||
+--v---+ +--v---+
|
||||
| foo | | bar |
|
||||
+--+---+ +--+---+
|
||||
| +------+ |
|
||||
+------> JOIN <-----+
|
||||
+---+--+
|
||||
|
|
||||
.
|
||||
.
|
||||
.. figure:: ../figures/cligraph.svg
|
||||
:align: center
|
||||
|
||||
FORK and JOIN nodes are plumbing nodes that don't correspond to user
|
||||
Graph of example CLI command
|
||||
|
||||
|
||||
``FORK`` and ``JOIN`` nodes are plumbing nodes that don't correspond to user
|
||||
input. They're necessary in order to deduplicate these constructs where
|
||||
applicable.
|
||||
|
||||
Options follow the same form, except that there is an edge from the FORK
|
||||
node to the JOIN node.
|
||||
Options follow the same form, except that there is an edge from the ``FORK``
|
||||
node to the ``JOIN`` node. Since all of the subgraphs in the example command
|
||||
are optional, all of them have this edge.
|
||||
|
||||
Keywords follow the same form, except that there is an edge from JOIN to
|
||||
FORK. Because of this the CLI graph cannot be called acyclic. There is
|
||||
special logic in the input matching code that keeps a stack of paths
|
||||
already taken through the node in order to disallow following the same
|
||||
path more than once.
|
||||
Keywords follow the same form, except that there is an edge from ``JOIN`` to
|
||||
``FORK``. Because of this the CLI graph cannot be called acyclic. There is
|
||||
special logic in the input matching code that keeps a stack of paths already
|
||||
taken through the node in order to disallow following the same path more than
|
||||
once.
|
||||
|
||||
Variadics are a bit special; they have an edge back to themselves, which
|
||||
allows repeating the same input indefinitely.
|
||||
Variadics are a bit special; they have an edge back to themselves, which allows
|
||||
repeating the same input indefinitely.
|
||||
|
||||
The leaves of the graph are nodes that have no out edges. These nodes
|
||||
are special; their data section does not contain a token, as most nodes
|
||||
do, or NULL, as in FORK/JOIN nodes, but instead has a pointer to a
|
||||
The leaves of the graph are nodes that have no out edges. These nodes are
|
||||
special; their data section does not contain a token, as most nodes do, or
|
||||
NULL, as in ``FORK``/``JOIN`` nodes, but instead has a pointer to a
|
||||
cmd\_element. All paths through the graph that terminate on a leaf are
|
||||
guaranteed to be defined by that command. When a user enters a complete
|
||||
command, the command matcher tokenizes the input and executes a DFS on
|
||||
the CLI graph. If it is simultaneously able to exhaust all input (one
|
||||
input token per graph node), and then find exactly one leaf connected to
|
||||
the last node it reaches, then the input has matched the corresponding
|
||||
command and the command is executed. If it finds more than one node,
|
||||
then the command is ambiguous (more on this in deduplication). If it
|
||||
cannot exhaust all input, the command is unknown. If it exhausts all
|
||||
input but does not find an edge node, the command is incomplete.
|
||||
command, the command matcher tokenizes the input and executes a DFS on the CLI
|
||||
graph. If it is simultaneously able to exhaust all input (one input token per
|
||||
graph node), and then find exactly one leaf connected to the last node it
|
||||
reaches, then the input has matched the corresponding command and the command
|
||||
is executed. If it finds more than one node, then the command is ambiguous
|
||||
(more on this in deduplication). If it cannot exhaust all input, the command is
|
||||
unknown. If it exhausts all input but does not find an edge node, the command
|
||||
is incomplete.
|
||||
|
||||
The parser uses an incremental strategy to build the CLI graph for a
|
||||
node. Each command is parsed into its own graph, and then this graph is
|
||||
merged into the overall graph. During this merge step, the parser makes
|
||||
a best-effort attempt to remove duplicate nodes. If it finds a node in
|
||||
the overall graph that is equal to a node in the corresponding position
|
||||
in the command graph, it will intelligently merge the properties from
|
||||
the node in the command graph into the already-existing node. Subgraphs
|
||||
are also checked for isomorphism and merged where possible. The
|
||||
definition of whether two nodes are 'equal' is based on the equality of
|
||||
some set of token properties; read the parser source for the most
|
||||
The parser uses an incremental strategy to build the CLI graph for a node. Each
|
||||
command is parsed into its own graph, and then this graph is merged into the
|
||||
overall graph. During this merge step, the parser makes a best-effort attempt
|
||||
to remove duplicate nodes. If it finds a node in the overall graph that is
|
||||
equal to a node in the corresponding position in the command graph, it will
|
||||
intelligently merge the properties from the node in the command graph into the
|
||||
already-existing node. Subgraphs are also checked for isomorphism and merged
|
||||
where possible. The definition of whether two nodes are 'equal' is based on the
|
||||
equality of some set of token properties; read the parser source for the most
|
||||
up-to-date definition of equality.
|
||||
|
||||
When the parser is unable to deduplicate some complicated constructs,
|
||||
this can result in two identical paths through separate parts of the
|
||||
graph. If this occurs and the user enters input that matches these
|
||||
paths, they will receive an 'ambiguous command' error and will be unable
|
||||
to execute the command. Most of the time the parser can detect and warn
|
||||
about duplicate commands, but it will not always be able to do this.
|
||||
Hence care should be taken before defining a new command to ensure it is
|
||||
not defined elsewhere.
|
||||
When the parser is unable to deduplicate some complicated constructs, this can
|
||||
result in two identical paths through separate parts of the graph. If this
|
||||
occurs and the user enters input that matches these paths, they will receive an
|
||||
'ambiguous command' error and will be unable to execute the command. Most of
|
||||
the time the parser can detect and warn about duplicate commands, but it will
|
||||
not always be able to do this. Hence care should be taken before defining a
|
||||
new command to ensure it is not defined elsewhere.
|
||||
|
||||
Command handlers
|
||||
----------------
|
||||
@ -481,7 +520,7 @@ this:
|
||||
|
||||
::
|
||||
|
||||
int (*func) (const struct cmd_element *, struct vty *, int, struct cmd_token *[]);
|
||||
int (*func) (const struct cmd_element *, struct vty *, int, struct cmd_token *[]);
|
||||
|
||||
The first argument is the command definition struct. The last argument
|
||||
is an ordered array of tokens that correspond to the path taken through
|
||||
|
||||
211
doc/figures/cligraph.svg
Normal file
211
doc/figures/cligraph.svg
Normal file
@ -0,0 +1,211 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
|
||||
-->
|
||||
<!-- Title: %3 Pages: 1 -->
|
||||
<svg width="300pt" height="980pt"
|
||||
viewBox="0.00 0.00 299.50 980.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 976)">
|
||||
<title>%3</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-976 295.5,-976 295.5,4 -4,4"/>
|
||||
<!-- n0xd46960 -->
|
||||
<g id="node1" class="node"><title>n0xd46960</title>
|
||||
<polygon fill="#ccffcc" stroke="black" points="158,-972 86,-972 86,-936 158,-936 158,-972"/>
|
||||
<text text-anchor="start" x="94" y="-952.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">START_TKN</text>
|
||||
</g>
|
||||
<!-- n0xd46be0 -->
|
||||
<g id="node2" class="node"><title>n0xd46be0</title>
|
||||
<polygon fill="#ffffff" stroke="black" points="159,-900 85,-900 85,-864 159,-864 159,-900"/>
|
||||
<text text-anchor="start" x="93" y="-885.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">WORD_TKN</text>
|
||||
<text text-anchor="start" x="101.5" y="-875.2" font-family="Fira Mono" font-size="9.00">"</text>
|
||||
<text text-anchor="start" x="105.5" y="-875.2" font-family="Fira Mono" font-weight="bold" font-size="11.00" fill="#0055ff">show</text>
|
||||
<text text-anchor="start" x="138.5" y="-875.2" font-family="Fira Mono" font-size="9.00">"</text>
|
||||
</g>
|
||||
<!-- n0xd46960->n0xd46be0 -->
|
||||
<g id="edge1" class="edge"><title>n0xd46960->n0xd46be0</title>
|
||||
<path fill="none" stroke="black" d="M122,-935.697C122,-927.983 122,-918.712 122,-910.112"/>
|
||||
<polygon fill="black" stroke="black" points="125.5,-910.104 122,-900.104 118.5,-910.104 125.5,-910.104"/>
|
||||
</g>
|
||||
<!-- n0xd47f80 -->
|
||||
<g id="node3" class="node"><title>n0xd47f80</title>
|
||||
<polygon fill="#aaddff" stroke="black" points="156.5,-828 87.5,-828 87.5,-792 156.5,-792 156.5,-828"/>
|
||||
<text text-anchor="start" x="95.5" y="-808.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">FORK_TKN</text>
|
||||
</g>
|
||||
<!-- n0xd46be0->n0xd47f80 -->
|
||||
<g id="edge2" class="edge"><title>n0xd46be0->n0xd47f80</title>
|
||||
<path fill="none" stroke="black" d="M122,-863.697C122,-855.983 122,-846.712 122,-838.112"/>
|
||||
<polygon fill="black" stroke="black" points="125.5,-838.104 122,-828.104 118.5,-838.104 125.5,-838.104"/>
|
||||
</g>
|
||||
<!-- n0xd47c70 -->
|
||||
<g id="node4" class="node"><title>n0xd47c70</title>
|
||||
<polygon fill="#ffffff" stroke="black" points="127,-756 53,-756 53,-720 127,-720 127,-756"/>
|
||||
<text text-anchor="start" x="61" y="-741.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">WORD_TKN</text>
|
||||
<text text-anchor="start" x="80.5" y="-731.2" font-family="Fira Mono" font-size="9.00">"</text>
|
||||
<text text-anchor="start" x="84.5" y="-731.2" font-family="Fira Mono" font-weight="bold" font-size="11.00" fill="#0055ff">ip</text>
|
||||
<text text-anchor="start" x="95.5" y="-731.2" font-family="Fira Mono" font-size="9.00">"</text>
|
||||
</g>
|
||||
<!-- n0xd47f80->n0xd47c70 -->
|
||||
<g id="edge3" class="edge"><title>n0xd47f80->n0xd47c70</title>
|
||||
<path fill="none" stroke="black" d="M114.09,-791.697C110.447,-783.728 106.046,-774.1 102.006,-765.264"/>
|
||||
<polygon fill="black" stroke="black" points="105.16,-763.744 97.8191,-756.104 98.7936,-766.654 105.16,-763.744"/>
|
||||
</g>
|
||||
<!-- n0xd484c0 -->
|
||||
<g id="node5" class="node"><title>n0xd484c0</title>
|
||||
<polygon fill="#ddaaff" stroke="black" points="153.5,-684 90.5,-684 90.5,-648 153.5,-648 153.5,-684"/>
|
||||
<text text-anchor="start" x="98.5" y="-664.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">JOIN_TKN</text>
|
||||
</g>
|
||||
<!-- n0xd47f80->n0xd484c0 -->
|
||||
<g id="edge20" class="edge"><title>n0xd47f80->n0xd484c0</title>
|
||||
<path fill="none" stroke="black" d="M127.824,-791.56C130.931,-781.33 134.431,-768.08 136,-756 138.06,-740.133 138.06,-735.867 136,-720 134.897,-711.506 132.839,-702.434 130.634,-694.24"/>
|
||||
<polygon fill="black" stroke="black" points="133.945,-693.087 127.824,-684.44 127.216,-695.017 133.945,-693.087"/>
|
||||
</g>
|
||||
<!-- n0xd47c70->n0xd484c0 -->
|
||||
<g id="edge4" class="edge"><title>n0xd47c70->n0xd484c0</title>
|
||||
<path fill="none" stroke="black" d="M97.9101,-719.697C101.553,-711.728 105.954,-702.1 109.994,-693.264"/>
|
||||
<polygon fill="black" stroke="black" points="113.206,-694.654 114.181,-684.104 106.84,-691.744 113.206,-694.654"/>
|
||||
</g>
|
||||
<!-- n0xd47ca0 -->
|
||||
<g id="node6" class="node"><title>n0xd47ca0</title>
|
||||
<polygon fill="#ffffff" stroke="black" points="159,-612 85,-612 85,-576 159,-576 159,-612"/>
|
||||
<text text-anchor="start" x="93" y="-597.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">WORD_TKN</text>
|
||||
<text text-anchor="start" x="106.5" y="-587.2" font-family="Fira Mono" font-size="9.00">"</text>
|
||||
<text text-anchor="start" x="110.5" y="-587.2" font-family="Fira Mono" font-weight="bold" font-size="11.00" fill="#0055ff">bgp</text>
|
||||
<text text-anchor="start" x="133.5" y="-587.2" font-family="Fira Mono" font-size="9.00">"</text>
|
||||
</g>
|
||||
<!-- n0xd484c0->n0xd47ca0 -->
|
||||
<g id="edge5" class="edge"><title>n0xd484c0->n0xd47ca0</title>
|
||||
<path fill="none" stroke="black" d="M122,-647.697C122,-639.983 122,-630.712 122,-622.112"/>
|
||||
<polygon fill="black" stroke="black" points="125.5,-622.104 122,-612.104 118.5,-622.104 125.5,-622.104"/>
|
||||
</g>
|
||||
<!-- n0xd48540 -->
|
||||
<g id="node7" class="node"><title>n0xd48540</title>
|
||||
<polygon fill="#ffffff" stroke="black" points="164.5,-540 79.5,-540 79.5,-504 164.5,-504 164.5,-540"/>
|
||||
<text text-anchor="start" x="93" y="-525.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">WORD_TKN</text>
|
||||
<text text-anchor="start" x="87.5" y="-515.2" font-family="Fira Mono" font-size="9.00">"</text>
|
||||
<text text-anchor="start" x="91.5" y="-515.2" font-family="Fira Mono" font-weight="bold" font-size="11.00" fill="#0055ff">neighbors</text>
|
||||
<text text-anchor="start" x="152.5" y="-515.2" font-family="Fira Mono" font-size="9.00">"</text>
|
||||
</g>
|
||||
<!-- n0xd47ca0->n0xd48540 -->
|
||||
<g id="edge6" class="edge"><title>n0xd47ca0->n0xd48540</title>
|
||||
<path fill="none" stroke="black" d="M122,-575.697C122,-567.983 122,-558.712 122,-550.112"/>
|
||||
<polygon fill="black" stroke="black" points="125.5,-550.104 122,-540.104 118.5,-550.104 125.5,-550.104"/>
|
||||
</g>
|
||||
<!-- n0xd490c0 -->
|
||||
<g id="node8" class="node"><title>n0xd490c0</title>
|
||||
<polygon fill="#aaddff" stroke="black" points="156.5,-468 87.5,-468 87.5,-432 156.5,-432 156.5,-468"/>
|
||||
<text text-anchor="start" x="95.5" y="-448.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">FORK_TKN</text>
|
||||
</g>
|
||||
<!-- n0xd48540->n0xd490c0 -->
|
||||
<g id="edge7" class="edge"><title>n0xd48540->n0xd490c0</title>
|
||||
<path fill="none" stroke="black" d="M122,-503.697C122,-495.983 122,-486.712 122,-478.112"/>
|
||||
<polygon fill="black" stroke="black" points="125.5,-478.104 122,-468.104 118.5,-478.104 125.5,-478.104"/>
|
||||
</g>
|
||||
<!-- n0xd48fc0 -->
|
||||
<g id="node9" class="node"><title>n0xd48fc0</title>
|
||||
<polygon fill="#ffffff" stroke="black" points="64,-396 0,-396 0,-360 64,-360 64,-396"/>
|
||||
<text text-anchor="start" x="8" y="-380.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">IPV4_TKN</text>
|
||||
<text text-anchor="start" x="15" y="-371.8" font-family="Fira Mono" font-size="9.00">A.B.C.D</text>
|
||||
</g>
|
||||
<!-- n0xd490c0->n0xd48fc0 -->
|
||||
<g id="edge8" class="edge"><title>n0xd490c0->n0xd48fc0</title>
|
||||
<path fill="none" stroke="black" d="M99.7528,-431.697C88.4181,-422.881 74.4698,-412.032 62.1811,-402.474"/>
|
||||
<polygon fill="black" stroke="black" points="64.0336,-399.481 53.9913,-396.104 59.736,-405.007 64.0336,-399.481"/>
|
||||
</g>
|
||||
<!-- n0xd491e0 -->
|
||||
<g id="node10" class="node"><title>n0xd491e0</title>
|
||||
<polygon fill="#ddaaff" stroke="black" points="153.5,-324 90.5,-324 90.5,-288 153.5,-288 153.5,-324"/>
|
||||
<text text-anchor="start" x="98.5" y="-304.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">JOIN_TKN</text>
|
||||
</g>
|
||||
<!-- n0xd490c0->n0xd491e0 -->
|
||||
<g id="edge19" class="edge"><title>n0xd490c0->n0xd491e0</title>
|
||||
<path fill="none" stroke="black" d="M117.536,-431.953C115.065,-421.63 112.248,-408.153 111,-396 109.366,-380.084 109.366,-375.916 111,-360 111.877,-351.455 113.531,-342.255 115.294,-333.958"/>
|
||||
<polygon fill="black" stroke="black" points="118.743,-334.573 117.536,-324.047 111.915,-333.028 118.743,-334.573"/>
|
||||
</g>
|
||||
<!-- n0xd49340 -->
|
||||
<g id="node15" class="node"><title>n0xd49340</title>
|
||||
<polygon fill="#ffffff" stroke="black" points="184,-396 120,-396 120,-360 184,-360 184,-396"/>
|
||||
<text text-anchor="start" x="128" y="-380.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">IPV6_TKN</text>
|
||||
<text text-anchor="start" x="135" y="-371.8" font-family="Fira Mono" font-size="9.00">X:X::X:X</text>
|
||||
</g>
|
||||
<!-- n0xd490c0->n0xd49340 -->
|
||||
<g id="edge15" class="edge"><title>n0xd490c0->n0xd49340</title>
|
||||
<path fill="none" stroke="black" d="M129.416,-431.697C132.794,-423.813 136.87,-414.304 140.623,-405.546"/>
|
||||
<polygon fill="black" stroke="black" points="143.947,-406.675 144.67,-396.104 137.513,-403.917 143.947,-406.675"/>
|
||||
</g>
|
||||
<!-- n0xd49480 -->
|
||||
<g id="node16" class="node"><title>n0xd49480</title>
|
||||
<polygon fill="#ffffff" stroke="black" points="291.5,-396 202.5,-396 202.5,-360 291.5,-360 291.5,-396"/>
|
||||
<text text-anchor="start" x="210.5" y="-380.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">VARIABLE_TKN</text>
|
||||
<text text-anchor="start" x="233" y="-371.8" font-family="Fira Mono" font-size="9.00">WORD</text>
|
||||
</g>
|
||||
<!-- n0xd490c0->n0xd49480 -->
|
||||
<g id="edge17" class="edge"><title>n0xd490c0->n0xd49480</title>
|
||||
<path fill="none" stroke="black" d="M152.578,-431.876C169.074,-422.639 189.624,-411.131 207.336,-401.212"/>
|
||||
<polygon fill="black" stroke="black" points="209.289,-404.13 216.304,-396.19 205.869,-398.022 209.289,-404.13"/>
|
||||
</g>
|
||||
<!-- n0xd48fc0->n0xd491e0 -->
|
||||
<g id="edge9" class="edge"><title>n0xd48fc0->n0xd491e0</title>
|
||||
<path fill="none" stroke="black" d="M54.2472,-359.697C65.5819,-350.881 79.5302,-340.032 91.8189,-330.474"/>
|
||||
<polygon fill="black" stroke="black" points="94.264,-333.007 100.009,-324.104 89.9664,-327.481 94.264,-333.007"/>
|
||||
</g>
|
||||
<!-- n0xd496e0 -->
|
||||
<g id="node11" class="node"><title>n0xd496e0</title>
|
||||
<polygon fill="#aaddff" stroke="black" points="156.5,-252 87.5,-252 87.5,-216 156.5,-216 156.5,-252"/>
|
||||
<text text-anchor="start" x="95.5" y="-232.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">FORK_TKN</text>
|
||||
</g>
|
||||
<!-- n0xd491e0->n0xd496e0 -->
|
||||
<g id="edge10" class="edge"><title>n0xd491e0->n0xd496e0</title>
|
||||
<path fill="none" stroke="black" d="M122,-287.697C122,-279.983 122,-270.712 122,-262.112"/>
|
||||
<polygon fill="black" stroke="black" points="125.5,-262.104 122,-252.104 118.5,-262.104 125.5,-262.104"/>
|
||||
</g>
|
||||
<!-- n0xd495e0 -->
|
||||
<g id="node12" class="node"><title>n0xd495e0</title>
|
||||
<polygon fill="#ffffff" stroke="black" points="127,-180 53,-180 53,-144 127,-144 127,-180"/>
|
||||
<text text-anchor="start" x="61" y="-165.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">WORD_TKN</text>
|
||||
<text text-anchor="start" x="73.5" y="-155.2" font-family="Fira Mono" font-size="9.00">"</text>
|
||||
<text text-anchor="start" x="77.5" y="-155.2" font-family="Fira Mono" font-weight="bold" font-size="11.00" fill="#0055ff">json</text>
|
||||
<text text-anchor="start" x="102.5" y="-155.2" font-family="Fira Mono" font-size="9.00">"</text>
|
||||
</g>
|
||||
<!-- n0xd496e0->n0xd495e0 -->
|
||||
<g id="edge11" class="edge"><title>n0xd496e0->n0xd495e0</title>
|
||||
<path fill="none" stroke="black" d="M114.09,-215.697C110.447,-207.728 106.046,-198.1 102.006,-189.264"/>
|
||||
<polygon fill="black" stroke="black" points="105.16,-187.744 97.8191,-180.104 98.7936,-190.654 105.16,-187.744"/>
|
||||
</g>
|
||||
<!-- n0xd497c0 -->
|
||||
<g id="node13" class="node"><title>n0xd497c0</title>
|
||||
<polygon fill="#ddaaff" stroke="black" points="153.5,-108 90.5,-108 90.5,-72 153.5,-72 153.5,-108"/>
|
||||
<text text-anchor="start" x="98.5" y="-88.8" font-family="Fira Mono" font-weight="bold" font-size="9.00">JOIN_TKN</text>
|
||||
</g>
|
||||
<!-- n0xd496e0->n0xd497c0 -->
|
||||
<g id="edge14" class="edge"><title>n0xd496e0->n0xd497c0</title>
|
||||
<path fill="none" stroke="black" d="M127.824,-215.56C130.931,-205.33 134.431,-192.08 136,-180 138.06,-164.133 138.06,-159.867 136,-144 134.897,-135.506 132.839,-126.434 130.634,-118.24"/>
|
||||
<polygon fill="black" stroke="black" points="133.945,-117.087 127.824,-108.44 127.216,-119.017 133.945,-117.087"/>
|
||||
</g>
|
||||
<!-- n0xd495e0->n0xd497c0 -->
|
||||
<g id="edge12" class="edge"><title>n0xd495e0->n0xd497c0</title>
|
||||
<path fill="none" stroke="black" d="M97.9101,-143.697C101.553,-135.728 105.954,-126.1 109.994,-117.264"/>
|
||||
<polygon fill="black" stroke="black" points="113.206,-118.654 114.181,-108.104 106.84,-115.744 113.206,-118.654"/>
|
||||
</g>
|
||||
<!-- end0xd49900 -->
|
||||
<g id="node14" class="node"><title>end0xd49900</title>
|
||||
<polygon fill="#ffddaa" stroke="black" points="149,-36 95,-36 95,-0 149,-0 149,-36"/>
|
||||
<text text-anchor="start" x="112.5" y="-15.8" font-family="Fira Mono" font-size="9.00">end</text>
|
||||
</g>
|
||||
<!-- n0xd497c0->end0xd49900 -->
|
||||
<g id="edge13" class="edge"><title>n0xd497c0->end0xd49900</title>
|
||||
<path fill="none" stroke="black" d="M122,-71.6966C122,-63.9827 122,-54.7125 122,-46.1124"/>
|
||||
<polygon fill="black" stroke="black" points="125.5,-46.1043 122,-36.1043 118.5,-46.1044 125.5,-46.1043"/>
|
||||
</g>
|
||||
<!-- n0xd49340->n0xd491e0 -->
|
||||
<g id="edge16" class="edge"><title>n0xd49340->n0xd491e0</title>
|
||||
<path fill="none" stroke="black" d="M144.584,-359.697C141.206,-351.813 137.13,-342.304 133.377,-333.546"/>
|
||||
<polygon fill="black" stroke="black" points="136.487,-331.917 129.33,-324.104 130.053,-334.675 136.487,-331.917"/>
|
||||
</g>
|
||||
<!-- n0xd49480->n0xd491e0 -->
|
||||
<g id="edge18" class="edge"><title>n0xd49480->n0xd491e0</title>
|
||||
<path fill="none" stroke="black" d="M216.422,-359.876C199.926,-350.639 179.376,-339.131 161.664,-329.212"/>
|
||||
<polygon fill="black" stroke="black" points="163.131,-326.022 152.696,-324.19 159.711,-332.13 163.131,-326.022"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
Loading…
Reference in New Issue
Block a user