bt_parse.y revision 1.13
1/* $OpenBSD: bt_parse.y,v 1.13 2020/04/24 15:10:41 mpi Exp $ */ 2 3/* 4 * Copyright (c) 2019 - 2020 Martin Pieuchot <mpi@openbsd.org> 5 * Copyright (c) 2019 Tobias Heider <tobhe@openbsd.org> 6 * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org> 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 */ 20 21/* 22 * B tracing language parser. 23 * 24 * The dialect of the language understood by this parser aims to be 25 * compatible with the one understood bpftrace(8), see: 26 * 27 * https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md 28 * 29 */ 30 31%{ 32#include <sys/queue.h> 33 34#include <assert.h> 35#include <ctype.h> 36#include <err.h> 37#include <limits.h> 38#include <stdarg.h> 39#include <stdint.h> 40#include <stdio.h> 41 42#include "bt_parser.h" 43 44/* Name for the default map @[], hopefully nobody will use this one ;) */ 45#define UNNAMED_MAP "___unnamed_map_doesnt_have_any_name" 46 47/* Number of rules to evaluate. */ 48struct bt_ruleq g_rules = TAILQ_HEAD_INITIALIZER(g_rules); 49 50/* Number of probes except BEGIN/END. */ 51int g_nprobes; 52 53/* List of global variables, including maps. */ 54SLIST_HEAD(, bt_var) g_variables; 55 56struct bt_rule *br_new(struct bt_probe *, struct bt_filter *, struct bt_stmt *, 57 enum bt_rtype); 58struct bt_filter *bf_new(enum bt_operand, enum bt_filtervar, int); 59struct bt_probe *bp_new(const char *, const char *, const char *, int32_t); 60struct bt_arg *ba_append(struct bt_arg *, struct bt_arg *); 61struct bt_stmt *bs_new(enum bt_action, struct bt_arg *, struct bt_var *); 62struct bt_stmt *bs_append(struct bt_stmt *, struct bt_stmt *); 63 64struct bt_var *bv_find(const char *); 65struct bt_arg *bv_get(const char *); 66struct bt_stmt *bv_set(const char *, struct bt_arg *); 67 68struct bt_arg *bm_get(const char *, struct bt_arg *); 69struct bt_stmt *bm_set(const char *, struct bt_arg *, struct bt_arg *); 70struct bt_stmt *bm_op(enum bt_action, struct bt_arg *, struct bt_arg *); 71 72/* 73 * Lexer 74 */ 75const char *pbuf; 76size_t plen; 77size_t pindex; 78int perrors = 0; 79 80typedef struct { 81 union { 82 long number; 83 int i; 84 const char *string; 85 struct bt_probe *probe; 86 struct bt_filter *filter; 87 struct bt_stmt *stmt; 88 struct bt_arg *arg; 89 enum bt_rtype rtype; 90 } v; 91 const char *filename; 92 int lineno; 93 int colno; 94} yystype; 95#define YYSTYPE yystype 96 97static void yyerror(const char *, ...); 98static int yylex(void); 99%} 100 101%token ERROR OP_EQ OP_NEQ BEGIN END 102/* Builtins */ 103%token BUILTIN PID TID 104/* Functions and Map operators */ 105%token F_DELETE FUNC0 FUNC1 FUNCN MOP0 MOP1 106%token <v.string> STRING CSTRING 107%token <v.number> NUMBER 108 109%type <v.string> gvar 110%type <v.i> filterval oper builtin 111%type <v.i> BUILTIN F_DELETE FUNC0 FUNC1 FUNCN MOP0 MOP1 112%type <v.probe> probe 113%type <v.filter> predicate 114%type <v.stmt> action stmt stmtlist 115%type <v.arg> expr vargs map mexpr term 116%type <v.rtype> beginend 117 118%left '+' '-' 119%left '/' '*' 120%% 121 122grammar : /* empty */ 123 | grammar '\n' 124 | grammar rule 125 | grammar error 126 ; 127 128rule : beginend action { br_new(NULL, NULL, $2, $1); } 129 | probe predicate action { br_new($1, $2, $3, B_RT_PROBE); } 130 ; 131 132beginend : BEGIN { $$ = B_RT_BEGIN; } 133 | END { $$ = B_RT_END; } 134 ; 135 136probe : STRING ':' STRING ':' STRING { $$ = bp_new($1, $3, $5, 0); } 137 | STRING ':' HZ ':' NUMBER { $$ = bp_new($1, "hz", NULL, $5); } 138 ; 139 140 141filterval : PID { $$ = B_FV_PID; } 142 | TID { $$ = B_FV_TID; } 143 ; 144 145oper : OP_EQ { $$ = B_OP_EQ; } 146 | OP_NEQ { $$ = B_OP_NE; } 147 ; 148 149predicate : /* empty */ { $$ = NULL; } 150 | '/' filterval oper NUMBER '/' { $$ = bf_new($3, $2, $4); } 151 | '/' NUMBER oper filterval '/' { $$ = bf_new($3, $4, $2); } 152 ; 153 154builtin : PID { $$ = B_AT_BI_PID; } 155 | TID { $$ = B_AT_BI_TID; } 156 | BUILTIN { $$ = $1; } 157 ; 158 159mexpr : MOP0 '(' ')' { $$ = ba_new(NULL, $1); } 160 | MOP1 '(' expr ')' { $$ = ba_new($3, $1); } 161 | expr { $$ = $1; } 162 ; 163 164expr : CSTRING { $$ = ba_new($1, B_AT_STR); } 165 | term 166 ; 167 168term : '(' term ')' { $$ = $2; } 169 | term '+' term { $$ = ba_op('+', $1, $3); } 170 | term '-' term { $$ = ba_op('-', $1, $3); } 171 | term '/' term { $$ = ba_op('/', $1, $3); } 172 | term '*' term { $$ = ba_op('*', $1, $3); } 173 | NUMBER { $$ = ba_new($1, B_AT_LONG); } 174 | builtin { $$ = ba_new(NULL, $1); } 175 | gvar { $$ = bv_get($1); } 176 | map { $$ = $1; } 177 178 179gvar : '@' STRING { $$ = $2; } 180 | '@' { $$ = UNNAMED_MAP; } 181 182map : gvar '[' vargs ']' { $$ = bm_get($1, $3); } 183 ; 184 185vargs : expr 186 | vargs ',' expr { $$ = ba_append($1, $3); } 187 ; 188 189NL : /* empty */ | '\n' 190 ; 191 192stmt : ';' NL { $$ = NULL; } 193 | gvar '=' expr { $$ = bv_set($1, $3); } 194 | gvar '[' vargs ']' '=' mexpr { $$ = bm_set($1, $3, $6); } 195 | FUNCN '(' vargs ')' { $$ = bs_new($1, $3, NULL); } 196 | FUNC1 '(' expr ')' { $$ = bs_new($1, $3, NULL); } 197 | FUNC0 '(' ')' { $$ = bs_new($1, NULL, NULL); } 198 | F_DELETE '(' map ')' { $$ = bm_op($1, $3, NULL); } 199 ; 200 201stmtlist : stmt 202 | stmtlist stmt { $$ = bs_append($1, $2); } 203 ; 204 205action : '{' stmtlist '}' { $$ = $2; } 206 ; 207 208%% 209 210/* Create a new rule, representing "probe / filter / { action }" */ 211struct bt_rule * 212br_new(struct bt_probe *probe, struct bt_filter *filter, struct bt_stmt *head, 213 enum bt_rtype rtype) 214{ 215 struct bt_rule *br; 216 217 br = calloc(1, sizeof(struct bt_rule)); 218 if (br == NULL) 219 err(1, "bt_rule: calloc"); 220 br->br_probe = probe; 221 br->br_filter = filter; 222 /* SLIST_INSERT_HEAD() nullify the next pointer. */ 223 SLIST_FIRST(&br->br_action) = head; 224 br->br_type = rtype; 225 226 if (rtype == B_RT_PROBE) { 227 g_nprobes++; 228 TAILQ_INSERT_TAIL(&g_rules, br, br_next); 229 } else { 230 TAILQ_INSERT_HEAD(&g_rules, br, br_next); 231 } 232 233 return br; 234} 235 236/* Create a new filter */ 237struct bt_filter * 238bf_new(enum bt_operand op, enum bt_filtervar var, int val) 239{ 240 struct bt_filter *df; 241 242 if (val < 0 || val > INT_MAX) 243 errx(1, "invalid pid '%d'", val); 244 245 df = calloc(1, sizeof(struct bt_filter)); 246 if (df == NULL) 247 err(1, "bt_filter: calloc"); 248 df->bf_op = op; 249 df->bf_var = var; 250 df->bf_val = val; 251 252 return df; 253} 254 255/* Create a new probe */ 256struct bt_probe * 257bp_new(const char *prov, const char *func, const char *name, int32_t rate) 258{ 259 struct bt_probe *bp; 260 261 if (rate < 0 || rate > INT32_MAX) 262 errx(1, "only positive values permitted"); 263 264 bp = calloc(1, sizeof(struct bt_probe)); 265 if (bp == NULL) 266 err(1, "bt_probe: calloc"); 267 bp->bp_prov = prov; 268 bp->bp_func = func; 269 bp->bp_name = name; 270 bp->bp_rate = rate; 271 272 return bp; 273} 274 275/* Create a new argument */ 276struct bt_arg * 277ba_new0(void *val, enum bt_argtype type) 278{ 279 struct bt_arg *ba; 280 281 ba = calloc(1, sizeof(struct bt_arg)); 282 if (ba == NULL) 283 err(1, "bt_arg: calloc"); 284 ba->ba_value = val; 285 ba->ba_type = type; 286 287 return ba; 288} 289 290/* 291 * Link two arguments together, to build an argument list used in 292 * function calls. 293 */ 294struct bt_arg * 295ba_append(struct bt_arg *da0, struct bt_arg *da1) 296{ 297 struct bt_arg *ba = da0; 298 299 assert(da1 != NULL); 300 301 if (da0 == NULL) 302 return da1; 303 304 while (SLIST_NEXT(ba, ba_next) != NULL) 305 ba = SLIST_NEXT(ba, ba_next); 306 307 SLIST_INSERT_AFTER(ba, da1, ba_next); 308 309 return da0; 310} 311 312/* Create an operator argument */ 313struct bt_arg * 314ba_op(const char op, struct bt_arg *da0, struct bt_arg *da1) 315{ 316 enum bt_argtype type; 317 318 switch (op) { 319 case '+': 320 type = B_AT_OP_ADD; 321 break; 322 case '-': 323 type = B_AT_OP_MINUS; 324 break; 325 case '*': 326 type = B_AT_OP_MULT; 327 break; 328 case '/': 329 type = B_AT_OP_DIVIDE; 330 break; 331 default: 332 assert(0); 333 } 334 335 return ba_new(ba_append(da0, da1), type); 336} 337 338/* Create a new statement: function call or assignment. */ 339struct bt_stmt * 340bs_new(enum bt_action act, struct bt_arg *head, struct bt_var *var) 341{ 342 struct bt_stmt *bs; 343 344 bs = calloc(1, sizeof(struct bt_stmt)); 345 if (bs == NULL) 346 err(1, "bt_stmt: calloc"); 347 bs->bs_act = act; 348 bs->bs_var = var; 349 /* SLIST_INSERT_HEAD() nullify the next pointer. */ 350 SLIST_FIRST(&bs->bs_args) = head; 351 352 return bs; 353} 354 355/* Link two statements together, to build an 'action'. */ 356struct bt_stmt * 357bs_append(struct bt_stmt *ds0, struct bt_stmt *ds1) 358{ 359 struct bt_stmt *bs = ds0; 360 361 if (ds0 == NULL) 362 return ds1; 363 364 if (ds1 == NULL) 365 return ds0; 366 367 while (SLIST_NEXT(bs, bs_next) != NULL) 368 bs = SLIST_NEXT(bs, bs_next); 369 370 SLIST_INSERT_AFTER(bs, ds1, bs_next); 371 372 return ds0; 373} 374 375const char * 376bv_name(struct bt_var *bv) 377{ 378 if (strncmp(bv->bv_name, UNNAMED_MAP, strlen(UNNAMED_MAP)) == 0) 379 return ""; 380 return bv->bv_name; 381} 382 383/* Return the global variable corresponding to `vname'. */ 384struct bt_var * 385bv_find(const char *vname) 386{ 387 struct bt_var *bv; 388 389 SLIST_FOREACH(bv, &g_variables, bv_next) { 390 if (strcmp(vname, bv->bv_name) == 0) 391 break; 392 } 393 394 return bv; 395} 396 397/* Find or allocate a global variable. */ 398struct bt_var * 399bv_new(const char *vname) 400{ 401 struct bt_var *bv; 402 403 bv = calloc(1, sizeof(struct bt_var)); 404 if (bv == NULL) 405 err(1, "bt_var: calloc"); 406 bv->bv_name = vname; 407 SLIST_INSERT_HEAD(&g_variables, bv, bv_next); 408 409 return bv; 410} 411 412/* Create a 'variable store' statement to assign a value to a variable. */ 413struct bt_stmt * 414bv_set(const char *vname, struct bt_arg *vval) 415{ 416 struct bt_var *bv; 417 418 bv = bv_find(vname); 419 if (bv == NULL) 420 bv = bv_new(vname); 421 return bs_new(B_AC_STORE, vval, bv); 422} 423 424/* Create an argument that points to a variable. */ 425struct bt_arg * 426bv_get(const char *vname) 427{ 428 struct bt_var *bv; 429 430 bv = bv_find(vname); 431 if (bv == NULL) 432 yyerror("variable '%s' accessed before being set", vname); 433 434 return ba_new(bv, B_AT_VAR); 435} 436 437struct bt_stmt * 438bm_op(enum bt_action mact, struct bt_arg *ba, struct bt_arg *mval) 439{ 440 return bs_new(mact, ba, (struct bt_var *)mval); 441} 442 443/* Create a 'map store' statement to assign a value to a map entry. */ 444struct bt_stmt * 445bm_set(const char *mname, struct bt_arg *mkey, struct bt_arg *mval) 446{ 447 struct bt_arg *ba; 448 struct bt_var *bv; 449 450 bv = bv_find(mname); 451 if (bv == NULL) 452 bv = bv_new(mname); 453 ba = ba_new(bv, B_AT_MAP); 454 ba->ba_key = mkey; 455 return bs_new(B_AC_INSERT, ba, (struct bt_var *)mval); 456} 457 458/* Create an argument that points to a variable and attach a key to it. */ 459struct bt_arg * 460bm_get(const char *mname, struct bt_arg *mkey) 461{ 462 struct bt_arg *ba; 463 464 ba = bv_get(mname); 465 ba->ba_type = B_AT_MAP; 466 ba->ba_key = mkey; 467 return ba; 468} 469 470struct keyword { 471 const char *word; 472 int token; 473 int type; 474}; 475 476int 477kw_cmp(const void *str, const void *xkw) 478{ 479 return (strcmp(str, ((const struct keyword *)xkw)->word)); 480} 481 482struct keyword * 483lookup(char *s) 484{ 485 static const struct keyword kws[] = { 486 { "!=", OP_NEQ, 0 }, 487 { "==", OP_EQ, 0 }, 488 { "BEGIN", BEGIN, 0 }, 489 { "END", END, 0 }, 490 { "arg0", BUILTIN, B_AT_BI_ARG0 }, 491 { "arg1", BUILTIN, B_AT_BI_ARG1 }, 492 { "arg2", BUILTIN, B_AT_BI_ARG2 }, 493 { "arg3", BUILTIN, B_AT_BI_ARG3 }, 494 { "arg4", BUILTIN, B_AT_BI_ARG4 }, 495 { "arg5", BUILTIN, B_AT_BI_ARG5 }, 496 { "arg6", BUILTIN, B_AT_BI_ARG6 }, 497 { "arg7", BUILTIN, B_AT_BI_ARG7 }, 498 { "arg8", BUILTIN, B_AT_BI_ARG8 }, 499 { "arg9", BUILTIN, B_AT_BI_ARG9 }, 500 { "clear", FUNC1, B_AC_CLEAR }, 501 { "comm", BUILTIN, B_AT_BI_COMM }, 502 { "count", MOP0, B_AT_MF_COUNT }, 503 { "cpu", BUILTIN, B_AT_BI_CPU }, 504 { "delete", F_DELETE, B_AC_DELETE }, 505 { "exit", FUNC0, B_AC_EXIT }, 506 { "hz", HZ, 0 }, 507 { "kstack", BUILTIN, B_AT_BI_KSTACK }, 508 { "max", MOP1, B_AT_MF_MAX }, 509 { "min", MOP1, B_AT_MF_MIN }, 510 { "nsecs", BUILTIN, B_AT_BI_NSECS }, 511 { "pid", PID, 0 /*B_AT_BI_PID*/ }, 512 { "print", FUNCN, B_AC_PRINT }, 513 { "printf", FUNCN, B_AC_PRINTF }, 514 { "retval", BUILTIN, B_AT_BI_RETVAL }, 515 { "sum", MOP1, B_AT_MF_SUM }, 516 { "tid", TID, 0 /*B_AT_BI_TID*/ }, 517 { "time", FUNC1, B_AC_TIME }, 518 { "ustack", BUILTIN, B_AT_BI_USTACK }, 519 { "zero", FUNC1, B_AC_ZERO }, 520 }; 521 522 return bsearch(s, kws, nitems(kws), sizeof(kws[0]), kw_cmp); 523} 524 525int 526peek(void) 527{ 528 if (pbuf != NULL) { 529 if (pindex < plen) 530 return pbuf[pindex]; 531 } 532 return EOF; 533} 534 535int 536lgetc(void) 537{ 538 if (pbuf != NULL) { 539 if (pindex < plen) { 540 yylval.colno++; 541 return pbuf[pindex++]; 542 } 543 } 544 return EOF; 545} 546 547void 548lungetc(void) 549{ 550 if (pbuf != NULL && pindex > 0) { 551 yylval.colno--; 552 pindex--; 553 } 554} 555 556int 557yylex(void) 558{ 559 unsigned char buf[1024]; 560 unsigned char *ebuf, *p, *str; 561 int c; 562 563 ebuf = buf + sizeof(buf); 564 p = buf; 565 566again: 567 /* skip whitespaces */ 568 for (c = lgetc(); isspace(c); c = lgetc()) { 569 if (c == '\n') { 570 yylval.lineno++; 571 yylval.colno = 0; 572 } 573 } 574 575 /* skip single line comments and shell magic */ 576 if ((c == '/' && peek() == '/') || 577 (yylval.lineno == 1 && yylval.colno == 1 && c == '#' && 578 peek() == '!')) { 579 for (c = lgetc(); c != EOF; c = lgetc()) { 580 if (c == '\n') { 581 yylval.lineno++; 582 yylval.colno = 0; 583 goto again; 584 } 585 } 586 } 587 588 /* skip multi line comments */ 589 if (c == '/' && peek() == '*') { 590 int pc; 591 592 for (pc = 0, c = lgetc(); c != EOF; c = lgetc()) { 593 if (pc == '*' && c == '/') 594 goto again; 595 pc = c; 596 } 597 } 598 599 switch (c) { 600 case '=': 601 if (peek() == '=') 602 break; 603 case ',': 604 case '(': 605 case ')': 606 case '{': 607 case '}': 608 case ':': 609 case ';': 610 case '/': 611 return c; 612 case EOF: 613 return 0; 614 case '"': 615 /* parse C-like string */ 616 while ((c = lgetc()) != EOF && c != '"') { 617 if (c == '\\') { 618 c = lgetc(); 619 switch (c) { 620 case '\\': c = '\\'; break; 621 case '\'': c = '\''; break; 622 case '"': c = '"'; break; 623 case 'a': c = '\a'; break; 624 case 'b': c = '\b'; break; 625 case 'e': c = 033; break; 626 case 'f': c = '\f'; break; 627 case 'n': c = '\n'; break; 628 case 'r': c = '\r'; break; 629 case 't': c = '\t'; break; 630 case 'v': c = '\v'; break; 631 default: 632 yyerror("'%c' unsuported escape", c); 633 return ERROR; 634 } 635 } 636 *p++ = c; 637 if (p == ebuf) { 638 yyerror("too long line"); 639 return ERROR; 640 } 641 } 642 if (c == EOF) { 643 yyerror("\"%s\" invalid EOF", buf); 644 return ERROR; 645 } 646 *p++ = '\0'; 647 if ((str = strdup(buf)) == NULL) 648 err(1, "%s", __func__); 649 yylval.v.string = str; 650 return CSTRING; 651 default: 652 break; 653 } 654 655#define allowed_to_end_number(x) \ 656 (isspace(x) || x == ')' || x == '/' || x == '{' || x == ';' || x == ']' || x == ',') 657 658 /* parsing number */ 659 if (isdigit(c)) { 660 do { 661 *p++ = c; 662 if (p == ebuf) { 663 yyerror("too long line"); 664 return ERROR; 665 } 666 } while ((c = lgetc()) != EOF && isdigit(c)); 667 lungetc(); 668 if (c == EOF || allowed_to_end_number(c)) { 669 const char *errstr = NULL; 670 671 *p = '\0'; 672 yylval.v.number = strtonum(buf, LONG_MIN, LONG_MAX, 673 &errstr); 674 if (errstr) { 675 yyerror("invalid number '%s' (%s)", buf, 676 errstr); 677 return ERROR; 678 } 679 return NUMBER; 680 } else { 681 while (p > buf + 1) { 682 --p; 683 lungetc(); 684 } 685 c = *--p; 686 } 687 } 688 689#define allowed_in_string(x) (isalnum(c) || c == '!' || c == '=' || c == '_') 690 691 /* parsing next word */ 692 if (allowed_in_string(c)) { 693 struct keyword *kwp; 694 do { 695 *p++ = c; 696 if (p == ebuf) { 697 yyerror("too long line"); 698 return ERROR; 699 } 700 } while ((c = lgetc()) != EOF && (allowed_in_string(c))); 701 lungetc(); 702 *p = '\0'; 703 kwp = lookup(buf); 704 if (kwp == NULL) { 705 if ((yylval.v.string = strdup(buf)) == NULL) 706 err(1, "%s", __func__); 707 return STRING; 708 } 709 yylval.v.i = kwp->type; 710 return kwp->token; 711 } 712 713 if (c == '\n') { 714 yylval.lineno++; 715 yylval.colno = 0; 716 } 717 if (c == EOF) 718 return 0; 719 return c; 720} 721 722void 723pprint_syntax_error(void) 724{ 725 char line[BUFSIZ]; 726 int c, indent = yylval.colno; 727 size_t i; 728 729 strlcpy(line, &pbuf[pindex - yylval.colno], sizeof(line)); 730 731 for (i = 0; line[i] != '\0' && (c = line[i]) != '\n'; i++) { 732 if (c == '\t') 733 indent += (8 - 1); 734 fputc(c, stderr); 735 } 736 737 fprintf(stderr, "\n%*c\n", indent, '^'); 738} 739 740void 741yyerror(const char *fmt, ...) 742{ 743 const char *prefix; 744 va_list va; 745 746 prefix = (yylval.filename != NULL) ? yylval.filename : getprogname(); 747 748 fprintf(stderr, "%s:%d:%d: ", prefix, yylval.lineno, yylval.colno); 749 va_start(va, fmt); 750 vfprintf(stderr, fmt, va); 751 va_end(va); 752 fprintf(stderr, ":\n"); 753 754 pprint_syntax_error(); 755 756 perrors++; 757} 758 759int 760btparse(const char *str, size_t len, const char *filename, int debug) 761{ 762 if (debug > 0) 763 yydebug = 1; 764 pbuf = str; 765 plen = len; 766 pindex = 0; 767 yylval.filename = filename; 768 yylval.lineno = 1; 769 770 yyparse(); 771 772 return perrors; 773} 774