]>
Commit | Line | Data |
---|---|---|
9d0662e0 | 1 | /* |
1ab84bf3 | 2 | * Command format string parser for CLI backend. |
9d0662e0 | 3 | * |
1ab84bf3 QY |
4 | * -- |
5 | * Copyright (C) 2015 Cumulus Networks, Inc. | |
9d0662e0 | 6 | * |
1ab84bf3 QY |
7 | * This file is part of GNU Zebra. |
8 | * | |
9 | * GNU Zebra is free software; you can redistribute it and/or modify it | |
10 | * under the terms of the GNU General Public License as published by the | |
11 | * Free Software Foundation; either version 2, or (at your option) any | |
12 | * later version. | |
13 | * | |
14 | * GNU Zebra is distributed in the hope that it will be useful, but | |
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | * General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with GNU Zebra; see the file COPYING. If not, write to the Free | |
21 | * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | |
22 | * 02111-1307, USA. | |
9d0662e0 QY |
23 | */ |
24 | ||
92055a92 | 25 | %{ |
1ab84bf3 QY |
26 | // compile with debugging facilities |
27 | #define YYDEBUG 1 | |
51fc9379 | 28 | %} |
782d9789 | 29 | |
51fc9379 QY |
30 | /* names for generated header and parser files */ |
31 | %defines "command_parse.h" | |
32 | %output "command_parse.c" | |
5a8bbed0 | 33 | |
51fc9379 | 34 | /* required external units */ |
eceb1066 QY |
35 | %code requires { |
36 | #include "command.h" | |
7a6ded40 | 37 | #include "graph.h" |
5a5d576b | 38 | #include "memory.h" |
1eb5e8dc | 39 | #include "grammar_sandbox.h" |
51fc9379 QY |
40 | |
41 | extern int | |
1ab84bf3 | 42 | yylex (void); |
51fc9379 | 43 | |
eceb1066 | 44 | extern void |
4427e9b3 QY |
45 | set_lexer_string (const char *); |
46 | ||
47 | extern void | |
48 | cleanup_lexer (void); | |
51fc9379 QY |
49 | } |
50 | ||
51 | /* functionality this unit exports */ | |
52 | %code provides { | |
1eb5e8dc | 53 | void |
7a6ded40 | 54 | command_parse_format (struct graph *, struct cmd_element *); |
92055a92 | 55 | |
51fc9379 QY |
56 | /* maximum length of a number, lexer will not match anything longer */ |
57 | #define DECIMAL_STRLEN_MAX 20 | |
58 | } | |
eceb1066 | 59 | |
51fc9379 QY |
60 | /* valid semantic types for tokens and rules */ |
61 | %union { | |
62 | long long number; | |
92055a92 | 63 | char *string; |
782d9789 | 64 | struct graph_node *node; |
92055a92 QY |
65 | } |
66 | ||
51fc9379 | 67 | /* union types for lexed tokens */ |
5a5d576b QY |
68 | %token <string> WORD |
69 | %token <string> IPV4 | |
70 | %token <string> IPV4_PREFIX | |
71 | %token <string> IPV6 | |
72 | %token <string> IPV6_PREFIX | |
73 | %token <string> VARIABLE | |
74 | %token <string> RANGE | |
51fc9379 | 75 | %token <number> NUMBER |
782d9789 | 76 | |
51fc9379 | 77 | /* union types for parsed rules */ |
782d9789 QY |
78 | %type <node> start |
79 | %type <node> sentence_root | |
782d9789 QY |
80 | %type <node> literal_token |
81 | %type <node> placeholder_token | |
782d9789 | 82 | %type <node> option |
478bdaeb QY |
83 | %type <node> option_token |
84 | %type <node> option_token_seq | |
782d9789 | 85 | %type <node> selector |
340a2b4a | 86 | %type <node> selector_element_root |
478bdaeb | 87 | %type <node> selector_token |
782d9789 | 88 | %type <node> selector_token_seq |
782d9789 | 89 | |
51fc9379 QY |
90 | %code { |
91 | /* bison declarations */ | |
92 | void | |
1eb5e8dc | 93 | yyerror (struct graph *, struct cmd_element *el, char const *msg); |
51fc9379 QY |
94 | |
95 | /* state variables for a single parser run */ | |
1eb5e8dc QY |
96 | struct graph_node *startnode; // start node of DFA |
97 | ||
51fc9379 QY |
98 | struct graph_node *currnode, // current position in DFA |
99 | *seqhead; // sequence head | |
100 | ||
101 | struct graph_node *optnode_start, // start node for option set | |
102 | *optnode_end; // end node for option set | |
103 | ||
104 | struct graph_node *selnode_start, // start node for selector set | |
105 | *selnode_end; // end node for selector set | |
106 | ||
107 | char *docstr_start, *docstr; // pointers to copy of command docstring | |
108 | ||
109 | /* helper functions for parser */ | |
110 | static char * | |
7a6ded40 | 111 | doc_next (void); |
51fc9379 QY |
112 | |
113 | static struct graph_node * | |
1eb5e8dc | 114 | node_adjacent (struct graph_node *, struct graph_node *); |
51fc9379 QY |
115 | |
116 | static struct graph_node * | |
1eb5e8dc | 117 | add_edge_dedup (struct graph_node *, struct graph_node *); |
51fc9379 QY |
118 | |
119 | static int | |
1eb5e8dc QY |
120 | cmp_token (struct cmd_token_t *, struct cmd_token_t *); |
121 | ||
122 | static struct graph_node * | |
123 | new_token_node (struct graph *, | |
124 | enum cmd_token_type_t type, | |
125 | char *text, char *doc); | |
51fc9379 QY |
126 | |
127 | static void | |
1eb5e8dc | 128 | terminate_graph (struct graph *, |
51fc9379 QY |
129 | struct graph_node *, |
130 | struct cmd_element *); | |
131 | ||
132 | static void | |
1ab84bf3 | 133 | cleanup (void); |
51fc9379 QY |
134 | } |
135 | ||
136 | /* yyparse parameters */ | |
7a6ded40 | 137 | %parse-param { struct graph *graph } |
1eb5e8dc | 138 | %parse-param { struct cmd_element *element } |
51fc9379 QY |
139 | |
140 | /* called automatically before yyparse */ | |
141 | %initial-action { | |
1eb5e8dc QY |
142 | startnode = vector_slot (graph->nodes, 0); |
143 | ||
51fc9379 QY |
144 | /* clear state pointers */ |
145 | seqhead = NULL; | |
146 | currnode = NULL; | |
147 | selnode_start = selnode_end = NULL; | |
148 | optnode_start = optnode_end = NULL; | |
149 | ||
150 | /* set string to parse */ | |
4427e9b3 | 151 | set_lexer_string (element->string); |
51fc9379 QY |
152 | |
153 | /* copy docstring and keep a pointer to the copy */ | |
154 | docstr = element->doc ? XSTRDUP(MTYPE_TMP, element->doc) : NULL; | |
155 | docstr_start = docstr; | |
156 | } | |
92055a92 | 157 | |
92055a92 QY |
158 | %% |
159 | ||
88255c7c QY |
160 | start: |
161 | sentence_root cmd_token_seq | |
880e24a1 | 162 | { |
51fc9379 | 163 | // tack on the command element |
1eb5e8dc | 164 | terminate_graph (graph, currnode, element); |
880e24a1 | 165 | } |
88255c7c QY |
166 | | sentence_root cmd_token_seq '.' placeholder_token |
167 | { | |
1eb5e8dc QY |
168 | if ((currnode = add_edge_dedup (currnode, $4)) != $4) |
169 | graph_delete_node (graph, $4); | |
88255c7c | 170 | |
1ab84bf3 QY |
171 | // adding a node as a child of itself accepts any number |
172 | // of the same token, which is what we want for varags | |
1eb5e8dc | 173 | add_edge_dedup (currnode, currnode); |
88255c7c | 174 | |
51fc9379 | 175 | // tack on the command element |
1eb5e8dc | 176 | terminate_graph (graph, currnode, element); |
88255c7c | 177 | } |
92055a92 | 178 | |
2a23ca6e | 179 | sentence_root: WORD |
478bdaeb | 180 | { |
7a6ded40 | 181 | struct graph_node *root = |
1eb5e8dc | 182 | new_token_node (graph, WORD_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); |
5a5d576b | 183 | |
1eb5e8dc QY |
184 | if ((currnode = add_edge_dedup (startnode, root)) != root) |
185 | graph_delete_node (graph, root); | |
4b0abf24 | 186 | |
5a5d576b QY |
187 | free ($1); |
188 | $$ = currnode; | |
478bdaeb | 189 | }; |
92055a92 | 190 | |
782d9789 QY |
191 | cmd_token: |
192 | placeholder_token | |
5a5d576b | 193 | { |
1eb5e8dc QY |
194 | if ((currnode = add_edge_dedup (currnode, $1)) != $1) |
195 | graph_delete_node (graph, $1); | |
5a5d576b | 196 | } |
782d9789 | 197 | | literal_token |
5a5d576b | 198 | { |
1eb5e8dc QY |
199 | if ((currnode = add_edge_dedup (currnode, $1)) != $1) |
200 | graph_delete_node (graph, $1); | |
5a5d576b | 201 | } |
478bdaeb | 202 | /* selectors and options are subgraphs with start and end nodes */ |
782d9789 | 203 | | selector |
478bdaeb | 204 | { |
7a6ded40 | 205 | graph_add_edge (currnode, $1); |
478bdaeb QY |
206 | currnode = selnode_end; |
207 | selnode_start = selnode_end = NULL; | |
208 | } | |
782d9789 | 209 | | option |
478bdaeb | 210 | { |
7a6ded40 | 211 | graph_add_edge (currnode, $1); |
478bdaeb QY |
212 | currnode = optnode_end; |
213 | optnode_start = optnode_end = NULL; | |
214 | } | |
782d9789 | 215 | ; |
92055a92 | 216 | |
782d9789 QY |
217 | cmd_token_seq: |
218 | %empty | |
219 | | cmd_token_seq cmd_token | |
220 | ; | |
221 | ||
222 | placeholder_token: | |
478bdaeb | 223 | IPV4 |
4b0abf24 | 224 | { |
1eb5e8dc | 225 | $$ = new_token_node (graph, IPV4_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); |
5a5d576b | 226 | free ($1); |
4b0abf24 | 227 | } |
478bdaeb | 228 | | IPV4_PREFIX |
340a2b4a | 229 | { |
1eb5e8dc | 230 | $$ = new_token_node (graph, IPV4_PREFIX_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); |
5a5d576b | 231 | free ($1); |
4b0abf24 | 232 | } |
478bdaeb | 233 | | IPV6 |
340a2b4a | 234 | { |
1eb5e8dc | 235 | $$ = new_token_node (graph, IPV6_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); |
5a5d576b | 236 | free ($1); |
4b0abf24 | 237 | } |
478bdaeb | 238 | | IPV6_PREFIX |
340a2b4a | 239 | { |
1eb5e8dc | 240 | $$ = new_token_node (graph, IPV6_PREFIX_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); |
5a5d576b | 241 | free ($1); |
4b0abf24 | 242 | } |
478bdaeb | 243 | | VARIABLE |
340a2b4a | 244 | { |
1eb5e8dc | 245 | $$ = new_token_node (graph, VARIABLE_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); |
5a5d576b | 246 | free ($1); |
4b0abf24 | 247 | } |
478bdaeb QY |
248 | | RANGE |
249 | { | |
1eb5e8dc QY |
250 | $$ = new_token_node (graph, RANGE_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); |
251 | struct cmd_token_t *token = $$->data; | |
478bdaeb QY |
252 | |
253 | // get the numbers out | |
b3899769 | 254 | yylval.string++; |
7a6ded40 | 255 | token->min = strtoll (yylval.string, &yylval.string, 10); |
ef955a80 | 256 | strsep (&yylval.string, "-"); |
7a6ded40 | 257 | token->max = strtoll (yylval.string, &yylval.string, 10); |
ef955a80 QY |
258 | |
259 | // validate range | |
1eb5e8dc | 260 | if (token->min >= token->max) yyerror (graph, element, "Invalid range."); |
5a5d576b QY |
261 | |
262 | free ($1); | |
478bdaeb | 263 | } |
782d9789 QY |
264 | ; |
265 | ||
266 | literal_token: | |
478bdaeb QY |
267 | WORD |
268 | { | |
1eb5e8dc | 269 | $$ = new_token_node (graph, WORD_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); |
5a5d576b | 270 | free ($1); |
478bdaeb QY |
271 | } |
272 | | NUMBER | |
273 | { | |
1eb5e8dc QY |
274 | $$ = new_token_node (graph, NUMBER_TKN, NULL, doc_next()); |
275 | struct cmd_token_t *token = $$->data; | |
7a6ded40 QY |
276 | |
277 | token->value = yylval.number; | |
278 | token->text = XCALLOC(MTYPE_CMD_TOKENS, DECIMAL_STRLEN_MAX+1); | |
279 | snprintf(token->text, DECIMAL_STRLEN_MAX, "%lld", token->value); | |
478bdaeb | 280 | } |
782d9789 | 281 | ; |
92055a92 | 282 | |
4b0abf24 | 283 | /* <selector|set> productions */ |
51fc9379 | 284 | selector: '<' selector_part '>' |
2a23ca6e | 285 | { |
478bdaeb QY |
286 | // all the graph building is done in selector_element, |
287 | // so just return the selector subgraph head | |
288 | $$ = selnode_start; | |
2a23ca6e | 289 | }; |
782d9789 QY |
290 | |
291 | selector_part: | |
2a23ca6e | 292 | selector_part '|' selector_element |
f4b0c72e | 293 | | selector_element '|' selector_element |
782d9789 QY |
294 | ; |
295 | ||
51fc9379 | 296 | selector_element: selector_element_root selector_token_seq |
478bdaeb QY |
297 | { |
298 | // if the selector start and end do not exist, create them | |
7a6ded40 QY |
299 | if (!selnode_start || !selnode_end) { |
300 | assert(!selnode_start && !selnode_end); | |
1eb5e8dc QY |
301 | selnode_start = new_token_node (graph, SELECTOR_TKN, NULL, NULL); |
302 | selnode_end = new_token_node (graph, NUL_TKN, NULL, NULL); | |
478bdaeb QY |
303 | } |
304 | ||
305 | // add element head as a child of the selector | |
7a6ded40 | 306 | graph_add_edge (selnode_start, $1); |
478bdaeb | 307 | |
d8074cc0 QY |
308 | if ($2) { |
309 | graph_add_edge ($1, seqhead); // tack on selector_token_seq | |
310 | graph_add_edge ($2, selnode_end); // $2 is the sequence tail | |
478bdaeb QY |
311 | } |
312 | else | |
7a6ded40 | 313 | graph_add_edge ($1, selnode_end); |
478bdaeb QY |
314 | |
315 | seqhead = NULL; | |
316 | } | |
782d9789 QY |
317 | |
318 | selector_token_seq: | |
d8074cc0 | 319 | %empty { $$ = NULL; } |
2a23ca6e QY |
320 | | selector_token_seq selector_token |
321 | { | |
1eb5e8dc | 322 | // if the sequence component is NUL_TKN, this is a sequence start |
d8074cc0 | 323 | if (!$1) { |
1eb5e8dc | 324 | assert(!seqhead); |
478bdaeb QY |
325 | seqhead = $2; |
326 | } | |
327 | else // chain on new node | |
7a6ded40 | 328 | graph_add_edge ($1, $2); |
478bdaeb QY |
329 | |
330 | $$ = $2; | |
2a23ca6e | 331 | } |
782d9789 QY |
332 | ; |
333 | ||
340a2b4a | 334 | selector_element_root: |
782d9789 QY |
335 | literal_token |
336 | | placeholder_token | |
478bdaeb QY |
337 | ; |
338 | ||
339 | selector_token: | |
340a2b4a | 340 | selector_element_root |
782d9789 | 341 | ; |
92055a92 QY |
342 | |
343 | /* [option|set] productions */ | |
2a23ca6e | 344 | option: '[' option_part ']' |
4b0abf24 QY |
345 | { |
346 | // add null path | |
7a6ded40 | 347 | graph_add_edge (optnode_start, optnode_end); |
4b0abf24 QY |
348 | $$ = optnode_start; |
349 | }; | |
782d9789 QY |
350 | |
351 | option_part: | |
478bdaeb QY |
352 | option_part '|' option_element |
353 | | option_element | |
782d9789 QY |
354 | ; |
355 | ||
478bdaeb QY |
356 | option_element: |
357 | option_token_seq | |
2a23ca6e | 358 | { |
478bdaeb QY |
359 | if (!optnode_start || !optnode_end) { |
360 | assert(!optnode_start && !optnode_end); | |
1eb5e8dc QY |
361 | optnode_start = new_token_node (graph, OPTION_TKN, NULL, NULL); |
362 | optnode_end = new_token_node (graph, NUL_TKN, NULL, NULL); | |
478bdaeb QY |
363 | } |
364 | ||
7a6ded40 QY |
365 | graph_add_edge (optnode_start, seqhead); |
366 | graph_add_edge ($1, optnode_end); | |
4427e9b3 | 367 | seqhead = NULL; |
2a23ca6e | 368 | } |
478bdaeb QY |
369 | |
370 | option_token_seq: | |
371 | option_token | |
372 | { $$ = seqhead = $1; } | |
373 | | option_token_seq option_token | |
7a6ded40 | 374 | { $$ = graph_add_edge ($1, $2); } |
782d9789 QY |
375 | ; |
376 | ||
377 | option_token: | |
378 | literal_token | |
379 | | placeholder_token | |
380 | ; | |
381 | ||
92055a92 | 382 | %% |
4b0abf24 | 383 | |
1eb5e8dc QY |
384 | void |
385 | command_parse_format (struct graph *graph, struct cmd_element *cmd) | |
51fc9379 | 386 | { |
1ab84bf3 | 387 | // set to 1 to enable parser traces |
51fc9379 QY |
388 | yydebug = 0; |
389 | ||
390 | // parse command into DFA | |
1eb5e8dc | 391 | yyparse (graph, cmd); |
07079d78 | 392 | |
7a6ded40 | 393 | // cleanup |
07079d78 | 394 | cleanup (); |
51fc9379 QY |
395 | } |
396 | ||
397 | /* parser helper functions */ | |
398 | ||
399 | void | |
1eb5e8dc | 400 | yyerror (struct graph *graph, struct cmd_element *el, char const *msg) |
51fc9379 | 401 | { |
1ab84bf3 QY |
402 | zlog_err ("%s: FATAL parse error: %s", __func__, msg); |
403 | zlog_err ("while parsing this command definition: \n\t%s\n", el->string); | |
5a8bbed0 | 404 | exit(EXIT_FAILURE); |
92055a92 | 405 | } |
782d9789 | 406 | |
51fc9379 QY |
407 | static void |
408 | cleanup() | |
782d9789 | 409 | { |
51fc9379 QY |
410 | /* free resources */ |
411 | free (docstr_start); | |
478bdaeb | 412 | |
4427e9b3 | 413 | /* cleanup lexer */ |
07079d78 | 414 | cleanup_lexer (); |
4427e9b3 | 415 | |
4b0abf24 | 416 | /* clear state pointers */ |
51fc9379 QY |
417 | seqhead = NULL; |
418 | currnode = NULL; | |
419 | docstr_start = docstr = NULL; | |
4b0abf24 QY |
420 | selnode_start = selnode_end = NULL; |
421 | optnode_start = optnode_end = NULL; | |
51fc9379 | 422 | } |
4b0abf24 | 423 | |
51fc9379 | 424 | static void |
1eb5e8dc | 425 | terminate_graph (struct graph *graph, struct graph_node *finalnode, struct cmd_element *element) |
51fc9379 | 426 | { |
1eb5e8dc QY |
427 | // end of graph should look like this |
428 | // * -> finalnode -> END_TKN -> cmd_element | |
d8074cc0 QY |
429 | struct graph_node *end_token_node = |
430 | new_token_node (graph, | |
431 | END_TKN, | |
432 | XSTRDUP (MTYPE_CMD_TOKENS, CMD_CR_TEXT), | |
433 | XSTRDUP (MTYPE_CMD_TOKENS, "")); | |
1eb5e8dc QY |
434 | struct graph_node *end_element_node = |
435 | graph_new_node (graph, element, (void (*)(void *)) &del_cmd_element); | |
7a6ded40 | 436 | |
1eb5e8dc QY |
437 | if (node_adjacent (finalnode, end_token_node)) |
438 | yyerror (graph, element, "Duplicate command."); | |
439 | ||
440 | graph_add_edge (finalnode, end_token_node); | |
441 | graph_add_edge (end_token_node, end_element_node); | |
782d9789 | 442 | } |
f4b0c72e | 443 | |
51fc9379 QY |
444 | static char * |
445 | doc_next() | |
446 | { | |
447 | char *piece = NULL; | |
1ab84bf3 | 448 | if (!docstr || !(piece = strsep (&docstr, "\n"))) |
ee551f48 | 449 | return XSTRDUP (MTYPE_CMD_TOKENS, ""); |
1eb5e8dc | 450 | return XSTRDUP (MTYPE_CMD_TOKENS, piece); |
51fc9379 QY |
451 | } |
452 | ||
453 | static struct graph_node * | |
1eb5e8dc | 454 | new_token_node (struct graph *graph, enum cmd_token_type_t type, char *text, char *doc) |
7a6ded40 | 455 | { |
fe2e10e8 | 456 | struct cmd_token_t *token = new_cmd_token (type, text, doc); |
1eb5e8dc | 457 | return graph_new_node (graph, token, (void (*)(void *)) &del_cmd_token); |
7a6ded40 QY |
458 | } |
459 | ||
460 | /** | |
1eb5e8dc | 461 | * Determines if there is an out edge from the first node to the second |
7a6ded40 QY |
462 | */ |
463 | static struct graph_node * | |
1eb5e8dc | 464 | node_adjacent (struct graph_node *first, struct graph_node *second) |
f4b0c72e | 465 | { |
7a6ded40 | 466 | struct graph_node *adj; |
1eb5e8dc | 467 | for (unsigned int i = 0; i < vector_active (first->to); i++) |
1ab84bf3 | 468 | { |
1eb5e8dc | 469 | adj = vector_slot (first->to, i); |
97f13f08 | 470 | struct cmd_token_t *ftok = adj->data, |
1eb5e8dc QY |
471 | *stok = second->data; |
472 | if (cmp_token (ftok, stok)) | |
7a6ded40 | 473 | return adj; |
1ab84bf3 | 474 | } |
f4b0c72e QY |
475 | return NULL; |
476 | } | |
477 | ||
1eb5e8dc QY |
478 | /** |
479 | * Creates an edge betwen two nodes, unless there is already an edge to an | |
480 | * equivalent node. | |
481 | * | |
482 | * The first node's out edges are searched to see if any of them point to a | |
483 | * node that is equivalent to the second node. If such a node exists, it is | |
484 | * returned. Otherwise an edge is created from the first node to the second. | |
485 | * | |
486 | * @param from start node for edge | |
487 | * @param to end node for edge | |
488 | * @return the node which the new edge points to | |
489 | */ | |
51fc9379 | 490 | static struct graph_node * |
1eb5e8dc | 491 | add_edge_dedup (struct graph_node *from, struct graph_node *to) |
f4b0c72e | 492 | { |
1eb5e8dc QY |
493 | struct graph_node *existing = node_adjacent (from, to); |
494 | return existing ? existing : graph_add_edge (from, to); | |
f4b0c72e | 495 | } |
0aa2c2ff | 496 | |
1eb5e8dc QY |
497 | /** |
498 | * Compares two cmd_token's for equality, | |
499 | * | |
500 | * As such, this function is the working definition of token equality | |
501 | * for parsing purposes and determines overall graph structure. | |
502 | */ | |
51fc9379 | 503 | static int |
7a6ded40 | 504 | cmp_token (struct cmd_token_t *first, struct cmd_token_t *second) |
0aa2c2ff | 505 | { |
51fc9379 QY |
506 | // compare types |
507 | if (first->type != second->type) return 0; | |
508 | ||
509 | switch (first->type) { | |
1eb5e8dc QY |
510 | case WORD_TKN: |
511 | case VARIABLE_TKN: | |
e52c05cd QY |
512 | if (first->text && second->text) |
513 | { | |
514 | if (strcmp (first->text, second->text)) | |
97f13f08 | 515 | return 0; |
e52c05cd | 516 | } |
51fc9379 QY |
517 | else if (first->text != second->text) return 0; |
518 | break; | |
1eb5e8dc | 519 | case RANGE_TKN: |
51fc9379 QY |
520 | if (first->min != second->min || first->max != second->max) |
521 | return 0; | |
522 | break; | |
1eb5e8dc | 523 | case NUMBER_TKN: |
51fc9379 QY |
524 | if (first->value != second->value) return 0; |
525 | break; | |
1eb5e8dc | 526 | |
55589d30 QY |
527 | /* selectors and options should be equal if their subgraphs are equal, |
528 | * but the graph isomorphism problem is not known to be solvable in | |
529 | * polynomial time so we consider selectors and options inequal in all | |
530 | * cases; ultimately this forks the graph, but the matcher can handle | |
531 | * this regardless | |
51fc9379 | 532 | */ |
1eb5e8dc QY |
533 | case SELECTOR_TKN: |
534 | case OPTION_TKN: | |
51fc9379 | 535 | return 0; |
1eb5e8dc | 536 | |
51fc9379 | 537 | /* end nodes are always considered equal, since each node may only |
1eb5e8dc | 538 | * have one END_TKN child at a time |
51fc9379 | 539 | */ |
1eb5e8dc QY |
540 | case START_TKN: |
541 | case END_TKN: | |
542 | case NUL_TKN: | |
51fc9379 QY |
543 | default: |
544 | break; | |
545 | } | |
546 | return 1; | |
0aa2c2ff | 547 | } |