eqn.c revision 1.9 1 1.9 wiz /* Id: eqn.c,v 1.84 2020/01/08 12:16:24 schwarze Exp */
2 1.1 joerg /*
3 1.2 christos * Copyright (c) 2011, 2014 Kristaps Dzonsons <kristaps (at) bsd.lv>
4 1.9 wiz * Copyright (c) 2014,2015,2017,2018,2020 Ingo Schwarze <schwarze (at) openbsd.org>
5 1.1 joerg *
6 1.1 joerg * Permission to use, copy, modify, and distribute this software for any
7 1.1 joerg * purpose with or without fee is hereby granted, provided that the above
8 1.1 joerg * copyright notice and this permission notice appear in all copies.
9 1.1 joerg *
10 1.1 joerg * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 1.1 joerg * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 1.1 joerg * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 1.1 joerg * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 1.1 joerg * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 1.1 joerg * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 1.1 joerg * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 1.1 joerg */
18 1.1 joerg #include "config.h"
19 1.2 christos
20 1.2 christos #include <sys/types.h>
21 1.1 joerg
22 1.1 joerg #include <assert.h>
23 1.7 christos #include <ctype.h>
24 1.2 christos #include <limits.h>
25 1.1 joerg #include <stdio.h>
26 1.1 joerg #include <stdlib.h>
27 1.1 joerg #include <string.h>
28 1.1 joerg #include <time.h>
29 1.1 joerg
30 1.7 christos #include "mandoc_aux.h"
31 1.1 joerg #include "mandoc.h"
32 1.7 christos #include "roff.h"
33 1.8 christos #include "eqn.h"
34 1.1 joerg #include "libmandoc.h"
35 1.8 christos #include "eqn_parse.h"
36 1.1 joerg
37 1.2 christos #define EQN_NEST_MAX 128 /* maximum nesting of defines */
38 1.2 christos #define STRNEQ(p1, sz1, p2, sz2) \
39 1.2 christos ((sz1) == (sz2) && 0 == strncmp((p1), (p2), (sz1)))
40 1.2 christos
41 1.2 christos enum eqn_tok {
42 1.2 christos EQN_TOK_DYAD = 0,
43 1.2 christos EQN_TOK_VEC,
44 1.2 christos EQN_TOK_UNDER,
45 1.2 christos EQN_TOK_BAR,
46 1.2 christos EQN_TOK_TILDE,
47 1.2 christos EQN_TOK_HAT,
48 1.2 christos EQN_TOK_DOT,
49 1.2 christos EQN_TOK_DOTDOT,
50 1.2 christos EQN_TOK_FWD,
51 1.2 christos EQN_TOK_BACK,
52 1.2 christos EQN_TOK_DOWN,
53 1.2 christos EQN_TOK_UP,
54 1.2 christos EQN_TOK_FAT,
55 1.2 christos EQN_TOK_ROMAN,
56 1.2 christos EQN_TOK_ITALIC,
57 1.2 christos EQN_TOK_BOLD,
58 1.2 christos EQN_TOK_SIZE,
59 1.2 christos EQN_TOK_SUB,
60 1.2 christos EQN_TOK_SUP,
61 1.2 christos EQN_TOK_SQRT,
62 1.2 christos EQN_TOK_OVER,
63 1.2 christos EQN_TOK_FROM,
64 1.2 christos EQN_TOK_TO,
65 1.2 christos EQN_TOK_BRACE_OPEN,
66 1.2 christos EQN_TOK_BRACE_CLOSE,
67 1.2 christos EQN_TOK_GSIZE,
68 1.2 christos EQN_TOK_GFONT,
69 1.2 christos EQN_TOK_MARK,
70 1.2 christos EQN_TOK_LINEUP,
71 1.2 christos EQN_TOK_LEFT,
72 1.2 christos EQN_TOK_RIGHT,
73 1.2 christos EQN_TOK_PILE,
74 1.2 christos EQN_TOK_LPILE,
75 1.2 christos EQN_TOK_RPILE,
76 1.2 christos EQN_TOK_CPILE,
77 1.2 christos EQN_TOK_MATRIX,
78 1.2 christos EQN_TOK_CCOL,
79 1.2 christos EQN_TOK_LCOL,
80 1.2 christos EQN_TOK_RCOL,
81 1.2 christos EQN_TOK_DELIM,
82 1.2 christos EQN_TOK_DEFINE,
83 1.2 christos EQN_TOK_TDEFINE,
84 1.2 christos EQN_TOK_NDEFINE,
85 1.2 christos EQN_TOK_UNDEF,
86 1.2 christos EQN_TOK_ABOVE,
87 1.7 christos EQN_TOK__MAX,
88 1.7 christos EQN_TOK_FUNC,
89 1.7 christos EQN_TOK_QUOTED,
90 1.7 christos EQN_TOK_SYM,
91 1.7 christos EQN_TOK_EOF
92 1.2 christos };
93 1.2 christos
94 1.2 christos static const char *eqn_toks[EQN_TOK__MAX] = {
95 1.2 christos "dyad", /* EQN_TOK_DYAD */
96 1.2 christos "vec", /* EQN_TOK_VEC */
97 1.2 christos "under", /* EQN_TOK_UNDER */
98 1.2 christos "bar", /* EQN_TOK_BAR */
99 1.2 christos "tilde", /* EQN_TOK_TILDE */
100 1.2 christos "hat", /* EQN_TOK_HAT */
101 1.2 christos "dot", /* EQN_TOK_DOT */
102 1.2 christos "dotdot", /* EQN_TOK_DOTDOT */
103 1.2 christos "fwd", /* EQN_TOK_FWD * */
104 1.2 christos "back", /* EQN_TOK_BACK */
105 1.2 christos "down", /* EQN_TOK_DOWN */
106 1.2 christos "up", /* EQN_TOK_UP */
107 1.2 christos "fat", /* EQN_TOK_FAT */
108 1.2 christos "roman", /* EQN_TOK_ROMAN */
109 1.2 christos "italic", /* EQN_TOK_ITALIC */
110 1.2 christos "bold", /* EQN_TOK_BOLD */
111 1.2 christos "size", /* EQN_TOK_SIZE */
112 1.2 christos "sub", /* EQN_TOK_SUB */
113 1.2 christos "sup", /* EQN_TOK_SUP */
114 1.2 christos "sqrt", /* EQN_TOK_SQRT */
115 1.2 christos "over", /* EQN_TOK_OVER */
116 1.2 christos "from", /* EQN_TOK_FROM */
117 1.2 christos "to", /* EQN_TOK_TO */
118 1.2 christos "{", /* EQN_TOK_BRACE_OPEN */
119 1.2 christos "}", /* EQN_TOK_BRACE_CLOSE */
120 1.2 christos "gsize", /* EQN_TOK_GSIZE */
121 1.2 christos "gfont", /* EQN_TOK_GFONT */
122 1.2 christos "mark", /* EQN_TOK_MARK */
123 1.2 christos "lineup", /* EQN_TOK_LINEUP */
124 1.2 christos "left", /* EQN_TOK_LEFT */
125 1.2 christos "right", /* EQN_TOK_RIGHT */
126 1.2 christos "pile", /* EQN_TOK_PILE */
127 1.2 christos "lpile", /* EQN_TOK_LPILE */
128 1.2 christos "rpile", /* EQN_TOK_RPILE */
129 1.2 christos "cpile", /* EQN_TOK_CPILE */
130 1.2 christos "matrix", /* EQN_TOK_MATRIX */
131 1.2 christos "ccol", /* EQN_TOK_CCOL */
132 1.2 christos "lcol", /* EQN_TOK_LCOL */
133 1.2 christos "rcol", /* EQN_TOK_RCOL */
134 1.2 christos "delim", /* EQN_TOK_DELIM */
135 1.2 christos "define", /* EQN_TOK_DEFINE */
136 1.2 christos "tdefine", /* EQN_TOK_TDEFINE */
137 1.2 christos "ndefine", /* EQN_TOK_NDEFINE */
138 1.2 christos "undef", /* EQN_TOK_UNDEF */
139 1.2 christos "above", /* EQN_TOK_ABOVE */
140 1.2 christos };
141 1.2 christos
142 1.7 christos static const char *const eqn_func[] = {
143 1.7 christos "acos", "acsc", "and", "arc", "asec", "asin", "atan",
144 1.7 christos "cos", "cosh", "coth", "csc", "det", "exp", "for",
145 1.7 christos "if", "lim", "ln", "log", "max", "min",
146 1.7 christos "sec", "sin", "sinh", "tan", "tanh", "Im", "Re",
147 1.7 christos };
148 1.7 christos
149 1.2 christos enum eqn_symt {
150 1.7 christos EQNSYM_alpha = 0,
151 1.2 christos EQNSYM_beta,
152 1.2 christos EQNSYM_chi,
153 1.2 christos EQNSYM_delta,
154 1.2 christos EQNSYM_epsilon,
155 1.2 christos EQNSYM_eta,
156 1.2 christos EQNSYM_gamma,
157 1.2 christos EQNSYM_iota,
158 1.2 christos EQNSYM_kappa,
159 1.2 christos EQNSYM_lambda,
160 1.2 christos EQNSYM_mu,
161 1.2 christos EQNSYM_nu,
162 1.2 christos EQNSYM_omega,
163 1.2 christos EQNSYM_omicron,
164 1.2 christos EQNSYM_phi,
165 1.2 christos EQNSYM_pi,
166 1.2 christos EQNSYM_ps,
167 1.2 christos EQNSYM_rho,
168 1.2 christos EQNSYM_sigma,
169 1.2 christos EQNSYM_tau,
170 1.2 christos EQNSYM_theta,
171 1.2 christos EQNSYM_upsilon,
172 1.2 christos EQNSYM_xi,
173 1.2 christos EQNSYM_zeta,
174 1.2 christos EQNSYM_DELTA,
175 1.2 christos EQNSYM_GAMMA,
176 1.2 christos EQNSYM_LAMBDA,
177 1.2 christos EQNSYM_OMEGA,
178 1.2 christos EQNSYM_PHI,
179 1.2 christos EQNSYM_PI,
180 1.2 christos EQNSYM_PSI,
181 1.2 christos EQNSYM_SIGMA,
182 1.2 christos EQNSYM_THETA,
183 1.2 christos EQNSYM_UPSILON,
184 1.2 christos EQNSYM_XI,
185 1.2 christos EQNSYM_inter,
186 1.2 christos EQNSYM_union,
187 1.2 christos EQNSYM_prod,
188 1.2 christos EQNSYM_int,
189 1.2 christos EQNSYM_sum,
190 1.2 christos EQNSYM_grad,
191 1.2 christos EQNSYM_del,
192 1.2 christos EQNSYM_times,
193 1.2 christos EQNSYM_cdot,
194 1.2 christos EQNSYM_nothing,
195 1.2 christos EQNSYM_approx,
196 1.2 christos EQNSYM_prime,
197 1.2 christos EQNSYM_half,
198 1.2 christos EQNSYM_partial,
199 1.2 christos EQNSYM_inf,
200 1.2 christos EQNSYM_muchgreat,
201 1.2 christos EQNSYM_muchless,
202 1.2 christos EQNSYM_larrow,
203 1.2 christos EQNSYM_rarrow,
204 1.2 christos EQNSYM_pm,
205 1.2 christos EQNSYM_nequal,
206 1.2 christos EQNSYM_equiv,
207 1.2 christos EQNSYM_lessequal,
208 1.2 christos EQNSYM_moreequal,
209 1.2 christos EQNSYM_minus,
210 1.2 christos EQNSYM__MAX
211 1.2 christos };
212 1.2 christos
213 1.2 christos struct eqnsym {
214 1.2 christos const char *str;
215 1.2 christos const char *sym;
216 1.2 christos };
217 1.2 christos
218 1.2 christos static const struct eqnsym eqnsyms[EQNSYM__MAX] = {
219 1.2 christos { "alpha", "*a" }, /* EQNSYM_alpha */
220 1.2 christos { "beta", "*b" }, /* EQNSYM_beta */
221 1.2 christos { "chi", "*x" }, /* EQNSYM_chi */
222 1.2 christos { "delta", "*d" }, /* EQNSYM_delta */
223 1.2 christos { "epsilon", "*e" }, /* EQNSYM_epsilon */
224 1.2 christos { "eta", "*y" }, /* EQNSYM_eta */
225 1.2 christos { "gamma", "*g" }, /* EQNSYM_gamma */
226 1.2 christos { "iota", "*i" }, /* EQNSYM_iota */
227 1.2 christos { "kappa", "*k" }, /* EQNSYM_kappa */
228 1.2 christos { "lambda", "*l" }, /* EQNSYM_lambda */
229 1.2 christos { "mu", "*m" }, /* EQNSYM_mu */
230 1.2 christos { "nu", "*n" }, /* EQNSYM_nu */
231 1.2 christos { "omega", "*w" }, /* EQNSYM_omega */
232 1.2 christos { "omicron", "*o" }, /* EQNSYM_omicron */
233 1.2 christos { "phi", "*f" }, /* EQNSYM_phi */
234 1.2 christos { "pi", "*p" }, /* EQNSYM_pi */
235 1.2 christos { "psi", "*q" }, /* EQNSYM_psi */
236 1.2 christos { "rho", "*r" }, /* EQNSYM_rho */
237 1.2 christos { "sigma", "*s" }, /* EQNSYM_sigma */
238 1.2 christos { "tau", "*t" }, /* EQNSYM_tau */
239 1.2 christos { "theta", "*h" }, /* EQNSYM_theta */
240 1.2 christos { "upsilon", "*u" }, /* EQNSYM_upsilon */
241 1.2 christos { "xi", "*c" }, /* EQNSYM_xi */
242 1.2 christos { "zeta", "*z" }, /* EQNSYM_zeta */
243 1.2 christos { "DELTA", "*D" }, /* EQNSYM_DELTA */
244 1.2 christos { "GAMMA", "*G" }, /* EQNSYM_GAMMA */
245 1.2 christos { "LAMBDA", "*L" }, /* EQNSYM_LAMBDA */
246 1.2 christos { "OMEGA", "*W" }, /* EQNSYM_OMEGA */
247 1.2 christos { "PHI", "*F" }, /* EQNSYM_PHI */
248 1.2 christos { "PI", "*P" }, /* EQNSYM_PI */
249 1.2 christos { "PSI", "*Q" }, /* EQNSYM_PSI */
250 1.2 christos { "SIGMA", "*S" }, /* EQNSYM_SIGMA */
251 1.2 christos { "THETA", "*H" }, /* EQNSYM_THETA */
252 1.2 christos { "UPSILON", "*U" }, /* EQNSYM_UPSILON */
253 1.2 christos { "XI", "*C" }, /* EQNSYM_XI */
254 1.2 christos { "inter", "ca" }, /* EQNSYM_inter */
255 1.2 christos { "union", "cu" }, /* EQNSYM_union */
256 1.2 christos { "prod", "product" }, /* EQNSYM_prod */
257 1.2 christos { "int", "integral" }, /* EQNSYM_int */
258 1.2 christos { "sum", "sum" }, /* EQNSYM_sum */
259 1.2 christos { "grad", "gr" }, /* EQNSYM_grad */
260 1.2 christos { "del", "gr" }, /* EQNSYM_del */
261 1.2 christos { "times", "mu" }, /* EQNSYM_times */
262 1.2 christos { "cdot", "pc" }, /* EQNSYM_cdot */
263 1.2 christos { "nothing", "&" }, /* EQNSYM_nothing */
264 1.2 christos { "approx", "~~" }, /* EQNSYM_approx */
265 1.2 christos { "prime", "fm" }, /* EQNSYM_prime */
266 1.2 christos { "half", "12" }, /* EQNSYM_half */
267 1.2 christos { "partial", "pd" }, /* EQNSYM_partial */
268 1.2 christos { "inf", "if" }, /* EQNSYM_inf */
269 1.2 christos { ">>", ">>" }, /* EQNSYM_muchgreat */
270 1.2 christos { "<<", "<<" }, /* EQNSYM_muchless */
271 1.2 christos { "<-", "<-" }, /* EQNSYM_larrow */
272 1.2 christos { "->", "->" }, /* EQNSYM_rarrow */
273 1.2 christos { "+-", "+-" }, /* EQNSYM_pm */
274 1.2 christos { "!=", "!=" }, /* EQNSYM_nequal */
275 1.2 christos { "==", "==" }, /* EQNSYM_equiv */
276 1.2 christos { "<=", "<=" }, /* EQNSYM_lessequal */
277 1.2 christos { ">=", ">=" }, /* EQNSYM_moreequal */
278 1.2 christos { "-", "mi" }, /* EQNSYM_minus */
279 1.2 christos };
280 1.2 christos
281 1.7 christos enum parse_mode {
282 1.7 christos MODE_QUOTED,
283 1.7 christos MODE_NOSUB,
284 1.7 christos MODE_SUB,
285 1.7 christos MODE_TOK
286 1.7 christos };
287 1.7 christos
288 1.8 christos struct eqn_def {
289 1.8 christos char *key;
290 1.8 christos size_t keysz;
291 1.8 christos char *val;
292 1.8 christos size_t valsz;
293 1.8 christos };
294 1.8 christos
295 1.2 christos static struct eqn_box *eqn_box_alloc(struct eqn_node *, struct eqn_box *);
296 1.2 christos static struct eqn_box *eqn_box_makebinary(struct eqn_node *,
297 1.7 christos struct eqn_box *);
298 1.2 christos static void eqn_def(struct eqn_node *);
299 1.7 christos static struct eqn_def *eqn_def_find(struct eqn_node *);
300 1.2 christos static void eqn_delim(struct eqn_node *);
301 1.7 christos static enum eqn_tok eqn_next(struct eqn_node *, enum parse_mode);
302 1.2 christos static void eqn_undef(struct eqn_node *);
303 1.2 christos
304 1.2 christos
305 1.7 christos struct eqn_node *
306 1.8 christos eqn_alloc(void)
307 1.1 joerg {
308 1.7 christos struct eqn_node *ep;
309 1.1 joerg
310 1.7 christos ep = mandoc_calloc(1, sizeof(*ep));
311 1.7 christos ep->gsize = EQN_DEFSIZE;
312 1.7 christos return ep;
313 1.7 christos }
314 1.2 christos
315 1.7 christos void
316 1.7 christos eqn_reset(struct eqn_node *ep)
317 1.7 christos {
318 1.7 christos free(ep->data);
319 1.7 christos ep->data = ep->start = ep->end = NULL;
320 1.7 christos ep->sz = ep->toksz = 0;
321 1.1 joerg }
322 1.1 joerg
323 1.7 christos void
324 1.7 christos eqn_read(struct eqn_node *ep, const char *p)
325 1.1 joerg {
326 1.7 christos char *cp;
327 1.1 joerg
328 1.7 christos if (ep->data == NULL) {
329 1.7 christos ep->sz = strlen(p);
330 1.7 christos ep->data = mandoc_strdup(p);
331 1.7 christos } else {
332 1.7 christos ep->sz = mandoc_asprintf(&cp, "%s %s", ep->data, p);
333 1.7 christos free(ep->data);
334 1.7 christos ep->data = cp;
335 1.7 christos }
336 1.7 christos ep->sz += 1;
337 1.1 joerg }
338 1.1 joerg
339 1.2 christos /*
340 1.2 christos * Find the key "key" of the give size within our eqn-defined values.
341 1.2 christos */
342 1.2 christos static struct eqn_def *
343 1.7 christos eqn_def_find(struct eqn_node *ep)
344 1.2 christos {
345 1.2 christos int i;
346 1.2 christos
347 1.2 christos for (i = 0; i < (int)ep->defsz; i++)
348 1.2 christos if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key,
349 1.7 christos ep->defs[i].keysz, ep->start, ep->toksz))
350 1.5 christos return &ep->defs[i];
351 1.2 christos
352 1.5 christos return NULL;
353 1.2 christos }
354 1.2 christos
355 1.2 christos /*
356 1.7 christos * Parse a token from the input text. The modes are:
357 1.7 christos * MODE_QUOTED: Use *ep->start as the delimiter; the token ends
358 1.7 christos * before its next occurence. Do not interpret the token in any
359 1.7 christos * way and return EQN_TOK_QUOTED. All other modes behave like
360 1.7 christos * MODE_QUOTED when *ep->start is '"'.
361 1.7 christos * MODE_NOSUB: If *ep->start is a curly brace, the token ends after it;
362 1.7 christos * otherwise, it ends before the next whitespace or brace.
363 1.7 christos * Do not interpret the token and return EQN_TOK__MAX.
364 1.7 christos * MODE_SUB: Like MODE_NOSUB, but try to interpret the token as an
365 1.7 christos * alias created with define. If it is an alias, replace it with
366 1.7 christos * its string value and reparse.
367 1.7 christos * MODE_TOK: Like MODE_SUB, but also check the token against the list
368 1.7 christos * of tokens, and if there is a match, return that token. Otherwise,
369 1.7 christos * if the token matches a symbol, return EQN_TOK_SYM; if it matches
370 1.7 christos * a function name, EQN_TOK_FUNC, or else EQN_TOK__MAX. Except for
371 1.7 christos * a token match, *ep->start is set to an allocated string that the
372 1.7 christos * caller is expected to free.
373 1.7 christos * All modes skip whitespace following the end of the token.
374 1.2 christos */
375 1.7 christos static enum eqn_tok
376 1.7 christos eqn_next(struct eqn_node *ep, enum parse_mode mode)
377 1.2 christos {
378 1.7 christos static int last_len, lim;
379 1.7 christos
380 1.2 christos struct eqn_def *def;
381 1.7 christos size_t start;
382 1.7 christos int diff, i, quoted;
383 1.7 christos enum eqn_tok tok;
384 1.2 christos
385 1.7 christos /*
386 1.7 christos * Reset the recursion counter after advancing
387 1.7 christos * beyond the end of the previous substitution.
388 1.7 christos */
389 1.7 christos if (ep->end - ep->data >= last_len)
390 1.7 christos lim = 0;
391 1.2 christos
392 1.7 christos ep->start = ep->end;
393 1.7 christos quoted = mode == MODE_QUOTED;
394 1.7 christos for (;;) {
395 1.7 christos switch (*ep->start) {
396 1.7 christos case '\0':
397 1.7 christos ep->toksz = 0;
398 1.7 christos return EQN_TOK_EOF;
399 1.7 christos case '"':
400 1.7 christos quoted = 1;
401 1.7 christos break;
402 1.9 wiz case ' ':
403 1.9 wiz case '\t':
404 1.9 wiz case '~':
405 1.9 wiz case '^':
406 1.9 wiz if (quoted)
407 1.9 wiz break;
408 1.9 wiz ep->start++;
409 1.9 wiz continue;
410 1.7 christos default:
411 1.7 christos break;
412 1.7 christos }
413 1.7 christos if (quoted) {
414 1.7 christos ep->end = strchr(ep->start + 1, *ep->start);
415 1.7 christos ep->start++; /* Skip opening quote. */
416 1.7 christos if (ep->end == NULL) {
417 1.8 christos mandoc_msg(MANDOCERR_ARG_QUOTE,
418 1.7 christos ep->node->line, ep->node->pos, NULL);
419 1.7 christos ep->end = strchr(ep->start, '\0');
420 1.7 christos }
421 1.7 christos } else {
422 1.7 christos ep->end = ep->start + 1;
423 1.7 christos if (*ep->start != '{' && *ep->start != '}')
424 1.7 christos ep->end += strcspn(ep->end, " ^~\"{}\t");
425 1.7 christos }
426 1.7 christos ep->toksz = ep->end - ep->start;
427 1.7 christos if (quoted && *ep->end != '\0')
428 1.7 christos ep->end++; /* Skip closing quote. */
429 1.7 christos while (*ep->end != '\0' && strchr(" \t^~", *ep->end) != NULL)
430 1.7 christos ep->end++;
431 1.7 christos if (quoted) /* Cannot return, may have to strndup. */
432 1.7 christos break;
433 1.7 christos if (mode == MODE_NOSUB)
434 1.7 christos return EQN_TOK__MAX;
435 1.7 christos if ((def = eqn_def_find(ep)) == NULL)
436 1.7 christos break;
437 1.7 christos if (++lim > EQN_NEST_MAX) {
438 1.8 christos mandoc_msg(MANDOCERR_ROFFLOOP,
439 1.7 christos ep->node->line, ep->node->pos, NULL);
440 1.7 christos return EQN_TOK_EOF;
441 1.7 christos }
442 1.2 christos
443 1.7 christos /* Replace a defined name with its string value. */
444 1.7 christos if ((diff = def->valsz - ep->toksz) > 0) {
445 1.7 christos start = ep->start - ep->data;
446 1.2 christos ep->sz += diff;
447 1.2 christos ep->data = mandoc_realloc(ep->data, ep->sz + 1);
448 1.7 christos ep->start = ep->data + start;
449 1.2 christos }
450 1.7 christos if (diff)
451 1.7 christos memmove(ep->start + def->valsz, ep->start + ep->toksz,
452 1.7 christos strlen(ep->start + ep->toksz) + 1);
453 1.7 christos memcpy(ep->start, def->val, def->valsz);
454 1.7 christos last_len = ep->start - ep->data + def->valsz;
455 1.2 christos }
456 1.7 christos if (mode != MODE_TOK)
457 1.7 christos return quoted ? EQN_TOK_QUOTED : EQN_TOK__MAX;
458 1.2 christos if (quoted) {
459 1.7 christos ep->start = mandoc_strndup(ep->start, ep->toksz);
460 1.7 christos return EQN_TOK_QUOTED;
461 1.2 christos }
462 1.7 christos for (tok = 0; tok < EQN_TOK__MAX; tok++)
463 1.7 christos if (STRNEQ(ep->start, ep->toksz,
464 1.7 christos eqn_toks[tok], strlen(eqn_toks[tok])))
465 1.7 christos return tok;
466 1.7 christos
467 1.7 christos for (i = 0; i < EQNSYM__MAX; i++) {
468 1.7 christos if (STRNEQ(ep->start, ep->toksz,
469 1.7 christos eqnsyms[i].str, strlen(eqnsyms[i].str))) {
470 1.7 christos mandoc_asprintf(&ep->start,
471 1.7 christos "\\[%s]", eqnsyms[i].sym);
472 1.7 christos return EQN_TOK_SYM;
473 1.7 christos }
474 1.2 christos }
475 1.7 christos ep->start = mandoc_strndup(ep->start, ep->toksz);
476 1.7 christos for (i = 0; i < (int)(sizeof(eqn_func)/sizeof(*eqn_func)); i++)
477 1.7 christos if (STRNEQ(ep->start, ep->toksz,
478 1.7 christos eqn_func[i], strlen(eqn_func[i])))
479 1.7 christos return EQN_TOK_FUNC;
480 1.7 christos return EQN_TOK__MAX;
481 1.2 christos }
482 1.2 christos
483 1.7 christos void
484 1.2 christos eqn_box_free(struct eqn_box *bp)
485 1.2 christos {
486 1.8 christos if (bp == NULL)
487 1.8 christos return;
488 1.2 christos
489 1.2 christos if (bp->first)
490 1.2 christos eqn_box_free(bp->first);
491 1.2 christos if (bp->next)
492 1.2 christos eqn_box_free(bp->next);
493 1.2 christos
494 1.2 christos free(bp->text);
495 1.2 christos free(bp->left);
496 1.2 christos free(bp->right);
497 1.2 christos free(bp->top);
498 1.2 christos free(bp->bottom);
499 1.2 christos free(bp);
500 1.2 christos }
501 1.2 christos
502 1.8 christos struct eqn_box *
503 1.8 christos eqn_box_new(void)
504 1.8 christos {
505 1.8 christos struct eqn_box *bp;
506 1.8 christos
507 1.8 christos bp = mandoc_calloc(1, sizeof(*bp));
508 1.8 christos bp->expectargs = UINT_MAX;
509 1.8 christos return bp;
510 1.8 christos }
511 1.8 christos
512 1.2 christos /*
513 1.2 christos * Allocate a box as the last child of the parent node.
514 1.2 christos */
515 1.2 christos static struct eqn_box *
516 1.2 christos eqn_box_alloc(struct eqn_node *ep, struct eqn_box *parent)
517 1.2 christos {
518 1.2 christos struct eqn_box *bp;
519 1.2 christos
520 1.8 christos bp = eqn_box_new();
521 1.2 christos bp->parent = parent;
522 1.2 christos bp->parent->args++;
523 1.7 christos bp->font = bp->parent->font;
524 1.2 christos bp->size = ep->gsize;
525 1.2 christos
526 1.2 christos if (NULL != parent->first) {
527 1.2 christos parent->last->next = bp;
528 1.2 christos bp->prev = parent->last;
529 1.2 christos } else
530 1.2 christos parent->first = bp;
531 1.2 christos
532 1.2 christos parent->last = bp;
533 1.5 christos return bp;
534 1.2 christos }
535 1.2 christos
536 1.2 christos /*
537 1.2 christos * Reparent the current last node (of the current parent) under a new
538 1.2 christos * EQN_SUBEXPR as the first element.
539 1.2 christos * Then return the new parent.
540 1.2 christos * The new EQN_SUBEXPR will have a two-child limit.
541 1.2 christos */
542 1.2 christos static struct eqn_box *
543 1.7 christos eqn_box_makebinary(struct eqn_node *ep, struct eqn_box *parent)
544 1.2 christos {
545 1.2 christos struct eqn_box *b, *newb;
546 1.2 christos
547 1.2 christos assert(NULL != parent->last);
548 1.2 christos b = parent->last;
549 1.2 christos if (parent->last == parent->first)
550 1.2 christos parent->first = NULL;
551 1.2 christos parent->args--;
552 1.2 christos parent->last = b->prev;
553 1.2 christos b->prev = NULL;
554 1.2 christos newb = eqn_box_alloc(ep, parent);
555 1.2 christos newb->type = EQN_SUBEXPR;
556 1.2 christos newb->expectargs = 2;
557 1.2 christos newb->args = 1;
558 1.2 christos newb->first = newb->last = b;
559 1.2 christos newb->first->next = NULL;
560 1.2 christos b->parent = newb;
561 1.5 christos return newb;
562 1.2 christos }
563 1.2 christos
564 1.2 christos /*
565 1.2 christos * Parse the "delim" control statement.
566 1.2 christos */
567 1.2 christos static void
568 1.2 christos eqn_delim(struct eqn_node *ep)
569 1.2 christos {
570 1.7 christos if (ep->end[0] == '\0' || ep->end[1] == '\0') {
571 1.8 christos mandoc_msg(MANDOCERR_REQ_EMPTY,
572 1.7 christos ep->node->line, ep->node->pos, "delim");
573 1.7 christos if (ep->end[0] != '\0')
574 1.7 christos ep->end++;
575 1.7 christos } else if (strncmp(ep->end, "off", 3) == 0) {
576 1.2 christos ep->delim = 0;
577 1.7 christos ep->end += 3;
578 1.7 christos } else if (strncmp(ep->end, "on", 2) == 0) {
579 1.2 christos if (ep->odelim && ep->cdelim)
580 1.2 christos ep->delim = 1;
581 1.7 christos ep->end += 2;
582 1.7 christos } else {
583 1.7 christos ep->odelim = *ep->end++;
584 1.7 christos ep->cdelim = *ep->end++;
585 1.2 christos ep->delim = 1;
586 1.2 christos }
587 1.2 christos }
588 1.2 christos
589 1.2 christos /*
590 1.2 christos * Undefine a previously-defined string.
591 1.2 christos */
592 1.2 christos static void
593 1.2 christos eqn_undef(struct eqn_node *ep)
594 1.2 christos {
595 1.2 christos struct eqn_def *def;
596 1.2 christos
597 1.7 christos if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF) {
598 1.8 christos mandoc_msg(MANDOCERR_REQ_EMPTY,
599 1.7 christos ep->node->line, ep->node->pos, "undef");
600 1.2 christos return;
601 1.2 christos }
602 1.7 christos if ((def = eqn_def_find(ep)) == NULL)
603 1.2 christos return;
604 1.2 christos free(def->key);
605 1.2 christos free(def->val);
606 1.2 christos def->key = def->val = NULL;
607 1.2 christos def->keysz = def->valsz = 0;
608 1.2 christos }
609 1.2 christos
610 1.2 christos static void
611 1.2 christos eqn_def(struct eqn_node *ep)
612 1.2 christos {
613 1.2 christos struct eqn_def *def;
614 1.2 christos int i;
615 1.2 christos
616 1.7 christos if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF) {
617 1.8 christos mandoc_msg(MANDOCERR_REQ_EMPTY,
618 1.7 christos ep->node->line, ep->node->pos, "define");
619 1.2 christos return;
620 1.2 christos }
621 1.2 christos
622 1.2 christos /*
623 1.2 christos * Search for a key that already exists.
624 1.2 christos * Create a new key if none is found.
625 1.2 christos */
626 1.7 christos if ((def = eqn_def_find(ep)) == NULL) {
627 1.2 christos /* Find holes in string array. */
628 1.2 christos for (i = 0; i < (int)ep->defsz; i++)
629 1.2 christos if (0 == ep->defs[i].keysz)
630 1.2 christos break;
631 1.2 christos
632 1.2 christos if (i == (int)ep->defsz) {
633 1.2 christos ep->defsz++;
634 1.2 christos ep->defs = mandoc_reallocarray(ep->defs,
635 1.2 christos ep->defsz, sizeof(struct eqn_def));
636 1.2 christos ep->defs[i].key = ep->defs[i].val = NULL;
637 1.2 christos }
638 1.2 christos
639 1.2 christos def = ep->defs + i;
640 1.2 christos free(def->key);
641 1.7 christos def->key = mandoc_strndup(ep->start, ep->toksz);
642 1.7 christos def->keysz = ep->toksz;
643 1.2 christos }
644 1.2 christos
645 1.7 christos if (eqn_next(ep, MODE_QUOTED) == EQN_TOK_EOF) {
646 1.8 christos mandoc_msg(MANDOCERR_REQ_EMPTY,
647 1.7 christos ep->node->line, ep->node->pos, "define %s", def->key);
648 1.2 christos free(def->key);
649 1.2 christos free(def->val);
650 1.2 christos def->key = def->val = NULL;
651 1.2 christos def->keysz = def->valsz = 0;
652 1.2 christos return;
653 1.2 christos }
654 1.2 christos free(def->val);
655 1.7 christos def->val = mandoc_strndup(ep->start, ep->toksz);
656 1.7 christos def->valsz = ep->toksz;
657 1.2 christos }
658 1.2 christos
659 1.7 christos void
660 1.7 christos eqn_parse(struct eqn_node *ep)
661 1.2 christos {
662 1.7 christos struct eqn_box *cur, *nbox, *parent, *split;
663 1.7 christos const char *cp, *cpn;
664 1.2 christos char *p;
665 1.7 christos enum eqn_tok tok;
666 1.7 christos enum { CCL_LET, CCL_DIG, CCL_PUN } ccl, ccln;
667 1.2 christos int size;
668 1.2 christos
669 1.7 christos parent = ep->node->eqn;
670 1.2 christos assert(parent != NULL);
671 1.2 christos
672 1.2 christos /*
673 1.2 christos * Empty equation.
674 1.2 christos * Do not add it to the high-level syntax tree.
675 1.2 christos */
676 1.2 christos
677 1.2 christos if (ep->data == NULL)
678 1.7 christos return;
679 1.7 christos
680 1.9 wiz ep->start = ep->end = ep->data;
681 1.2 christos
682 1.2 christos next_tok:
683 1.7 christos tok = eqn_next(ep, MODE_TOK);
684 1.2 christos switch (tok) {
685 1.7 christos case EQN_TOK_UNDEF:
686 1.2 christos eqn_undef(ep);
687 1.2 christos break;
688 1.7 christos case EQN_TOK_NDEFINE:
689 1.7 christos case EQN_TOK_DEFINE:
690 1.2 christos eqn_def(ep);
691 1.2 christos break;
692 1.7 christos case EQN_TOK_TDEFINE:
693 1.7 christos if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF ||
694 1.7 christos eqn_next(ep, MODE_QUOTED) == EQN_TOK_EOF)
695 1.8 christos mandoc_msg(MANDOCERR_REQ_EMPTY,
696 1.7 christos ep->node->line, ep->node->pos, "tdefine");
697 1.2 christos break;
698 1.7 christos case EQN_TOK_DELIM:
699 1.2 christos eqn_delim(ep);
700 1.2 christos break;
701 1.7 christos case EQN_TOK_GFONT:
702 1.7 christos if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF)
703 1.8 christos mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
704 1.8 christos ep->node->pos, "%s", eqn_toks[tok]);
705 1.2 christos break;
706 1.7 christos case EQN_TOK_MARK:
707 1.7 christos case EQN_TOK_LINEUP:
708 1.2 christos /* Ignore these. */
709 1.2 christos break;
710 1.7 christos case EQN_TOK_DYAD:
711 1.7 christos case EQN_TOK_VEC:
712 1.7 christos case EQN_TOK_UNDER:
713 1.7 christos case EQN_TOK_BAR:
714 1.7 christos case EQN_TOK_TILDE:
715 1.7 christos case EQN_TOK_HAT:
716 1.7 christos case EQN_TOK_DOT:
717 1.7 christos case EQN_TOK_DOTDOT:
718 1.2 christos if (parent->last == NULL) {
719 1.8 christos mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
720 1.8 christos ep->node->pos, "%s", eqn_toks[tok]);
721 1.2 christos cur = eqn_box_alloc(ep, parent);
722 1.2 christos cur->type = EQN_TEXT;
723 1.2 christos cur->text = mandoc_strdup("");
724 1.2 christos }
725 1.7 christos parent = eqn_box_makebinary(ep, parent);
726 1.7 christos parent->type = EQN_LIST;
727 1.2 christos parent->expectargs = 1;
728 1.7 christos parent->font = EQNFONT_ROMAN;
729 1.2 christos switch (tok) {
730 1.7 christos case EQN_TOK_DOTDOT:
731 1.7 christos parent->top = mandoc_strdup("\\[ad]");
732 1.2 christos break;
733 1.7 christos case EQN_TOK_VEC:
734 1.7 christos parent->top = mandoc_strdup("\\[->]");
735 1.2 christos break;
736 1.7 christos case EQN_TOK_DYAD:
737 1.7 christos parent->top = mandoc_strdup("\\[<>]");
738 1.2 christos break;
739 1.7 christos case EQN_TOK_TILDE:
740 1.7 christos parent->top = mandoc_strdup("\\[a~]");
741 1.2 christos break;
742 1.7 christos case EQN_TOK_UNDER:
743 1.7 christos parent->bottom = mandoc_strdup("\\[ul]");
744 1.2 christos break;
745 1.7 christos case EQN_TOK_BAR:
746 1.7 christos parent->top = mandoc_strdup("\\[rn]");
747 1.2 christos break;
748 1.7 christos case EQN_TOK_DOT:
749 1.7 christos parent->top = mandoc_strdup("\\[a.]");
750 1.2 christos break;
751 1.7 christos case EQN_TOK_HAT:
752 1.7 christos parent->top = mandoc_strdup("\\[ha]");
753 1.2 christos break;
754 1.2 christos default:
755 1.2 christos abort();
756 1.2 christos }
757 1.2 christos parent = parent->parent;
758 1.2 christos break;
759 1.7 christos case EQN_TOK_FWD:
760 1.7 christos case EQN_TOK_BACK:
761 1.7 christos case EQN_TOK_DOWN:
762 1.7 christos case EQN_TOK_UP:
763 1.7 christos if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF)
764 1.8 christos mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
765 1.8 christos ep->node->pos, "%s", eqn_toks[tok]);
766 1.2 christos break;
767 1.7 christos case EQN_TOK_FAT:
768 1.7 christos case EQN_TOK_ROMAN:
769 1.7 christos case EQN_TOK_ITALIC:
770 1.7 christos case EQN_TOK_BOLD:
771 1.2 christos while (parent->args == parent->expectargs)
772 1.2 christos parent = parent->parent;
773 1.2 christos /*
774 1.2 christos * These values apply to the next word or sequence of
775 1.2 christos * words; thus, we mark that we'll have a child with
776 1.2 christos * exactly one of those.
777 1.2 christos */
778 1.2 christos parent = eqn_box_alloc(ep, parent);
779 1.7 christos parent->type = EQN_LIST;
780 1.2 christos parent->expectargs = 1;
781 1.2 christos switch (tok) {
782 1.7 christos case EQN_TOK_FAT:
783 1.2 christos parent->font = EQNFONT_FAT;
784 1.2 christos break;
785 1.7 christos case EQN_TOK_ROMAN:
786 1.2 christos parent->font = EQNFONT_ROMAN;
787 1.2 christos break;
788 1.7 christos case EQN_TOK_ITALIC:
789 1.2 christos parent->font = EQNFONT_ITALIC;
790 1.2 christos break;
791 1.7 christos case EQN_TOK_BOLD:
792 1.2 christos parent->font = EQNFONT_BOLD;
793 1.2 christos break;
794 1.2 christos default:
795 1.2 christos abort();
796 1.2 christos }
797 1.2 christos break;
798 1.7 christos case EQN_TOK_SIZE:
799 1.7 christos case EQN_TOK_GSIZE:
800 1.2 christos /* Accept two values: integral size and a single. */
801 1.7 christos if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
802 1.8 christos mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
803 1.8 christos ep->node->pos, "%s", eqn_toks[tok]);
804 1.2 christos break;
805 1.2 christos }
806 1.7 christos size = mandoc_strntoi(ep->start, ep->toksz, 10);
807 1.2 christos if (-1 == size) {
808 1.8 christos mandoc_msg(MANDOCERR_IT_NONUM, ep->node->line,
809 1.8 christos ep->node->pos, "%s", eqn_toks[tok]);
810 1.2 christos break;
811 1.2 christos }
812 1.2 christos if (EQN_TOK_GSIZE == tok) {
813 1.2 christos ep->gsize = size;
814 1.2 christos break;
815 1.2 christos }
816 1.7 christos while (parent->args == parent->expectargs)
817 1.7 christos parent = parent->parent;
818 1.2 christos parent = eqn_box_alloc(ep, parent);
819 1.7 christos parent->type = EQN_LIST;
820 1.2 christos parent->expectargs = 1;
821 1.2 christos parent->size = size;
822 1.2 christos break;
823 1.7 christos case EQN_TOK_FROM:
824 1.7 christos case EQN_TOK_TO:
825 1.7 christos case EQN_TOK_SUB:
826 1.7 christos case EQN_TOK_SUP:
827 1.2 christos /*
828 1.2 christos * We have a left-right-associative expression.
829 1.2 christos * Repivot under a positional node, open a child scope
830 1.2 christos * and keep on reading.
831 1.2 christos */
832 1.2 christos if (parent->last == NULL) {
833 1.8 christos mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
834 1.8 christos ep->node->pos, "%s", eqn_toks[tok]);
835 1.2 christos cur = eqn_box_alloc(ep, parent);
836 1.2 christos cur->type = EQN_TEXT;
837 1.2 christos cur->text = mandoc_strdup("");
838 1.2 christos }
839 1.7 christos while (parent->expectargs == 1 && parent->args == 1)
840 1.7 christos parent = parent->parent;
841 1.7 christos if (tok == EQN_TOK_FROM || tok == EQN_TOK_TO) {
842 1.7 christos for (cur = parent; cur != NULL; cur = cur->parent)
843 1.7 christos if (cur->pos == EQNPOS_SUB ||
844 1.7 christos cur->pos == EQNPOS_SUP ||
845 1.7 christos cur->pos == EQNPOS_SUBSUP ||
846 1.7 christos cur->pos == EQNPOS_SQRT ||
847 1.7 christos cur->pos == EQNPOS_OVER)
848 1.7 christos break;
849 1.7 christos if (cur != NULL)
850 1.7 christos parent = cur->parent;
851 1.7 christos }
852 1.7 christos if (tok == EQN_TOK_SUP && parent->pos == EQNPOS_SUB) {
853 1.2 christos parent->expectargs = 3;
854 1.2 christos parent->pos = EQNPOS_SUBSUP;
855 1.2 christos break;
856 1.2 christos }
857 1.7 christos if (tok == EQN_TOK_TO && parent->pos == EQNPOS_FROM) {
858 1.2 christos parent->expectargs = 3;
859 1.2 christos parent->pos = EQNPOS_FROMTO;
860 1.2 christos break;
861 1.2 christos }
862 1.7 christos parent = eqn_box_makebinary(ep, parent);
863 1.2 christos switch (tok) {
864 1.7 christos case EQN_TOK_FROM:
865 1.7 christos parent->pos = EQNPOS_FROM;
866 1.2 christos break;
867 1.7 christos case EQN_TOK_TO:
868 1.7 christos parent->pos = EQNPOS_TO;
869 1.2 christos break;
870 1.7 christos case EQN_TOK_SUP:
871 1.7 christos parent->pos = EQNPOS_SUP;
872 1.2 christos break;
873 1.7 christos case EQN_TOK_SUB:
874 1.7 christos parent->pos = EQNPOS_SUB;
875 1.2 christos break;
876 1.2 christos default:
877 1.2 christos abort();
878 1.2 christos }
879 1.2 christos break;
880 1.7 christos case EQN_TOK_SQRT:
881 1.2 christos while (parent->args == parent->expectargs)
882 1.2 christos parent = parent->parent;
883 1.2 christos /*
884 1.2 christos * Accept a left-right-associative set of arguments just
885 1.2 christos * like sub and sup and friends but without rebalancing
886 1.2 christos * under a pivot.
887 1.2 christos */
888 1.2 christos parent = eqn_box_alloc(ep, parent);
889 1.2 christos parent->type = EQN_SUBEXPR;
890 1.2 christos parent->pos = EQNPOS_SQRT;
891 1.2 christos parent->expectargs = 1;
892 1.2 christos break;
893 1.7 christos case EQN_TOK_OVER:
894 1.2 christos /*
895 1.2 christos * We have a right-left-associative fraction.
896 1.2 christos * Close out anything that's currently open, then
897 1.2 christos * rebalance and continue reading.
898 1.2 christos */
899 1.2 christos if (parent->last == NULL) {
900 1.8 christos mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
901 1.8 christos ep->node->pos, "%s", eqn_toks[tok]);
902 1.2 christos cur = eqn_box_alloc(ep, parent);
903 1.2 christos cur->type = EQN_TEXT;
904 1.2 christos cur->text = mandoc_strdup("");
905 1.2 christos }
906 1.7 christos while (parent->args == parent->expectargs)
907 1.7 christos parent = parent->parent;
908 1.2 christos while (EQN_SUBEXPR == parent->type)
909 1.2 christos parent = parent->parent;
910 1.7 christos parent = eqn_box_makebinary(ep, parent);
911 1.7 christos parent->pos = EQNPOS_OVER;
912 1.2 christos break;
913 1.7 christos case EQN_TOK_RIGHT:
914 1.7 christos case EQN_TOK_BRACE_CLOSE:
915 1.2 christos /*
916 1.2 christos * Close out the existing brace.
917 1.2 christos * FIXME: this is a shitty sentinel: we should really
918 1.2 christos * have a native EQN_BRACE type or whatnot.
919 1.2 christos */
920 1.2 christos for (cur = parent; cur != NULL; cur = cur->parent)
921 1.2 christos if (cur->type == EQN_LIST &&
922 1.7 christos cur->expectargs > 1 &&
923 1.2 christos (tok == EQN_TOK_BRACE_CLOSE ||
924 1.2 christos cur->left != NULL))
925 1.2 christos break;
926 1.2 christos if (cur == NULL) {
927 1.8 christos mandoc_msg(MANDOCERR_BLK_NOTOPEN, ep->node->line,
928 1.8 christos ep->node->pos, "%s", eqn_toks[tok]);
929 1.2 christos break;
930 1.2 christos }
931 1.2 christos parent = cur;
932 1.2 christos if (EQN_TOK_RIGHT == tok) {
933 1.7 christos if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
934 1.2 christos mandoc_msg(MANDOCERR_REQ_EMPTY,
935 1.8 christos ep->node->line, ep->node->pos,
936 1.8 christos "%s", eqn_toks[tok]);
937 1.2 christos break;
938 1.2 christos }
939 1.2 christos /* Handling depends on right/left. */
940 1.7 christos if (STRNEQ(ep->start, ep->toksz, "ceiling", 7))
941 1.7 christos parent->right = mandoc_strdup("\\[rc]");
942 1.7 christos else if (STRNEQ(ep->start, ep->toksz, "floor", 5))
943 1.7 christos parent->right = mandoc_strdup("\\[rf]");
944 1.7 christos else
945 1.7 christos parent->right =
946 1.7 christos mandoc_strndup(ep->start, ep->toksz);
947 1.2 christos }
948 1.2 christos parent = parent->parent;
949 1.5 christos if (tok == EQN_TOK_BRACE_CLOSE &&
950 1.2 christos (parent->type == EQN_PILE ||
951 1.2 christos parent->type == EQN_MATRIX))
952 1.2 christos parent = parent->parent;
953 1.2 christos /* Close out any "singleton" lists. */
954 1.7 christos while (parent->type == EQN_LIST &&
955 1.7 christos parent->expectargs == 1 &&
956 1.7 christos parent->args == 1)
957 1.2 christos parent = parent->parent;
958 1.2 christos break;
959 1.7 christos case EQN_TOK_BRACE_OPEN:
960 1.7 christos case EQN_TOK_LEFT:
961 1.2 christos /*
962 1.2 christos * If we already have something in the stack and we're
963 1.2 christos * in an expression, then rewind til we're not any more
964 1.2 christos * (just like with the text node).
965 1.2 christos */
966 1.2 christos while (parent->args == parent->expectargs)
967 1.2 christos parent = parent->parent;
968 1.2 christos if (EQN_TOK_LEFT == tok &&
969 1.7 christos eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
970 1.8 christos mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
971 1.8 christos ep->node->pos, "%s", eqn_toks[tok]);
972 1.2 christos break;
973 1.2 christos }
974 1.2 christos parent = eqn_box_alloc(ep, parent);
975 1.2 christos parent->type = EQN_LIST;
976 1.2 christos if (EQN_TOK_LEFT == tok) {
977 1.7 christos if (STRNEQ(ep->start, ep->toksz, "ceiling", 7))
978 1.7 christos parent->left = mandoc_strdup("\\[lc]");
979 1.7 christos else if (STRNEQ(ep->start, ep->toksz, "floor", 5))
980 1.7 christos parent->left = mandoc_strdup("\\[lf]");
981 1.7 christos else
982 1.7 christos parent->left =
983 1.7 christos mandoc_strndup(ep->start, ep->toksz);
984 1.2 christos }
985 1.2 christos break;
986 1.7 christos case EQN_TOK_PILE:
987 1.7 christos case EQN_TOK_LPILE:
988 1.7 christos case EQN_TOK_RPILE:
989 1.7 christos case EQN_TOK_CPILE:
990 1.7 christos case EQN_TOK_CCOL:
991 1.7 christos case EQN_TOK_LCOL:
992 1.7 christos case EQN_TOK_RCOL:
993 1.2 christos while (parent->args == parent->expectargs)
994 1.2 christos parent = parent->parent;
995 1.2 christos parent = eqn_box_alloc(ep, parent);
996 1.2 christos parent->type = EQN_PILE;
997 1.2 christos parent->expectargs = 1;
998 1.2 christos break;
999 1.7 christos case EQN_TOK_ABOVE:
1000 1.2 christos for (cur = parent; cur != NULL; cur = cur->parent)
1001 1.2 christos if (cur->type == EQN_PILE)
1002 1.2 christos break;
1003 1.2 christos if (cur == NULL) {
1004 1.8 christos mandoc_msg(MANDOCERR_IT_STRAY, ep->node->line,
1005 1.8 christos ep->node->pos, "%s", eqn_toks[tok]);
1006 1.2 christos break;
1007 1.2 christos }
1008 1.2 christos parent = eqn_box_alloc(ep, cur);
1009 1.2 christos parent->type = EQN_LIST;
1010 1.2 christos break;
1011 1.7 christos case EQN_TOK_MATRIX:
1012 1.2 christos while (parent->args == parent->expectargs)
1013 1.2 christos parent = parent->parent;
1014 1.2 christos parent = eqn_box_alloc(ep, parent);
1015 1.2 christos parent->type = EQN_MATRIX;
1016 1.2 christos parent->expectargs = 1;
1017 1.2 christos break;
1018 1.7 christos case EQN_TOK_EOF:
1019 1.7 christos return;
1020 1.7 christos case EQN_TOK__MAX:
1021 1.7 christos case EQN_TOK_FUNC:
1022 1.7 christos case EQN_TOK_QUOTED:
1023 1.7 christos case EQN_TOK_SYM:
1024 1.7 christos p = ep->start;
1025 1.7 christos assert(p != NULL);
1026 1.2 christos /*
1027 1.2 christos * If we already have something in the stack and we're
1028 1.2 christos * in an expression, then rewind til we're not any more.
1029 1.2 christos */
1030 1.2 christos while (parent->args == parent->expectargs)
1031 1.2 christos parent = parent->parent;
1032 1.2 christos cur = eqn_box_alloc(ep, parent);
1033 1.2 christos cur->type = EQN_TEXT;
1034 1.7 christos cur->text = p;
1035 1.7 christos switch (tok) {
1036 1.7 christos case EQN_TOK_FUNC:
1037 1.7 christos cur->font = EQNFONT_ROMAN;
1038 1.7 christos break;
1039 1.7 christos case EQN_TOK_QUOTED:
1040 1.7 christos if (cur->font == EQNFONT_NONE)
1041 1.7 christos cur->font = EQNFONT_ITALIC;
1042 1.7 christos break;
1043 1.7 christos case EQN_TOK_SYM:
1044 1.7 christos break;
1045 1.7 christos default:
1046 1.7 christos if (cur->font != EQNFONT_NONE || *p == '\0')
1047 1.2 christos break;
1048 1.7 christos cpn = p - 1;
1049 1.7 christos ccln = CCL_LET;
1050 1.7 christos split = NULL;
1051 1.7 christos for (;;) {
1052 1.7 christos /* Advance to next character. */
1053 1.7 christos cp = cpn++;
1054 1.7 christos ccl = ccln;
1055 1.7 christos ccln = isalpha((unsigned char)*cpn) ? CCL_LET :
1056 1.7 christos isdigit((unsigned char)*cpn) ||
1057 1.7 christos (*cpn == '.' && (ccl == CCL_DIG ||
1058 1.7 christos isdigit((unsigned char)cpn[1]))) ?
1059 1.7 christos CCL_DIG : CCL_PUN;
1060 1.7 christos /* No boundary before first character. */
1061 1.7 christos if (cp < p)
1062 1.7 christos continue;
1063 1.7 christos cur->font = ccl == CCL_LET ?
1064 1.7 christos EQNFONT_ITALIC : EQNFONT_ROMAN;
1065 1.7 christos if (*cp == '\\')
1066 1.7 christos mandoc_escape(&cpn, NULL, NULL);
1067 1.7 christos /* No boundary after last character. */
1068 1.7 christos if (*cpn == '\0')
1069 1.7 christos break;
1070 1.7 christos if (ccln == ccl && *cp != ',' && *cpn != ',')
1071 1.7 christos continue;
1072 1.7 christos /* Boundary found, split the text. */
1073 1.7 christos if (parent->args == parent->expectargs) {
1074 1.7 christos /* Remove the text from the tree. */
1075 1.7 christos if (cur->prev == NULL)
1076 1.7 christos parent->first = cur->next;
1077 1.7 christos else
1078 1.7 christos cur->prev->next = NULL;
1079 1.7 christos parent->last = cur->prev;
1080 1.7 christos parent->args--;
1081 1.7 christos /* Set up a list instead. */
1082 1.7 christos split = eqn_box_alloc(ep, parent);
1083 1.7 christos split->type = EQN_LIST;
1084 1.7 christos /* Insert the word into the list. */
1085 1.7 christos split->first = split->last = cur;
1086 1.7 christos cur->parent = split;
1087 1.7 christos cur->prev = NULL;
1088 1.7 christos parent = split;
1089 1.7 christos }
1090 1.7 christos /* Append a new text box. */
1091 1.7 christos nbox = eqn_box_alloc(ep, parent);
1092 1.7 christos nbox->type = EQN_TEXT;
1093 1.7 christos nbox->text = mandoc_strdup(cpn);
1094 1.7 christos /* Truncate the old box. */
1095 1.7 christos p = mandoc_strndup(cur->text,
1096 1.7 christos cpn - cur->text);
1097 1.7 christos free(cur->text);
1098 1.7 christos cur->text = p;
1099 1.7 christos /* Setup to process the new box. */
1100 1.7 christos cur = nbox;
1101 1.7 christos p = nbox->text;
1102 1.7 christos cpn = p - 1;
1103 1.7 christos ccln = CCL_LET;
1104 1.2 christos }
1105 1.7 christos if (split != NULL)
1106 1.7 christos parent = split->parent;
1107 1.7 christos break;
1108 1.7 christos }
1109 1.2 christos break;
1110 1.7 christos default:
1111 1.7 christos abort();
1112 1.2 christos }
1113 1.2 christos goto next_tok;
1114 1.2 christos }
1115 1.2 christos
1116 1.1 joerg void
1117 1.1 joerg eqn_free(struct eqn_node *p)
1118 1.1 joerg {
1119 1.2 christos int i;
1120 1.2 christos
1121 1.8 christos if (p == NULL)
1122 1.8 christos return;
1123 1.8 christos
1124 1.2 christos for (i = 0; i < (int)p->defsz; i++) {
1125 1.2 christos free(p->defs[i].key);
1126 1.2 christos free(p->defs[i].val);
1127 1.2 christos }
1128 1.1 joerg
1129 1.2 christos free(p->data);
1130 1.2 christos free(p->defs);
1131 1.1 joerg free(p);
1132 1.1 joerg }
1133