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