1
2 /**
3 * @file loadPseudo.c
4 *
5 * Find the start and end macro markers. In btween we must find the
6 * "autogen" and "template" keywords, followed by any suffix specs.
7 *
8 * This module processes the "pseudo" macro.
9 *
10 * @addtogroup autogen
11 * @{
12 */
13 /*
14 * This file is part of AutoGen.
15 * AutoGen Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
16 *
17 * AutoGen is free software: you can redistribute it and/or modify it
18 * under the terms of the GNU General Public License as published by the
19 * Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * AutoGen is distributed in the hope that it will be useful, but
23 * WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
25 * See the GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License along
28 * with this program. If not, see <http://www.gnu.org/licenses/>.
29 */
30
31 /**
32 * do_scheme_expr
33 *
34 * Process a scheme specification
35 */
36 static char const *
do_scheme_expr(char const * text,char const * fname)37 do_scheme_expr(char const * text, char const * fname)
38 {
39 char * pzEnd = (char *)text + strlen(text);
40 char ch;
41 macro_t * pCM = cur_macro;
42 macro_t mac = { (mac_func_t)~0, 0, 0, 0, 0, 0, 0, NULL };
43
44 mac.md_line = tpl_line;
45 pzEnd = (char *)skip_scheme(text, pzEnd);
46 ch = *pzEnd;
47 *pzEnd = NUL;
48 cur_macro = &mac;
49
50 ag_scm_c_eval_string_from_file_line(
51 (char *)text, fname, tpl_line );
52
53 cur_macro = pCM;
54 *pzEnd = ch;
55 while (text < pzEnd)
56 if (*(text++) == NL)
57 tpl_line++;
58 return (char const *)pzEnd;
59 }
60
61
62 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
63 *
64 * do_suffix
65 *
66 * Process a suffix specification
67 */
68 static char const *
do_suffix(char const * const text,char const * fname,int lineNo)69 do_suffix(char const * const text, char const * fname, int lineNo)
70 {
71 /*
72 * The following is the complete list of POSIX required-to-be-legal
73 * file name characters. These are the only characters we allow to
74 * appear in a suffix. We do, however, add '=' and '%' because we
75 * also allow a format specification to follow the suffix,
76 * separated by an '=' character.
77 */
78 out_spec_t * pOS;
79 char const * pzSfxFmt;
80 char const * pzResult;
81 size_t spn;
82 static out_spec_t ** ppOSList = &output_specs;
83
84 /*
85 * Skip over the suffix construct
86 */
87 pzSfxFmt = SPN_SUFFIX_CHARS(text);
88
89 if (*pzSfxFmt != '=') {
90 pzResult = pzSfxFmt;
91 pzSfxFmt = NULL;
92
93 } else {
94 pzSfxFmt++;
95
96 if (*pzSfxFmt == '(') {
97 char const *pe = pzSfxFmt + strlen(pzSfxFmt);
98 pzResult = skip_scheme(pzSfxFmt, pe);
99
100 } else {
101 pzResult = SPN_SUFFIX_FMT_CHARS(pzSfxFmt);
102
103 if (pzSfxFmt == pzResult)
104 AG_ABEND(DO_SUFFIX_EMPTY);
105 }
106 }
107
108 /*
109 * If fname is NULL, then we are called by --select-suffix.
110 * Otherwise, the suffix construct is saved only for the main template,
111 * and only when the --select-suffix option was not specified.
112 */
113 if ( (fname != NULL)
114 && ( (processing_state != PROC_STATE_LOAD_TPL)
115 || HAVE_OPT(SELECT_SUFFIX)))
116 return pzResult;
117
118 /*
119 * Allocate Output Spec and link into the global list. Copy all the
120 * "spanned" text, including any '=' character, scheme expression or
121 * file name format string.
122 */
123 spn = (size_t)(pzResult - text);
124 {
125 size_t sz = sizeof(*pOS) + spn + 1;
126 pOS = AGALOC(sz, "Output Specification");
127 memset(pOS, NUL, sz);
128 }
129
130 *ppOSList = pOS;
131 ppOSList = &pOS->os_next; /* NULL, from memset */
132 memcpy(pOS->os_sfx, text, spn);
133 pOS->os_sfx[spn] = NUL;
134
135 /*
136 * IF the suffix contains its own formatting construct,
137 * THEN split it off from the suffix and set the formatting ptr.
138 * ELSE supply a default.
139 */
140 if (pzSfxFmt != NULL) {
141 size_t sfx_len = (size_t)(pzSfxFmt - text);
142 pOS->os_sfx[sfx_len-1] = NUL;
143 pOS->os_file_fmt = pOS->os_sfx + sfx_len;
144
145 if (*pOS->os_file_fmt == '(') {
146 SCM str =
147 ag_scm_c_eval_string_from_file_line(
148 pOS->os_file_fmt, fname, lineNo );
149 size_t str_length;
150 char const * pz;
151
152 pzSfxFmt = pz = scm2display(str);
153 str_length = strlen(pzSfxFmt);
154
155 if (str_length == 0)
156 AG_ABEND(DO_SUFFIX_EMPTY);
157 pz = SPN_SUFFIX_FMT_CHARS(pz);
158
159 if ((unsigned)(pz - pzSfxFmt) != str_length)
160 AG_ABEND(aprf(DO_SUFFIX_BAD_CHARS, pz));
161
162 /*
163 * IF the scheme replacement text fits in the space, don't
164 * mess with allocating another string.
165 */
166 if (str_length < spn - sfx_len)
167 strcpy(pOS->os_sfx + sfx_len, pzSfxFmt);
168 else {
169 AGDUPSTR(pOS->os_file_fmt, pzSfxFmt, "suffix format");
170 pOS->os_dealloc_fmt = true;
171 }
172 }
173
174 } else {
175 /*
176 * IF the suffix does not start with punctuation,
177 * THEN we will insert a '.' of our own.
178 */
179 pOS->os_file_fmt = IS_VAR_FIRST_CHAR(pOS->os_sfx[0])
180 ? DOT_SFX_FMT : SFX_FMT;
181 }
182
183 return pzResult;
184 }
185
186 static char const *
handle_hash_line(char const * pz)187 handle_hash_line(char const * pz)
188 {
189 char const * res = strchr(pz, NL);
190 if (res == NULL)
191 AG_ABEND(HANDLE_HASH_BAD_TPL);
192
193 /*
194 * If the comment starts with "#!/", then see if it names
195 * an executable. If it does, it is specifying a shell to use.
196 */
197 if ((pz[1] == '!') && (pz[2] == '/')) {
198 char const * pzScn = pz + 3;
199 char * nmbuf;
200 size_t len;
201
202 pzScn = SPN_FILE_NAME_CHARS(pzScn);
203
204 len = (size_t)(pzScn - (pz + 2));
205 nmbuf = scribble_get((ssize_t)len);
206 memcpy(nmbuf, pz+2, len);
207 nmbuf[len] = NUL;
208
209 /*
210 * If we find the executable, then change the configured shell and
211 * the SHELL environment variable to this executable.
212 */
213 if (access(nmbuf, X_OK) == 0) {
214 char * sp = AGALOC(len + HANDLE_HASH_SHELL_LEN + 1, "set shell");
215 memcpy(sp, HANDLE_HASH_SHELL, HANDLE_HASH_SHELL_LEN);
216 memcpy(sp + HANDLE_HASH_SHELL_LEN, nmbuf, len + 1);
217 putenv(sp);
218 AGDUPSTR(shell_program, nmbuf, "prog shell");
219 AGDUPSTR(server_args[0], nmbuf, "shell name");
220 }
221 }
222
223 return res;
224 }
225
226 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
227 *
228 * next_pm_token
229 *
230 * Skiping leading white space, figure out what sort of token is under
231 * the scan pointer (text).
232 */
233 static te_pm_event
next_pm_token(char const ** ptext,te_pm_state fsm_state,char const * fnm)234 next_pm_token(char const ** ptext, te_pm_state fsm_state, char const * fnm)
235 {
236 char const * text = *ptext;
237
238 /*
239 * At the start of processing in this function, we can never be at
240 * the "start of a line". A '#' type comment before the initial
241 * start macro marker is illegal. Otherwise, our scan pointer is
242 * after some valid token, which won't be the start of a line, either.
243 */
244 bool line_start = false;
245
246 skipWhiteSpace:
247 while (IS_WHITESPACE_CHAR(*text)) {
248 if (*(text++) == NL) {
249 line_start = true;
250 tpl_line++;
251
252 /*
253 * IF we are done with the macro markers,
254 * THEN we skip white space only thru the first new line.
255 */
256 if (fsm_state == PM_ST_END_MARK) {
257 *ptext = text;
258 return PM_EV_END_PSEUDO;
259 }
260 }
261 }
262
263 if (line_start && (*text == '#')) {
264 text = handle_hash_line(text);
265 goto skipWhiteSpace;
266 }
267
268 *ptext = text; /* in case we return */
269
270 /*
271 * After the end marker has been found,
272 * anything else is really the start of the data.
273 */
274 if (fsm_state == PM_ST_END_MARK)
275 return PM_EV_END_PSEUDO;
276
277 /*
278 * IF the token starts with an alphanumeric,
279 * THEN it must be "autogen5" or "template" or a suffix specification
280 */
281 if (IS_VAR_FIRST_CHAR(*text)) {
282 if (strneqvcmp(text, AG_MARK, AG_MARK_LEN) == 0) {
283 if (IS_WHITESPACE_CHAR(text[ AG_MARK_LEN ])) {
284 *ptext = text + AG_MARK_LEN + 1;
285 return PM_EV_AUTOGEN;
286 }
287
288 return PM_EV_SUFFIX;
289 }
290
291 if ( (strneqvcmp(text, TPL_MARK, TPL_MARK_LEN) == 0)
292 && (IS_WHITESPACE_CHAR(text[ TPL_MARK_LEN ])) ) {
293 *ptext = text + TPL_MARK_LEN;
294 return PM_EV_TEMPLATE;
295 }
296
297 return PM_EV_SUFFIX;
298 }
299
300 /*
301 * Handle emacs mode markers and scheme expressions only once we've
302 * gotten past "init" state.
303 */
304 if (fsm_state > PM_ST_INIT)
305 switch (*text) {
306 case '-':
307 if ((text[1] == '*') && (text[2] == '-'))
308 return PM_EV_ED_MODE;
309 break;
310
311 case '(':
312 return PM_EV_SCHEME;
313 }
314
315 /*
316 * Alphanumerics and underscore are already handled. Thus, it must be
317 * a punctuation character that may introduce a suffix: '.' '-' '_'
318 */
319 if (IS_SUFFIX_CHAR(*text))
320 return PM_EV_SUFFIX;
321
322 /*
323 * IF it is some other punctuation,
324 * THEN it must be a start/end marker.
325 */
326 if (IS_PUNCTUATION_CHAR(*text))
327 return PM_EV_MARKER;
328
329 /*
330 * Otherwise, it is just junk.
331 */
332 AG_ABEND(aprf(NEXT_PM_TOKEN_INVALID, fnm));
333 /* NOTREACHED */
334 return PM_EV_INVALID;
335 }
336
337
338 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
339 /**
340 * Some sort of marker is under the scan pointer. Copy it for as long
341 * as we find punctuation characters.
342 */
343 static char const *
copy_mark(char const * text,char * marker,size_t * ret_ct)344 copy_mark(char const * text, char * marker, size_t * ret_ct)
345 {
346 size_t ct = 0;
347
348 for (;;) {
349 char ch = *text;
350 if (! IS_PUNCTUATION_CHAR(ch))
351 break;
352 *(marker++) = ch;
353 if (++ct >= sizeof(st_mac_mark))
354 return NULL;
355
356 text++;
357 }
358
359 *ret_ct = ct;
360 *marker = NUL;
361
362 if (OPT_VALUE_TRACE >= TRACE_EXPRESSIONS)
363 fprintf(trace_fp, TRACE_COPY_MARK, marker - ct);
364
365 return text;
366 }
367
368
369 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
370 /**
371 * Using a finite state machine, scan over the tokens that make up the
372 * "pseudo macro" at the start of every template.
373 *
374 * @param[in,out] text text of template
375 * @param[in] fname name of template file
376 * @returns the address of the byte following the pseudo macro
377 */
378 static char const *
load_pseudo_mac(char const * text,char const * fname)379 load_pseudo_mac(char const * text, char const * fname)
380 {
381 char const * pzBadness;
382 # define BAD_MARKER(t) { pzBadness = t; goto abort_load; }
383
384 te_pm_state fsm_state = PM_ST_INIT;
385
386 tpl_line = 1;
387
388 while (fsm_state != PM_ST_DONE) {
389 te_pm_event fsm_tkn = next_pm_token(&text, fsm_state, fname);
390 te_pm_state nxt_state;
391 te_pm_trans trans;
392
393 nxt_state = pm_trans_table[ fsm_state ][ fsm_tkn ].next_state;
394 trans = pm_trans_table[ fsm_state ][ fsm_tkn ].transition;
395
396 /*
397 * There are only so many "PM_TR_<state-name>_<token-name>"
398 * transitions that are legal. See which one we got.
399 * It is legal to alter "nxt_state" while processing these.
400 */
401 switch (trans) {
402 case PM_TR_SKIP_ED_MODE:
403 {
404 char * pzEnd = strstr(text + 3, PSEUDO_MAC_MODE_MARK);
405 char * pzNL = strchr(text + 3, NL);
406 if ((pzEnd == NULL) || (pzNL < pzEnd))
407 BAD_MARKER(PSEUDO_MAC_BAD_MODE);
408
409 text = pzEnd + 3;
410 break;
411 }
412
413 case PM_TR_INIT_MARKER:
414 text = copy_mark(text, st_mac_mark, &st_mac_len);
415 if (text == NULL)
416 BAD_MARKER(PSEUDO_MAC_BAD_LENGTH);
417
418 break;
419
420 case PM_TR_TEMPL_MARKER:
421 text = copy_mark(text, end_mac_mark, &end_mac_len);
422 if (text == NULL)
423 BAD_MARKER(PSEUDO_MAC_BAD_LENGTH);
424
425 /*
426 * IF the end macro seems to end with the start macro and
427 * it is exactly twice as long as the start macro, then
428 * presume that someone ran the two markers together.
429 */
430 if ( (end_mac_len == 2 * st_mac_len)
431 && (strcmp(st_mac_mark, end_mac_mark + st_mac_len) == 0)) {
432 text -= st_mac_len;
433 end_mac_mark[ st_mac_len ] = NUL;
434 end_mac_len = st_mac_len;
435 }
436
437 if (strstr(end_mac_mark, st_mac_mark) != NULL)
438 BAD_MARKER(PSEUDO_MAC_BAD_ENDER);
439 if (strstr(st_mac_mark, end_mac_mark) != NULL)
440 BAD_MARKER(PSEUDO_MAC_BAD_STARTER);
441 break;
442
443 case PM_TR_TEMPL_SUFFIX:
444 text = do_suffix(text, fname, tpl_line);
445 break;
446
447 case PM_TR_TEMPL_SCHEME:
448 text = do_scheme_expr(text, fname);
449 break;
450
451 case PM_TR_INVALID:
452 pm_invalid_transition(fsm_state, fsm_tkn);
453 switch (fsm_state) {
454 case PM_ST_INIT: BAD_MARKER(PSEUDO_MAC_BAD_NOSTART);
455 case PM_ST_ST_MARK: BAD_MARKER(PSEUDO_MAC_BAD_NOAG5);
456 case PM_ST_AGEN: BAD_MARKER(PSEUDO_MAC_BAD_NOTPL);
457 case PM_ST_TEMPL: BAD_MARKER(PSEUDO_MAC_BAD_NOEND);
458 case PM_ST_END_MARK: BAD_MARKER(PSEUDO_MAC_BAD_NOEOL);
459 default: BAD_MARKER(PSEUDO_MAC_BAD_FSM);
460 }
461
462 case PM_TR_NOOP:
463 break;
464
465 default:
466 BAD_MARKER(PSEUDO_MAC_BAD_PSEUDO);
467 }
468
469 fsm_state = nxt_state;
470 }
471
472 return text;
473
474 abort_load:
475 AG_ABEND(aprf(PSEUDO_MAC_ERR_FMT, fname, tpl_line, pzBadness));
476 # undef BAD_MARKER
477 return NULL;
478 }
479 /**
480 * @}
481 *
482 * Local Variables:
483 * mode: C
484 * c-file-style: "stroustrup"
485 * indent-tabs-mode: nil
486 * End:
487 * end of agen5/loadPseudo.c */
488