-
Notifications
You must be signed in to change notification settings - Fork 3
Cli Architecture
+------------+ +--------------+ +------+ +----------+ | | | Readline | | LiSA | | LiSA | | CLI Parser |<-->| CLI |<-->| Menu |<-->| Command | | | | Abstraction | | Tree | | Handlers | +------------+ +--------------+ +------+ +----------+ ^ | | ^ | '..............+.............' '..................................'
CLI Parser - API for parsing Cisco-like commands; provides basic tokenizing functions and validation against menu tree structures.
Readline CLI abstraction - Integrates the CLI Parser with readline library, providing Cisco-like CLI behavior.
LiSA Menu Tree - Data structures for all LiSA CLI commands.
LiSA Command Handlers - Functions that actually execute the CLI commands.
(rest of) menu node array command to to lookup subnodes in parse (context) | | | const char *buf | struct menu_node *tree | | \|/ \|/ +-----------------------------------------------------------+ | | | Tokenizer Function | | | +-----------------------------------------------------------+ | | | | (struct | int | int | int | menu_node *) | <retval> | out->offset | out->len | out->matches | | | | \|/ \|/ \|/ \|/ status token token matching offset length subnodes array
The tokenizing function is initially called with the whole command as input and the root menu tree node as context. As it extracts the first token from the input, it is iteratively called on the remaindering input (buffer). For each extracted token, the context (subnodes list) descends in the menu tree.
The tokenizing function skips initial whitespace and returns the offset of the actual token within the given buffer, and the token length. Therefore, it is appropriate to increase the buffer by the sum of out→offset and out→len prior to the next call.
In the following table, size means the number of (non-null) components in the matching subnodes array (by convention, the list ends at the first null component) and status is the return value of the tokenizer.
Command Output on Cisco CLI size status ----------------------------------------------------------------- "is a?" access-group address 2 0 "is a ?" % Ambiguous command: "ip a " 2 1 "ip addr 192.168?" A.B.C.D 1 0 "ip addr 192.168 ?" % Unrecognized command 0 1 "sh ver?" version 1 0 "sh ver ?" <cr> 1 1 "sh jjj" % Unrecognized command 0 0 "sh jjj " % Unrecognized command 0 1
For size, there are 3 possible cases:
-
0 No match (syntax error)
-
1 Correct or unfinished command
-
larger than 1 Ambiguity
For status, there are only 2 possible cases:
-
0 Buffer ends with this token and there is nothing more to parse
-
1 There is more input to parse
The actual case is determined by both size and status. The logic to iteratively call the tokenizer and determine the case is implemented at the next layer (CLI Abstraction).
Parsing patterns (such as ip addresses in the example above) is accomplished through custom tokenizing functions (that do more than just looking up the token in the subnodes array).
There are 2 more possible conditions:
Condition size status ----------------------------------------------------------------- Empty buffer (or only whitespace in buffer) 0 0 Trailing whitespace at end of buffer 0 0
Distinguishing between the two conditions is done in the next layer logic, by counting the tokens: the first happens when there is no previous token, the second happens when there is at least one previous token.
The second condition happens because the tokenizing function skips whitespace at the beginning of the buffer and stops at the first whitespace character after the token. Therefore, a subsequent call to the function will happen after getting the last token.
To distinguish between a non-existing command (such as "sh jjj") and the two whitespace condition (both return size = 0, status = 0), the next layer must look at out→len, which is 0 for whitespace and non-zero for erroneous commands.