1/*	$NetBSD: mac_parse.c,v 1.2 2017/02/14 01:16:49 christos Exp $	*/
2
3/*++
4/* NAME
5/*	mac_parse 3
6/* SUMMARY
7/*	locate macro references in string
8/* SYNOPSIS
9/*	#include <mac_parse.h>
10/*
11/*	int	mac_parse(string, action, context)
12/*	const char *string;
13/*	int	(*action)(int type, VSTRING *buf, void *context);
14/* DESCRIPTION
15/*	This module recognizes macro expressions in null-terminated
16/*	strings.  Macro expressions have the form $name, $(text) or
17/*	${text}. A macro name consists of alphanumerics and/or
18/*	underscore. Text other than macro expressions is treated
19/*	as literal text.
20/*
21/*	mac_parse() breaks up its string argument into macro references
22/*	and other text, and invokes the \fIaction\fR routine for each item
23/*	found.  With each action routine call, the \fItype\fR argument
24/*	indicates what was found, \fIbuf\fR contains a copy of the text
25/*	found, and \fIcontext\fR is passed on unmodified from the caller.
26/*	The application is at liberty to clobber \fIbuf\fR.
27/* .IP MAC_PARSE_LITERAL
28/*	The content of \fIbuf\fR is literal text.
29/* .IP MAC_PARSE_EXPR
30/*	The content of \fIbuf\fR is a macro expression: either a
31/*	bare macro name without the preceding "$", or all the text
32/*	inside $() or ${}.
33/* .PP
34/*	The action routine result value is the bit-wise OR of zero or more
35/*	of the following:
36/* .IP	MAC_PARSE_ERROR
37/*	A parsing error was detected.
38/* .IP	MAC_PARSE_UNDEF
39/*	A macro was expanded but not defined.
40/* .PP
41/*	Use the constant MAC_PARSE_OK when no error was detected.
42/* SEE ALSO
43/*	dict(3) dictionary interface.
44/* DIAGNOSTICS
45/*	Fatal errors: out of memory. malformed macro name.
46/*
47/*	The result value is the bit-wise OR of zero or more of the
48/*	following:
49/* .IP	MAC_PARSE_ERROR
50/*	A parsing error was detected.
51/* .IP	MAC_PARSE_UNDEF
52/*	A macro was expanded but not defined.
53/* LICENSE
54/* .ad
55/* .fi
56/*	The Secure Mailer license must be distributed with this software.
57/* AUTHOR(S)
58/*	Wietse Venema
59/*	IBM T.J. Watson Research
60/*	P.O. Box 704
61/*	Yorktown Heights, NY 10598, USA
62/*--*/
63
64/* System library. */
65
66#include <sys_defs.h>
67#include <ctype.h>
68
69/* Utility library. */
70
71#include <msg.h>
72#include <mac_parse.h>
73
74 /*
75  * Helper macro for consistency. Null-terminate the temporary buffer,
76  * execute the action, and reset the temporary buffer for re-use.
77  */
78#define MAC_PARSE_ACTION(status, type, buf, context) \
79	do { \
80	    VSTRING_TERMINATE(buf); \
81	    status |= action((type), (buf), (context)); \
82	    VSTRING_RESET(buf); \
83	} while(0)
84
85/* mac_parse - split string into literal text and macro references */
86
87int     mac_parse(const char *value, MAC_PARSE_FN action, void *context)
88{
89    const char *myname = "mac_parse";
90    VSTRING *buf = vstring_alloc(1);	/* result buffer */
91    const char *vp;			/* value pointer */
92    const char *pp;			/* open_paren pointer */
93    const char *ep;			/* string end pointer */
94    static char open_paren[] = "({";
95    static char close_paren[] = ")}";
96    int     level;
97    int     status = 0;
98
99#define SKIP(start, var, cond) do { \
100        for (var = start; *var && (cond); var++) \
101	    /* void */; \
102    } while (0)
103
104    if (msg_verbose > 1)
105	msg_info("%s: %s", myname, value);
106
107    for (vp = value; *vp;) {
108	if (*vp != '$') {			/* ordinary character */
109	    VSTRING_ADDCH(buf, *vp);
110	    vp += 1;
111	} else if (vp[1] == '$') {		/* $$ becomes $ */
112	    VSTRING_ADDCH(buf, *vp);
113	    vp += 2;
114	} else {				/* found bare $ */
115	    if (VSTRING_LEN(buf) > 0)
116		MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context);
117	    vp += 1;
118	    pp = open_paren;
119	    if (*vp == *pp || *vp == *++pp) {	/* ${x} or $(x) */
120		level = 1;
121		vp += 1;
122		for (ep = vp; level > 0; ep++) {
123		    if (*ep == 0) {
124			msg_warn("truncated macro reference: \"%s\"", value);
125			status |= MAC_PARSE_ERROR;
126			break;
127		    }
128		    if (*ep == *pp)
129			level++;
130		    if (*ep == close_paren[pp - open_paren])
131			level--;
132		}
133		if (status & MAC_PARSE_ERROR)
134		    break;
135		vstring_strncat(buf, vp, level > 0 ? ep - vp : ep - vp - 1);
136		vp = ep;
137	    } else {				/* plain $x */
138		SKIP(vp, ep, ISALNUM(*ep) || *ep == '_');
139		vstring_strncat(buf, vp, ep - vp);
140		vp = ep;
141	    }
142	    if (VSTRING_LEN(buf) == 0) {
143		status |= MAC_PARSE_ERROR;
144		msg_warn("empty macro name: \"%s\"", value);
145		break;
146	    }
147	    MAC_PARSE_ACTION(status, MAC_PARSE_EXPR, buf, context);
148	}
149    }
150    if (VSTRING_LEN(buf) > 0 && (status & MAC_PARSE_ERROR) == 0)
151	MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context);
152
153    /*
154     * Cleanup.
155     */
156    vstring_free(buf);
157
158    return (status);
159}
160
161#ifdef TEST
162
163 /*
164  * Proof-of-concept test program. Read strings from stdin, print parsed
165  * result to stdout.
166  */
167#include <vstring_vstream.h>
168
169/* mac_parse_print - print parse tree */
170
171static int mac_parse_print(int type, VSTRING *buf, void *unused_context)
172{
173    char   *type_name;
174
175    switch (type) {
176    case MAC_PARSE_EXPR:
177	type_name = "MAC_PARSE_EXPR";
178	break;
179    case MAC_PARSE_LITERAL:
180	type_name = "MAC_PARSE_LITERAL";
181	break;
182    default:
183	msg_panic("unknown token type %d", type);
184    }
185    vstream_printf("%s \"%s\"\n", type_name, vstring_str(buf));
186    return (0);
187}
188
189int     main(int unused_argc, char **unused_argv)
190{
191    VSTRING *buf = vstring_alloc(1);
192
193    while (vstring_fgets_nonl(buf, VSTREAM_IN)) {
194	mac_parse(vstring_str(buf), mac_parse_print, (void *) 0);
195	vstream_fflush(VSTREAM_OUT);
196    }
197    vstring_free(buf);
198    return (0);
199}
200
201#endif
202