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