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