]>
Commit | Line | Data |
---|---|---|
47a3a827 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
9d0662e0 | 2 | /* |
1ab84bf3 | 3 | * Command format string parser for CLI backend. |
9d0662e0 | 4 | * |
1ab84bf3 | 5 | * -- |
9547b5d0 | 6 | * Copyright (C) 2016 Cumulus Networks, Inc. |
9d0662e0 QY |
7 | */ |
8 | ||
92055a92 | 9 | %{ |
1ab84bf3 QY |
10 | // compile with debugging facilities |
11 | #define YYDEBUG 1 | |
51fc9379 | 12 | %} |
782d9789 | 13 | |
8bb647a8 | 14 | %locations |
05dbb7df | 15 | /* define parse.error verbose */ |
e9484f70 | 16 | %define api.pure full |
0d37f9f3 | 17 | /* define api.prefix {cmd_yy} */ |
e9484f70 | 18 | |
51fc9379 | 19 | /* names for generated header and parser files */ |
4a121f99 DL |
20 | %defines "lib/command_parse.h" |
21 | %output "lib/command_parse.c" | |
5a8bbed0 | 22 | |
afc3a6ce DL |
23 | /* note: code blocks are output in order, to both .c and .h: |
24 | * 1. %code requires | |
25 | * 2. %union + bison forward decls | |
26 | * 3. %code provides | |
27 | * command_lex.h needs to be included at 3.; it needs the union and YYSTYPE. | |
28 | * struct parser_ctx is needed for the bison forward decls. | |
29 | */ | |
eceb1066 | 30 | %code requires { |
92e50261 DL |
31 | #include "config.h" |
32 | ||
eb44e1aa | 33 | #include <stdbool.h> |
5894e76d DL |
34 | #include <stdlib.h> |
35 | #include <string.h> | |
36 | #include <ctype.h> | |
37 | ||
38 | #include "command_graph.h" | |
a9958674 | 39 | #include "log.h" |
51fc9379 | 40 | |
bf8d3d6a | 41 | DECLARE_MTYPE(LEX); |
55b7f20f | 42 | |
afc3a6ce | 43 | #define YYSTYPE CMD_YYSTYPE |
8bb647a8 | 44 | #define YYLTYPE CMD_YYLTYPE |
afc3a6ce | 45 | struct parser_ctx; |
2020b1c8 DL |
46 | |
47 | /* subgraph semantic value */ | |
48 | struct subgraph { | |
49 | struct graph_node *start, *end; | |
50 | }; | |
afc3a6ce DL |
51 | } |
52 | ||
53 | %union { | |
54 | long long number; | |
55 | char *string; | |
56 | struct graph_node *node; | |
2020b1c8 | 57 | struct subgraph subgraph; |
afc3a6ce DL |
58 | } |
59 | ||
60 | %code provides { | |
61 | #ifndef FLEX_SCANNER | |
09781197 | 62 | #include "lib/command_lex.h" |
afc3a6ce DL |
63 | #endif |
64 | ||
65 | extern void set_lexer_string (yyscan_t *scn, const char *string); | |
66 | extern void cleanup_lexer (yyscan_t *scn); | |
67 | ||
b07a15f8 | 68 | struct parser_ctx { |
e9484f70 DL |
69 | yyscan_t scanner; |
70 | ||
154e9ca1 | 71 | const struct cmd_element *el; |
b07a15f8 DL |
72 | |
73 | struct graph *graph; | |
fd19e7a2 | 74 | struct graph_node *currnode; |
b07a15f8 DL |
75 | |
76 | /* pointers to copy of command docstring */ | |
77 | char *docstr_start, *docstr; | |
78 | }; | |
92055a92 QY |
79 | } |
80 | ||
51fc9379 | 81 | /* union types for lexed tokens */ |
5a5d576b QY |
82 | %token <string> WORD |
83 | %token <string> IPV4 | |
84 | %token <string> IPV4_PREFIX | |
85 | %token <string> IPV6 | |
86 | %token <string> IPV6_PREFIX | |
87 | %token <string> VARIABLE | |
88 | %token <string> RANGE | |
9779e3f1 QY |
89 | %token <string> MAC |
90 | %token <string> MAC_PREFIX | |
8079a413 | 91 | %token <string> ASNUM |
782d9789 | 92 | |
90c8406c DL |
93 | /* special syntax, value is irrelevant */ |
94 | %token <string> EXCL_BRACKET | |
95 | ||
51fc9379 | 96 | /* union types for parsed rules */ |
782d9789 | 97 | %type <node> start |
782d9789 QY |
98 | %type <node> literal_token |
99 | %type <node> placeholder_token | |
16705ecc | 100 | %type <node> placeholder_token_real |
9286efab | 101 | %type <node> simple_token |
9286efab QY |
102 | %type <subgraph> selector |
103 | %type <subgraph> selector_token | |
104 | %type <subgraph> selector_token_seq | |
105 | %type <subgraph> selector_seq_seq | |
782d9789 | 106 | |
16705ecc DL |
107 | %type <string> varname_token |
108 | ||
51fc9379 | 109 | %code { |
e9484f70 | 110 | |
51fc9379 QY |
111 | /* bison declarations */ |
112 | void | |
8bb647a8 | 113 | cmd_yyerror (CMD_YYLTYPE *locp, struct parser_ctx *ctx, char const *msg); |
1eb5e8dc | 114 | |
51fc9379 | 115 | /* helper functions for parser */ |
83364d20 | 116 | static const char * |
b07a15f8 | 117 | doc_next (struct parser_ctx *ctx); |
51fc9379 | 118 | |
1eb5e8dc | 119 | static struct graph_node * |
b07a15f8 | 120 | new_token_node (struct parser_ctx *, |
d0bfb22c | 121 | enum cmd_token_type type, |
83364d20 DL |
122 | const char *text, |
123 | const char *doc); | |
51fc9379 QY |
124 | |
125 | static void | |
8bb647a8 | 126 | terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx, |
b07a15f8 | 127 | struct graph_node *); |
51fc9379 QY |
128 | |
129 | static void | |
b07a15f8 | 130 | cleanup (struct parser_ctx *ctx); |
e9484f70 | 131 | |
eb44e1aa DL |
132 | static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg); |
133 | ||
e9484f70 | 134 | #define scanner ctx->scanner |
51fc9379 QY |
135 | } |
136 | ||
137 | /* yyparse parameters */ | |
e9484f70 DL |
138 | %lex-param {yyscan_t scanner} |
139 | %parse-param {struct parser_ctx *ctx} | |
51fc9379 QY |
140 | |
141 | /* called automatically before yyparse */ | |
142 | %initial-action { | |
143 | /* clear state pointers */ | |
fd19e7a2 | 144 | ctx->currnode = vector_slot (ctx->graph->nodes, 0); |
51fc9379 | 145 | |
51fc9379 | 146 | /* copy docstring and keep a pointer to the copy */ |
b07a15f8 | 147 | if (ctx->el->doc) |
a9958674 QY |
148 | { |
149 | // allocate a new buffer, making room for a flag | |
b07a15f8 DL |
150 | size_t length = (size_t) strlen (ctx->el->doc) + 2; |
151 | ctx->docstr = malloc (length); | |
152 | memcpy (ctx->docstr, ctx->el->doc, strlen (ctx->el->doc)); | |
a9958674 | 153 | // set the flag so doc_next knows when to print a warning |
b07a15f8 | 154 | ctx->docstr[length - 2] = 0x03; |
a9958674 | 155 | // null terminate |
b07a15f8 | 156 | ctx->docstr[length - 1] = 0x00; |
a9958674 | 157 | } |
b07a15f8 | 158 | ctx->docstr_start = ctx->docstr; |
51fc9379 | 159 | } |
92055a92 | 160 | |
92055a92 QY |
161 | %% |
162 | ||
88255c7c | 163 | start: |
fd19e7a2 | 164 | cmd_token_seq |
880e24a1 | 165 | { |
51fc9379 | 166 | // tack on the command element |
8bb647a8 | 167 | terminate_graph (&@1, ctx, ctx->currnode); |
880e24a1 | 168 | } |
fd19e7a2 | 169 | | cmd_token_seq placeholder_token '.' '.' '.' |
88255c7c | 170 | { |
6432969d | 171 | if ((ctx->currnode = graph_add_edge (ctx->currnode, $2)) != $2) |
fd19e7a2 | 172 | graph_delete_node (ctx->graph, $2); |
88255c7c | 173 | |
4d94b292 | 174 | ((struct cmd_token *)ctx->currnode->data)->allowrepeat = 1; |
7d5718c1 | 175 | |
1ab84bf3 | 176 | // adding a node as a child of itself accepts any number |
a9958674 | 177 | // of the same token, which is what we want for variadics |
6432969d | 178 | graph_add_edge (ctx->currnode, ctx->currnode); |
88255c7c | 179 | |
51fc9379 | 180 | // tack on the command element |
8bb647a8 | 181 | terminate_graph (&@1, ctx, ctx->currnode); |
88255c7c | 182 | } |
9286efab | 183 | ; |
92055a92 | 184 | |
16705ecc DL |
185 | varname_token: '$' WORD |
186 | { | |
4d85f868 | 187 | $$ = $2; |
16705ecc DL |
188 | } |
189 | | /* empty */ | |
190 | { | |
191 | $$ = NULL; | |
192 | } | |
193 | ; | |
194 | ||
9286efab | 195 | cmd_token_seq: |
0d4aa1b1 | 196 | /* empty */ |
9286efab QY |
197 | | cmd_token_seq cmd_token |
198 | ; | |
92055a92 | 199 | |
782d9789 | 200 | cmd_token: |
9286efab | 201 | simple_token |
5a5d576b | 202 | { |
6432969d | 203 | if ((ctx->currnode = graph_add_edge (ctx->currnode, $1)) != $1) |
b07a15f8 | 204 | graph_delete_node (ctx->graph, $1); |
8005767b | 205 | cmd_token_varname_seqappend($1); |
5a5d576b | 206 | } |
663982cd | 207 | | selector |
5a5d576b | 208 | { |
2020b1c8 | 209 | graph_add_edge (ctx->currnode, $1.start); |
8005767b | 210 | cmd_token_varname_seqappend($1.start); |
2020b1c8 | 211 | ctx->currnode = $1.end; |
478bdaeb | 212 | } |
9286efab QY |
213 | ; |
214 | ||
215 | simple_token: | |
216 | literal_token | |
217 | | placeholder_token | |
218 | ; | |
219 | ||
16705ecc | 220 | literal_token: WORD varname_token |
9286efab | 221 | { |
83364d20 | 222 | $$ = new_token_node (ctx, WORD_TKN, $1, doc_next(ctx)); |
5894e76d | 223 | cmd_token_varname_set ($$->data, $2); |
16705ecc | 224 | XFREE (MTYPE_LEX, $2); |
55b7f20f | 225 | XFREE (MTYPE_LEX, $1); |
9286efab | 226 | } |
782d9789 QY |
227 | ; |
228 | ||
16705ecc | 229 | placeholder_token_real: |
478bdaeb | 230 | IPV4 |
4b0abf24 | 231 | { |
83364d20 | 232 | $$ = new_token_node (ctx, IPV4_TKN, $1, doc_next(ctx)); |
55b7f20f | 233 | XFREE (MTYPE_LEX, $1); |
4b0abf24 | 234 | } |
478bdaeb | 235 | | IPV4_PREFIX |
340a2b4a | 236 | { |
83364d20 | 237 | $$ = new_token_node (ctx, IPV4_PREFIX_TKN, $1, doc_next(ctx)); |
55b7f20f | 238 | XFREE (MTYPE_LEX, $1); |
4b0abf24 | 239 | } |
478bdaeb | 240 | | IPV6 |
340a2b4a | 241 | { |
83364d20 | 242 | $$ = new_token_node (ctx, IPV6_TKN, $1, doc_next(ctx)); |
55b7f20f | 243 | XFREE (MTYPE_LEX, $1); |
4b0abf24 | 244 | } |
478bdaeb | 245 | | IPV6_PREFIX |
340a2b4a | 246 | { |
83364d20 | 247 | $$ = new_token_node (ctx, IPV6_PREFIX_TKN, $1, doc_next(ctx)); |
55b7f20f | 248 | XFREE (MTYPE_LEX, $1); |
4b0abf24 | 249 | } |
478bdaeb | 250 | | VARIABLE |
340a2b4a | 251 | { |
83364d20 | 252 | $$ = new_token_node (ctx, VARIABLE_TKN, $1, doc_next(ctx)); |
55b7f20f | 253 | XFREE (MTYPE_LEX, $1); |
4b0abf24 | 254 | } |
478bdaeb QY |
255 | | RANGE |
256 | { | |
83364d20 | 257 | $$ = new_token_node (ctx, RANGE_TKN, $1, doc_next(ctx)); |
d0bfb22c | 258 | struct cmd_token *token = $$->data; |
478bdaeb QY |
259 | |
260 | // get the numbers out | |
b3899769 | 261 | yylval.string++; |
7a6ded40 | 262 | token->min = strtoll (yylval.string, &yylval.string, 10); |
ef955a80 | 263 | strsep (&yylval.string, "-"); |
7a6ded40 | 264 | token->max = strtoll (yylval.string, &yylval.string, 10); |
ef955a80 QY |
265 | |
266 | // validate range | |
8bb647a8 | 267 | if (token->min > token->max) cmd_yyerror (&@1, ctx, "Invalid range."); |
5a5d576b | 268 | |
55b7f20f | 269 | XFREE (MTYPE_LEX, $1); |
478bdaeb | 270 | } |
9779e3f1 QY |
271 | | MAC |
272 | { | |
273 | $$ = new_token_node (ctx, MAC_TKN, $1, doc_next(ctx)); | |
274 | XFREE (MTYPE_LEX, $1); | |
275 | } | |
276 | | MAC_PREFIX | |
277 | { | |
278 | $$ = new_token_node (ctx, MAC_PREFIX_TKN, $1, doc_next(ctx)); | |
279 | XFREE (MTYPE_LEX, $1); | |
280 | } | |
8079a413 PG |
281 | | ASNUM |
282 | { | |
283 | $$ = new_token_node (ctx, ASNUM_TKN, $1, doc_next(ctx)); | |
284 | XFREE (MTYPE_LEX, $1); | |
285 | } | |
782d9789 | 286 | |
16705ecc DL |
287 | placeholder_token: |
288 | placeholder_token_real varname_token | |
289 | { | |
16705ecc | 290 | $$ = $1; |
8005767b | 291 | cmd_token_varname_set ($$->data, $2); |
16705ecc DL |
292 | XFREE (MTYPE_LEX, $2); |
293 | }; | |
294 | ||
295 | ||
4b0abf24 | 296 | /* <selector|set> productions */ |
16705ecc | 297 | selector: '<' selector_seq_seq '>' varname_token |
2a23ca6e | 298 | { |
663982cd | 299 | $$ = $2; |
8005767b | 300 | cmd_token_varname_join ($2.end, $4); |
16705ecc | 301 | XFREE (MTYPE_LEX, $4); |
2a23ca6e | 302 | }; |
782d9789 | 303 | |
9286efab QY |
304 | selector_seq_seq: |
305 | selector_seq_seq '|' selector_token_seq | |
478bdaeb | 306 | { |
663982cd | 307 | $$ = $1; |
2020b1c8 DL |
308 | graph_add_edge ($$.start, $3.start); |
309 | graph_add_edge ($3.end, $$.end); | |
9286efab | 310 | } |
663982cd | 311 | | selector_token_seq |
9286efab | 312 | { |
663982cd DL |
313 | $$.start = new_token_node (ctx, FORK_TKN, NULL, NULL); |
314 | $$.end = new_token_node (ctx, JOIN_TKN, NULL, NULL); | |
315 | ((struct cmd_token *)$$.start->data)->forkjoin = $$.end; | |
316 | ((struct cmd_token *)$$.end->data)->forkjoin = $$.start; | |
317 | ||
2020b1c8 DL |
318 | graph_add_edge ($$.start, $1.start); |
319 | graph_add_edge ($1.end, $$.end); | |
478bdaeb | 320 | } |
9286efab | 321 | ; |
782d9789 | 322 | |
7d5718c1 | 323 | /* {keyword} productions */ |
16705ecc | 324 | selector: '{' selector_seq_seq '}' varname_token |
7d5718c1 | 325 | { |
663982cd DL |
326 | $$ = $2; |
327 | graph_add_edge ($$.end, $$.start); | |
328 | /* there is intentionally no start->end link, for two reasons: | |
329 | * 1) this allows "at least 1 of" semantics, which are otherwise impossible | |
330 | * 2) this would add a start->end->start loop in the graph that the current | |
331 | * loop-avoidal fails to handle | |
214d8a60 | 332 | * just use [{a|b}] if necessary, that will work perfectly fine, and reason |
663982cd | 333 | * #1 is good enough to keep it this way. */ |
16705ecc | 334 | |
eb44e1aa | 335 | loopcheck(ctx, &$$); |
8005767b | 336 | cmd_token_varname_join ($2.end, $4); |
16705ecc | 337 | XFREE (MTYPE_LEX, $4); |
7d5718c1 DL |
338 | }; |
339 | ||
340 | ||
535ef556 | 341 | selector_token: |
9286efab | 342 | simple_token |
4b0abf24 | 343 | { |
2020b1c8 | 344 | $$.start = $$.end = $1; |
9286efab | 345 | } |
4d12266b | 346 | | selector |
9286efab | 347 | ; |
782d9789 | 348 | |
663982cd DL |
349 | selector_token_seq: |
350 | selector_token_seq selector_token | |
2a23ca6e | 351 | { |
2020b1c8 | 352 | graph_add_edge ($1.end, $2.start); |
8005767b | 353 | cmd_token_varname_seqappend($2.start); |
2020b1c8 DL |
354 | $$.start = $1.start; |
355 | $$.end = $2.end; | |
2a23ca6e | 356 | } |
663982cd | 357 | | selector_token |
9286efab | 358 | ; |
478bdaeb | 359 | |
663982cd | 360 | /* [option] productions */ |
16705ecc | 361 | selector: '[' selector_seq_seq ']' varname_token |
9286efab | 362 | { |
663982cd DL |
363 | $$ = $2; |
364 | graph_add_edge ($$.start, $$.end); | |
8005767b | 365 | cmd_token_varname_join ($2.end, $4); |
16705ecc | 366 | XFREE (MTYPE_LEX, $4); |
9286efab | 367 | } |
782d9789 QY |
368 | ; |
369 | ||
90c8406c DL |
370 | /* ![option] productions */ |
371 | selector: EXCL_BRACKET selector_seq_seq ']' varname_token | |
372 | { | |
373 | struct graph_node *neg_only = new_token_node (ctx, NEG_ONLY_TKN, NULL, NULL); | |
374 | ||
375 | $$ = $2; | |
376 | graph_add_edge ($$.start, neg_only); | |
377 | graph_add_edge (neg_only, $$.end); | |
8005767b | 378 | cmd_token_varname_join ($2.end, $4); |
90c8406c DL |
379 | XFREE (MTYPE_LEX, $4); |
380 | } | |
381 | ; | |
382 | ||
92055a92 | 383 | %% |
4b0abf24 | 384 | |
e9484f70 DL |
385 | #undef scanner |
386 | ||
bf8d3d6a | 387 | DEFINE_MTYPE(LIB, LEX, "Lexer token (temporary)"); |
55b7f20f | 388 | |
1eb5e8dc | 389 | void |
154e9ca1 | 390 | cmd_graph_parse (struct graph *graph, const struct cmd_element *cmd) |
51fc9379 | 391 | { |
b07a15f8 DL |
392 | struct parser_ctx ctx = { .graph = graph, .el = cmd }; |
393 | ||
1ab84bf3 | 394 | // set to 1 to enable parser traces |
51fc9379 QY |
395 | yydebug = 0; |
396 | ||
e9484f70 DL |
397 | set_lexer_string (&ctx.scanner, cmd->string); |
398 | ||
51fc9379 | 399 | // parse command into DFA |
e9484f70 DL |
400 | cmd_yyparse (&ctx); |
401 | ||
402 | /* cleanup lexer */ | |
403 | cleanup_lexer (&ctx.scanner); | |
07079d78 | 404 | |
7a6ded40 | 405 | // cleanup |
b07a15f8 | 406 | cleanup (&ctx); |
51fc9379 QY |
407 | } |
408 | ||
409 | /* parser helper functions */ | |
410 | ||
eb44e1aa DL |
411 | static bool loopcheck_inner(struct graph_node *start, struct graph_node *node, |
412 | struct graph_node *end, size_t depth) | |
413 | { | |
414 | size_t i; | |
415 | bool ret; | |
416 | ||
417 | /* safety check */ | |
418 | if (depth++ == 64) | |
419 | return true; | |
420 | ||
421 | for (i = 0; i < vector_active(node->to); i++) { | |
422 | struct graph_node *next = vector_slot(node->to, i); | |
423 | struct cmd_token *tok = next->data; | |
424 | ||
425 | if (next == end || next == start) | |
426 | return true; | |
427 | if (tok->type < SPECIAL_TKN) | |
428 | continue; | |
429 | ret = loopcheck_inner(start, next, end, depth); | |
430 | if (ret) | |
431 | return true; | |
432 | } | |
433 | return false; | |
434 | } | |
435 | ||
436 | static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg) | |
437 | { | |
438 | if (loopcheck_inner(sg->start, sg->start, sg->end, 0)) | |
439 | zlog_err("FATAL: '%s': {} contains an empty path! Use [{...}]", | |
440 | ctx->el->string); | |
441 | } | |
442 | ||
51fc9379 | 443 | void |
8bb647a8 | 444 | yyerror (CMD_YYLTYPE *loc, struct parser_ctx *ctx, char const *msg) |
51fc9379 | 445 | { |
8bb647a8 DL |
446 | char *tmpstr = strdup(ctx->el->string); |
447 | char *line, *eol; | |
448 | char spacing[256]; | |
449 | int lineno = 0; | |
450 | ||
b87478cb DS |
451 | zlog_notice ("%s: FATAL parse error: %s", __func__, msg); |
452 | zlog_notice ("%s: %d:%d-%d of this command definition:", __func__, loc->first_line, loc->first_column, loc->last_column); | |
8bb647a8 DL |
453 | |
454 | line = tmpstr; | |
455 | do { | |
456 | lineno++; | |
457 | eol = strchr(line, '\n'); | |
458 | if (eol) | |
459 | *eol++ = '\0'; | |
460 | ||
b87478cb | 461 | zlog_notice ("%s: | %s", __func__, line); |
8bb647a8 DL |
462 | if (lineno == loc->first_line && lineno == loc->last_line |
463 | && loc->first_column < (int)sizeof(spacing) - 1 | |
464 | && loc->last_column < (int)sizeof(spacing) - 1) { | |
465 | ||
466 | int len = loc->last_column - loc->first_column; | |
467 | if (len == 0) | |
468 | len = 1; | |
469 | ||
470 | memset(spacing, ' ', loc->first_column - 1); | |
471 | memset(spacing + loc->first_column - 1, '^', len); | |
472 | spacing[loc->first_column - 1 + len] = '\0'; | |
b87478cb | 473 | zlog_notice ("%s: | %s", __func__, spacing); |
8bb647a8 DL |
474 | } |
475 | } while ((line = eol)); | |
476 | free(tmpstr); | |
92055a92 | 477 | } |
782d9789 | 478 | |
51fc9379 | 479 | static void |
b07a15f8 | 480 | cleanup (struct parser_ctx *ctx) |
782d9789 | 481 | { |
51fc9379 | 482 | /* free resources */ |
b07a15f8 | 483 | free (ctx->docstr_start); |
478bdaeb | 484 | |
4b0abf24 | 485 | /* clear state pointers */ |
b07a15f8 DL |
486 | ctx->currnode = NULL; |
487 | ctx->docstr_start = ctx->docstr = NULL; | |
51fc9379 | 488 | } |
4b0abf24 | 489 | |
51fc9379 | 490 | static void |
8bb647a8 DL |
491 | terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx, |
492 | struct graph_node *finalnode) | |
51fc9379 | 493 | { |
1eb5e8dc QY |
494 | // end of graph should look like this |
495 | // * -> finalnode -> END_TKN -> cmd_element | |
154e9ca1 | 496 | const struct cmd_element *element = ctx->el; |
d8074cc0 | 497 | struct graph_node *end_token_node = |
83364d20 | 498 | new_token_node (ctx, END_TKN, CMD_CR_TEXT, ""); |
1eb5e8dc | 499 | struct graph_node *end_element_node = |
154e9ca1 | 500 | graph_new_node (ctx->graph, (void *)element, NULL); |
7a6ded40 | 501 | |
ebb08130 | 502 | if (ctx->docstr && strlen (ctx->docstr) > 1) { |
c8a400f3 DS |
503 | zlog_err ("Excessive docstring while parsing '%s'", ctx->el->string); |
504 | zlog_err ("----------"); | |
ebb08130 | 505 | while (ctx->docstr && ctx->docstr[1] != '\0') |
c8a400f3 | 506 | zlog_err ("%s", strsep(&ctx->docstr, "\n")); |
1d5453d6 | 507 | zlog_err ("----------"); |
ebb08130 QY |
508 | } |
509 | ||
1eb5e8dc QY |
510 | graph_add_edge (finalnode, end_token_node); |
511 | graph_add_edge (end_token_node, end_element_node); | |
782d9789 | 512 | } |
f4b0c72e | 513 | |
83364d20 | 514 | static const char * |
b07a15f8 | 515 | doc_next (struct parser_ctx *ctx) |
51fc9379 | 516 | { |
b07a15f8 | 517 | const char *piece = ctx->docstr ? strsep (&ctx->docstr, "\n") : ""; |
a9958674 QY |
518 | if (*piece == 0x03) |
519 | { | |
c8a400f3 | 520 | zlog_err ("Ran out of docstring while parsing '%s'", ctx->el->string); |
a9958674 QY |
521 | piece = ""; |
522 | } | |
523 | ||
83364d20 | 524 | return piece; |
51fc9379 QY |
525 | } |
526 | ||
527 | static struct graph_node * | |
b07a15f8 | 528 | new_token_node (struct parser_ctx *ctx, enum cmd_token_type type, |
83364d20 | 529 | const char *text, const char *doc) |
7a6ded40 | 530 | { |
5894e76d DL |
531 | struct cmd_token *token = cmd_token_new (type, ctx->el->attr, text, doc); |
532 | return graph_new_node (ctx->graph, token, (void (*)(void *)) &cmd_token_del); | |
7a6ded40 | 533 | } |