mdoc.c revision 1.1.1.12
1/*	$Vendor-Id: mdoc.c,v 1.177 2011/01/03 11:27:33 kristaps Exp $ */
2/*
3 * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18#ifdef HAVE_CONFIG_H
19#include "config.h"
20#endif
21
22#include <sys/types.h>
23
24#include <assert.h>
25#include <stdarg.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <time.h>
30
31#include "mandoc.h"
32#include "libmdoc.h"
33#include "libmandoc.h"
34
35const	char *const __mdoc_macronames[MDOC_MAX] = {
36	"Ap",		"Dd",		"Dt",		"Os",
37	"Sh",		"Ss",		"Pp",		"D1",
38	"Dl",		"Bd",		"Ed",		"Bl",
39	"El",		"It",		"Ad",		"An",
40	"Ar",		"Cd",		"Cm",		"Dv",
41	"Er",		"Ev",		"Ex",		"Fa",
42	"Fd",		"Fl",		"Fn",		"Ft",
43	"Ic",		"In",		"Li",		"Nd",
44	"Nm",		"Op",		"Ot",		"Pa",
45	"Rv",		"St",		"Va",		"Vt",
46	/* LINTED */
47	"Xr",		"%A",		"%B",		"%D",
48	/* LINTED */
49	"%I",		"%J",		"%N",		"%O",
50	/* LINTED */
51	"%P",		"%R",		"%T",		"%V",
52	"Ac",		"Ao",		"Aq",		"At",
53	"Bc",		"Bf",		"Bo",		"Bq",
54	"Bsx",		"Bx",		"Db",		"Dc",
55	"Do",		"Dq",		"Ec",		"Ef",
56	"Em",		"Eo",		"Fx",		"Ms",
57	"No",		"Ns",		"Nx",		"Ox",
58	"Pc",		"Pf",		"Po",		"Pq",
59	"Qc",		"Ql",		"Qo",		"Qq",
60	"Re",		"Rs",		"Sc",		"So",
61	"Sq",		"Sm",		"Sx",		"Sy",
62	"Tn",		"Ux",		"Xc",		"Xo",
63	"Fo",		"Fc",		"Oo",		"Oc",
64	"Bk",		"Ek",		"Bt",		"Hf",
65	"Fr",		"Ud",		"Lb",		"Lp",
66	"Lk",		"Mt",		"Brq",		"Bro",
67	/* LINTED */
68	"Brc",		"%C",		"Es",		"En",
69	/* LINTED */
70	"Dx",		"%Q",		"br",		"sp",
71	/* LINTED */
72	"%U",		"Ta"
73	};
74
75const	char *const __mdoc_argnames[MDOC_ARG_MAX] = {
76	"split",		"nosplit",		"ragged",
77	"unfilled",		"literal",		"file",
78	"offset",		"bullet",		"dash",
79	"hyphen",		"item",			"enum",
80	"tag",			"diag",			"hang",
81	"ohang",		"inset",		"column",
82	"width",		"compact",		"std",
83	"filled",		"words",		"emphasis",
84	"symbolic",		"nested",		"centered"
85	};
86
87const	char * const *mdoc_macronames = __mdoc_macronames;
88const	char * const *mdoc_argnames = __mdoc_argnames;
89
90static	void		  mdoc_node_free(struct mdoc_node *);
91static	void		  mdoc_node_unlink(struct mdoc *,
92				struct mdoc_node *);
93static	void		  mdoc_free1(struct mdoc *);
94static	void		  mdoc_alloc1(struct mdoc *);
95static	struct mdoc_node *node_alloc(struct mdoc *, int, int,
96				enum mdoct, enum mdoc_type);
97static	int		  node_append(struct mdoc *,
98				struct mdoc_node *);
99static	int		  mdoc_ptext(struct mdoc *, int, char *, int);
100static	int		  mdoc_pmacro(struct mdoc *, int, char *, int);
101static	int		  mdoc_span_alloc(struct mdoc *,
102				const struct tbl_span *);
103
104
105const struct mdoc_node *
106mdoc_node(const struct mdoc *m)
107{
108
109	assert( ! (MDOC_HALT & m->flags));
110	return(m->first);
111}
112
113
114const struct mdoc_meta *
115mdoc_meta(const struct mdoc *m)
116{
117
118	assert( ! (MDOC_HALT & m->flags));
119	return(&m->meta);
120}
121
122
123/*
124 * Frees volatile resources (parse tree, meta-data, fields).
125 */
126static void
127mdoc_free1(struct mdoc *mdoc)
128{
129
130	if (mdoc->first)
131		mdoc_node_delete(mdoc, mdoc->first);
132	if (mdoc->meta.title)
133		free(mdoc->meta.title);
134	if (mdoc->meta.os)
135		free(mdoc->meta.os);
136	if (mdoc->meta.name)
137		free(mdoc->meta.name);
138	if (mdoc->meta.arch)
139		free(mdoc->meta.arch);
140	if (mdoc->meta.vol)
141		free(mdoc->meta.vol);
142	if (mdoc->meta.msec)
143		free(mdoc->meta.msec);
144}
145
146
147/*
148 * Allocate all volatile resources (parse tree, meta-data, fields).
149 */
150static void
151mdoc_alloc1(struct mdoc *mdoc)
152{
153
154	memset(&mdoc->meta, 0, sizeof(struct mdoc_meta));
155	mdoc->flags = 0;
156	mdoc->lastnamed = mdoc->lastsec = SEC_NONE;
157	mdoc->last = mandoc_calloc(1, sizeof(struct mdoc_node));
158	mdoc->first = mdoc->last;
159	mdoc->last->type = MDOC_ROOT;
160	mdoc->next = MDOC_NEXT_CHILD;
161}
162
163
164/*
165 * Free up volatile resources (see mdoc_free1()) then re-initialises the
166 * data with mdoc_alloc1().  After invocation, parse data has been reset
167 * and the parser is ready for re-invocation on a new tree; however,
168 * cross-parse non-volatile data is kept intact.
169 */
170void
171mdoc_reset(struct mdoc *mdoc)
172{
173
174	mdoc_free1(mdoc);
175	mdoc_alloc1(mdoc);
176}
177
178
179/*
180 * Completely free up all volatile and non-volatile parse resources.
181 * After invocation, the pointer is no longer usable.
182 */
183void
184mdoc_free(struct mdoc *mdoc)
185{
186
187	mdoc_free1(mdoc);
188	free(mdoc);
189}
190
191
192/*
193 * Allocate volatile and non-volatile parse resources.
194 */
195struct mdoc *
196mdoc_alloc(struct regset *regs, void *data, mandocmsg msg)
197{
198	struct mdoc	*p;
199
200	p = mandoc_calloc(1, sizeof(struct mdoc));
201
202	p->msg = msg;
203	p->data = data;
204	p->regs = regs;
205
206	mdoc_hash_init();
207	mdoc_alloc1(p);
208	return(p);
209}
210
211
212/*
213 * Climb back up the parse tree, validating open scopes.  Mostly calls
214 * through to macro_end() in macro.c.
215 */
216int
217mdoc_endparse(struct mdoc *m)
218{
219
220	assert( ! (MDOC_HALT & m->flags));
221	if (mdoc_macroend(m))
222		return(1);
223	m->flags |= MDOC_HALT;
224	return(0);
225}
226
227int
228mdoc_addspan(struct mdoc *m, const struct tbl_span *sp)
229{
230
231	assert( ! (MDOC_HALT & m->flags));
232
233	/* No text before an initial macro. */
234
235	if (SEC_NONE == m->lastnamed) {
236		/* FIXME: grab from span. */
237		mdoc_pmsg(m, 0, 0, MANDOCERR_NOTEXT);
238		return(1);
239	}
240
241	return(mdoc_span_alloc(m, sp));
242}
243
244
245/*
246 * Main parse routine.  Parses a single line -- really just hands off to
247 * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()).
248 */
249int
250mdoc_parseln(struct mdoc *m, int ln, char *buf, int offs)
251{
252
253	assert( ! (MDOC_HALT & m->flags));
254
255	m->flags |= MDOC_NEWLINE;
256
257	/*
258	 * Let the roff nS register switch SYNOPSIS mode early,
259	 * such that the parser knows at all times
260	 * whether this mode is on or off.
261	 * Note that this mode is also switched by the Sh macro.
262	 */
263	if (m->regs->regs[(int)REG_nS].set) {
264		if (m->regs->regs[(int)REG_nS].v.u)
265			m->flags |= MDOC_SYNOPSIS;
266		else
267			m->flags &= ~MDOC_SYNOPSIS;
268	}
269
270	return(('.' == buf[offs] || '\'' == buf[offs]) ?
271			mdoc_pmacro(m, ln, buf, offs) :
272			mdoc_ptext(m, ln, buf, offs));
273}
274
275
276int
277mdoc_vmsg(struct mdoc *mdoc, enum mandocerr t,
278		int ln, int pos, const char *fmt, ...)
279{
280	char		 buf[256];
281	va_list		 ap;
282
283	va_start(ap, fmt);
284	vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
285	va_end(ap);
286
287	return((*mdoc->msg)(t, mdoc->data, ln, pos, buf));
288}
289
290
291int
292mdoc_macro(MACRO_PROT_ARGS)
293{
294	assert(tok < MDOC_MAX);
295
296	/* If we're in the body, deny prologue calls. */
297
298	if (MDOC_PROLOGUE & mdoc_macros[tok].flags &&
299			MDOC_PBODY & m->flags) {
300		mdoc_pmsg(m, line, ppos, MANDOCERR_BADBODY);
301		return(1);
302	}
303
304	/* If we're in the prologue, deny "body" macros.  */
305
306	if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) &&
307			! (MDOC_PBODY & m->flags)) {
308		mdoc_pmsg(m, line, ppos, MANDOCERR_BADPROLOG);
309		if (NULL == m->meta.msec)
310			m->meta.msec = mandoc_strdup("1");
311		if (NULL == m->meta.title)
312			m->meta.title = mandoc_strdup("UNKNOWN");
313		if (NULL == m->meta.vol)
314			m->meta.vol = mandoc_strdup("LOCAL");
315		if (NULL == m->meta.os)
316			m->meta.os = mandoc_strdup("LOCAL");
317		if (0 == m->meta.date)
318			m->meta.date = time(NULL);
319		m->flags |= MDOC_PBODY;
320	}
321
322	return((*mdoc_macros[tok].fp)(m, tok, line, ppos, pos, buf));
323}
324
325
326static int
327node_append(struct mdoc *mdoc, struct mdoc_node *p)
328{
329
330	assert(mdoc->last);
331	assert(mdoc->first);
332	assert(MDOC_ROOT != p->type);
333
334	switch (mdoc->next) {
335	case (MDOC_NEXT_SIBLING):
336		mdoc->last->next = p;
337		p->prev = mdoc->last;
338		p->parent = mdoc->last->parent;
339		break;
340	case (MDOC_NEXT_CHILD):
341		mdoc->last->child = p;
342		p->parent = mdoc->last;
343		break;
344	default:
345		abort();
346		/* NOTREACHED */
347	}
348
349	p->parent->nchild++;
350
351	/*
352	 * Copy over the normalised-data pointer of our parent.  Not
353	 * everybody has one, but copying a null pointer is fine.
354	 */
355
356	switch (p->type) {
357	case (MDOC_BODY):
358		/* FALLTHROUGH */
359	case (MDOC_TAIL):
360		/* FALLTHROUGH */
361	case (MDOC_HEAD):
362		p->norm = p->parent->norm;
363		break;
364	default:
365		break;
366	}
367
368	if ( ! mdoc_valid_pre(mdoc, p))
369		return(0);
370
371	switch (p->type) {
372	case (MDOC_HEAD):
373		assert(MDOC_BLOCK == p->parent->type);
374		p->parent->head = p;
375		break;
376	case (MDOC_TAIL):
377		assert(MDOC_BLOCK == p->parent->type);
378		p->parent->tail = p;
379		break;
380	case (MDOC_BODY):
381		if (p->end)
382			break;
383		assert(MDOC_BLOCK == p->parent->type);
384		p->parent->body = p;
385		break;
386	default:
387		break;
388	}
389
390	mdoc->last = p;
391
392	switch (p->type) {
393	case (MDOC_TBL):
394		/* FALLTHROUGH */
395	case (MDOC_TEXT):
396		if ( ! mdoc_valid_post(mdoc))
397			return(0);
398		break;
399	default:
400		break;
401	}
402
403	return(1);
404}
405
406
407static struct mdoc_node *
408node_alloc(struct mdoc *m, int line, int pos,
409		enum mdoct tok, enum mdoc_type type)
410{
411	struct mdoc_node *p;
412
413	p = mandoc_calloc(1, sizeof(struct mdoc_node));
414	p->sec = m->lastsec;
415	p->line = line;
416	p->pos = pos;
417	p->tok = tok;
418	p->type = type;
419
420	/* Flag analysis. */
421
422	if (MDOC_SYNOPSIS & m->flags)
423		p->flags |= MDOC_SYNPRETTY;
424	else
425		p->flags &= ~MDOC_SYNPRETTY;
426	if (MDOC_NEWLINE & m->flags)
427		p->flags |= MDOC_LINE;
428	m->flags &= ~MDOC_NEWLINE;
429
430	return(p);
431}
432
433
434int
435mdoc_tail_alloc(struct mdoc *m, int line, int pos, enum mdoct tok)
436{
437	struct mdoc_node *p;
438
439	p = node_alloc(m, line, pos, tok, MDOC_TAIL);
440	if ( ! node_append(m, p))
441		return(0);
442	m->next = MDOC_NEXT_CHILD;
443	return(1);
444}
445
446
447int
448mdoc_head_alloc(struct mdoc *m, int line, int pos, enum mdoct tok)
449{
450	struct mdoc_node *p;
451
452	assert(m->first);
453	assert(m->last);
454
455	p = node_alloc(m, line, pos, tok, MDOC_HEAD);
456	if ( ! node_append(m, p))
457		return(0);
458	m->next = MDOC_NEXT_CHILD;
459	return(1);
460}
461
462
463int
464mdoc_body_alloc(struct mdoc *m, int line, int pos, enum mdoct tok)
465{
466	struct mdoc_node *p;
467
468	p = node_alloc(m, line, pos, tok, MDOC_BODY);
469	if ( ! node_append(m, p))
470		return(0);
471	m->next = MDOC_NEXT_CHILD;
472	return(1);
473}
474
475
476int
477mdoc_endbody_alloc(struct mdoc *m, int line, int pos, enum mdoct tok,
478		struct mdoc_node *body, enum mdoc_endbody end)
479{
480	struct mdoc_node *p;
481
482	p = node_alloc(m, line, pos, tok, MDOC_BODY);
483	p->pending = body;
484	p->end = end;
485	if ( ! node_append(m, p))
486		return(0);
487	m->next = MDOC_NEXT_SIBLING;
488	return(1);
489}
490
491
492int
493mdoc_block_alloc(struct mdoc *m, int line, int pos,
494		enum mdoct tok, struct mdoc_arg *args)
495{
496	struct mdoc_node *p;
497
498	p = node_alloc(m, line, pos, tok, MDOC_BLOCK);
499	p->args = args;
500	if (p->args)
501		(args->refcnt)++;
502
503	switch (tok) {
504	case (MDOC_Bd):
505		/* FALLTHROUGH */
506	case (MDOC_Bf):
507		/* FALLTHROUGH */
508	case (MDOC_Bl):
509		/* FALLTHROUGH */
510	case (MDOC_Rs):
511		p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
512		break;
513	default:
514		break;
515	}
516
517	if ( ! node_append(m, p))
518		return(0);
519	m->next = MDOC_NEXT_CHILD;
520	return(1);
521}
522
523
524int
525mdoc_elem_alloc(struct mdoc *m, int line, int pos,
526		enum mdoct tok, struct mdoc_arg *args)
527{
528	struct mdoc_node *p;
529
530	p = node_alloc(m, line, pos, tok, MDOC_ELEM);
531	p->args = args;
532	if (p->args)
533		(args->refcnt)++;
534
535	switch (tok) {
536	case (MDOC_An):
537		p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
538		break;
539	default:
540		break;
541	}
542
543	if ( ! node_append(m, p))
544		return(0);
545	m->next = MDOC_NEXT_CHILD;
546	return(1);
547}
548
549static int
550mdoc_span_alloc(struct mdoc *m, const struct tbl_span *sp)
551{
552	struct mdoc_node *n;
553
554	/* FIXME: grab from tbl_span. */
555	n = node_alloc(m, 0, 0, MDOC_MAX, MDOC_TBL);
556	n->span = sp;
557
558	if ( ! node_append(m, n))
559		return(0);
560
561	m->next = MDOC_NEXT_SIBLING;
562	return(1);
563}
564
565
566int
567mdoc_word_alloc(struct mdoc *m, int line, int pos, const char *p)
568{
569	struct mdoc_node *n;
570	size_t		  sv, len;
571
572	len = strlen(p);
573
574	n = node_alloc(m, line, pos, MDOC_MAX, MDOC_TEXT);
575	n->string = mandoc_malloc(len + 1);
576	sv = strlcpy(n->string, p, len + 1);
577
578	/* Prohibit truncation. */
579	assert(sv < len + 1);
580
581	if ( ! node_append(m, n))
582		return(0);
583
584	m->next = MDOC_NEXT_SIBLING;
585	return(1);
586}
587
588
589static void
590mdoc_node_free(struct mdoc_node *p)
591{
592
593	if (MDOC_BLOCK == p->type || MDOC_ELEM == p->type)
594		free(p->norm);
595	if (p->string)
596		free(p->string);
597	if (p->args)
598		mdoc_argv_free(p->args);
599	free(p);
600}
601
602
603static void
604mdoc_node_unlink(struct mdoc *m, struct mdoc_node *n)
605{
606
607	/* Adjust siblings. */
608
609	if (n->prev)
610		n->prev->next = n->next;
611	if (n->next)
612		n->next->prev = n->prev;
613
614	/* Adjust parent. */
615
616	if (n->parent) {
617		n->parent->nchild--;
618		if (n->parent->child == n)
619			n->parent->child = n->prev ? n->prev : n->next;
620		if (n->parent->last == n)
621			n->parent->last = n->prev ? n->prev : NULL;
622	}
623
624	/* Adjust parse point, if applicable. */
625
626	if (m && m->last == n) {
627		if (n->prev) {
628			m->last = n->prev;
629			m->next = MDOC_NEXT_SIBLING;
630		} else {
631			m->last = n->parent;
632			m->next = MDOC_NEXT_CHILD;
633		}
634	}
635
636	if (m && m->first == n)
637		m->first = NULL;
638}
639
640
641void
642mdoc_node_delete(struct mdoc *m, struct mdoc_node *p)
643{
644
645	while (p->child) {
646		assert(p->nchild);
647		mdoc_node_delete(m, p->child);
648	}
649	assert(0 == p->nchild);
650
651	mdoc_node_unlink(m, p);
652	mdoc_node_free(p);
653}
654
655
656/*
657 * Parse free-form text, that is, a line that does not begin with the
658 * control character.
659 */
660static int
661mdoc_ptext(struct mdoc *m, int line, char *buf, int offs)
662{
663	char		 *c, *ws, *end;
664	struct mdoc_node *n;
665
666	/* Ignore bogus comments. */
667
668	if ('\\' == buf[offs] &&
669			'.' == buf[offs + 1] &&
670			'"' == buf[offs + 2]) {
671		mdoc_pmsg(m, line, offs, MANDOCERR_BADCOMMENT);
672		return(1);
673	}
674
675	/* No text before an initial macro. */
676
677	if (SEC_NONE == m->lastnamed) {
678		mdoc_pmsg(m, line, offs, MANDOCERR_NOTEXT);
679		return(1);
680	}
681
682	assert(m->last);
683	n = m->last;
684
685	/*
686	 * Divert directly to list processing if we're encountering a
687	 * columnar MDOC_BLOCK with or without a prior MDOC_BLOCK entry
688	 * (a MDOC_BODY means it's already open, in which case we should
689	 * process within its context in the normal way).
690	 */
691
692	if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
693			LIST_column == n->norm->Bl.type) {
694		/* `Bl' is open without any children. */
695		m->flags |= MDOC_FREECOL;
696		return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf));
697	}
698
699	if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
700			NULL != n->parent &&
701			MDOC_Bl == n->parent->tok &&
702			LIST_column == n->parent->norm->Bl.type) {
703		/* `Bl' has block-level `It' children. */
704		m->flags |= MDOC_FREECOL;
705		return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf));
706	}
707
708	/*
709	 * Search for the beginning of unescaped trailing whitespace (ws)
710	 * and for the first character not to be output (end).
711	 */
712
713	/* FIXME: replace with strcspn(). */
714	ws = NULL;
715	for (c = end = buf + offs; *c; c++) {
716		switch (*c) {
717		case '-':
718			if (mandoc_hyph(buf + offs, c))
719				*c = ASCII_HYPH;
720			ws = NULL;
721			break;
722		case ' ':
723			if (NULL == ws)
724				ws = c;
725			continue;
726		case '\t':
727			/*
728			 * Always warn about trailing tabs,
729			 * even outside literal context,
730			 * where they should be put on the next line.
731			 */
732			if (NULL == ws)
733				ws = c;
734			/*
735			 * Strip trailing tabs in literal context only;
736			 * outside, they affect the next line.
737			 */
738			if (MDOC_LITERAL & m->flags)
739				continue;
740			break;
741		case '\\':
742			/* Skip the escaped character, too, if any. */
743			if (c[1])
744				c++;
745			/* FALLTHROUGH */
746		default:
747			ws = NULL;
748			break;
749		}
750		end = c + 1;
751	}
752	*end = '\0';
753
754	if (ws)
755		mdoc_pmsg(m, line, (int)(ws-buf), MANDOCERR_EOLNSPACE);
756
757	if ('\0' == buf[offs] && ! (MDOC_LITERAL & m->flags)) {
758		mdoc_pmsg(m, line, (int)(c-buf), MANDOCERR_NOBLANKLN);
759
760		/*
761		 * Insert a `sp' in the case of a blank line.  Technically,
762		 * blank lines aren't allowed, but enough manuals assume this
763		 * behaviour that we want to work around it.
764		 */
765		if ( ! mdoc_elem_alloc(m, line, offs, MDOC_sp, NULL))
766			return(0);
767
768		m->next = MDOC_NEXT_SIBLING;
769		return(1);
770	}
771
772	if ( ! mdoc_word_alloc(m, line, offs, buf+offs))
773		return(0);
774
775	if (MDOC_LITERAL & m->flags)
776		return(1);
777
778	/*
779	 * End-of-sentence check.  If the last character is an unescaped
780	 * EOS character, then flag the node as being the end of a
781	 * sentence.  The front-end will know how to interpret this.
782	 */
783
784	assert(buf < end);
785
786	if (mandoc_eos(buf+offs, (size_t)(end-buf-offs), 0))
787		m->last->flags |= MDOC_EOS;
788
789	return(1);
790}
791
792
793/*
794 * Parse a macro line, that is, a line beginning with the control
795 * character.
796 */
797static int
798mdoc_pmacro(struct mdoc *m, int ln, char *buf, int offs)
799{
800	enum mdoct	  tok;
801	int		  i, j, sv;
802	char		  mac[5];
803	struct mdoc_node *n;
804
805	/* Empty lines are ignored. */
806
807	offs++;
808
809	if ('\0' == buf[offs])
810		return(1);
811
812	i = offs;
813
814	/* Accept tabs/whitespace after the initial control char. */
815
816	if (' ' == buf[i] || '\t' == buf[i]) {
817		i++;
818		while (buf[i] && (' ' == buf[i] || '\t' == buf[i]))
819			i++;
820		if ('\0' == buf[i])
821			return(1);
822	}
823
824	sv = i;
825
826	/*
827	 * Copy the first word into a nil-terminated buffer.
828	 * Stop copying when a tab, space, or eoln is encountered.
829	 */
830
831	j = 0;
832	while (j < 4 && '\0' != buf[i] && ' ' != buf[i] && '\t' != buf[i])
833		mac[j++] = buf[i++];
834	mac[j] = '\0';
835
836	tok = (j > 1 || j < 4) ? mdoc_hash_find(mac) : MDOC_MAX;
837	if (MDOC_MAX == tok) {
838		mdoc_vmsg(m, MANDOCERR_MACRO, ln, sv, "%s", buf + sv - 1);
839		return(1);
840	}
841
842	/* Disregard the first trailing tab, if applicable. */
843
844	if ('\t' == buf[i])
845		i++;
846
847	/* Jump to the next non-whitespace word. */
848
849	while (buf[i] && ' ' == buf[i])
850		i++;
851
852	/*
853	 * Trailing whitespace.  Note that tabs are allowed to be passed
854	 * into the parser as "text", so we only warn about spaces here.
855	 */
856
857	if ('\0' == buf[i] && ' ' == buf[i - 1])
858		mdoc_pmsg(m, ln, i - 1, MANDOCERR_EOLNSPACE);
859
860	/*
861	 * If an initial macro or a list invocation, divert directly
862	 * into macro processing.
863	 */
864
865	if (NULL == m->last || MDOC_It == tok || MDOC_El == tok) {
866		if ( ! mdoc_macro(m, tok, ln, sv, &i, buf))
867			goto err;
868		return(1);
869	}
870
871	n = m->last;
872	assert(m->last);
873
874	/*
875	 * If the first macro of a `Bl -column', open an `It' block
876	 * context around the parsed macro.
877	 */
878
879	if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
880			LIST_column == n->norm->Bl.type) {
881		m->flags |= MDOC_FREECOL;
882		if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf))
883			goto err;
884		return(1);
885	}
886
887	/*
888	 * If we're following a block-level `It' within a `Bl -column'
889	 * context (perhaps opened in the above block or in ptext()),
890	 * then open an `It' block context around the parsed macro.
891	 */
892
893	if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
894			NULL != n->parent &&
895			MDOC_Bl == n->parent->tok &&
896			LIST_column == n->parent->norm->Bl.type) {
897		m->flags |= MDOC_FREECOL;
898		if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf))
899			goto err;
900		return(1);
901	}
902
903	/* Normal processing of a macro. */
904
905	if ( ! mdoc_macro(m, tok, ln, sv, &i, buf))
906		goto err;
907
908	return(1);
909
910err:	/* Error out. */
911
912	m->flags |= MDOC_HALT;
913	return(0);
914}
915
916
917