bt_parse.y revision 1.11
1/*	$OpenBSD: bt_parse.y,v 1.11 2020/04/23 14:54:12 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_fn(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	ARG0 ARG1 ARG2 ARG3 ARG4 ARG5 ARG6 ARG7 ARG8 ARG9
104%token	COMM HZ KSTACK USTACK NSECS PID RETVAL TID
105/* Functions */
106%token  F_CLEAR F_DELETE F_EXIT F_PRINT F_PRINTF F_TIME F_ZERO
107/* Map functions */
108%token  M_COUNT M_MAX M_MIN M_SUM
109%token	<v.string>	STRING CSTRING
110%token	<v.number>	NUMBER
111%type	<v.string>	gvar
112%type	<v.i>		filterval oper builtin fn0 fn1 fnN mfn0 mfn1 mfnN
113%type	<v.probe>	probe
114%type	<v.filter>	predicate
115%type	<v.stmt>	action stmt stmtlist
116%type	<v.arg>		arg arglist map marg term
117%type	<v.rtype>	beginend
118
119%left	'+' '-'
120%left	'/' '*'
121%%
122
123grammar		: /* empty */
124		| grammar '\n'
125		| grammar rule
126		| grammar error
127		;
128
129rule		: beginend action	 { br_new(NULL, NULL, $2, $1); }
130		| probe predicate action { br_new($1, $2, $3, B_RT_PROBE); }
131		;
132
133beginend	: BEGIN				{ $$ = B_RT_BEGIN; }
134		| END				{ $$ = B_RT_END; }
135		;
136
137probe		: STRING ':' STRING ':' STRING	{ $$ = bp_new($1, $3, $5, 0); }
138		| STRING ':' HZ ':' NUMBER	{ $$ = bp_new($1, "hz", NULL, $5); }
139		;
140
141
142filterval	: PID				{ $$ = B_FV_PID; }
143		| TID				{ $$ = B_FV_TID; }
144		;
145
146oper		: OP_EQ				{ $$ = B_OP_EQ; }
147		| OP_NEQ			{ $$ = B_OP_NE; }
148		;
149
150predicate	: /* empty */			{ $$ = NULL; }
151		| '/' filterval oper NUMBER '/' { $$ = bf_new($3, $2, $4); }
152		| '/' NUMBER oper filterval '/' { $$ = bf_new($3, $4, $2); }
153		;
154
155builtin		: PID 				{ $$ = B_AT_BI_PID; }
156		| TID 				{ $$ = B_AT_BI_TID; }
157		| COMM 				{ $$ = B_AT_BI_COMM; }
158		| NSECS 			{ $$ = B_AT_BI_NSECS; }
159		| KSTACK			{ $$ = B_AT_BI_KSTACK; }
160		| USTACK			{ $$ = B_AT_BI_USTACK; }
161		| ARG0				{ $$ = B_AT_BI_ARG0; }
162		| ARG1				{ $$ = B_AT_BI_ARG1; }
163		| ARG2				{ $$ = B_AT_BI_ARG2; }
164		| ARG3				{ $$ = B_AT_BI_ARG3; }
165		| ARG4				{ $$ = B_AT_BI_ARG4; }
166		| ARG5				{ $$ = B_AT_BI_ARG5; }
167		| ARG6				{ $$ = B_AT_BI_ARG6; }
168		| ARG7				{ $$ = B_AT_BI_ARG7; }
169		| ARG8				{ $$ = B_AT_BI_ARG8; }
170		| ARG9				{ $$ = B_AT_BI_ARG9; }
171		| RETVAL			{ $$ = B_AT_BI_RETVAL; }
172		;
173
174fn0		: F_EXIT			{ $$ = B_AC_EXIT; }
175		;
176
177fn1		: F_CLEAR			{ $$ = B_AC_CLEAR; }
178		| F_TIME			{ $$ = B_AC_TIME; }
179		| F_ZERO			{ $$ = B_AC_ZERO; }
180		;
181
182fnN		: F_PRINTF			{ $$ = B_AC_PRINTF; }
183		| F_PRINT			{ $$ = B_AC_PRINT; }
184		;
185
186mfn0		: M_COUNT 			{ $$ = B_AT_MF_COUNT; }
187		;
188
189mfn1		: F_DELETE			{ $$ = B_AC_DELETE; }
190		;
191
192mfnN		: M_MAX				{ $$ = B_AT_MF_MAX; }
193		| M_MIN				{ $$ = B_AT_MF_MIN; }
194		| M_SUM				{ $$ = B_AT_MF_SUM; }
195		;
196
197term		: '(' term ')'			{ $$ = $2; }
198		| term '+' term			{ $$ = ba_op('+', $1, $3); }
199		| term '-' term			{ $$ = ba_op('-', $1, $3); }
200		| term '/' term			{ $$ = ba_op('/', $1, $3); }
201		| term '*' term			{ $$ = ba_op('*', $1, $3); }
202		| NUMBER			{ $$ = ba_new($1, B_AT_LONG); }
203		| builtin			{ $$ = ba_new(NULL, $1); }
204		| gvar				{ $$ = bv_get($1); }
205		| map				{ $$ = $1; }
206		;
207
208gvar		: '@' STRING			{ $$ = $2; }
209		| '@'				{ $$ = UNNAMED_MAP; }
210
211map		: gvar '[' arglist ']'		{ $$ = bm_get($1, $3); }
212		;
213
214marg		: arg				{ $$ = $1; }
215		| mfn0 '(' ')'			{ $$ = ba_new(NULL, $1); }
216		| mfnN '(' arg ')'		{ $$ = ba_new($3, $1); }
217		;
218
219arg		: CSTRING			{ $$ = ba_new($1, B_AT_STR); }
220		| term
221		;
222
223arglist		: arg
224		| arglist ',' arg		{ $$ = ba_append($1, $3); }
225		;
226
227NL		: /* empty */ | '\n'
228		;
229
230stmt		: ';' NL			{ $$ = NULL; }
231		| gvar '=' arg			{ $$ = bv_set($1, $3); }
232		| gvar '[' arglist ']' '=' marg	{ $$ = bm_set($1, $3, $6); }
233		| fnN '(' arglist ')'		{ $$ = bs_new($1, $3, NULL); }
234		| fn1 '(' arg ')'		{ $$ = bs_new($1, $3, NULL); }
235		| fn0 '(' ')'			{ $$ = bs_new($1, NULL, NULL); }
236		| mfn1 '(' map ')'		{ $$ = bm_fn($1, $3, NULL); }
237		;
238
239stmtlist	: stmt
240		| stmtlist stmt			{ $$ = bs_append($1, $2); }
241		;
242
243action		: '{' stmtlist '}'		{ $$ = $2; }
244		;
245
246%%
247
248/* Create a new rule, representing  "probe / filter / { action }" */
249struct bt_rule *
250br_new(struct bt_probe *probe, struct bt_filter *filter, struct bt_stmt *head,
251    enum bt_rtype rtype)
252{
253	struct bt_rule *br;
254
255	br = calloc(1, sizeof(struct bt_rule));
256	if (br == NULL)
257		err(1, "bt_rule: calloc");
258	br->br_probe = probe;
259	br->br_filter = filter;
260	/* SLIST_INSERT_HEAD() nullify the next pointer. */
261	SLIST_FIRST(&br->br_action) = head;
262	br->br_type = rtype;
263
264	if (rtype == B_RT_PROBE) {
265		g_nprobes++;
266		TAILQ_INSERT_TAIL(&g_rules, br, br_next);
267	} else {
268		TAILQ_INSERT_HEAD(&g_rules, br, br_next);
269	}
270
271	return br;
272}
273
274/* Create a new filter */
275struct bt_filter *
276bf_new(enum bt_operand op, enum bt_filtervar var, int val)
277{
278	struct bt_filter *df;
279
280	if (val < 0 || val > INT_MAX)
281		errx(1, "invalid pid '%d'", val);
282
283	df = calloc(1, sizeof(struct bt_filter));
284	if (df == NULL)
285		err(1, "bt_filter: calloc");
286	df->bf_op = op;
287	df->bf_var = var;
288	df->bf_val = val;
289
290	return df;
291}
292
293/* Create a new probe */
294struct bt_probe *
295bp_new(const char *prov, const char *func, const char *name, int32_t rate)
296{
297	struct bt_probe *bp;
298
299	if (rate < 0 || rate > INT32_MAX)
300		errx(1, "only positive values permitted");
301
302	bp = calloc(1, sizeof(struct bt_probe));
303	if (bp == NULL)
304		err(1, "bt_probe: calloc");
305	bp->bp_prov = prov;
306	bp->bp_func = func;
307	bp->bp_name = name;
308	bp->bp_rate = rate;
309
310	return bp;
311}
312
313/* Create a new argument */
314struct bt_arg *
315ba_new0(void *val, enum bt_argtype type)
316{
317	struct bt_arg *ba;
318
319	ba = calloc(1, sizeof(struct bt_arg));
320	if (ba == NULL)
321		err(1, "bt_arg: calloc");
322	ba->ba_value = val;
323	ba->ba_type = type;
324
325	return ba;
326}
327
328/*
329 * Link two arguments together, to build an argument list used in
330 * function calls.
331 */
332struct bt_arg *
333ba_append(struct bt_arg *da0, struct bt_arg *da1)
334{
335	struct bt_arg *ba = da0;
336
337	assert(da1 != NULL);
338
339	if (da0 == NULL)
340		return da1;
341
342	while (SLIST_NEXT(ba, ba_next) != NULL)
343		ba = SLIST_NEXT(ba, ba_next);
344
345	SLIST_INSERT_AFTER(ba, da1, ba_next);
346
347	return da0;
348}
349
350/* Create an operator argument */
351struct bt_arg *
352ba_op(const char op, struct bt_arg *da0, struct bt_arg *da1)
353{
354	enum bt_argtype type;
355
356	switch (op) {
357	case '+':
358		type = B_AT_OP_ADD;
359		break;
360	case '-':
361		type = B_AT_OP_MINUS;
362		break;
363	case '*':
364		type = B_AT_OP_MULT;
365		break;
366	case '/':
367		type = B_AT_OP_DIVIDE;
368		break;
369	default:
370		assert(0);
371	}
372
373	return ba_new(ba_append(da0, da1), type);
374}
375
376/* Create a new statement: function call or assignment. */
377struct bt_stmt *
378bs_new(enum bt_action act, struct bt_arg *head, struct bt_var *var)
379{
380	struct bt_stmt *bs;
381
382	bs = calloc(1, sizeof(struct bt_stmt));
383	if (bs == NULL)
384		err(1, "bt_stmt: calloc");
385	bs->bs_act = act;
386	bs->bs_var = var;
387	/* SLIST_INSERT_HEAD() nullify the next pointer. */
388	SLIST_FIRST(&bs->bs_args) = head;
389
390	return bs;
391}
392
393/* Link two statements together, to build an 'action'. */
394struct bt_stmt *
395bs_append(struct bt_stmt *ds0, struct bt_stmt *ds1)
396{
397	struct bt_stmt *bs = ds0;
398
399	if (ds0 == NULL)
400		return ds1;
401
402	if (ds1 == NULL)
403		return ds0;
404
405	while (SLIST_NEXT(bs, bs_next) != NULL)
406		bs = SLIST_NEXT(bs, bs_next);
407
408	SLIST_INSERT_AFTER(bs, ds1, bs_next);
409
410	return ds0;
411}
412
413const char *
414bv_name(struct bt_var *bv)
415{
416	if (strncmp(bv->bv_name, UNNAMED_MAP, strlen(UNNAMED_MAP)) == 0)
417		return "";
418	return bv->bv_name;
419}
420
421/* Return the global variable corresponding to `vname'. */
422struct bt_var *
423bv_find(const char *vname)
424{
425	struct bt_var *bv;
426
427	SLIST_FOREACH(bv, &g_variables, bv_next) {
428		if (strcmp(vname, bv->bv_name) == 0)
429			break;
430	}
431
432	return bv;
433}
434
435/* Find or allocate a global variable. */
436struct bt_var *
437bv_new(const char *vname)
438{
439	struct bt_var *bv;
440
441	bv = calloc(1, sizeof(struct bt_var));
442	if (bv == NULL)
443		err(1, "bt_var: calloc");
444	bv->bv_name = vname;
445	SLIST_INSERT_HEAD(&g_variables, bv, bv_next);
446
447	return bv;
448}
449
450/* Create a 'variable store' statement to assign a value to a variable. */
451struct bt_stmt *
452bv_set(const char *vname, struct bt_arg *vval)
453{
454	struct bt_var *bv;
455
456	bv = bv_find(vname);
457	if (bv == NULL)
458		bv = bv_new(vname);
459	return bs_new(B_AC_STORE, vval, bv);
460}
461
462/* Create an argument that points to a variable. */
463struct bt_arg *
464bv_get(const char *vname)
465{
466	struct bt_var *bv;
467
468	bv = bv_find(vname);
469	if (bv == NULL)
470		yyerror("variable '%s' accessed before being set", vname);
471
472	return ba_new(bv, B_AT_VAR);
473}
474
475struct bt_stmt *
476bm_fn(enum bt_action mact, struct bt_arg *ba, struct bt_arg *mval)
477{
478	return bs_new(mact, ba, (struct bt_var *)mval);
479}
480
481/* Create a 'map store' statement to assign a value to a map entry. */
482struct bt_stmt *
483bm_set(const char *mname, struct bt_arg *mkey, struct bt_arg *mval)
484{
485	struct bt_arg *ba;
486	struct bt_var *bv;
487
488	bv = bv_find(mname);
489	if (bv == NULL)
490		bv = bv_new(mname);
491	ba = ba_new(bv, B_AT_MAP);
492	ba->ba_key = mkey;
493	return bs_new(B_AC_INSERT, ba, (struct bt_var *)mval);
494}
495
496/* Create an argument that points to a variable and attach a key to it. */
497struct bt_arg *
498bm_get(const char *mname, struct bt_arg *mkey)
499{
500	struct bt_arg *ba;
501
502	ba = bv_get(mname);
503	ba->ba_type = B_AT_MAP;
504	ba->ba_key = mkey;
505	return ba;
506}
507
508struct keyword {
509	const char	*word;
510	int		 token;
511};
512
513int
514kw_cmp(const void *str, const void *xkw)
515{
516	return (strcmp(str, ((const struct keyword *)xkw)->word));
517}
518
519int
520lookup(char *s)
521{
522	static const struct keyword kws[] = {
523		{ "!=",		OP_NEQ },
524		{ "==",		OP_EQ },
525		{ "BEGIN",	BEGIN },
526		{ "END",	END },
527		{ "arg0",	ARG0 },
528		{ "arg1",	ARG1 },
529		{ "arg2",	ARG2 },
530		{ "arg3",	ARG3 },
531		{ "arg4",	ARG4 },
532		{ "arg5",	ARG5 },
533		{ "arg6",	ARG6 },
534		{ "arg7",	ARG7 },
535		{ "arg8",	ARG8 },
536		{ "arg9",	ARG9 },
537		{ "clear",	F_CLEAR },
538		{ "comm",	COMM },
539		{ "count",	M_COUNT },
540		{ "delete",	F_DELETE },
541		{ "exit",	F_EXIT },
542		{ "hz",		HZ },
543		{ "kstack",	KSTACK },
544		{ "max",	M_MAX },
545		{ "min",	M_MIN },
546		{ "nsecs",	NSECS },
547		{ "pid",	PID },
548		{ "print",	F_PRINT },
549		{ "printf",	F_PRINTF },
550		{ "retval",	RETVAL },
551		{ "sum",	M_SUM },
552		{ "tid",	TID },
553		{ "time",	F_TIME },
554		{ "ustack",	USTACK },
555		{ "zero",	F_ZERO },
556	};
557	const struct keyword	*kw;
558
559	kw = bsearch(s, kws, nitems(kws), sizeof(kws[0]), kw_cmp);
560	if (kw != NULL) {
561		return kw->token;
562	} else {
563		return STRING;
564	}
565}
566
567int
568peek(void)
569{
570	if (pbuf != NULL) {
571		if (pindex < plen)
572			return pbuf[pindex];
573	}
574	return EOF;
575}
576
577int
578lgetc(void)
579{
580	if (pbuf != NULL) {
581		if (pindex < plen) {
582			yylval.colno++;
583			return pbuf[pindex++];
584		}
585	}
586	return EOF;
587}
588
589void
590lungetc(void)
591{
592	if (pbuf != NULL && pindex > 0) {
593		yylval.colno--;
594		pindex--;
595	}
596}
597
598int
599yylex(void)
600{
601	unsigned char	 buf[1024];
602	unsigned char	*ebuf, *p, *str;
603	int		 c, token;
604
605	ebuf = buf + sizeof(buf);
606	p = buf;
607
608again:
609	/* skip whitespaces */
610	for (c = lgetc(); isspace(c); c = lgetc()) {
611		if (c == '\n') {
612			yylval.lineno++;
613			yylval.colno = 0;
614		}
615	}
616
617	/* skip single line comments and shell magic */
618	if ((c == '/' && peek() == '/') ||
619	    (yylval.lineno == 1 && yylval.colno == 1 && c == '#' &&
620	     peek() == '!')) {
621		for (c = lgetc(); c != EOF; c = lgetc()) {
622			if (c == '\n') {
623				yylval.lineno++;
624				yylval.colno = 0;
625				goto again;
626			}
627		}
628	}
629
630	/* skip multi line comments */
631	if (c == '/' && peek() == '*') {
632		int pc;
633
634		for (pc = 0, c = lgetc(); c != EOF; c = lgetc()) {
635			if (pc == '*' && c == '/')
636				goto again;
637			pc = c;
638		}
639	}
640
641	switch (c) {
642	case '=':
643		if (peek() == '=')
644			break;
645	case ',':
646	case '(':
647	case ')':
648	case '{':
649	case '}':
650	case ':':
651	case ';':
652	case '/':
653		return c;
654	case EOF:
655		return 0;
656	case '"':
657		/* parse C-like string */
658		while ((c = lgetc()) != EOF && c != '"') {
659			if (c == '\\') {
660				c = lgetc();
661				switch (c) {
662				case '\\':	c = '\\';	break;
663				case '\'':	c = '\'';	break;
664				case '"':	c = '"';	break;
665				case 'a':	c = '\a';	break;
666				case 'b':	c = '\b';	break;
667				case 'e':	c = 033;	break;
668				case 'f':	c = '\f';	break;
669				case 'n':	c = '\n';	break;
670				case 'r':	c = '\r';	break;
671				case 't':	c = '\t';	break;
672				case 'v':	c = '\v';	break;
673				default:
674					yyerror("'%c' unsuported escape", c);
675					return ERROR;
676				}
677			}
678			*p++ = c;
679			if (p == ebuf) {
680				yyerror("too long line");
681				return ERROR;
682			}
683		}
684		if (c == EOF) {
685			yyerror("\"%s\" invalid EOF", buf);
686			return ERROR;
687		}
688		*p++ = '\0';
689		if ((str = strdup(buf)) == NULL)
690			err(1, "%s", __func__);
691		yylval.v.string = str;
692		return CSTRING;
693	default:
694		break;
695	}
696
697#define allowed_to_end_number(x) \
698    (isspace(x) || x == ')' || x == '/' || x == '{' || x == ';' || x == ']' || x == ',')
699
700	/* parsing number */
701	if (isdigit(c)) {
702		do {
703			*p++ = c;
704			if (p == ebuf) {
705				yyerror("too long line");
706				return ERROR;
707			}
708		} while ((c = lgetc()) != EOF && isdigit(c));
709		lungetc();
710		if (c == EOF || allowed_to_end_number(c)) {
711			const char *errstr = NULL;
712
713			*p = '\0';
714			yylval.v.number = strtonum(buf, LONG_MIN, LONG_MAX,
715			    &errstr);
716			if (errstr) {
717				yyerror("invalid number '%s' (%s)", buf,
718				    errstr);
719				return ERROR;
720			}
721			return NUMBER;
722		} else {
723			while (p > buf + 1) {
724				--p;
725				lungetc();
726			}
727			c = *--p;
728		}
729	}
730
731#define allowed_in_string(x) (isalnum(c) || c == '!' || c == '=' || c == '_')
732
733	/* parsing next word */
734	if (allowed_in_string(c)) {
735		do {
736			*p++ = c;
737			if (p == ebuf) {
738				yyerror("too long line");
739				return ERROR;
740			}
741		} while ((c = lgetc()) != EOF && (allowed_in_string(c)));
742		lungetc();
743		*p = '\0';
744		if ((token = lookup(buf)) == STRING)
745			if ((yylval.v.string = strdup(buf)) == NULL)
746				err(1, "%s", __func__);
747		return token;
748	}
749
750	if (c == '\n') {
751		yylval.lineno++;
752		yylval.colno = 0;
753	}
754	if (c == EOF)
755		return 0;
756	return c;
757}
758
759void
760pprint_syntax_error(void)
761{
762	char line[BUFSIZ];
763	int c, indent = yylval.colno;
764	size_t i;
765
766	strlcpy(line, &pbuf[pindex - yylval.colno], sizeof(line));
767
768	for (i = 0; line[i] != '\0' && (c = line[i]) != '\n'; i++) {
769		if (c == '\t')
770			indent += (8 - 1);
771		fputc(c, stderr);
772	}
773
774	fprintf(stderr, "\n%*c\n", indent, '^');
775}
776
777void
778yyerror(const char *fmt, ...)
779{
780	const char *prefix;
781	va_list	va;
782
783	prefix = (yylval.filename != NULL) ? yylval.filename : getprogname();
784
785	fprintf(stderr, "%s:%d:%d: ", prefix, yylval.lineno, yylval.colno);
786	va_start(va, fmt);
787	vfprintf(stderr, fmt, va);
788	va_end(va);
789	fprintf(stderr, ":\n");
790
791	pprint_syntax_error();
792
793	perrors++;
794}
795
796int
797btparse(const char *str, size_t len, const char *filename, int debug)
798{
799	if (debug > 0)
800		yydebug = 1;
801	pbuf = str;
802	plen = len;
803	pindex = 0;
804	yylval.filename = filename;
805	yylval.lineno = 1;
806
807	yyparse();
808
809	return perrors;
810}
811