Add '3rd-party/xfpt/' from commit '24eaa721effcf2f56d1da62344ee27ac9721d3ec'
[exim.git] / 3rd-party / xfpt / src / read.c
1 /*************************************************
2 *     xfpt - Simple ASCII->Docbook processor     *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge, 2010 */
6 /* Written by Philip Hazel. */
7
8 /* This module contains code for reading the input. */
9
10 #include "xfpt.h"
11
12
13
14
15 /*************************************************
16 *             Process macro line                 *
17 *************************************************/
18
19 /* This is the place where macro arguments are substituted. In a section
20 delimited by .eacharg/.endeach, the variable macro_argbase is set to the first
21 of the relative arguments. This function is also called from para.c in order to
22 handle inline macro calls.
23
24 Arguments:
25   p         the macro input line
26   b         where to put the result
27
28 Returns:    nothing
29 */
30
31 void
32 read_process_macroline(uschar *p, uschar *b)
33 {
34 int optend = 0;
35
36 while (*p != 0)
37   {
38   int i;
39   int argn = 0;
40   argstr *argbase, *arg;
41
42   /* If we are including an optional substring, when we get to the terminator,
43   just skip it. */
44
45   if (*p == optend)
46     {
47     optend = 0;
48     p++;
49     continue;
50     }
51
52   /* Until we hit a dollar, just copy verbatim */
53
54   if (*p != '$') { *b++ = *p++; continue; }
55
56   /* If the character after $ is another $, insert a literal $. */
57
58   if (p[1] == '$') { p++; *b++ = *p++; continue; }
59
60   /* If the character after $ is +, we are dealing with arguments
61   relative to macro_arg0 in a ".eacharg" section. Otherwise, we are dealing
62   with an absolute argument number. */
63
64   if (p[1] == '+')
65     {
66     p++;
67     if (macro_argbase == NULL)       /* Not in a .eacharg section */
68       {
69       error(18);
70       *b++ = '+';
71       *b++ = *p++;
72       continue;
73       }
74     argbase = macro_argbase;
75     }
76   else argbase = macrocurrent->args;
77
78   /* $= introduces an optional substring */
79
80   if (p[1] == '=')
81     {
82     p++;
83     if (!isdigit(p[1]))
84       {
85       error(17, p[1], "$=");
86       *b++ = '$';
87       *b++ = *p++;
88       continue;
89       }
90     while (isdigit(*(++p))) argn = argn * 10 + *p - '0';
91
92     optend = *p++;
93
94     arg = argbase;
95     for (i = 1; i < argn; i++)
96       {
97       if (arg == NULL) break;
98       arg = arg->next;
99       }
100
101     if (arg == NULL || arg->string[0] == 0)
102       {
103       while (*p != 0 && *p != optend) p++;
104       if (*p == optend) p++;
105       }
106
107     continue;
108     }
109
110   /* Not '=' after $; this is an argument substitution */
111
112   if (!isdigit(p[1]))
113     {
114     error(17, p[1], "$");
115     *b++ = *p++;
116     continue;
117     }
118
119   while (isdigit(*(++p))) argn = argn * 10 + *p - '0';
120
121   /* Handle $0 - currently no meaning */
122
123   if (argn == 0)
124     {
125     continue;
126     }
127
128   /* Seek an argument in this invocation */
129
130   arg = argbase;
131   for (i = 1; i < argn; i++)
132     {
133     if (arg == NULL) break;
134     arg = arg->next;
135     }
136
137   /* If not found, seek a default argument for an absolute substitution, but
138   not for a relative one. */
139
140   if (arg == NULL && argbase == macrocurrent->args)
141     {
142     arg = macrocurrent->macro->args;
143     for (i = 1; i < argn; i++)
144       {
145       if (arg == NULL) break;
146       arg = arg->next;
147       }
148     }
149
150   /* If we have found an argument, substitute it. */
151
152   if (arg != NULL) b += sprintf(CS b, "%s", arg->string);
153   }
154
155 *b = 0;
156 }
157
158
159
160 /*************************************************
161 *         Get the next line of input             *
162 *************************************************/
163
164 /* There may be a saved line already in the buffer, following the reading of a
165 paragraph or a .nonl directive. Otherwise, take the next line from one of three
166 sources:
167
168   (1) If popto is not negative, get an appropropriate line off the stack.
169   (2) If we are in a macro, get the next macro line.
170   (3) If we are in a file, read a new line from a file and handle any
171       continuations.
172
173 There can be arbitrary nesting of macros and files, because a .include
174 directive may appear inside a macro. The current from_type vector is used to
175 keep track of what is current.
176
177 Arguments:  none
178 Returns:    pointer to the next line or NULL
179 */
180
181 uschar *
182 read_nextline(void)
183 {
184 int len;
185 uschar *p, *q;
186
187 /* Handle a dot line that terminated a paragraph, or a .nonl line */
188
189 if (next_line != NULL)
190   {
191   uschar *yield = next_line;
192   next_line = NULL;
193   return yield;
194   }
195
196 /* Handle a line off the stack */
197
198 if (popto == 0)
199   {
200   pushstr *ps = pushed;
201   if (ps == NULL) error(12); else
202     {
203     popto = -1;
204     (void)sprintf(CS inbuffer, "%s\n", ps->string);
205     pushed = ps->next;
206     free(ps);
207     return inbuffer;
208     }
209   }
210
211 /* Handle a line off the stack when there is a matching line at the top or
212 below for the given letter. When we reach the matching line, stop popping. The
213 value of popto is set greater than zero only when it is known that there's a
214 matching line. */
215
216 if (popto > 0)
217   {
218   pushstr *ps = pushed;
219   if (ps->letter == popto) popto = -1;
220   (void)sprintf(CS inbuffer, "%s\n", ps->string);
221   pushed = ps->next;
222   free(ps);
223   return inbuffer;
224   }
225
226 /* Get the next line from the current macro or the current file. We need a loop
227 for handling the ends of macros and files. First check for having previously
228 reached the end of the input. */
229
230 if (from_type_ptr < 0) return NULL;
231
232 for (;;)
233   {
234   if (from_type[from_type_ptr] == FROM_MACRO)
235     {
236     if (macrocurrent->nextline == NULL)
237       {
238       macroexe *temp = macrocurrent;
239       macrocurrent = macrocurrent->prev;
240       free(temp);
241       }
242     else
243       {
244       read_process_macroline(macrocurrent->nextline->string, inbuffer);
245       macrocurrent->nextline = macrocurrent->nextline->next;
246       break;
247       }
248     }
249
250   /* When reading from a file, handle continuation lines, but only within the
251   single file. */
252
253   else
254     {
255     if (Ufgets(inbuffer, INBUFFSIZE, istack->file) == NULL)
256       {
257       istackstr *prev = istack->prev;
258       fclose(istack->file);
259       free(istack);
260       istack = prev;
261       }
262     else
263       {
264       istack->linenumber++;
265
266       q = inbuffer;
267       len = Ustrlen(q);
268
269       for (;;)
270         {
271         p = q + len;
272         while (p > q && isspace(p[-1])) p--;
273
274         if (p - q < 3 || Ustrncmp(p - 3, "&&&", 3) != 0) break;
275
276         q = p - 3;
277         *q = 0;
278
279         if (istack == NULL ||
280             Ufgets(q, INBUFFSIZE - (q - inbuffer), istack->file) == NULL)
281           break;
282
283         istack->linenumber++;
284         p = q;
285         while (*p == ' ' || *p == '\t') p++;
286         len = Ustrlen(p);
287         if (p > q) memmove(q, p, len + 1);
288         }
289
290       break;
291       }
292     }
293
294   /* We get here if the end of a macro or a file was reached. The appropriate
295   chain has been popped. Back up the stack of input types before the loop
296   repeats. When we reach the end of the stack, we have reached the end of all
297   the input. */
298
299   if (--from_type_ptr < 0) return NULL;
300   }
301
302 return inbuffer;
303 }
304
305
306
307 /*************************************************
308 *        Complete the reading of a paragraph     *
309 *************************************************/
310
311 /* This function is called after a line has been identified as the start of a
312 paragraph. We need to read the rest so that flags can be matched across the
313 entire paragraph. (If there is nested material such as a footnote, this applies
314 only to the separate parts, not across the nesting.) The text is copied into
315 the paragraph buffer. Directives that are encountered in the paragraph are
316 processed, with two exceptions.
317
318 (1) For .literal, we set next_line so it is processed next, and exit. This is
319 the end of the paragraph.
320
321 (2) For .nest, we set *nest_info, according to whether it is the start or
322 end of a nested section, and exit.
323
324 Arguments:
325   p           the first line
326   nest_info   returns NEST_NO, NEST_START, or NEST_END
327
328 Returns:      the paragraph
329 */
330
331
332 uschar *
333 read_paragraph(uschar *p, int *nest_info)
334 {
335 uschar *q = parabuffer;
336 int length = Ustrlen(p);
337
338 memcpy(q, p, length);
339 q += length;
340
341 *nest_info = NEST_NO;    /* Not hit .nest */
342
343 for (;;)
344   {
345   uschar *s;
346
347   if ((p = read_nextline()) == NULL) break;
348
349   if (Ustrncmp(p, ".literal ", 9) == 0)
350     {
351     next_line = p;
352     break;
353     }
354
355   if (Ustrncmp(p, ".nest ", 6) == 0)
356     {
357     p += 6;
358     while (isspace(*p)) p++;
359     s = p + Ustrlen(p);
360     while (s > p && isspace(s[-1])) s--;
361     *s = 0;
362     if (Ustrcmp(p, "begin") == 0) *nest_info = NEST_BEGIN;
363     else if (Ustrcmp(p, "end") == 0) *nest_info = NEST_END;
364     else error(26, p);
365     break;
366     }
367
368   else if (*p == '.')
369     {
370     dot_process(p);
371     continue;
372     }
373
374   /* End paragraph on encountering a completely blank line */
375
376   for (s = p;  *s == ' ' || *s == '\t'; s++);
377   if (*s == '\n') break;
378
379   length = Ustrlen(p);
380   memcpy(q, p, length);
381   q += length;
382   }
383
384 *q = 0;
385 return parabuffer;
386 }
387
388 /* End of read.c */