xref: /openbsd-src/usr.bin/mandoc/eqn.c (revision d9a51c353c88dac7b4a389c112b4cfe97b8e3a46)
1*d9a51c35Sjmc /* $OpenBSD: eqn.c,v 1.49 2022/12/26 19:16:02 jmc Exp $ */
2883d305eSschwarze /*
35030fd5bSschwarze  * Copyright (c) 2014, 2015, 2017, 2018, 2020, 2022
45030fd5bSschwarze  *               Ingo Schwarze <schwarze@openbsd.org>
5a3108a0aSschwarze  * Copyright (c) 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
6883d305eSschwarze  *
7883d305eSschwarze  * Permission to use, copy, modify, and distribute this software for any
8883d305eSschwarze  * purpose with or without fee is hereby granted, provided that the above
9883d305eSschwarze  * copyright notice and this permission notice appear in all copies.
10883d305eSschwarze  *
11883d305eSschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12883d305eSschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13883d305eSschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14883d305eSschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15883d305eSschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16883d305eSschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17883d305eSschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18883d305eSschwarze  */
19f8617809Sschwarze #include <sys/types.h>
20f8617809Sschwarze 
21883d305eSschwarze #include <assert.h>
2259c226a5Sschwarze #include <ctype.h>
23f8618d99Sschwarze #include <limits.h>
24f8618d99Sschwarze #include <stdio.h>
25883d305eSschwarze #include <stdlib.h>
26883d305eSschwarze #include <string.h>
2704e980cbSschwarze #include <time.h>
28883d305eSschwarze 
294f4f7972Sschwarze #include "mandoc_aux.h"
30bf9acac6Sschwarze #include "mandoc.h"
31bf9acac6Sschwarze #include "roff.h"
32d4c8d4a3Sschwarze #include "eqn.h"
33883d305eSschwarze #include "libmandoc.h"
34abe31b56Sschwarze #include "eqn_parse.h"
35883d305eSschwarze 
36f8618d99Sschwarze #define	EQN_NEST_MAX	 128 /* maximum nesting of defines */
37f8617809Sschwarze #define	STRNEQ(p1, sz1, p2, sz2) \
38f8617809Sschwarze 	((sz1) == (sz2) && 0 == strncmp((p1), (p2), (sz1)))
39f8618d99Sschwarze 
40f8617809Sschwarze enum	eqn_tok {
41f8617809Sschwarze 	EQN_TOK_DYAD = 0,
42f8617809Sschwarze 	EQN_TOK_VEC,
43f8617809Sschwarze 	EQN_TOK_UNDER,
44f8617809Sschwarze 	EQN_TOK_BAR,
45f8617809Sschwarze 	EQN_TOK_TILDE,
46f8617809Sschwarze 	EQN_TOK_HAT,
47f8617809Sschwarze 	EQN_TOK_DOT,
48f8617809Sschwarze 	EQN_TOK_DOTDOT,
49f8617809Sschwarze 	EQN_TOK_FWD,
50f8617809Sschwarze 	EQN_TOK_BACK,
51f8617809Sschwarze 	EQN_TOK_DOWN,
52f8617809Sschwarze 	EQN_TOK_UP,
53f8617809Sschwarze 	EQN_TOK_FAT,
54f8617809Sschwarze 	EQN_TOK_ROMAN,
55f8617809Sschwarze 	EQN_TOK_ITALIC,
56f8617809Sschwarze 	EQN_TOK_BOLD,
57f8617809Sschwarze 	EQN_TOK_SIZE,
58f8617809Sschwarze 	EQN_TOK_SUB,
59f8617809Sschwarze 	EQN_TOK_SUP,
60f8617809Sschwarze 	EQN_TOK_SQRT,
61f8617809Sschwarze 	EQN_TOK_OVER,
62f8617809Sschwarze 	EQN_TOK_FROM,
63f8617809Sschwarze 	EQN_TOK_TO,
64f8617809Sschwarze 	EQN_TOK_BRACE_OPEN,
65f8617809Sschwarze 	EQN_TOK_BRACE_CLOSE,
66f8617809Sschwarze 	EQN_TOK_GSIZE,
67f8617809Sschwarze 	EQN_TOK_GFONT,
68f8617809Sschwarze 	EQN_TOK_MARK,
69f8617809Sschwarze 	EQN_TOK_LINEUP,
70f8617809Sschwarze 	EQN_TOK_LEFT,
71f8617809Sschwarze 	EQN_TOK_RIGHT,
72f8617809Sschwarze 	EQN_TOK_PILE,
73f8617809Sschwarze 	EQN_TOK_LPILE,
74f8617809Sschwarze 	EQN_TOK_RPILE,
75f8617809Sschwarze 	EQN_TOK_CPILE,
76f8617809Sschwarze 	EQN_TOK_MATRIX,
77f8617809Sschwarze 	EQN_TOK_CCOL,
78f8617809Sschwarze 	EQN_TOK_LCOL,
79f8617809Sschwarze 	EQN_TOK_RCOL,
80f8617809Sschwarze 	EQN_TOK_DELIM,
81f8617809Sschwarze 	EQN_TOK_DEFINE,
82f8617809Sschwarze 	EQN_TOK_TDEFINE,
83f8617809Sschwarze 	EQN_TOK_NDEFINE,
84f8617809Sschwarze 	EQN_TOK_UNDEF,
85f8617809Sschwarze 	EQN_TOK_ABOVE,
8666b13c11Sschwarze 	EQN_TOK__MAX,
8766b13c11Sschwarze 	EQN_TOK_FUNC,
8821b46522Sschwarze 	EQN_TOK_QUOTED,
8921b46522Sschwarze 	EQN_TOK_SYM,
9066b13c11Sschwarze 	EQN_TOK_EOF
91f8617809Sschwarze };
92f8617809Sschwarze 
93f8617809Sschwarze static	const char *eqn_toks[EQN_TOK__MAX] = {
94f8617809Sschwarze 	"dyad", /* EQN_TOK_DYAD */
95f8617809Sschwarze 	"vec", /* EQN_TOK_VEC */
96f8617809Sschwarze 	"under", /* EQN_TOK_UNDER */
97f8617809Sschwarze 	"bar", /* EQN_TOK_BAR */
98f8617809Sschwarze 	"tilde", /* EQN_TOK_TILDE */
99f8617809Sschwarze 	"hat", /* EQN_TOK_HAT */
100f8617809Sschwarze 	"dot", /* EQN_TOK_DOT */
101f8617809Sschwarze 	"dotdot", /* EQN_TOK_DOTDOT */
102f8617809Sschwarze 	"fwd", /* EQN_TOK_FWD * */
103f8617809Sschwarze 	"back", /* EQN_TOK_BACK */
104f8617809Sschwarze 	"down", /* EQN_TOK_DOWN */
105f8617809Sschwarze 	"up", /* EQN_TOK_UP */
106f8617809Sschwarze 	"fat", /* EQN_TOK_FAT */
107f8617809Sschwarze 	"roman", /* EQN_TOK_ROMAN */
108f8617809Sschwarze 	"italic", /* EQN_TOK_ITALIC */
109f8617809Sschwarze 	"bold", /* EQN_TOK_BOLD */
110f8617809Sschwarze 	"size", /* EQN_TOK_SIZE */
111f8617809Sschwarze 	"sub", /* EQN_TOK_SUB */
112f8617809Sschwarze 	"sup", /* EQN_TOK_SUP */
113f8617809Sschwarze 	"sqrt", /* EQN_TOK_SQRT */
114f8617809Sschwarze 	"over", /* EQN_TOK_OVER */
115f8617809Sschwarze 	"from", /* EQN_TOK_FROM */
116f8617809Sschwarze 	"to", /* EQN_TOK_TO */
117f8617809Sschwarze 	"{", /* EQN_TOK_BRACE_OPEN */
118f8617809Sschwarze 	"}", /* EQN_TOK_BRACE_CLOSE */
119f8617809Sschwarze 	"gsize", /* EQN_TOK_GSIZE */
120f8617809Sschwarze 	"gfont", /* EQN_TOK_GFONT */
121f8617809Sschwarze 	"mark", /* EQN_TOK_MARK */
122f8617809Sschwarze 	"lineup", /* EQN_TOK_LINEUP */
123f8617809Sschwarze 	"left", /* EQN_TOK_LEFT */
124f8617809Sschwarze 	"right", /* EQN_TOK_RIGHT */
125f8617809Sschwarze 	"pile", /* EQN_TOK_PILE */
126f8617809Sschwarze 	"lpile", /* EQN_TOK_LPILE */
127f8617809Sschwarze 	"rpile", /* EQN_TOK_RPILE */
128f8617809Sschwarze 	"cpile", /* EQN_TOK_CPILE */
129f8617809Sschwarze 	"matrix", /* EQN_TOK_MATRIX */
130f8617809Sschwarze 	"ccol", /* EQN_TOK_CCOL */
131f8617809Sschwarze 	"lcol", /* EQN_TOK_LCOL */
132f8617809Sschwarze 	"rcol", /* EQN_TOK_RCOL */
133f8617809Sschwarze 	"delim", /* EQN_TOK_DELIM */
134f8617809Sschwarze 	"define", /* EQN_TOK_DEFINE */
135f8617809Sschwarze 	"tdefine", /* EQN_TOK_TDEFINE */
136f8617809Sschwarze 	"ndefine", /* EQN_TOK_NDEFINE */
137f8617809Sschwarze 	"undef", /* EQN_TOK_UNDEF */
138f8617809Sschwarze 	"above", /* EQN_TOK_ABOVE */
139f8618d99Sschwarze };
140f8618d99Sschwarze 
14166b13c11Sschwarze static	const char *const eqn_func[] = {
14266b13c11Sschwarze 	"acos",	"acsc",	"and",	"arc",	"asec",	"asin", "atan",
14366b13c11Sschwarze 	"cos",	"cosh", "coth",	"csc",	"det",	"exp",	"for",
14466b13c11Sschwarze 	"if",	"lim",	"ln",	"log",	"max",	"min",
14566b13c11Sschwarze 	"sec",	"sin",	"sinh",	"tan",	"tanh",	"Im",	"Re",
14666b13c11Sschwarze };
14766b13c11Sschwarze 
148f8618d99Sschwarze enum	eqn_symt {
1496538659fSschwarze 	EQNSYM_alpha = 0,
150f8618d99Sschwarze 	EQNSYM_beta,
151f8618d99Sschwarze 	EQNSYM_chi,
152f8618d99Sschwarze 	EQNSYM_delta,
153f8618d99Sschwarze 	EQNSYM_epsilon,
154f8618d99Sschwarze 	EQNSYM_eta,
155f8618d99Sschwarze 	EQNSYM_gamma,
156f8618d99Sschwarze 	EQNSYM_iota,
157f8618d99Sschwarze 	EQNSYM_kappa,
158f8618d99Sschwarze 	EQNSYM_lambda,
159f8618d99Sschwarze 	EQNSYM_mu,
160f8618d99Sschwarze 	EQNSYM_nu,
161f8618d99Sschwarze 	EQNSYM_omega,
162f8618d99Sschwarze 	EQNSYM_omicron,
163f8618d99Sschwarze 	EQNSYM_phi,
164f8618d99Sschwarze 	EQNSYM_pi,
165f8618d99Sschwarze 	EQNSYM_ps,
166f8618d99Sschwarze 	EQNSYM_rho,
167f8618d99Sschwarze 	EQNSYM_sigma,
168f8618d99Sschwarze 	EQNSYM_tau,
169f8618d99Sschwarze 	EQNSYM_theta,
170f8618d99Sschwarze 	EQNSYM_upsilon,
171f8618d99Sschwarze 	EQNSYM_xi,
172f8618d99Sschwarze 	EQNSYM_zeta,
173f8618d99Sschwarze 	EQNSYM_DELTA,
174f8618d99Sschwarze 	EQNSYM_GAMMA,
175f8618d99Sschwarze 	EQNSYM_LAMBDA,
176f8618d99Sschwarze 	EQNSYM_OMEGA,
177f8618d99Sschwarze 	EQNSYM_PHI,
178f8618d99Sschwarze 	EQNSYM_PI,
179f8618d99Sschwarze 	EQNSYM_PSI,
180f8618d99Sschwarze 	EQNSYM_SIGMA,
181f8618d99Sschwarze 	EQNSYM_THETA,
182f8618d99Sschwarze 	EQNSYM_UPSILON,
183f8618d99Sschwarze 	EQNSYM_XI,
184f8618d99Sschwarze 	EQNSYM_inter,
185f8618d99Sschwarze 	EQNSYM_union,
186f8618d99Sschwarze 	EQNSYM_prod,
187f8618d99Sschwarze 	EQNSYM_int,
188f8618d99Sschwarze 	EQNSYM_sum,
189f8618d99Sschwarze 	EQNSYM_grad,
190f8618d99Sschwarze 	EQNSYM_del,
191f8618d99Sschwarze 	EQNSYM_times,
192f8618d99Sschwarze 	EQNSYM_cdot,
193f8618d99Sschwarze 	EQNSYM_nothing,
194f8618d99Sschwarze 	EQNSYM_approx,
195f8618d99Sschwarze 	EQNSYM_prime,
196f8618d99Sschwarze 	EQNSYM_half,
197f8618d99Sschwarze 	EQNSYM_partial,
198f8618d99Sschwarze 	EQNSYM_inf,
199f8618d99Sschwarze 	EQNSYM_muchgreat,
200f8618d99Sschwarze 	EQNSYM_muchless,
201f8618d99Sschwarze 	EQNSYM_larrow,
202f8618d99Sschwarze 	EQNSYM_rarrow,
203f8618d99Sschwarze 	EQNSYM_pm,
204f8618d99Sschwarze 	EQNSYM_nequal,
205f8618d99Sschwarze 	EQNSYM_equiv,
206f8618d99Sschwarze 	EQNSYM_lessequal,
207f8618d99Sschwarze 	EQNSYM_moreequal,
208edec86bbSbentley 	EQNSYM_minus,
209f8618d99Sschwarze 	EQNSYM__MAX
210f8618d99Sschwarze };
211f8618d99Sschwarze 
212f8618d99Sschwarze struct	eqnsym {
213f8617809Sschwarze 	const char	*str;
214f8618d99Sschwarze 	const char	*sym;
215f8618d99Sschwarze };
216f8618d99Sschwarze 
217f8618d99Sschwarze static	const struct eqnsym eqnsyms[EQNSYM__MAX] = {
218f8617809Sschwarze 	{ "alpha", "*a" }, /* EQNSYM_alpha */
219f8617809Sschwarze 	{ "beta", "*b" }, /* EQNSYM_beta */
220f8617809Sschwarze 	{ "chi", "*x" }, /* EQNSYM_chi */
221f8617809Sschwarze 	{ "delta", "*d" }, /* EQNSYM_delta */
222f8617809Sschwarze 	{ "epsilon", "*e" }, /* EQNSYM_epsilon */
223f8617809Sschwarze 	{ "eta", "*y" }, /* EQNSYM_eta */
224f8617809Sschwarze 	{ "gamma", "*g" }, /* EQNSYM_gamma */
225f8617809Sschwarze 	{ "iota", "*i" }, /* EQNSYM_iota */
226f8617809Sschwarze 	{ "kappa", "*k" }, /* EQNSYM_kappa */
227f8617809Sschwarze 	{ "lambda", "*l" }, /* EQNSYM_lambda */
228f8617809Sschwarze 	{ "mu", "*m" }, /* EQNSYM_mu */
229f8617809Sschwarze 	{ "nu", "*n" }, /* EQNSYM_nu */
230f8617809Sschwarze 	{ "omega", "*w" }, /* EQNSYM_omega */
231f8617809Sschwarze 	{ "omicron", "*o" }, /* EQNSYM_omicron */
232f8617809Sschwarze 	{ "phi", "*f" }, /* EQNSYM_phi */
233f8617809Sschwarze 	{ "pi", "*p" }, /* EQNSYM_pi */
234f8617809Sschwarze 	{ "psi", "*q" }, /* EQNSYM_psi */
235f8617809Sschwarze 	{ "rho", "*r" }, /* EQNSYM_rho */
236f8617809Sschwarze 	{ "sigma", "*s" }, /* EQNSYM_sigma */
237f8617809Sschwarze 	{ "tau", "*t" }, /* EQNSYM_tau */
238f8617809Sschwarze 	{ "theta", "*h" }, /* EQNSYM_theta */
239f8617809Sschwarze 	{ "upsilon", "*u" }, /* EQNSYM_upsilon */
240f8617809Sschwarze 	{ "xi", "*c" }, /* EQNSYM_xi */
241f8617809Sschwarze 	{ "zeta", "*z" }, /* EQNSYM_zeta */
242f8617809Sschwarze 	{ "DELTA", "*D" }, /* EQNSYM_DELTA */
243f8617809Sschwarze 	{ "GAMMA", "*G" }, /* EQNSYM_GAMMA */
244f8617809Sschwarze 	{ "LAMBDA", "*L" }, /* EQNSYM_LAMBDA */
245f8617809Sschwarze 	{ "OMEGA", "*W" }, /* EQNSYM_OMEGA */
246f8617809Sschwarze 	{ "PHI", "*F" }, /* EQNSYM_PHI */
247f8617809Sschwarze 	{ "PI", "*P" }, /* EQNSYM_PI */
248f8617809Sschwarze 	{ "PSI", "*Q" }, /* EQNSYM_PSI */
249f8617809Sschwarze 	{ "SIGMA", "*S" }, /* EQNSYM_SIGMA */
250f8617809Sschwarze 	{ "THETA", "*H" }, /* EQNSYM_THETA */
251f8617809Sschwarze 	{ "UPSILON", "*U" }, /* EQNSYM_UPSILON */
252f8617809Sschwarze 	{ "XI", "*C" }, /* EQNSYM_XI */
253f8617809Sschwarze 	{ "inter", "ca" }, /* EQNSYM_inter */
254f8617809Sschwarze 	{ "union", "cu" }, /* EQNSYM_union */
255f8617809Sschwarze 	{ "prod", "product" }, /* EQNSYM_prod */
256f8617809Sschwarze 	{ "int", "integral" }, /* EQNSYM_int */
257f8617809Sschwarze 	{ "sum", "sum" }, /* EQNSYM_sum */
258f8617809Sschwarze 	{ "grad", "gr" }, /* EQNSYM_grad */
259f8617809Sschwarze 	{ "del", "gr" }, /* EQNSYM_del */
260f8617809Sschwarze 	{ "times", "mu" }, /* EQNSYM_times */
261f8617809Sschwarze 	{ "cdot", "pc" }, /* EQNSYM_cdot */
262f8617809Sschwarze 	{ "nothing", "&" }, /* EQNSYM_nothing */
263f8617809Sschwarze 	{ "approx", "~~" }, /* EQNSYM_approx */
264edec86bbSbentley 	{ "prime", "fm" }, /* EQNSYM_prime */
265f8617809Sschwarze 	{ "half", "12" }, /* EQNSYM_half */
266f8617809Sschwarze 	{ "partial", "pd" }, /* EQNSYM_partial */
267f8617809Sschwarze 	{ "inf", "if" }, /* EQNSYM_inf */
268f8617809Sschwarze 	{ ">>", ">>" }, /* EQNSYM_muchgreat */
269f8617809Sschwarze 	{ "<<", "<<" }, /* EQNSYM_muchless */
270f8617809Sschwarze 	{ "<-", "<-" }, /* EQNSYM_larrow */
271f8617809Sschwarze 	{ "->", "->" }, /* EQNSYM_rarrow */
272f8617809Sschwarze 	{ "+-", "+-" }, /* EQNSYM_pm */
273f8617809Sschwarze 	{ "!=", "!=" }, /* EQNSYM_nequal */
274f8617809Sschwarze 	{ "==", "==" }, /* EQNSYM_equiv */
275f8617809Sschwarze 	{ "<=", "<=" }, /* EQNSYM_lessequal */
276f8617809Sschwarze 	{ ">=", ">=" }, /* EQNSYM_moreequal */
277edec86bbSbentley 	{ "-", "mi" }, /* EQNSYM_minus */
278f8618d99Sschwarze };
279f8618d99Sschwarze 
2806538659fSschwarze enum	parse_mode {
2816538659fSschwarze 	MODE_QUOTED,
2826538659fSschwarze 	MODE_NOSUB,
2836538659fSschwarze 	MODE_SUB,
2846538659fSschwarze 	MODE_TOK
2856538659fSschwarze };
2866538659fSschwarze 
287abe31b56Sschwarze struct	eqn_def {
288abe31b56Sschwarze 	char		 *key;
289abe31b56Sschwarze 	size_t		  keysz;
290abe31b56Sschwarze 	char		 *val;
291abe31b56Sschwarze 	size_t		  valsz;
292abe31b56Sschwarze };
293abe31b56Sschwarze 
2943578e616Sschwarze static	struct eqn_box	*eqn_box_alloc(struct eqn_node *, struct eqn_box *);
2953578e616Sschwarze static	struct eqn_box	*eqn_box_makebinary(struct eqn_node *,
296244ed75aSschwarze 				struct eqn_box *);
2973578e616Sschwarze static	void		 eqn_def(struct eqn_node *);
2986538659fSschwarze static	struct eqn_def	*eqn_def_find(struct eqn_node *);
2993578e616Sschwarze static	void		 eqn_delim(struct eqn_node *);
3006538659fSschwarze static	enum eqn_tok	 eqn_next(struct eqn_node *, enum parse_mode);
3013578e616Sschwarze static	void		 eqn_undef(struct eqn_node *);
3023578e616Sschwarze 
3033578e616Sschwarze 
304883d305eSschwarze struct eqn_node *
eqn_alloc(void)30591305757Sschwarze eqn_alloc(void)
306883d305eSschwarze {
307bf9acac6Sschwarze 	struct eqn_node *ep;
308883d305eSschwarze 
309bf9acac6Sschwarze 	ep = mandoc_calloc(1, sizeof(*ep));
310bf9acac6Sschwarze 	ep->gsize = EQN_DEFSIZE;
311bf9acac6Sschwarze 	return ep;
312bf9acac6Sschwarze }
313f8618d99Sschwarze 
314bf9acac6Sschwarze void
eqn_reset(struct eqn_node * ep)315bf9acac6Sschwarze eqn_reset(struct eqn_node *ep)
316bf9acac6Sschwarze {
317bf9acac6Sschwarze 	free(ep->data);
318bf9acac6Sschwarze 	ep->data = ep->start = ep->end = NULL;
319bf9acac6Sschwarze 	ep->sz = ep->toksz = 0;
320bf9acac6Sschwarze }
321883d305eSschwarze 
322bf9acac6Sschwarze void
eqn_read(struct eqn_node * ep,const char * p)323bf9acac6Sschwarze eqn_read(struct eqn_node *ep, const char *p)
324bf9acac6Sschwarze {
325bf9acac6Sschwarze 	char		*cp;
326bf9acac6Sschwarze 
327bf9acac6Sschwarze 	if (ep->data == NULL) {
328bf9acac6Sschwarze 		ep->sz = strlen(p);
329bf9acac6Sschwarze 		ep->data = mandoc_strdup(p);
330bf9acac6Sschwarze 	} else {
331bf9acac6Sschwarze 		ep->sz = mandoc_asprintf(&cp, "%s %s", ep->data, p);
332bf9acac6Sschwarze 		free(ep->data);
333bf9acac6Sschwarze 		ep->data = cp;
334bf9acac6Sschwarze 	}
335bf9acac6Sschwarze 	ep->sz += 1;
336883d305eSschwarze }
337883d305eSschwarze 
3388aae8206Sschwarze /*
339f8617809Sschwarze  * Find the key "key" of the give size within our eqn-defined values.
3408aae8206Sschwarze  */
341f8617809Sschwarze static struct eqn_def *
eqn_def_find(struct eqn_node * ep)3426538659fSschwarze eqn_def_find(struct eqn_node *ep)
343883d305eSschwarze {
344f8618d99Sschwarze 	int		 i;
345883d305eSschwarze 
346f8617809Sschwarze 	for (i = 0; i < (int)ep->defsz; i++)
347f8617809Sschwarze 		if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key,
3486538659fSschwarze 		    ep->defs[i].keysz, ep->start, ep->toksz))
349526e306bSschwarze 			return &ep->defs[i];
350f8618d99Sschwarze 
351526e306bSschwarze 	return NULL;
352f8618d99Sschwarze }
353f8618d99Sschwarze 
354f8617809Sschwarze /*
3556538659fSschwarze  * Parse a token from the input text.  The modes are:
3566538659fSschwarze  * MODE_QUOTED: Use *ep->start as the delimiter; the token ends
357*d9a51c35Sjmc  *   before its next occurrence.  Do not interpret the token in any
3586538659fSschwarze  *   way and return EQN_TOK_QUOTED.  All other modes behave like
3596538659fSschwarze  *   MODE_QUOTED when *ep->start is '"'.
3606538659fSschwarze  * MODE_NOSUB: If *ep->start is a curly brace, the token ends after it;
3616538659fSschwarze  *   otherwise, it ends before the next whitespace or brace.
3626538659fSschwarze  *   Do not interpret the token and return EQN_TOK__MAX.
3636538659fSschwarze  * MODE_SUB: Like MODE_NOSUB, but try to interpret the token as an
3646538659fSschwarze  *   alias created with define.  If it is an alias, replace it with
3656538659fSschwarze  *   its string value and reparse.
3666538659fSschwarze  * MODE_TOK: Like MODE_SUB, but also check the token against the list
3676538659fSschwarze  *   of tokens, and if there is a match, return that token.  Otherwise,
3686538659fSschwarze  *   if the token matches a symbol, return EQN_TOK_SYM; if it matches
3696538659fSschwarze  *   a function name, EQN_TOK_FUNC, or else EQN_TOK__MAX.  Except for
3706538659fSschwarze  *   a token match, *ep->start is set to an allocated string that the
3716538659fSschwarze  *   caller is expected to free.
3726538659fSschwarze  * All modes skip whitespace following the end of the token.
373f8617809Sschwarze  */
374f8617809Sschwarze static enum eqn_tok
eqn_next(struct eqn_node * ep,enum parse_mode mode)3756538659fSschwarze eqn_next(struct eqn_node *ep, enum parse_mode mode)
376f8617809Sschwarze {
3776538659fSschwarze 	struct eqn_def	*def;
3786538659fSschwarze 	size_t		 start;
3795030fd5bSschwarze 	int		 diff, i, newlen, quoted;
3806538659fSschwarze 	enum eqn_tok	 tok;
381f8617809Sschwarze 
3826538659fSschwarze 	/*
3836538659fSschwarze 	 * Reset the recursion counter after advancing
3845030fd5bSschwarze 	 * beyond the end of the rightmost substitution.
3856538659fSschwarze 	 */
3865030fd5bSschwarze 	if (ep->end - ep->data >= ep->sublen)
3875030fd5bSschwarze 		ep->subcnt = 0;
3886c081f56Sschwarze 
3896538659fSschwarze 	ep->start = ep->end;
3906538659fSschwarze 	quoted = mode == MODE_QUOTED;
3916538659fSschwarze 	for (;;) {
3926538659fSschwarze 		switch (*ep->start) {
3936538659fSschwarze 		case '\0':
3946538659fSschwarze 			ep->toksz = 0;
395526e306bSschwarze 			return EQN_TOK_EOF;
3966538659fSschwarze 		case '"':
3976538659fSschwarze 			quoted = 1;
3986538659fSschwarze 			break;
3990f5f5b53Sschwarze 		case ' ':
4000f5f5b53Sschwarze 		case '\t':
4010f5f5b53Sschwarze 		case '~':
4020f5f5b53Sschwarze 		case '^':
4030f5f5b53Sschwarze 			if (quoted)
4040f5f5b53Sschwarze 				break;
4050f5f5b53Sschwarze 			ep->start++;
4060f5f5b53Sschwarze 			continue;
4076538659fSschwarze 		default:
4086538659fSschwarze 			break;
4096538659fSschwarze 		}
4106c081f56Sschwarze 		if (quoted) {
4116538659fSschwarze 			ep->end = strchr(ep->start + 1, *ep->start);
4126538659fSschwarze 			ep->start++;  /* Skip opening quote. */
4136538659fSschwarze 			if (ep->end == NULL) {
414a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_ARG_QUOTE,
415bf9acac6Sschwarze 				    ep->node->line, ep->node->pos, NULL);
4166538659fSschwarze 				ep->end = strchr(ep->start, '\0');
4176538659fSschwarze 			}
4186538659fSschwarze 		} else {
4196538659fSschwarze 			ep->end = ep->start + 1;
4206538659fSschwarze 			if (*ep->start != '{' && *ep->start != '}')
4216538659fSschwarze 				ep->end += strcspn(ep->end, " ^~\"{}\t");
4226538659fSschwarze 		}
4236538659fSschwarze 		ep->toksz = ep->end - ep->start;
4246538659fSschwarze 		if (quoted && *ep->end != '\0')
4256538659fSschwarze 			ep->end++;  /* Skip closing quote. */
4266538659fSschwarze 		while (*ep->end != '\0' && strchr(" \t^~", *ep->end) != NULL)
4276538659fSschwarze 			ep->end++;
4286538659fSschwarze 		if (quoted)  /* Cannot return, may have to strndup. */
4296538659fSschwarze 			break;
4306538659fSschwarze 		if (mode == MODE_NOSUB)
4316538659fSschwarze 			return EQN_TOK__MAX;
4326538659fSschwarze 		if ((def = eqn_def_find(ep)) == NULL)
4336538659fSschwarze 			break;
4345030fd5bSschwarze 		if (++ep->subcnt > EQN_NEST_MAX) {
435a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_ROFFLOOP,
436bf9acac6Sschwarze 			    ep->node->line, ep->node->pos, NULL);
4375030fd5bSschwarze 			break;
4386c081f56Sschwarze 		}
4396c081f56Sschwarze 
4406538659fSschwarze 		/* Replace a defined name with its string value. */
4416538659fSschwarze 		if ((diff = def->valsz - ep->toksz) > 0) {
4426538659fSschwarze 			start = ep->start - ep->data;
4436538659fSschwarze 			ep->sz += diff;
4446538659fSschwarze 			ep->data = mandoc_realloc(ep->data, ep->sz + 1);
4456538659fSschwarze 			ep->start = ep->data + start;
4465030fd5bSschwarze 			ep->sublen += diff;
4476538659fSschwarze 		}
4486538659fSschwarze 		if (diff)
4496538659fSschwarze 			memmove(ep->start + def->valsz, ep->start + ep->toksz,
4506538659fSschwarze 			    strlen(ep->start + ep->toksz) + 1);
4516538659fSschwarze 		memcpy(ep->start, def->val, def->valsz);
4525030fd5bSschwarze 		newlen = ep->start - ep->data + def->valsz;
4535030fd5bSschwarze 		if (ep->sublen < newlen)
4545030fd5bSschwarze 			ep->sublen = newlen;
4556538659fSschwarze 	}
4566538659fSschwarze 	if (mode != MODE_TOK)
4576538659fSschwarze 		return quoted ? EQN_TOK_QUOTED : EQN_TOK__MAX;
4586538659fSschwarze 	if (quoted) {
4596538659fSschwarze 		ep->start = mandoc_strndup(ep->start, ep->toksz);
4606538659fSschwarze 		return EQN_TOK_QUOTED;
4616538659fSschwarze 	}
4626538659fSschwarze 	for (tok = 0; tok < EQN_TOK__MAX; tok++)
4636538659fSschwarze 		if (STRNEQ(ep->start, ep->toksz,
4646538659fSschwarze 		    eqn_toks[tok], strlen(eqn_toks[tok])))
4656538659fSschwarze 			return tok;
466f8617809Sschwarze 
467d5ed6f1bSschwarze 	for (i = 0; i < EQNSYM__MAX; i++) {
4686538659fSschwarze 		if (STRNEQ(ep->start, ep->toksz,
469d5ed6f1bSschwarze 		    eqnsyms[i].str, strlen(eqnsyms[i].str))) {
4706538659fSschwarze 			mandoc_asprintf(&ep->start,
4716538659fSschwarze 			    "\\[%s]", eqnsyms[i].sym);
47221b46522Sschwarze 			return EQN_TOK_SYM;
473d5ed6f1bSschwarze 		}
474d5ed6f1bSschwarze 	}
4756538659fSschwarze 	ep->start = mandoc_strndup(ep->start, ep->toksz);
4766538659fSschwarze 	for (i = 0; i < (int)(sizeof(eqn_func)/sizeof(*eqn_func)); i++)
4776538659fSschwarze 		if (STRNEQ(ep->start, ep->toksz,
4786538659fSschwarze 		    eqn_func[i], strlen(eqn_func[i])))
47966b13c11Sschwarze 			return EQN_TOK_FUNC;
48066b13c11Sschwarze 	return EQN_TOK__MAX;
481f8617809Sschwarze }
482f8617809Sschwarze 
483bf9acac6Sschwarze void
eqn_box_free(struct eqn_box * bp)484f8617809Sschwarze eqn_box_free(struct eqn_box *bp)
485f8617809Sschwarze {
486abe31b56Sschwarze 	if (bp == NULL)
487abe31b56Sschwarze 		return;
488f8617809Sschwarze 
489f8617809Sschwarze 	if (bp->first)
490f8617809Sschwarze 		eqn_box_free(bp->first);
491f8617809Sschwarze 	if (bp->next)
492f8617809Sschwarze 		eqn_box_free(bp->next);
493f8617809Sschwarze 
494f8617809Sschwarze 	free(bp->text);
495f8617809Sschwarze 	free(bp->left);
496f8617809Sschwarze 	free(bp->right);
497f8617809Sschwarze 	free(bp->top);
498f8617809Sschwarze 	free(bp->bottom);
499f8617809Sschwarze 	free(bp);
500f8617809Sschwarze }
501f8617809Sschwarze 
502d4c8d4a3Sschwarze struct eqn_box *
eqn_box_new(void)503d4c8d4a3Sschwarze eqn_box_new(void)
504d4c8d4a3Sschwarze {
505d4c8d4a3Sschwarze 	struct eqn_box	*bp;
506d4c8d4a3Sschwarze 
507d4c8d4a3Sschwarze 	bp = mandoc_calloc(1, sizeof(*bp));
508d4c8d4a3Sschwarze 	bp->expectargs = UINT_MAX;
509d4c8d4a3Sschwarze 	return bp;
510d4c8d4a3Sschwarze }
511d4c8d4a3Sschwarze 
512f8617809Sschwarze /*
513f8617809Sschwarze  * Allocate a box as the last child of the parent node.
514f8617809Sschwarze  */
515f8617809Sschwarze static struct eqn_box *
eqn_box_alloc(struct eqn_node * ep,struct eqn_box * parent)516f8617809Sschwarze eqn_box_alloc(struct eqn_node *ep, struct eqn_box *parent)
517f8617809Sschwarze {
518f8617809Sschwarze 	struct eqn_box	*bp;
519f8617809Sschwarze 
520d4c8d4a3Sschwarze 	bp = eqn_box_new();
521f8617809Sschwarze 	bp->parent = parent;
522f8617809Sschwarze 	bp->parent->args++;
523a8c898eaSschwarze 	bp->font = bp->parent->font;
524f8617809Sschwarze 	bp->size = ep->gsize;
525f8617809Sschwarze 
526f8617809Sschwarze 	if (NULL != parent->first) {
527f8617809Sschwarze 		parent->last->next = bp;
528f8617809Sschwarze 		bp->prev = parent->last;
529f8617809Sschwarze 	} else
530f8617809Sschwarze 		parent->first = bp;
531f8617809Sschwarze 
532f8617809Sschwarze 	parent->last = bp;
533526e306bSschwarze 	return bp;
534f8617809Sschwarze }
535f8617809Sschwarze 
536f8617809Sschwarze /*
537f8617809Sschwarze  * Reparent the current last node (of the current parent) under a new
538f8617809Sschwarze  * EQN_SUBEXPR as the first element.
539f8617809Sschwarze  * Then return the new parent.
540f8617809Sschwarze  * The new EQN_SUBEXPR will have a two-child limit.
541f8617809Sschwarze  */
542f8617809Sschwarze static struct eqn_box *
eqn_box_makebinary(struct eqn_node * ep,struct eqn_box * parent)543244ed75aSschwarze eqn_box_makebinary(struct eqn_node *ep, struct eqn_box *parent)
544f8617809Sschwarze {
545f8617809Sschwarze 	struct eqn_box	*b, *newb;
546f8617809Sschwarze 
547f8617809Sschwarze 	assert(NULL != parent->last);
548f8617809Sschwarze 	b = parent->last;
549f8617809Sschwarze 	if (parent->last == parent->first)
550f8617809Sschwarze 		parent->first = NULL;
551f8617809Sschwarze 	parent->args--;
552f8617809Sschwarze 	parent->last = b->prev;
553f8617809Sschwarze 	b->prev = NULL;
554f8617809Sschwarze 	newb = eqn_box_alloc(ep, parent);
555f8617809Sschwarze 	newb->type = EQN_SUBEXPR;
556f8617809Sschwarze 	newb->expectargs = 2;
557f8617809Sschwarze 	newb->args = 1;
558f8617809Sschwarze 	newb->first = newb->last = b;
559f8617809Sschwarze 	newb->first->next = NULL;
560f8617809Sschwarze 	b->parent = newb;
561526e306bSschwarze 	return newb;
562f8617809Sschwarze }
563f8617809Sschwarze 
564f8617809Sschwarze /*
56557ca9bd5Sschwarze  * Parse the "delim" control statement.
56657ca9bd5Sschwarze  */
56757ca9bd5Sschwarze static void
eqn_delim(struct eqn_node * ep)56857ca9bd5Sschwarze eqn_delim(struct eqn_node *ep)
56957ca9bd5Sschwarze {
5706538659fSschwarze 	if (ep->end[0] == '\0' || ep->end[1] == '\0') {
571a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_REQ_EMPTY,
572bf9acac6Sschwarze 		    ep->node->line, ep->node->pos, "delim");
5736538659fSschwarze 		if (ep->end[0] != '\0')
5746538659fSschwarze 			ep->end++;
5756538659fSschwarze 	} else if (strncmp(ep->end, "off", 3) == 0) {
57657ca9bd5Sschwarze 		ep->delim = 0;
5776538659fSschwarze 		ep->end += 3;
5786538659fSschwarze 	} else if (strncmp(ep->end, "on", 2) == 0) {
57957ca9bd5Sschwarze 		if (ep->odelim && ep->cdelim)
58057ca9bd5Sschwarze 			ep->delim = 1;
5816538659fSschwarze 		ep->end += 2;
5826538659fSschwarze 	} else {
5836538659fSschwarze 		ep->odelim = *ep->end++;
5846538659fSschwarze 		ep->cdelim = *ep->end++;
58557ca9bd5Sschwarze 		ep->delim = 1;
58657ca9bd5Sschwarze 	}
58757ca9bd5Sschwarze }
58857ca9bd5Sschwarze 
58957ca9bd5Sschwarze /*
590f8617809Sschwarze  * Undefine a previously-defined string.
591f8617809Sschwarze  */
5923578e616Sschwarze static void
eqn_undef(struct eqn_node * ep)593f8617809Sschwarze eqn_undef(struct eqn_node *ep)
594f8617809Sschwarze {
595f8617809Sschwarze 	struct eqn_def	*def;
596f8617809Sschwarze 
5976538659fSschwarze 	if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF) {
598a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_REQ_EMPTY,
599bf9acac6Sschwarze 		    ep->node->line, ep->node->pos, "undef");
6003578e616Sschwarze 		return;
6013578e616Sschwarze 	}
6026538659fSschwarze 	if ((def = eqn_def_find(ep)) == NULL)
6033578e616Sschwarze 		return;
6043578e616Sschwarze 	free(def->key);
6053578e616Sschwarze 	free(def->val);
6063578e616Sschwarze 	def->key = def->val = NULL;
6073578e616Sschwarze 	def->keysz = def->valsz = 0;
608f8618d99Sschwarze }
609f8618d99Sschwarze 
6103578e616Sschwarze static void
eqn_def(struct eqn_node * ep)611f8617809Sschwarze eqn_def(struct eqn_node *ep)
612f8618d99Sschwarze {
613f8618d99Sschwarze 	struct eqn_def	*def;
614f8618d99Sschwarze 	int		 i;
615f8618d99Sschwarze 
6166538659fSschwarze 	if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF) {
617a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_REQ_EMPTY,
618bf9acac6Sschwarze 		    ep->node->line, ep->node->pos, "define");
6193578e616Sschwarze 		return;
620f8618d99Sschwarze 	}
621f8618d99Sschwarze 
622f8618d99Sschwarze 	/*
623f8618d99Sschwarze 	 * Search for a key that already exists.
624f8618d99Sschwarze 	 * Create a new key if none is found.
625f8618d99Sschwarze 	 */
6266538659fSschwarze 	if ((def = eqn_def_find(ep)) == NULL) {
627f8618d99Sschwarze 		/* Find holes in string array. */
628f8618d99Sschwarze 		for (i = 0; i < (int)ep->defsz; i++)
629f8618d99Sschwarze 			if (0 == ep->defs[i].keysz)
630f8618d99Sschwarze 				break;
631f8618d99Sschwarze 
632f8618d99Sschwarze 		if (i == (int)ep->defsz) {
633f8618d99Sschwarze 			ep->defsz++;
6348286bf36Sschwarze 			ep->defs = mandoc_reallocarray(ep->defs,
6358286bf36Sschwarze 			    ep->defsz, sizeof(struct eqn_def));
636f8618d99Sschwarze 			ep->defs[i].key = ep->defs[i].val = NULL;
637f8618d99Sschwarze 		}
638f8618d99Sschwarze 
6393578e616Sschwarze 		def = ep->defs + i;
6403578e616Sschwarze 		free(def->key);
6416538659fSschwarze 		def->key = mandoc_strndup(ep->start, ep->toksz);
6426538659fSschwarze 		def->keysz = ep->toksz;
643f8618d99Sschwarze 	}
644f8618d99Sschwarze 
6456538659fSschwarze 	if (eqn_next(ep, MODE_QUOTED) == EQN_TOK_EOF) {
646a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_REQ_EMPTY,
647bf9acac6Sschwarze 		    ep->node->line, ep->node->pos, "define %s", def->key);
6483578e616Sschwarze 		free(def->key);
6493578e616Sschwarze 		free(def->val);
6503578e616Sschwarze 		def->key = def->val = NULL;
6513578e616Sschwarze 		def->keysz = def->valsz = 0;
6523578e616Sschwarze 		return;
653f8618d99Sschwarze 	}
6543578e616Sschwarze 	free(def->val);
6556538659fSschwarze 	def->val = mandoc_strndup(ep->start, ep->toksz);
6566538659fSschwarze 	def->valsz = ep->toksz;
657f8618d99Sschwarze }
658f8618d99Sschwarze 
659bf9acac6Sschwarze void
eqn_parse(struct eqn_node * ep)660bf9acac6Sschwarze eqn_parse(struct eqn_node *ep)
661f8618d99Sschwarze {
662bf9acac6Sschwarze 	struct eqn_box	*cur, *nbox, *parent, *split;
6636538659fSschwarze 	const char	*cp, *cpn;
664f8617809Sschwarze 	char		*p;
6656538659fSschwarze 	enum eqn_tok	 tok;
666a8c898eaSschwarze 	enum { CCL_LET, CCL_DIG, CCL_PUN } ccl, ccln;
6673578e616Sschwarze 	int		 size;
668f8618d99Sschwarze 
669bf9acac6Sschwarze 	parent = ep->node->eqn;
6708b6ab528Sschwarze 	assert(parent != NULL);
6713578e616Sschwarze 
6723578e616Sschwarze 	/*
6733578e616Sschwarze 	 * Empty equation.
6743578e616Sschwarze 	 * Do not add it to the high-level syntax tree.
6753578e616Sschwarze 	 */
6763578e616Sschwarze 
6778b6ab528Sschwarze 	if (ep->data == NULL)
678bf9acac6Sschwarze 		return;
679f8617809Sschwarze 
6800f5f5b53Sschwarze 	ep->start = ep->end = ep->data;
6815030fd5bSschwarze 	ep->sublen = 0;
6825030fd5bSschwarze 	ep->subcnt = 0;
683a4dfe1bcSschwarze 
6846538659fSschwarze next_tok:
6856538659fSschwarze 	tok = eqn_next(ep, MODE_TOK);
686a4dfe1bcSschwarze 	switch (tok) {
687e74fa2aeSschwarze 	case EQN_TOK_UNDEF:
6883578e616Sschwarze 		eqn_undef(ep);
689f8617809Sschwarze 		break;
690e74fa2aeSschwarze 	case EQN_TOK_NDEFINE:
691e74fa2aeSschwarze 	case EQN_TOK_DEFINE:
6923578e616Sschwarze 		eqn_def(ep);
693f8617809Sschwarze 		break;
694e74fa2aeSschwarze 	case EQN_TOK_TDEFINE:
6956538659fSschwarze 		if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF ||
6966538659fSschwarze 		    eqn_next(ep, MODE_QUOTED) == EQN_TOK_EOF)
697a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_REQ_EMPTY,
698bf9acac6Sschwarze 			    ep->node->line, ep->node->pos, "tdefine");
699f8617809Sschwarze 		break;
700e74fa2aeSschwarze 	case EQN_TOK_DELIM:
70157ca9bd5Sschwarze 		eqn_delim(ep);
70257ca9bd5Sschwarze 		break;
703e74fa2aeSschwarze 	case EQN_TOK_GFONT:
7046538659fSschwarze 		if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF)
705a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
706a5a5f808Sschwarze 			    ep->node->pos, "%s", eqn_toks[tok]);
707f8617809Sschwarze 		break;
708e74fa2aeSschwarze 	case EQN_TOK_MARK:
709e74fa2aeSschwarze 	case EQN_TOK_LINEUP:
710f8617809Sschwarze 		/* Ignore these. */
711f8617809Sschwarze 		break;
712e74fa2aeSschwarze 	case EQN_TOK_DYAD:
713e74fa2aeSschwarze 	case EQN_TOK_VEC:
714e74fa2aeSschwarze 	case EQN_TOK_UNDER:
715e74fa2aeSschwarze 	case EQN_TOK_BAR:
716e74fa2aeSschwarze 	case EQN_TOK_TILDE:
717e74fa2aeSschwarze 	case EQN_TOK_HAT:
718e74fa2aeSschwarze 	case EQN_TOK_DOT:
719e74fa2aeSschwarze 	case EQN_TOK_DOTDOT:
720a4dfe1bcSschwarze 		if (parent->last == NULL) {
721a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
722a5a5f808Sschwarze 			    ep->node->pos, "%s", eqn_toks[tok]);
723a4dfe1bcSschwarze 			cur = eqn_box_alloc(ep, parent);
724a4dfe1bcSschwarze 			cur->type = EQN_TEXT;
725a4dfe1bcSschwarze 			cur->text = mandoc_strdup("");
726f8617809Sschwarze 		}
727244ed75aSschwarze 		parent = eqn_box_makebinary(ep, parent);
72832e49996Sschwarze 		parent->type = EQN_LIST;
729f8617809Sschwarze 		parent->expectargs = 1;
730a8c898eaSschwarze 		parent->font = EQNFONT_ROMAN;
731f8617809Sschwarze 		switch (tok) {
732e74fa2aeSschwarze 		case EQN_TOK_DOTDOT:
7336538659fSschwarze 			parent->top = mandoc_strdup("\\[ad]");
734f8617809Sschwarze 			break;
735e74fa2aeSschwarze 		case EQN_TOK_VEC:
7366538659fSschwarze 			parent->top = mandoc_strdup("\\[->]");
737f8617809Sschwarze 			break;
738e74fa2aeSschwarze 		case EQN_TOK_DYAD:
7396538659fSschwarze 			parent->top = mandoc_strdup("\\[<>]");
740f8617809Sschwarze 			break;
741e74fa2aeSschwarze 		case EQN_TOK_TILDE:
7426538659fSschwarze 			parent->top = mandoc_strdup("\\[a~]");
743f8617809Sschwarze 			break;
744e74fa2aeSschwarze 		case EQN_TOK_UNDER:
7456538659fSschwarze 			parent->bottom = mandoc_strdup("\\[ul]");
746f8617809Sschwarze 			break;
747e74fa2aeSschwarze 		case EQN_TOK_BAR:
7487779627fSbentley 			parent->top = mandoc_strdup("\\[rn]");
749f8617809Sschwarze 			break;
750e74fa2aeSschwarze 		case EQN_TOK_DOT:
7516538659fSschwarze 			parent->top = mandoc_strdup("\\[a.]");
752f8617809Sschwarze 			break;
753e74fa2aeSschwarze 		case EQN_TOK_HAT:
7546538659fSschwarze 			parent->top = mandoc_strdup("\\[ha]");
755f8617809Sschwarze 			break;
756f8617809Sschwarze 		default:
757f8617809Sschwarze 			abort();
758f8617809Sschwarze 		}
759f8617809Sschwarze 		parent = parent->parent;
760f8617809Sschwarze 		break;
761e74fa2aeSschwarze 	case EQN_TOK_FWD:
762e74fa2aeSschwarze 	case EQN_TOK_BACK:
763e74fa2aeSschwarze 	case EQN_TOK_DOWN:
764e74fa2aeSschwarze 	case EQN_TOK_UP:
7656538659fSschwarze 		if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF)
766a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
767a5a5f808Sschwarze 			    ep->node->pos, "%s", eqn_toks[tok]);
768f8617809Sschwarze 		break;
769e74fa2aeSschwarze 	case EQN_TOK_FAT:
770e74fa2aeSschwarze 	case EQN_TOK_ROMAN:
771e74fa2aeSschwarze 	case EQN_TOK_ITALIC:
772e74fa2aeSschwarze 	case EQN_TOK_BOLD:
773f8617809Sschwarze 		while (parent->args == parent->expectargs)
774a4dfe1bcSschwarze 			parent = parent->parent;
775f8617809Sschwarze 		/*
776f8617809Sschwarze 		 * These values apply to the next word or sequence of
777f8617809Sschwarze 		 * words; thus, we mark that we'll have a child with
778f8617809Sschwarze 		 * exactly one of those.
779f8617809Sschwarze 		 */
780f8617809Sschwarze 		parent = eqn_box_alloc(ep, parent);
78132e49996Sschwarze 		parent->type = EQN_LIST;
782f8617809Sschwarze 		parent->expectargs = 1;
783f8617809Sschwarze 		switch (tok) {
784e74fa2aeSschwarze 		case EQN_TOK_FAT:
785f8617809Sschwarze 			parent->font = EQNFONT_FAT;
786f8617809Sschwarze 			break;
787e74fa2aeSschwarze 		case EQN_TOK_ROMAN:
788f8617809Sschwarze 			parent->font = EQNFONT_ROMAN;
789f8617809Sschwarze 			break;
790e74fa2aeSschwarze 		case EQN_TOK_ITALIC:
791f8617809Sschwarze 			parent->font = EQNFONT_ITALIC;
792f8617809Sschwarze 			break;
793e74fa2aeSschwarze 		case EQN_TOK_BOLD:
794f8617809Sschwarze 			parent->font = EQNFONT_BOLD;
795f8617809Sschwarze 			break;
796f8617809Sschwarze 		default:
797f8617809Sschwarze 			abort();
798f8617809Sschwarze 		}
799f8617809Sschwarze 		break;
800e74fa2aeSschwarze 	case EQN_TOK_SIZE:
801e74fa2aeSschwarze 	case EQN_TOK_GSIZE:
802f8617809Sschwarze 		/* Accept two values: integral size and a single. */
8036538659fSschwarze 		if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
804a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
805a5a5f808Sschwarze 			    ep->node->pos, "%s", eqn_toks[tok]);
806a4dfe1bcSschwarze 			break;
807f8617809Sschwarze 		}
8086538659fSschwarze 		size = mandoc_strntoi(ep->start, ep->toksz, 10);
809f8617809Sschwarze 		if (-1 == size) {
810a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_IT_NONUM, ep->node->line,
811a5a5f808Sschwarze 			    ep->node->pos, "%s", eqn_toks[tok]);
812a4dfe1bcSschwarze 			break;
813f8617809Sschwarze 		}
814f8617809Sschwarze 		if (EQN_TOK_GSIZE == tok) {
815f8617809Sschwarze 			ep->gsize = size;
816f8617809Sschwarze 			break;
817f8617809Sschwarze 		}
8182fe51f68Sschwarze 		while (parent->args == parent->expectargs)
8192fe51f68Sschwarze 			parent = parent->parent;
820f8617809Sschwarze 		parent = eqn_box_alloc(ep, parent);
82132e49996Sschwarze 		parent->type = EQN_LIST;
822f8617809Sschwarze 		parent->expectargs = 1;
823f8617809Sschwarze 		parent->size = size;
824f8617809Sschwarze 		break;
825e74fa2aeSschwarze 	case EQN_TOK_FROM:
826e74fa2aeSschwarze 	case EQN_TOK_TO:
827e74fa2aeSschwarze 	case EQN_TOK_SUB:
828e74fa2aeSschwarze 	case EQN_TOK_SUP:
829f8617809Sschwarze 		/*
830f8617809Sschwarze 		 * We have a left-right-associative expression.
831f8617809Sschwarze 		 * Repivot under a positional node, open a child scope
832f8617809Sschwarze 		 * and keep on reading.
833f8617809Sschwarze 		 */
834a4dfe1bcSschwarze 		if (parent->last == NULL) {
835a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
836a5a5f808Sschwarze 			    ep->node->pos, "%s", eqn_toks[tok]);
837a4dfe1bcSschwarze 			cur = eqn_box_alloc(ep, parent);
838a4dfe1bcSschwarze 			cur->type = EQN_TEXT;
839a4dfe1bcSschwarze 			cur->text = mandoc_strdup("");
840f8617809Sschwarze 		}
8412fe51f68Sschwarze 		while (parent->expectargs == 1 && parent->args == 1)
8422fe51f68Sschwarze 			parent = parent->parent;
8432fe51f68Sschwarze 		if (tok == EQN_TOK_FROM || tok == EQN_TOK_TO)  {
8442fe51f68Sschwarze 			for (cur = parent; cur != NULL; cur = cur->parent)
8452fe51f68Sschwarze 				if (cur->pos == EQNPOS_SUB ||
8462fe51f68Sschwarze 				    cur->pos == EQNPOS_SUP ||
8472fe51f68Sschwarze 				    cur->pos == EQNPOS_SUBSUP ||
8482fe51f68Sschwarze 				    cur->pos == EQNPOS_SQRT ||
8492fe51f68Sschwarze 				    cur->pos == EQNPOS_OVER)
8502fe51f68Sschwarze 					break;
8512fe51f68Sschwarze 			if (cur != NULL)
8522fe51f68Sschwarze 				parent = cur->parent;
8532fe51f68Sschwarze 		}
8542fe51f68Sschwarze 		if (tok == EQN_TOK_SUP && parent->pos == EQNPOS_SUB) {
855f8617809Sschwarze 			parent->expectargs = 3;
856f8617809Sschwarze 			parent->pos = EQNPOS_SUBSUP;
857f8617809Sschwarze 			break;
858f8617809Sschwarze 		}
8592fe51f68Sschwarze 		if (tok == EQN_TOK_TO && parent->pos == EQNPOS_FROM) {
860f8617809Sschwarze 			parent->expectargs = 3;
861f8617809Sschwarze 			parent->pos = EQNPOS_FROMTO;
862f8617809Sschwarze 			break;
863f8617809Sschwarze 		}
864244ed75aSschwarze 		parent = eqn_box_makebinary(ep, parent);
865f8617809Sschwarze 		switch (tok) {
866e74fa2aeSschwarze 		case EQN_TOK_FROM:
867244ed75aSschwarze 			parent->pos = EQNPOS_FROM;
868f8617809Sschwarze 			break;
869e74fa2aeSschwarze 		case EQN_TOK_TO:
870244ed75aSschwarze 			parent->pos = EQNPOS_TO;
871f8617809Sschwarze 			break;
872e74fa2aeSschwarze 		case EQN_TOK_SUP:
873244ed75aSschwarze 			parent->pos = EQNPOS_SUP;
874f8617809Sschwarze 			break;
875e74fa2aeSschwarze 		case EQN_TOK_SUB:
876244ed75aSschwarze 			parent->pos = EQNPOS_SUB;
877f8617809Sschwarze 			break;
878f8617809Sschwarze 		default:
879f8617809Sschwarze 			abort();
880f8617809Sschwarze 		}
881f8617809Sschwarze 		break;
882e74fa2aeSschwarze 	case EQN_TOK_SQRT:
883f8617809Sschwarze 		while (parent->args == parent->expectargs)
884a4dfe1bcSschwarze 			parent = parent->parent;
885f8617809Sschwarze 		/*
886f8617809Sschwarze 		 * Accept a left-right-associative set of arguments just
887f8617809Sschwarze 		 * like sub and sup and friends but without rebalancing
888f8617809Sschwarze 		 * under a pivot.
889f8617809Sschwarze 		 */
890f8617809Sschwarze 		parent = eqn_box_alloc(ep, parent);
891f8617809Sschwarze 		parent->type = EQN_SUBEXPR;
892f8617809Sschwarze 		parent->pos = EQNPOS_SQRT;
893f8617809Sschwarze 		parent->expectargs = 1;
894f8617809Sschwarze 		break;
895e74fa2aeSschwarze 	case EQN_TOK_OVER:
896f8617809Sschwarze 		/*
897f8617809Sschwarze 		 * We have a right-left-associative fraction.
898f8617809Sschwarze 		 * Close out anything that's currently open, then
899f8617809Sschwarze 		 * rebalance and continue reading.
900f8617809Sschwarze 		 */
901a4dfe1bcSschwarze 		if (parent->last == NULL) {
902a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
903a5a5f808Sschwarze 			    ep->node->pos, "%s", eqn_toks[tok]);
904a4dfe1bcSschwarze 			cur = eqn_box_alloc(ep, parent);
905a4dfe1bcSschwarze 			cur->type = EQN_TEXT;
906a4dfe1bcSschwarze 			cur->text = mandoc_strdup("");
907f8617809Sschwarze 		}
9082fe51f68Sschwarze 		while (parent->args == parent->expectargs)
9092fe51f68Sschwarze 			parent = parent->parent;
910f8617809Sschwarze 		while (EQN_SUBEXPR == parent->type)
911a4dfe1bcSschwarze 			parent = parent->parent;
912244ed75aSschwarze 		parent = eqn_box_makebinary(ep, parent);
913244ed75aSschwarze 		parent->pos = EQNPOS_OVER;
914f8617809Sschwarze 		break;
915e74fa2aeSschwarze 	case EQN_TOK_RIGHT:
916e74fa2aeSschwarze 	case EQN_TOK_BRACE_CLOSE:
917f8617809Sschwarze 		/*
918f8617809Sschwarze 		 * Close out the existing brace.
919f8617809Sschwarze 		 * FIXME: this is a shitty sentinel: we should really
920f8617809Sschwarze 		 * have a native EQN_BRACE type or whatnot.
921f8617809Sschwarze 		 */
922a4dfe1bcSschwarze 		for (cur = parent; cur != NULL; cur = cur->parent)
923a4dfe1bcSschwarze 			if (cur->type == EQN_LIST &&
92432e49996Sschwarze 			    cur->expectargs > 1 &&
925a4dfe1bcSschwarze 			    (tok == EQN_TOK_BRACE_CLOSE ||
926a4dfe1bcSschwarze 			     cur->left != NULL))
927a4dfe1bcSschwarze 				break;
928a4dfe1bcSschwarze 		if (cur == NULL) {
929a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BLK_NOTOPEN, ep->node->line,
930a5a5f808Sschwarze 			    ep->node->pos, "%s", eqn_toks[tok]);
931a4dfe1bcSschwarze 			break;
932f8617809Sschwarze 		}
933a4dfe1bcSschwarze 		parent = cur;
934f8617809Sschwarze 		if (EQN_TOK_RIGHT == tok) {
9356538659fSschwarze 			if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
936a4dfe1bcSschwarze 				mandoc_msg(MANDOCERR_REQ_EMPTY,
937a5a5f808Sschwarze 				    ep->node->line, ep->node->pos,
938a5a5f808Sschwarze 				    "%s", eqn_toks[tok]);
939a4dfe1bcSschwarze 				break;
940f8617809Sschwarze 			}
941f8617809Sschwarze 			/* Handling depends on right/left. */
9426538659fSschwarze 			if (STRNEQ(ep->start, ep->toksz, "ceiling", 7))
9436538659fSschwarze 				parent->right = mandoc_strdup("\\[rc]");
9446538659fSschwarze 			else if (STRNEQ(ep->start, ep->toksz, "floor", 5))
9456538659fSschwarze 				parent->right = mandoc_strdup("\\[rf]");
9466538659fSschwarze 			else
9476538659fSschwarze 				parent->right =
9486538659fSschwarze 				    mandoc_strndup(ep->start, ep->toksz);
949f8617809Sschwarze 		}
950a4dfe1bcSschwarze 		parent = parent->parent;
95152888185Sschwarze 		if (tok == EQN_TOK_BRACE_CLOSE &&
952f8617809Sschwarze 		    (parent->type == EQN_PILE ||
953f8617809Sschwarze 		     parent->type == EQN_MATRIX))
954f8617809Sschwarze 			parent = parent->parent;
955f8617809Sschwarze 		/* Close out any "singleton" lists. */
95632e49996Sschwarze 		while (parent->type == EQN_LIST &&
95732e49996Sschwarze 		    parent->expectargs == 1 &&
95832e49996Sschwarze 		    parent->args == 1)
959a4dfe1bcSschwarze 			parent = parent->parent;
960f8617809Sschwarze 		break;
961e74fa2aeSschwarze 	case EQN_TOK_BRACE_OPEN:
962e74fa2aeSschwarze 	case EQN_TOK_LEFT:
963f8617809Sschwarze 		/*
964f8617809Sschwarze 		 * If we already have something in the stack and we're
965f8617809Sschwarze 		 * in an expression, then rewind til we're not any more
966f8617809Sschwarze 		 * (just like with the text node).
967f8617809Sschwarze 		 */
968f8617809Sschwarze 		while (parent->args == parent->expectargs)
969a4dfe1bcSschwarze 			parent = parent->parent;
970a4dfe1bcSschwarze 		if (EQN_TOK_LEFT == tok &&
9716538659fSschwarze 		    eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
972a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
973a5a5f808Sschwarze 			    ep->node->pos, "%s", eqn_toks[tok]);
974a4dfe1bcSschwarze 			break;
975f8617809Sschwarze 		}
976f8617809Sschwarze 		parent = eqn_box_alloc(ep, parent);
977f8617809Sschwarze 		parent->type = EQN_LIST;
978f8617809Sschwarze 		if (EQN_TOK_LEFT == tok) {
9796538659fSschwarze 			if (STRNEQ(ep->start, ep->toksz, "ceiling", 7))
9806538659fSschwarze 				parent->left = mandoc_strdup("\\[lc]");
9816538659fSschwarze 			else if (STRNEQ(ep->start, ep->toksz, "floor", 5))
9826538659fSschwarze 				parent->left = mandoc_strdup("\\[lf]");
9836538659fSschwarze 			else
9846538659fSschwarze 				parent->left =
9856538659fSschwarze 				    mandoc_strndup(ep->start, ep->toksz);
986f8617809Sschwarze 		}
987f8617809Sschwarze 		break;
988e74fa2aeSschwarze 	case EQN_TOK_PILE:
989e74fa2aeSschwarze 	case EQN_TOK_LPILE:
990e74fa2aeSschwarze 	case EQN_TOK_RPILE:
991e74fa2aeSschwarze 	case EQN_TOK_CPILE:
992e74fa2aeSschwarze 	case EQN_TOK_CCOL:
993e74fa2aeSschwarze 	case EQN_TOK_LCOL:
994e74fa2aeSschwarze 	case EQN_TOK_RCOL:
995f8617809Sschwarze 		while (parent->args == parent->expectargs)
996a4dfe1bcSschwarze 			parent = parent->parent;
997f8617809Sschwarze 		parent = eqn_box_alloc(ep, parent);
998f8617809Sschwarze 		parent->type = EQN_PILE;
999a4dfe1bcSschwarze 		parent->expectargs = 1;
1000f8617809Sschwarze 		break;
1001e74fa2aeSschwarze 	case EQN_TOK_ABOVE:
1002a4dfe1bcSschwarze 		for (cur = parent; cur != NULL; cur = cur->parent)
1003a4dfe1bcSschwarze 			if (cur->type == EQN_PILE)
1004a4dfe1bcSschwarze 				break;
1005a4dfe1bcSschwarze 		if (cur == NULL) {
1006a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_IT_STRAY, ep->node->line,
1007a5a5f808Sschwarze 			    ep->node->pos, "%s", eqn_toks[tok]);
1008a4dfe1bcSschwarze 			break;
1009f8617809Sschwarze 		}
1010a4dfe1bcSschwarze 		parent = eqn_box_alloc(ep, cur);
1011f8617809Sschwarze 		parent->type = EQN_LIST;
1012f8617809Sschwarze 		break;
1013e74fa2aeSschwarze 	case EQN_TOK_MATRIX:
1014f8617809Sschwarze 		while (parent->args == parent->expectargs)
1015a4dfe1bcSschwarze 			parent = parent->parent;
1016f8617809Sschwarze 		parent = eqn_box_alloc(ep, parent);
1017f8617809Sschwarze 		parent->type = EQN_MATRIX;
1018a4dfe1bcSschwarze 		parent->expectargs = 1;
1019f8617809Sschwarze 		break;
1020e74fa2aeSschwarze 	case EQN_TOK_EOF:
1021bf9acac6Sschwarze 		return;
102266b13c11Sschwarze 	case EQN_TOK__MAX:
102321b46522Sschwarze 	case EQN_TOK_FUNC:
102421b46522Sschwarze 	case EQN_TOK_QUOTED:
102521b46522Sschwarze 	case EQN_TOK_SYM:
10266538659fSschwarze 		p = ep->start;
102766b13c11Sschwarze 		assert(p != NULL);
1028f8617809Sschwarze 		/*
1029f8617809Sschwarze 		 * If we already have something in the stack and we're
1030f8617809Sschwarze 		 * in an expression, then rewind til we're not any more.
1031f8617809Sschwarze 		 */
1032f8617809Sschwarze 		while (parent->args == parent->expectargs)
1033a4dfe1bcSschwarze 			parent = parent->parent;
1034f8617809Sschwarze 		cur = eqn_box_alloc(ep, parent);
1035f8617809Sschwarze 		cur->type = EQN_TEXT;
1036f8617809Sschwarze 		cur->text = p;
1037a8c898eaSschwarze 		switch (tok) {
1038a8c898eaSschwarze 		case EQN_TOK_FUNC:
1039a8c898eaSschwarze 			cur->font = EQNFONT_ROMAN;
104021b46522Sschwarze 			break;
1041a8c898eaSschwarze 		case EQN_TOK_QUOTED:
1042a8c898eaSschwarze 			if (cur->font == EQNFONT_NONE)
104321b46522Sschwarze 				cur->font = EQNFONT_ITALIC;
104421b46522Sschwarze 			break;
1045a8c898eaSschwarze 		case EQN_TOK_SYM:
1046a8c898eaSschwarze 			break;
1047a8c898eaSschwarze 		default:
1048a8c898eaSschwarze 			if (cur->font != EQNFONT_NONE || *p == '\0')
1049a8c898eaSschwarze 				break;
1050a8c898eaSschwarze 			cpn = p - 1;
1051a8c898eaSschwarze 			ccln = CCL_LET;
1052698f1840Sschwarze 			split = NULL;
105359c226a5Sschwarze 			for (;;) {
1054a8c898eaSschwarze 				/* Advance to next character. */
1055a8c898eaSschwarze 				cp = cpn++;
1056a8c898eaSschwarze 				ccl = ccln;
1057a8c898eaSschwarze 				ccln = isalpha((unsigned char)*cpn) ? CCL_LET :
1058a8c898eaSschwarze 				    isdigit((unsigned char)*cpn) ||
1059a8c898eaSschwarze 				    (*cpn == '.' && (ccl == CCL_DIG ||
1060a8c898eaSschwarze 				     isdigit((unsigned char)cpn[1]))) ?
1061a8c898eaSschwarze 				    CCL_DIG : CCL_PUN;
1062a8c898eaSschwarze 				/* No boundary before first character. */
1063a8c898eaSschwarze 				if (cp < p)
1064a8c898eaSschwarze 					continue;
1065a8c898eaSschwarze 				cur->font = ccl == CCL_LET ?
1066a8c898eaSschwarze 				    EQNFONT_ITALIC : EQNFONT_ROMAN;
106759c226a5Sschwarze 				if (*cp == '\\')
106859c226a5Sschwarze 					mandoc_escape(&cpn, NULL, NULL);
1069a8c898eaSschwarze 				/* No boundary after last character. */
107059c226a5Sschwarze 				if (*cpn == '\0')
107159c226a5Sschwarze 					break;
10726898e1e9Sschwarze 				if (ccln == ccl && *cp != ',' && *cpn != ',')
107359c226a5Sschwarze 					continue;
107426deb957Sschwarze 				/* Boundary found, split the text. */
107526deb957Sschwarze 				if (parent->args == parent->expectargs) {
107626deb957Sschwarze 					/* Remove the text from the tree. */
107726deb957Sschwarze 					if (cur->prev == NULL)
107826deb957Sschwarze 						parent->first = cur->next;
107926deb957Sschwarze 					else
108026deb957Sschwarze 						cur->prev->next = NULL;
108126deb957Sschwarze 					parent->last = cur->prev;
108226deb957Sschwarze 					parent->args--;
108326deb957Sschwarze 					/* Set up a list instead. */
1084698f1840Sschwarze 					split = eqn_box_alloc(ep, parent);
1085698f1840Sschwarze 					split->type = EQN_LIST;
108626deb957Sschwarze 					/* Insert the word into the list. */
1087698f1840Sschwarze 					split->first = split->last = cur;
1088698f1840Sschwarze 					cur->parent = split;
108926deb957Sschwarze 					cur->prev = NULL;
1090698f1840Sschwarze 					parent = split;
109126deb957Sschwarze 				}
109226deb957Sschwarze 				/* Append a new text box. */
109359c226a5Sschwarze 				nbox = eqn_box_alloc(ep, parent);
109459c226a5Sschwarze 				nbox->type = EQN_TEXT;
109559c226a5Sschwarze 				nbox->text = mandoc_strdup(cpn);
1096a8c898eaSschwarze 				/* Truncate the old box. */
109759c226a5Sschwarze 				p = mandoc_strndup(cur->text,
109859c226a5Sschwarze 				    cpn - cur->text);
109959c226a5Sschwarze 				free(cur->text);
110059c226a5Sschwarze 				cur->text = p;
1101a8c898eaSschwarze 				/* Setup to process the new box. */
110259c226a5Sschwarze 				cur = nbox;
1103a8c898eaSschwarze 				p = nbox->text;
1104a8c898eaSschwarze 				cpn = p - 1;
1105a8c898eaSschwarze 				ccln = CCL_LET;
110659c226a5Sschwarze 			}
1107698f1840Sschwarze 			if (split != NULL)
1108698f1840Sschwarze 				parent = split->parent;
1109a8c898eaSschwarze 			break;
1110a8c898eaSschwarze 		}
1111f8617809Sschwarze 		break;
111266b13c11Sschwarze 	default:
111366b13c11Sschwarze 		abort();
1114f8617809Sschwarze 	}
1115a4dfe1bcSschwarze 	goto next_tok;
1116f8617809Sschwarze }
1117f8617809Sschwarze 
1118f8617809Sschwarze void
eqn_free(struct eqn_node * p)1119f8617809Sschwarze eqn_free(struct eqn_node *p)
1120f8618d99Sschwarze {
1121f8618d99Sschwarze 	int		 i;
1122f8618d99Sschwarze 
1123abe31b56Sschwarze 	if (p == NULL)
1124abe31b56Sschwarze 		return;
1125abe31b56Sschwarze 
1126f8617809Sschwarze 	for (i = 0; i < (int)p->defsz; i++) {
1127f8617809Sschwarze 		free(p->defs[i].key);
1128f8617809Sschwarze 		free(p->defs[i].val);
1129f8617809Sschwarze 	}
1130f8617809Sschwarze 
1131f8617809Sschwarze 	free(p->data);
1132f8617809Sschwarze 	free(p->defs);
1133f8617809Sschwarze 	free(p);
1134f8618d99Sschwarze }
1135