Annotation of texi2mdoc/util.c, Revision 1.2
1.2 ! kristaps 1: /* $Id: util.c,v 1.1 2015/02/20 09:58:50 kristaps Exp $ */
1.1 kristaps 2: /*
3: * Copyright (c) 2015 Kristaps Dzonsons <kristaps@bsd.lv>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17: #include <sys/mman.h>
18: #include <sys/stat.h>
19:
20: #include <assert.h>
21: #include <ctype.h>
22: #include <fcntl.h>
23: #include <getopt.h>
24: #include <libgen.h>
25: #include <limits.h>
26: #include <stdarg.h>
27: #include <stdio.h>
28: #include <stdlib.h>
29: #include <string.h>
30: #include <time.h>
31: #include <unistd.h>
32:
33: #include "extern.h"
34:
35: /*
36: * Unmap the top-most file in the stack of files currently opened (that
37: * is, nested calls to parsefile()).
38: */
39: void
40: texifilepop(struct texi *p)
41: {
42: struct texifile *f;
43:
44: assert(p->filepos > 0);
45: f = &p->files[--p->filepos];
46: munmap(f->map, f->mapsz);
47: }
48:
49: /*
50: * Unmap all files that we're currently using and free all resources
51: * that we've allocated during the parse.
52: * The utility should exit(...) after this is called.
53: */
54: void
55: texiexit(struct texi *p)
56: {
57: size_t i;
58:
59: /* Make sure we're newline-terminated. */
60: if (p->outcol)
61: putchar('\n');
62:
63: /* Unmap all files. */
64: while (p->filepos > 0)
65: texifilepop(p);
66:
67: for (i = 0; i < p->dirsz; i++)
68: free(p->dirs[i]);
69:
70: for (i = 0; i < p->valsz; i++) {
71: free(p->vals[i].value);
72: free(p->vals[i].key);
73: }
74:
75: free(p->vals);
76: free(p->dirs);
77: free(p->subtitle);
78: free(p->title);
79: }
80:
81: /*
82: * Fatal error: unmap all files and exit.
83: * The "errstring" is passed to perror(3).
84: */
85: void
86: texiabort(struct texi *p, const char *errstring)
87: {
88:
89: perror(errstring);
90: texiexit(p);
91: exit(EXIT_FAILURE);
92: }
93:
94: /*
95: * Print a generic warning message (to stderr) tied to our current
96: * location in the parse sequence.
97: */
98: void
99: texiwarn(const struct texi *p, const char *fmt, ...)
100: {
101: va_list ap;
102:
103: fprintf(stderr, "%s:%zu:%zu: warning: ",
104: p->files[p->filepos - 1].name,
105: p->files[p->filepos - 1].line + 1,
106: p->files[p->filepos - 1].col + 1);
107: va_start(ap, fmt);
108: vfprintf(stderr, fmt, ap);
109: va_end(ap);
110: fputc('\n', stderr);
111: }
112:
113: /*
114: * Print an error message (to stderr) tied to our current location in
115: * the parse sequence, invoke texiexit(), then die.
116: */
117: void
118: texierr(struct texi *p, const char *fmt, ...)
119: {
120: va_list ap;
121:
122: fprintf(stderr, "%s:%zu:%zu: error: ",
123: p->files[p->filepos - 1].name,
124: p->files[p->filepos - 1].line + 1,
125: p->files[p->filepos - 1].col + 1);
126: va_start(ap, fmt);
127: vfprintf(stderr, fmt, ap);
128: va_end(ap);
129: fputc('\n', stderr);
130: texiexit(p);
131: exit(EXIT_FAILURE);
132: }
133:
134: /*
135: * Put a single data character to the output if we're not ignoring.
136: * Makes sure we don't spurriously start a macro.
137: * Adjusts our output status.
138: * This shouldn't be called for macros: just for ordinary text.
139: */
140: void
141: texiputchar(struct texi *p, char c)
142: {
143:
144: if (p->ign)
145: return;
146:
147: if ('.' == c && 0 == p->outcol)
148: fputs("\\&", stdout);
149:
150: putchar(c);
151: p->seenvs = 0;
152: if ('\n' == c) {
153: p->outcol = 0;
154: p->seenws = 0;
155: } else
156: p->outcol++;
157: }
158:
159: /*
160: * Put multiple characters (see texiputchar()).
161: * This shouldn't be called for macros: just for ordinary text.
162: */
163: void
164: texiputchars(struct texi *p, const char *s)
165: {
166:
167: while ('\0' != *s)
168: texiputchar(p, *s++);
169: }
170:
171: /*
172: * Close an mdoc(7) macro opened with teximacroopen().
173: * If there are no more macros on the line, prints a newline.
174: */
175: void
176: teximacroclose(struct texi *p)
177: {
178:
179: if (p->ign)
180: return;
181:
182: if (0 == --p->outmacro) {
183: putchar('\n');
184: p->outcol = p->seenws = 0;
185: }
186: }
187:
188: /*
189: * Open a mdoc(7) macro.
190: * This is used for line macros, e.g., Qq [foo bar baz].
191: * It can be invoked for nested macros, e.g., Qq Li foo .
192: * TODO: flush-right punctuation (e.g., parenthesis).
193: */
194: void
195: teximacroopen(struct texi *p, const char *s)
196: {
197: int rc;
198:
199: if (p->ign)
200: return;
201:
202: if (p->outcol && 0 == p->outmacro) {
203: putchar('\n');
204: p->outcol = 0;
205: }
206:
207: if (0 == p->outmacro)
208: putchar('.');
209: else
210: putchar(' ');
211:
212: if (EOF != (rc = fputs(s, stdout)))
213: p->outcol += rc;
214:
215: putchar(' ');
216: p->outcol++;
217: p->outmacro++;
218: p->seenws = 0;
219: }
220:
221: /*
222: * Put a stadnalone mdoc(7) command with the trailing newline.
223: */
224: void
225: teximacro(struct texi *p, const char *s)
226: {
227:
228: if (p->ign)
229: return;
230:
231: if (p->outmacro)
232: texierr(p, "\"%s\" in open line scope!?", s);
233: if (p->literal)
234: texierr(p, "\"%s\" in a literal scope!?", s);
235:
236: if (p->outcol)
237: putchar('\n');
238:
239: putchar('.');
240: puts(s);
241: p->outcol = p->seenws = 0;
242: }
243:
244: /*
245: * Introduce vertical space during normal (non-macro) input.
246: */
247: void
248: texivspace(struct texi *p)
249: {
250:
251: if (p->seenvs)
252: return;
253: teximacro(p, "Pp");
254: p->seenvs = 1;
255: }
256:
257: /*
258: * Advance by a single byte in the input stream, adjusting our location
259: * in the current input file.
260: */
261: void
262: advance(struct texi *p, const char *buf, size_t *pos)
263: {
264:
265: if ('\n' == buf[*pos]) {
266: p->files[p->filepos - 1].line++;
267: p->files[p->filepos - 1].col = 0;
268: } else
269: p->files[p->filepos - 1].col++;
270:
271: (*pos)++;
272: }
273:
274: /*
275: * It's common to wait punctuation to float on the right side of macro
276: * lines in mdoc(7), e.g., ".Em hello ) ."
277: * This function does so, and should be called before teximacroclose().
278: * It will detect that it's the last in the nested macros and
279: * appropriately flush-left punctuation alongside the macro.
280: */
281: void
282: texipunctuate(struct texi *p, const char *buf, size_t sz, size_t *pos)
283: {
284: size_t start, end;
285:
286: if (1 != p->outmacro)
287: return;
288:
289: for (start = end = *pos; end < sz; end++) {
290: switch (buf[end]) {
291: case (','):
292: case (')'):
293: case ('.'):
294: case ('"'):
295: case (':'):
296: case ('!'):
297: case ('?'):
298: continue;
299: default:
300: break;
301: }
302: break;
303: }
304: if (end == *pos)
305: return;
306: if (end + 1 == sz || ' ' == buf[end] || '\n' == buf[end]) {
307: for ( ; start < end; start++) {
308: texiputchar(p, ' ');
309: texiputchar(p, buf[start]);
310: advance(p, buf, pos);
311: }
312: }
313: }
314:
315: /*
316: * Advance to the next non-whitespace word in the input stream.
317: * If we're in literal mode, then print all of the whitespace as we're
318: * doing so.
319: */
320: static size_t
321: advancenext(struct texi *p, const char *buf, size_t sz, size_t *pos)
322: {
323:
324: if (p->literal) {
325: while (*pos < sz && ismspace(buf[*pos])) {
326: if (*pos && '\n' == buf[*pos] &&
327: '\\' == buf[*pos - 1])
328: texiputchar(p, 'e');
329: texiputchar(p, buf[*pos]);
330: advance(p, buf, pos);
331: }
332: return(*pos);
333: }
334:
335: while (*pos < sz && ismspace(buf[*pos])) {
336: p->seenws = 1;
337: /*
338: * If it looks like we've printed a double-line, then
339: * output a paragraph.
340: * FIXME: this is stupid.
341: */
342: if (*pos && '\n' == buf[*pos] && '\n' == buf[*pos - 1])
343: texivspace(p);
344: advance(p, buf, pos);
345: }
346: return(*pos);
347: }
348:
349: /*
350: * Advance to the EOLN in the input stream.
351: * NOTE: THIS SHOULD NOT BE CALLED ON BLANK TEXT, as it will read up to
352: * the @\n.
353: */
354: size_t
355: advanceeoln(struct texi *p, const char *buf,
356: size_t sz, size_t *pos, int consumenl)
357: {
358:
359: while (*pos < sz && '\n' != buf[*pos])
360: advance(p, buf, pos);
361: if (*pos < sz && consumenl)
362: advance(p, buf, pos);
363: return(*pos);
364: }
365:
366: /*
367: * Advance to position "end", which is an absolute position in the
368: * current buffer greater than or equal to the current position.
369: */
370: void
371: advanceto(struct texi *p, const char *buf, size_t *pos, size_t end)
372: {
373:
374: assert(*pos <= end);
375: while (*pos < end)
376: advance(p, buf, pos);
377: }
378:
379: /*
380: * Output a free-form word in the input stream, progressing to the next
381: * command or white-space.
382: * This also will advance the input stream.
383: */
384: static void
385: texiword(struct texi *p, const char *buf,
386: size_t sz, size_t *pos, char extra)
387: {
388:
389: if (p->seenws && 0 == p->outmacro &&
390: p->outcol > 72 && 0 == p->literal)
391: texiputchar(p, '\n');
392: /* FIXME: abstract this: we use it elsewhere. */
393: if (p->seenws && p->outcol && 0 == p->literal)
394: texiputchar(p, ' ');
395:
396: p->seenws = 0;
397:
398: while (*pos < sz && ! ismspace(buf[*pos])) {
399: switch (buf[*pos]) {
400: case ('@'):
401: case ('}'):
402: case ('{'):
403: return;
404: }
405: if ('\0' != extra && buf[*pos] == extra)
406: return;
407: if (*pos < sz - 1 &&
408: '`' == buf[*pos] &&
409: '`' == buf[*pos + 1]) {
410: texiputchars(p, "\\(lq");
411: advance(p, buf, pos);
412: } else if (*pos < sz - 1 &&
413: '\'' == buf[*pos] &&
414: '\'' == buf[*pos + 1]) {
415: texiputchars(p, "\\(rq");
416: advance(p, buf, pos);
417: } else
418: texiputchar(p, buf[*pos]);
419: advance(p, buf, pos);
420: }
421: }
422:
423: /*
424: * Look up the command at position "pos" in the buffer, returning it (or
425: * TEXICMD__MAX if none found) and setting "end" to be the absolute
426: * index after the command name.
427: */
428: enum texicmd
429: texicmd(struct texi *p, const char *buf,
430: size_t pos, size_t sz, size_t *end)
431: {
432: size_t i, len;
433:
434: assert('@' == buf[pos]);
435:
436: if ((*end = pos) == sz)
437: return(TEXICMD__MAX);
438: else if ((*end = ++pos) == sz)
439: return(TEXICMD__MAX);
440:
441: /* Alphabetic commands are special. */
442: if ( ! isalpha(buf[pos])) {
443: if ((*end = pos + 1) == sz)
444: return(TEXICMD__MAX);
445: for (i = 0; i < TEXICMD__MAX; i++) {
446: if (1 != texitoks[i].len)
447: continue;
448: if (0 == strncmp(texitoks[i].tok, &buf[pos], 1))
449: return(i);
450: }
451: texiwarn(p, "bad command: @%c", buf[pos]);
452: return(TEXICMD__MAX);
453: }
454:
455: for (*end = pos; *end < sz && ! ismspace(buf[*end]); (*end)++)
456: if ((*end > pos && ('@' == buf[*end] ||
457: '{' == buf[*end] || '}' == buf[*end])))
458: break;
459:
460: len = *end - pos;
461: for (i = 0; i < TEXICMD__MAX; i++) {
462: if (len != texitoks[i].len)
463: continue;
464: if (0 == strncmp(texitoks[i].tok, &buf[pos], len))
465: return(i);
466: }
467:
468: texiwarn(p, "bad command: @%.*s", (int)len, &buf[pos]);
469: return(TEXICMD__MAX);
470: }
471:
472: /*
473: * Parse an argument from a bracketed command, e.g., @url{foo, baz}.
474: * Num should be set to the argument we're currently parsing, although
475: * it suffixes for it to be zero or non-zero.
476: * This will return 1 if there are more arguments, 0 otherwise.
477: * This will stop (returning 0) in the event of EOF or if we're not at a
478: * bracket for the zeroth parse.
479: */
480: int
481: parsearg(struct texi *p, const char *buf,
482: size_t sz, size_t *pos, size_t num)
483: {
484: size_t end;
485: enum texicmd cmd;
486:
487: while (*pos < sz && ismspace(buf[*pos]))
488: advance(p, buf, pos);
489: if (*pos == sz || (0 == num && '{' != buf[*pos]))
490: return(0);
491: if (0 == num)
492: advance(p, buf, pos);
493:
494: while ((*pos = advancenext(p, buf, sz, pos)) < sz) {
495: switch (buf[*pos]) {
496: case (','):
497: advance(p, buf, pos);
498: return(1);
499: case ('}'):
500: advance(p, buf, pos);
501: return(0);
502: case ('{'):
503: if (0 == p->ign)
504: texiwarn(p, "unexpected \"{\"");
505: advance(p, buf, pos);
506: continue;
507: case ('@'):
508: break;
509: default:
510: texiword(p, buf, sz, pos, ',');
511: continue;
512: }
513:
514: cmd = texicmd(p, buf, *pos, sz, &end);
515: advanceto(p, buf, pos, end);
516: if (TEXICMD__MAX == cmd)
517: continue;
518: if (NULL != texitoks[cmd].fp)
519: (*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
520: }
521: return(0);
522: }
523:
524: /*
525: * Parse until the end of a bracketed statement, e.g., @foo{bar baz}.
526: * This will stop in the event of EOF or if we're not at a bracket.
527: */
528: void
529: parsebracket(struct texi *p, const char *buf, size_t sz, size_t *pos)
530: {
531: size_t end;
532: enum texicmd cmd;
533:
534: while (*pos < sz && ismspace(buf[*pos]))
535: advance(p, buf, pos);
536:
537: if (*pos == sz || '{' != buf[*pos])
538: return;
539: advance(p, buf, pos);
540:
541: while ((*pos = advancenext(p, buf, sz, pos)) < sz) {
542: switch (buf[*pos]) {
543: case ('}'):
544: advance(p, buf, pos);
545: return;
546: case ('{'):
547: if (0 == p->ign)
548: texiwarn(p, "unexpected \"{\"");
549: advance(p, buf, pos);
550: continue;
551: case ('@'):
552: break;
553: default:
554: texiword(p, buf, sz, pos, '\0');
555: continue;
556: }
557:
558: cmd = texicmd(p, buf, *pos, sz, &end);
559: advanceto(p, buf, pos, end);
560: if (TEXICMD__MAX == cmd)
561: continue;
562: if (NULL != texitoks[cmd].fp)
563: (*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
564: }
565: }
566:
567: /*
568: * This should be invoked when we're on a macro line and want to process
569: * to the end of the current input line, doing all of our macros along
570: * the way.
571: */
572: void
573: parseeoln(struct texi *p, const char *buf, size_t sz, size_t *pos)
574: {
575: size_t end;
576: enum texicmd cmd;
577:
578: while (*pos < sz && '\n' != buf[*pos]) {
579: while (*pos < sz && isws(buf[*pos])) {
580: p->seenws = 1;
581: if (p->literal)
582: texiputchar(p, buf[*pos]);
583: advance(p, buf, pos);
584: }
585: switch (buf[*pos]) {
586: case ('}'):
587: if (0 == p->ign)
588: texiwarn(p, "unexpected \"}\"");
589: advance(p, buf, pos);
590: continue;
591: case ('{'):
592: if (0 == p->ign)
593: texiwarn(p, "unexpected \"{\"");
594: advance(p, buf, pos);
595: continue;
596: case ('@'):
597: break;
598: default:
599: texiword(p, buf, sz, pos, '\0');
600: continue;
601: }
602:
603: cmd = texicmd(p, buf, *pos, sz, &end);
604: advanceto(p, buf, pos, end);
605: if (TEXICMD__MAX == cmd)
606: continue;
607: if (NULL != texitoks[cmd].fp)
608: (*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
609: }
610: }
611:
612: /*
613: * Parse a single word or command.
614: * This will return immediately at the EOF.
615: */
616: void
617: parsesingle(struct texi *p, const char *buf, size_t sz, size_t *pos)
618: {
619: size_t end;
620: enum texicmd cmd;
621:
622: if ((*pos = advancenext(p, buf, sz, pos)) >= sz)
623: return;
624:
625: switch (buf[*pos]) {
626: case ('}'):
627: if (0 == p->ign)
628: texiwarn(p, "unexpected \"}\"");
629: advance(p, buf, pos);
630: return;
631: case ('{'):
632: if (0 == p->ign)
633: texiwarn(p, "unexpected \"{\"");
634: advance(p, buf, pos);
635: return;
636: case ('@'):
637: break;
638: default:
639: texiword(p, buf, sz, pos, '\0');
640: return;
641: }
642:
643: cmd = texicmd(p, buf, *pos, sz, &end);
644: advanceto(p, buf, pos, end);
645: if (TEXICMD__MAX == cmd)
646: return;
647: if (NULL != texitoks[cmd].fp)
648: (*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
649: }
650:
651: /*
652: * This is used in the @deffn type of command.
653: * These have an arbitrary number of line arguments; however, these
654: * arguments may or may not be surrounded by brackets.
655: * In this function, we parse each one as either a bracketed or
656: * non-bracketed argument, returning 0 when we've reached the end of
657: * line or 1 otherwise.
658: */
659: int
660: parselinearg(struct texi *p, const char *buf, size_t sz, size_t *pos)
661: {
662:
663: while (*pos < sz && isws(buf[*pos])) {
664: p->seenws = 1;
665: advance(p, buf, pos);
666: }
667:
668: if (*pos < sz && '{' == buf[*pos])
669: parsebracket(p, buf, sz, pos);
670: else if ('\n' != buf[*pos])
671: parsesingle(p, buf, sz, pos);
672: else
673: return(0);
674:
675: return(1);
676: }
677:
678: /*
679: * Parse til the end of the buffer.
680: */
681: void
682: parseeof(struct texi *p, const char *buf, size_t sz)
683: {
684: size_t pos;
685:
686: for (pos = 0; pos < sz; )
687: parsesingle(p, buf, sz, &pos);
688: }
689:
690: /*
691: * Parse a block sequence until we have the "@end endtoken" command
692: * invocation.
693: * This will return immediately at EOF.
694: */
695: void
696: parseto(struct texi *p, const char *buf,
697: size_t sz, size_t *pos, const char *endtoken)
698: {
699: size_t end;
700: enum texicmd cmd;
701: size_t endtoksz;
702:
703: endtoksz = strlen(endtoken);
704: assert(endtoksz > 0);
705:
706: while ((*pos = advancenext(p, buf, sz, pos)) < sz) {
707: switch (buf[*pos]) {
708: case ('}'):
709: if (0 == p->ign)
710: texiwarn(p, "unexpected \"}\"");
711: advance(p, buf, pos);
712: continue;
713: case ('{'):
714: if (0 == p->ign)
715: texiwarn(p, "unexpected \"{\"");
716: advance(p, buf, pos);
717: continue;
718: case ('@'):
719: break;
720: default:
721: texiword(p, buf, sz, pos, '\0');
722: continue;
723: }
724:
725: cmd = texicmd(p, buf, *pos, sz, &end);
726: advanceto(p, buf, pos, end);
727: if (TEXICMD_END == cmd) {
728: while (*pos < sz && isws(buf[*pos]))
729: advance(p, buf, pos);
730: /*
731: * FIXME: check the full word, not just its
732: * initial substring!
733: */
734: if (sz - *pos >= endtoksz && 0 == strncmp
735: (&buf[*pos], endtoken, endtoksz)) {
736: advanceeoln(p, buf, sz, pos, 0);
737: break;
738: }
739: if (0 == p->ign)
740: texiwarn(p, "unexpected \"end\"");
741: advanceeoln(p, buf, sz, pos, 0);
742: continue;
743: } else if (TEXICMD__MAX != cmd)
744: if (NULL != texitoks[cmd].fp)
745: (*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
746: }
747: }
748:
749: /*
750: * Memory-map the file "fname" and begin parsing it unless "parse" is
751: * zero, in which case we just dump the file to stdout (making sure it
752: * doesn't trip up mdoc(7) along the way).
753: * This can be called in a nested context.
754: */
755: void
756: parsefile(struct texi *p, const char *fname, int parse)
757: {
758: struct texifile *f;
759: int fd;
760: struct stat st;
761: size_t i;
762:
763: assert(p->filepos < 64);
764: f = &p->files[p->filepos];
765: memset(f, 0, sizeof(struct texifile));
766:
767: f->name = fname;
768: if (-1 == (fd = open(fname, O_RDONLY, 0))) {
769: texiabort(p, fname);
770: } else if (-1 == fstat(fd, &st)) {
771: close(fd);
772: texiabort(p, fname);
773: }
774:
775: f->mapsz = st.st_size;
776: f->map = mmap(NULL, f->mapsz,
777: PROT_READ, MAP_SHARED, fd, 0);
778: close(fd);
779:
780: if (MAP_FAILED == f->map)
781: texiabort(p, fname);
782:
783: p->filepos++;
784: if ( ! parse) {
785: /*
786: * We're printing verbatim output.
787: * Make sure it doesn't get interpreted as mdoc by
788: * escaping escapes and making sure leading dots don't
789: * trigger mdoc(7) expansion.
790: */
791: for (i = 0; i < f->mapsz; i++) {
792: if (i > 0 && '.' == f->map[i])
793: if ('\n' == f->map[i - 1])
794: fputs("\\&", stdout);
795: putchar(f->map[i]);
796: if ('\\' == f->map[i])
797: putchar('e');
798: }
799: } else
800: parseeof(p, f->map, f->mapsz);
801: texifilepop(p);
802: }
803:
1.2 ! kristaps 804: /*
! 805: * Look up the value to a stored pair's value starting in "buf" from
! 806: * start to end.
! 807: * Return the pointer to the value memory, which can be NULL if the
! 808: * pointer key does not exist.
! 809: * The pointer can point to NULL if the value has been unset.
! 810: */
! 811: static char **
! 812: valuequery(const struct texi *p,
! 813: const char *buf, size_t start, size_t end)
! 814: {
! 815: size_t i, sz, len;
! 816:
! 817: assert(end >= start);
! 818: /* Ignore zero-length. */
! 819: if (0 == (len = (end - start)))
! 820: return(NULL);
! 821: for (i = 0; i < p->valsz; i++) {
! 822: sz = strlen(p->vals[i].key);
! 823: if (sz != len)
! 824: continue;
! 825: if (0 == strncmp(p->vals[i].key, &buf[start], len))
! 826: return(&p->vals[i].value);
! 827: }
! 828: return(NULL);
! 829: }
! 830:
! 831: /*
! 832: * Parse a key until the end of line, e.g., @clear foo\n, and return the
! 833: * pointer to its value via valuequery().
! 834: */
! 835: static char **
! 836: valuelquery(struct texi *p, const char *buf, size_t sz, size_t *pos)
! 837: {
! 838: size_t start, end;
! 839: char **ret;
! 840:
! 841: while (*pos < sz && isws(buf[*pos]))
! 842: advance(p, buf, pos);
! 843: if (*pos == sz)
! 844: return(NULL);
! 845: for (start = end = *pos; end < sz; end++)
! 846: if ('\n' == buf[end])
! 847: break;
! 848: advanceto(p, buf, pos, end);
! 849: if (*pos < sz) {
! 850: assert('\n' == buf[*pos]);
! 851: advance(p, buf, pos);
! 852: }
! 853: if (NULL == (ret = valuequery(p, buf, start, end)))
! 854: return(NULL);
! 855: return(ret);
! 856: }
! 857:
! 858: void
! 859: valuelclear(struct texi *p, const char *buf, size_t sz, size_t *pos)
! 860: {
! 861: char **ret;
! 862:
! 863: if (NULL == (ret = valuelquery(p, buf, sz, pos)))
! 864: return;
! 865: free(*ret);
! 866: *ret = NULL;
! 867: }
! 868:
! 869: const char *
! 870: valuellookup(struct texi *p, const char *buf, size_t sz, size_t *pos)
! 871: {
! 872: char **ret;
! 873:
! 874: if (NULL == (ret = valuelquery(p, buf, sz, pos)))
! 875: return(NULL);
! 876: return(*ret);
! 877: }
! 878:
! 879: /*
! 880: * Parse a key from a bracketed string, e.g., @value{foo}, and return
! 881: * the pointer to its value.
! 882: * If the returned pointer is NULL, either there was no string within
! 883: * the brackets (or no brackets), or the value was not found, or the
! 884: * value had previously been unset.
! 885: */
! 886: const char *
! 887: valueblookup(struct texi *p, const char *buf, size_t sz, size_t *pos)
! 888: {
! 889: size_t start, end;
! 890: char **ret;
! 891:
! 892: while (*pos < sz && isws(buf[*pos]))
! 893: advance(p, buf, pos);
! 894: if (*pos == sz || '{' != buf[*pos])
! 895: return(NULL);
! 896: advance(p, buf, pos);
! 897: for (start = end = *pos; end < sz; end++)
! 898: if ('}' == buf[end])
! 899: break;
! 900: advanceto(p, buf, pos, end);
! 901: if (*pos < sz) {
! 902: assert('}' == buf[*pos]);
! 903: advance(p, buf, pos);
! 904: }
! 905: if (NULL == (ret = valuequery(p, buf, start, end)))
! 906: return(NULL);
! 907: return(*ret);
! 908: }
! 909:
! 910: void
! 911: valueadd(struct texi *p, char *key, char *val)
! 912: {
! 913: size_t i;
! 914:
! 915: assert(NULL != key);
! 916: assert(NULL != val);
! 917:
! 918: for (i = 0; i < p->valsz; i++)
! 919: if (0 == strcmp(p->vals[i].key, key))
! 920: break;
! 921:
! 922: if (i < p->valsz) {
! 923: free(key);
! 924: free(p->vals[i].value);
! 925: p->vals[i].value = val;
! 926: } else {
! 927: p->vals = realloc(p->vals,
! 928: (p->valsz + 1) *
! 929: sizeof(struct texivalue));
! 930: if (NULL == p->vals) {
! 931: perror(NULL);
! 932: exit(EXIT_FAILURE);
! 933: }
! 934: p->vals[p->valsz].key = key;
! 935: p->vals[p->valsz].value = val;
! 936: p->valsz++;
! 937: }
! 938: }
CVSweb