lib: Finish implementing grammar, DFA construction

Signed-off-by: Quentin Young <qlyoung@cumulusnetworks.com>
This commit is contained in:
Quentin Young 2016-07-17 21:49:16 +00:00
parent 071b077cad
commit 478bdaeb3b
3 changed files with 178 additions and 129 deletions

View File

@ -20,13 +20,24 @@ struct graph_node
{ {
enum graph_node_type type; enum graph_node_type type;
vector children; vector children;
int is_leaf, is_root; int is_root; // true if first token in command
int is_leaf; // true if last token in command
int (*func)(struct vty *, int, const char *[]); int (*func)(struct vty *, int, const char *[]);
/* various data fields for nodes */
char* text; // for words and variables
int value; // for numbers
int start, end; // for ranges
}; };
/* /*
* Adds a child to a node. If the node already has the exact same * Adds a child to a node. If the node already has the exact same
* child, nothing is done. * child, nothing is done.
* @param[in] parent node
* @param[in] child node
* @return the new child, or the existing child if the parent already has the
* new child
*/ */
extern struct graph_node * extern struct graph_node *
add_node(struct graph_node *, struct graph_node *); add_node(struct graph_node *, struct graph_node *);

View File

@ -3,12 +3,14 @@
extern int yylex(void); extern int yylex(void);
extern void yyerror(const char *); extern void yyerror(const char *);
extern int cmd_parse_format(const char *, const char *);
extern void set_buffer_string(const char *);
// compile with debugging facilities // compile with debugging facilities
#define YYDEBUG 1 #define YYDEBUG 1
%} %}
%code provides {
extern struct graph_node *cmd_parse_format_new(const char *, const char *);
extern void set_buffer_string(const char *);
}
%union{ %union{
int integer; int integer;
@ -18,37 +20,39 @@ extern void set_buffer_string(const char *);
%{ %{
// last top-level node // last top-level node
struct graph_node *topnode, // command root node struct graph_node *startnode, // command root node
*currnode; // current node *currnode, // current node
*tmpnode, // temp node pointer
*seqhead; // sequence head
struct graph_node *optnode_start, // start node for option set
*optnode_end, // end node for option set
*optnode_el; // start node for an option set element
struct graph_node *selnode_start, // start node for selector set struct graph_node *optnode_start = NULL, // start node for option set
*selnode_end, // end node for selector set *optnode_end = NULL; // end node for option set
*selnode_el; // start node for an selector set element
struct graph_node *selnode_start = NULL, // start node for selector set
*selnode_end = NULL; // end node for selector set
%} %}
%token <string> WORD %token <node> WORD
%token <string> IPV4 %token <node> IPV4
%token <string> IPV4_PREFIX %token <node> IPV4_PREFIX
%token <string> IPV6 %token <node> IPV6
%token <string> IPV6_PREFIX %token <node> IPV6_PREFIX
%token <string> VARIABLE %token <node> VARIABLE
%token <string> RANGE %token <node> RANGE
%token <integer> NUMBER %token <node> NUMBER
%type <node> start %type <node> start
%type <node> sentence_root %type <node> sentence_root
%type <node> literal_token %type <node> literal_token
%type <node> placeholder_token %type <node> placeholder_token
%type <node> option_token
%type <node> selector_token
%type <node> option %type <node> option
%type <node> selector %type <node> option_token
%type <node> selector_token_seq
%type <node> option_token_seq %type <node> option_token_seq
%type <node> selector
%type <node> selector_root
%type <node> selector_token
%type <node> selector_token_seq
%defines "command_parse.h" %defines "command_parse.h"
%output "command_parse.c" %output "command_parse.c"
@ -60,28 +64,31 @@ start: sentence_root
cmd_token_seq; cmd_token_seq;
sentence_root: WORD sentence_root: WORD
{ {
currnode = new_node(WORD_GN); currnode = new_node(WORD_GN);
currnode->is_root = 1; currnode->is_root = 1;
add_node(topnode, currnode); add_node(startnode, currnode);
}; };
/* valid top level tokens */ /* valid top level tokens */
cmd_token: cmd_token:
placeholder_token placeholder_token
{ currnode = add_node(currnode, $1); } { currnode = add_node(currnode, $1); }
| literal_token | literal_token
{ currnode = add_node(currnode, $1); } { currnode = add_node(currnode, $1); }
/* selectors and options are subgraphs with start and end nodes */
| selector | selector
{ {
add_node(currnode, selnode_start); add_node(currnode, $1);
currnode = selnode_end; currnode = selnode_end;
} selnode_start = selnode_end = NULL;
}
| option | option
{ {
add_node(currnode, optnode_start); add_node(currnode, $1);
currnode = optnode_end; currnode = optnode_end;
} optnode_start = optnode_end = NULL;
}
; ;
cmd_token_seq: cmd_token_seq:
@ -90,26 +97,52 @@ cmd_token_seq:
; ;
placeholder_token: placeholder_token:
IPV4 {$$ = new_node(IPV4_GN);} IPV4
| IPV4_PREFIX {$$ = new_node(IPV4_PREFIX_GN);} { $$ = new_node(IPV4_GN); }
| IPV6 {$$ = new_node(IPV6_GN);} | IPV4_PREFIX
| IPV6_PREFIX {$$ = new_node(IPV6_PREFIX_GN);} { $$ = new_node(IPV4_PREFIX_GN); }
| VARIABLE {$$ = new_node(VARIABLE_GN);} | IPV6
| RANGE {$$ = new_node(RANGE_GN);} { $$ = new_node(IPV6_GN); }
| IPV6_PREFIX
{ $$ = new_node(IPV6_PREFIX_GN); }
| VARIABLE
{ $$ = new_node(VARIABLE_GN); }
| RANGE
{
$$ = new_node(RANGE_GN);
// get the numbers out
strsep(&yylval.string, "(-)");
$$->start = atoi( strsep(&yylval.string, "(-)") );
strsep(&yylval.string, "(-)");
$$->end = atoi( strsep(&yylval.string, "(-)") );
// we could do this a variety of ways with either
// the lexer or the parser, but this is the simplest
// and involves the least amount of free()
}
; ;
literal_token: literal_token:
WORD {$$ = new_node(WORD_GN);} WORD
| NUMBER {$$ = new_node(NUMBER_GN);} {
$$ = new_node(WORD_GN);
$$->text = strdup(yylval.string);
}
| NUMBER
{
$$ = new_node(NUMBER_GN);
$$->value = yylval.integer;
}
; ;
/* <selector|token> productions */ /* <selector|token> productions */
selector: selector:
'<' selector_part '|' '<' selector_part '|' selector_element '>'
selector_element '>'
{ {
$$ = new_node(SELECTOR_GN); // all the graph building is done in selector_element,
// attach subtree here // so just return the selector subgraph head
$$ = selnode_start;
}; };
selector_part: selector_part:
@ -118,90 +151,115 @@ selector_part:
; ;
selector_element: selector_element:
WORD selector_token_seq; selector_root selector_token_seq
{
// if the selector start and end do not exist, create them
if (!selnode_start || !selnode_end) { // if one is null
assert(!selnode_start && !selnode_end); // both should be null
selnode_start = new_node(SELECTOR_GN); // diverging node
selnode_end = new_node(NUL_GN); // converging node
}
// add element head as a child of the selector
add_node(selnode_start, $1);
if ($2->type != NUL_GN) {
add_node($1, seqhead);
add_node($2, selnode_end);
}
else
add_node($1, selnode_end);
seqhead = NULL;
}
selector_token_seq: selector_token_seq:
%empty {$$ = NULL;} %empty { $$ = new_node(NUL_GN); }
| selector_token_seq selector_token | selector_token_seq selector_token
{ {
currnode = add_node(currnode, $2); // if the sequence component is NUL_GN, this is a sequence start
if ($1->type == NUL_GN) {
assert(!seqhead); // sequence head should always be null here
seqhead = $2;
}
else // chain on new node
add_node($1, $2);
$$ = $2;
} }
; ;
selector_token: selector_root:
literal_token literal_token
| placeholder_token | placeholder_token
;
selector_token:
selector_root
| option | option
; ;
/* [option|set] productions */ /* [option|set] productions */
option: '[' option_part ']' option: '[' option_part ']'
{ { $$ = optnode_start; };
$$ = new_node(OPTION_GN);
// attach subtree here
};
option_part: option_part:
option_part '|' option_token_seq option_part '|' option_element
| option_token_seq | option_element
; ;
option_token_seq: option_element:
option_token_seq option_token option_token_seq
| option_token
{ {
printf("Matched singular option token in sequence, type: %d\n", $1->type); if (!optnode_start || !optnode_end) {
assert(!optnode_start && !optnode_end);
optnode_start = new_node(OPTION_GN);
optnode_end = new_node(NUL_GN);
}
add_node(optnode_start, seqhead);
add_node($1, optnode_end);
} }
option_token_seq:
option_token
{ $$ = seqhead = $1; }
| option_token_seq option_token
{ $$ = add_node($1, $2); }
; ;
option_token: option_token:
literal_token literal_token
{
// optnode_el points to root of option element
if (optnode_el == NULL) {
optnode_el = $1;
currnode = $1;
}
else
add_node(currnode, $1);
}
| placeholder_token | placeholder_token
{
// optnode_el points to root of option element
if (optnode_el == NULL) {
optnode_el = $1;
currnode = $1;
}
else
add_node(currnode, $1);
}
; ;
%% %%
/*
int int
main (void) main (void)
{ {
yydebug = 1;
const char* input = "show [random conf NAME] thing"; const char* input = "show [random conf NAME] thing";
printf("Parsing:\n\t%s\n", input); printf("Parsing:\n\t%s\n", input);
return cmd_parse_format(input, "description"); return cmd_parse_format_new(input, "description");
} }
*/
void yyerror(char const *message) { void yyerror(char const *message) {
printf("Grammar error: %s\n", message); printf("Grammar error: %s\n", message);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
int struct graph_node *
cmd_parse_format(const char *string, const char *desc) cmd_parse_format_new(const char *string, const char *desc)
{ {
fprintf(stderr, "parsing: %s\n", string);
yydebug = 1;
// make flex read from a string // make flex read from a string
set_buffer_string(string); set_buffer_string(string);
// initialize the start node of this command dfa // initialize the start node of this command dfa
topnode = new_node(NUL_GN); startnode = new_node(NUL_GN);
// parse command into DFA // parse command into DFA
yyparse(); yyparse();
// topnode points to command DFA // startnode points to command DFA
return 0; return startnode;
} }

View File

@ -1,53 +1,33 @@
#include "command.h" #include "command.h"
#include "command_parse.h"
#define GRAMMAR_STR "CLI grammar sandbox\n" #define GRAMMAR_STR "CLI grammar sandbox\n"
DEFUN (grammar_midkey_test, DEFUN (grammar_test,
grammar_midkey_test_cmd, grammar_test_cmd,
"grammar {one|two} test", "grammar .COMMAND",
GRAMMAR_STR GRAMMAR_STR
"First option\n" "command to pass to new parser\n")
"Second option\n"
"Test parameter to end string\n")
{ {
return CMD_SUCCESS; size_t linesize = 0;
} for (int i = 0; i < argc; i++)
linesize += strlen(argv[i]) + 1;
DEFUN (grammar_onemidkey_test, char* cat = malloc(linesize);
grammar_onemidkey_test_cmd, cat[0] = '\0';
"grammar {onekey} test", for (int i = 0; i < argc; i++) {
GRAMMAR_STR strcat(cat, argv[i]);
"First option\n" if (i != argc)
"Test parameter to end string\n") strcat(cat, " ");
{ }
return CMD_SUCCESS;
}
DEFUN (grammar_smashmouth_test, cmd_parse_format_new((const char*) cat, "lol");
grammar_smashmouth_test_cmd,
"grammar {smash MOUTH} test",
GRAMMAR_STR
"It ain't easy bein' cheesy\n"
"Test parameter to end string\n")
{
return CMD_SUCCESS;
}
DEFUN (grammar_midopt_test, return CMD_SUCCESS;
grammar_midopt_test_cmd,
"grammar [option] test",
GRAMMAR_STR
"optional argument\n"
"Test parameter to end string\n")
{
return CMD_SUCCESS;
} }
void grammar_sandbox_init(void); void grammar_sandbox_init(void);
void grammar_sandbox_init() { void grammar_sandbox_init() {
install_element (ENABLE_NODE, &grammar_midkey_test_cmd); install_element (ENABLE_NODE, &grammar_test_cmd);
install_element (ENABLE_NODE, &grammar_onemidkey_test_cmd);
install_element (ENABLE_NODE, &grammar_midopt_test_cmd);
install_element (ENABLE_NODE, &grammar_smashmouth_test_cmd);
} }