]>
git.cameronkatri.com Git - mandoc.git/blob - eqn.c
1 /* $Id: eqn.c,v 1.78 2017/07/15 16:26:17 schwarze Exp $ */
3 * Copyright (c) 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 #include <sys/types.h>
30 #include "mandoc_aux.h"
33 #include "libmandoc.h"
36 #define EQN_NEST_MAX 128 /* maximum nesting of defines */
37 #define STRNEQ(p1, sz1, p2, sz2) \
38 ((sz1) == (sz2) && 0 == strncmp((p1), (p2), (sz1)))
93 static const char *eqn_toks
[EQN_TOK__MAX
] = {
94 "dyad", /* EQN_TOK_DYAD */
95 "vec", /* EQN_TOK_VEC */
96 "under", /* EQN_TOK_UNDER */
97 "bar", /* EQN_TOK_BAR */
98 "tilde", /* EQN_TOK_TILDE */
99 "hat", /* EQN_TOK_HAT */
100 "dot", /* EQN_TOK_DOT */
101 "dotdot", /* EQN_TOK_DOTDOT */
102 "fwd", /* EQN_TOK_FWD * */
103 "back", /* EQN_TOK_BACK */
104 "down", /* EQN_TOK_DOWN */
105 "up", /* EQN_TOK_UP */
106 "fat", /* EQN_TOK_FAT */
107 "roman", /* EQN_TOK_ROMAN */
108 "italic", /* EQN_TOK_ITALIC */
109 "bold", /* EQN_TOK_BOLD */
110 "size", /* EQN_TOK_SIZE */
111 "sub", /* EQN_TOK_SUB */
112 "sup", /* EQN_TOK_SUP */
113 "sqrt", /* EQN_TOK_SQRT */
114 "over", /* EQN_TOK_OVER */
115 "from", /* EQN_TOK_FROM */
116 "to", /* EQN_TOK_TO */
117 "{", /* EQN_TOK_BRACE_OPEN */
118 "}", /* EQN_TOK_BRACE_CLOSE */
119 "gsize", /* EQN_TOK_GSIZE */
120 "gfont", /* EQN_TOK_GFONT */
121 "mark", /* EQN_TOK_MARK */
122 "lineup", /* EQN_TOK_LINEUP */
123 "left", /* EQN_TOK_LEFT */
124 "right", /* EQN_TOK_RIGHT */
125 "pile", /* EQN_TOK_PILE */
126 "lpile", /* EQN_TOK_LPILE */
127 "rpile", /* EQN_TOK_RPILE */
128 "cpile", /* EQN_TOK_CPILE */
129 "matrix", /* EQN_TOK_MATRIX */
130 "ccol", /* EQN_TOK_CCOL */
131 "lcol", /* EQN_TOK_LCOL */
132 "rcol", /* EQN_TOK_RCOL */
133 "delim", /* EQN_TOK_DELIM */
134 "define", /* EQN_TOK_DEFINE */
135 "tdefine", /* EQN_TOK_TDEFINE */
136 "ndefine", /* EQN_TOK_NDEFINE */
137 "undef", /* EQN_TOK_UNDEF */
138 "above", /* EQN_TOK_ABOVE */
141 static const char *const eqn_func
[] = {
142 "acos", "acsc", "and", "arc", "asec", "asin", "atan",
143 "cos", "cosh", "coth", "csc", "det", "exp", "for",
144 "if", "lim", "ln", "log", "max", "min",
145 "sec", "sin", "sinh", "tan", "tanh", "Im", "Re",
217 static const struct eqnsym eqnsyms
[EQNSYM__MAX
] = {
218 { "alpha", "*a" }, /* EQNSYM_alpha */
219 { "beta", "*b" }, /* EQNSYM_beta */
220 { "chi", "*x" }, /* EQNSYM_chi */
221 { "delta", "*d" }, /* EQNSYM_delta */
222 { "epsilon", "*e" }, /* EQNSYM_epsilon */
223 { "eta", "*y" }, /* EQNSYM_eta */
224 { "gamma", "*g" }, /* EQNSYM_gamma */
225 { "iota", "*i" }, /* EQNSYM_iota */
226 { "kappa", "*k" }, /* EQNSYM_kappa */
227 { "lambda", "*l" }, /* EQNSYM_lambda */
228 { "mu", "*m" }, /* EQNSYM_mu */
229 { "nu", "*n" }, /* EQNSYM_nu */
230 { "omega", "*w" }, /* EQNSYM_omega */
231 { "omicron", "*o" }, /* EQNSYM_omicron */
232 { "phi", "*f" }, /* EQNSYM_phi */
233 { "pi", "*p" }, /* EQNSYM_pi */
234 { "psi", "*q" }, /* EQNSYM_psi */
235 { "rho", "*r" }, /* EQNSYM_rho */
236 { "sigma", "*s" }, /* EQNSYM_sigma */
237 { "tau", "*t" }, /* EQNSYM_tau */
238 { "theta", "*h" }, /* EQNSYM_theta */
239 { "upsilon", "*u" }, /* EQNSYM_upsilon */
240 { "xi", "*c" }, /* EQNSYM_xi */
241 { "zeta", "*z" }, /* EQNSYM_zeta */
242 { "DELTA", "*D" }, /* EQNSYM_DELTA */
243 { "GAMMA", "*G" }, /* EQNSYM_GAMMA */
244 { "LAMBDA", "*L" }, /* EQNSYM_LAMBDA */
245 { "OMEGA", "*W" }, /* EQNSYM_OMEGA */
246 { "PHI", "*F" }, /* EQNSYM_PHI */
247 { "PI", "*P" }, /* EQNSYM_PI */
248 { "PSI", "*Q" }, /* EQNSYM_PSI */
249 { "SIGMA", "*S" }, /* EQNSYM_SIGMA */
250 { "THETA", "*H" }, /* EQNSYM_THETA */
251 { "UPSILON", "*U" }, /* EQNSYM_UPSILON */
252 { "XI", "*C" }, /* EQNSYM_XI */
253 { "inter", "ca" }, /* EQNSYM_inter */
254 { "union", "cu" }, /* EQNSYM_union */
255 { "prod", "product" }, /* EQNSYM_prod */
256 { "int", "integral" }, /* EQNSYM_int */
257 { "sum", "sum" }, /* EQNSYM_sum */
258 { "grad", "gr" }, /* EQNSYM_grad */
259 { "del", "gr" }, /* EQNSYM_del */
260 { "times", "mu" }, /* EQNSYM_times */
261 { "cdot", "pc" }, /* EQNSYM_cdot */
262 { "nothing", "&" }, /* EQNSYM_nothing */
263 { "approx", "~~" }, /* EQNSYM_approx */
264 { "prime", "fm" }, /* EQNSYM_prime */
265 { "half", "12" }, /* EQNSYM_half */
266 { "partial", "pd" }, /* EQNSYM_partial */
267 { "inf", "if" }, /* EQNSYM_inf */
268 { ">>", ">>" }, /* EQNSYM_muchgreat */
269 { "<<", "<<" }, /* EQNSYM_muchless */
270 { "<-", "<-" }, /* EQNSYM_larrow */
271 { "->", "->" }, /* EQNSYM_rarrow */
272 { "+-", "+-" }, /* EQNSYM_pm */
273 { "!=", "!=" }, /* EQNSYM_nequal */
274 { "==", "==" }, /* EQNSYM_equiv */
275 { "<=", "<=" }, /* EQNSYM_lessequal */
276 { ">=", ">=" }, /* EQNSYM_moreequal */
277 { "-", "mi" }, /* EQNSYM_minus */
287 static struct eqn_box
*eqn_box_alloc(struct eqn_node
*, struct eqn_box
*);
288 static struct eqn_box
*eqn_box_makebinary(struct eqn_node
*,
290 static void eqn_def(struct eqn_node
*);
291 static struct eqn_def
*eqn_def_find(struct eqn_node
*);
292 static void eqn_delim(struct eqn_node
*);
293 static enum eqn_tok
eqn_next(struct eqn_node
*, enum parse_mode
);
294 static void eqn_undef(struct eqn_node
*);
298 eqn_alloc(struct mparse
*parse
)
302 ep
= mandoc_calloc(1, sizeof(*ep
));
304 ep
->gsize
= EQN_DEFSIZE
;
309 eqn_reset(struct eqn_node
*ep
)
312 ep
->data
= ep
->start
= ep
->end
= NULL
;
313 ep
->sz
= ep
->toksz
= 0;
317 eqn_read(struct eqn_node
*ep
, const char *p
)
321 if (ep
->data
== NULL
) {
323 ep
->data
= mandoc_strdup(p
);
325 ep
->sz
= mandoc_asprintf(&cp
, "%s %s", ep
->data
, p
);
333 * Find the key "key" of the give size within our eqn-defined values.
335 static struct eqn_def
*
336 eqn_def_find(struct eqn_node
*ep
)
340 for (i
= 0; i
< (int)ep
->defsz
; i
++)
341 if (ep
->defs
[i
].keysz
&& STRNEQ(ep
->defs
[i
].key
,
342 ep
->defs
[i
].keysz
, ep
->start
, ep
->toksz
))
349 * Parse a token from the input text. The modes are:
350 * MODE_QUOTED: Use *ep->start as the delimiter; the token ends
351 * before its next occurence. Do not interpret the token in any
352 * way and return EQN_TOK_QUOTED. All other modes behave like
353 * MODE_QUOTED when *ep->start is '"'.
354 * MODE_NOSUB: If *ep->start is a curly brace, the token ends after it;
355 * otherwise, it ends before the next whitespace or brace.
356 * Do not interpret the token and return EQN_TOK__MAX.
357 * MODE_SUB: Like MODE_NOSUB, but try to interpret the token as an
358 * alias created with define. If it is an alias, replace it with
359 * its string value and reparse.
360 * MODE_TOK: Like MODE_SUB, but also check the token against the list
361 * of tokens, and if there is a match, return that token. Otherwise,
362 * if the token matches a symbol, return EQN_TOK_SYM; if it matches
363 * a function name, EQN_TOK_FUNC, or else EQN_TOK__MAX. Except for
364 * a token match, *ep->start is set to an allocated string that the
365 * caller is expected to free.
366 * All modes skip whitespace following the end of the token.
369 eqn_next(struct eqn_node
*ep
, enum parse_mode mode
)
371 static int last_len
, lim
;
379 * Reset the recursion counter after advancing
380 * beyond the end of the previous substitution.
382 if (ep
->end
- ep
->data
>= last_len
)
386 quoted
= mode
== MODE_QUOTED
;
388 switch (*ep
->start
) {
399 ep
->end
= strchr(ep
->start
+ 1, *ep
->start
);
400 ep
->start
++; /* Skip opening quote. */
401 if (ep
->end
== NULL
) {
402 mandoc_msg(MANDOCERR_ARG_QUOTE
, ep
->parse
,
403 ep
->node
->line
, ep
->node
->pos
, NULL
);
404 ep
->end
= strchr(ep
->start
, '\0');
407 ep
->end
= ep
->start
+ 1;
408 if (*ep
->start
!= '{' && *ep
->start
!= '}')
409 ep
->end
+= strcspn(ep
->end
, " ^~\"{}\t");
411 ep
->toksz
= ep
->end
- ep
->start
;
412 if (quoted
&& *ep
->end
!= '\0')
413 ep
->end
++; /* Skip closing quote. */
414 while (*ep
->end
!= '\0' && strchr(" \t^~", *ep
->end
) != NULL
)
416 if (quoted
) /* Cannot return, may have to strndup. */
418 if (mode
== MODE_NOSUB
)
420 if ((def
= eqn_def_find(ep
)) == NULL
)
422 if (++lim
> EQN_NEST_MAX
) {
423 mandoc_msg(MANDOCERR_ROFFLOOP
, ep
->parse
,
424 ep
->node
->line
, ep
->node
->pos
, NULL
);
428 /* Replace a defined name with its string value. */
429 if ((diff
= def
->valsz
- ep
->toksz
) > 0) {
430 start
= ep
->start
- ep
->data
;
432 ep
->data
= mandoc_realloc(ep
->data
, ep
->sz
+ 1);
433 ep
->start
= ep
->data
+ start
;
436 memmove(ep
->start
+ def
->valsz
, ep
->start
+ ep
->toksz
,
437 strlen(ep
->start
+ ep
->toksz
) + 1);
438 memcpy(ep
->start
, def
->val
, def
->valsz
);
439 last_len
= ep
->start
- ep
->data
+ def
->valsz
;
441 if (mode
!= MODE_TOK
)
442 return quoted
? EQN_TOK_QUOTED
: EQN_TOK__MAX
;
444 ep
->start
= mandoc_strndup(ep
->start
, ep
->toksz
);
445 return EQN_TOK_QUOTED
;
447 for (tok
= 0; tok
< EQN_TOK__MAX
; tok
++)
448 if (STRNEQ(ep
->start
, ep
->toksz
,
449 eqn_toks
[tok
], strlen(eqn_toks
[tok
])))
452 for (i
= 0; i
< EQNSYM__MAX
; i
++) {
453 if (STRNEQ(ep
->start
, ep
->toksz
,
454 eqnsyms
[i
].str
, strlen(eqnsyms
[i
].str
))) {
455 mandoc_asprintf(&ep
->start
,
456 "\\[%s]", eqnsyms
[i
].sym
);
460 ep
->start
= mandoc_strndup(ep
->start
, ep
->toksz
);
461 for (i
= 0; i
< (int)(sizeof(eqn_func
)/sizeof(*eqn_func
)); i
++)
462 if (STRNEQ(ep
->start
, ep
->toksz
,
463 eqn_func
[i
], strlen(eqn_func
[i
])))
469 eqn_box_free(struct eqn_box
*bp
)
473 eqn_box_free(bp
->first
);
475 eqn_box_free(bp
->next
);
486 * Allocate a box as the last child of the parent node.
488 static struct eqn_box
*
489 eqn_box_alloc(struct eqn_node
*ep
, struct eqn_box
*parent
)
493 bp
= mandoc_calloc(1, sizeof(struct eqn_box
));
496 bp
->expectargs
= UINT_MAX
;
497 bp
->font
= bp
->parent
->font
;
498 bp
->size
= ep
->gsize
;
500 if (NULL
!= parent
->first
) {
501 parent
->last
->next
= bp
;
502 bp
->prev
= parent
->last
;
511 * Reparent the current last node (of the current parent) under a new
512 * EQN_SUBEXPR as the first element.
513 * Then return the new parent.
514 * The new EQN_SUBEXPR will have a two-child limit.
516 static struct eqn_box
*
517 eqn_box_makebinary(struct eqn_node
*ep
, struct eqn_box
*parent
)
519 struct eqn_box
*b
, *newb
;
521 assert(NULL
!= parent
->last
);
523 if (parent
->last
== parent
->first
)
524 parent
->first
= NULL
;
526 parent
->last
= b
->prev
;
528 newb
= eqn_box_alloc(ep
, parent
);
529 newb
->type
= EQN_SUBEXPR
;
530 newb
->expectargs
= 2;
532 newb
->first
= newb
->last
= b
;
533 newb
->first
->next
= NULL
;
539 * Parse the "delim" control statement.
542 eqn_delim(struct eqn_node
*ep
)
544 if (ep
->end
[0] == '\0' || ep
->end
[1] == '\0') {
545 mandoc_msg(MANDOCERR_REQ_EMPTY
, ep
->parse
,
546 ep
->node
->line
, ep
->node
->pos
, "delim");
547 if (ep
->end
[0] != '\0')
549 } else if (strncmp(ep
->end
, "off", 3) == 0) {
552 } else if (strncmp(ep
->end
, "on", 2) == 0) {
553 if (ep
->odelim
&& ep
->cdelim
)
557 ep
->odelim
= *ep
->end
++;
558 ep
->cdelim
= *ep
->end
++;
564 * Undefine a previously-defined string.
567 eqn_undef(struct eqn_node
*ep
)
571 if (eqn_next(ep
, MODE_NOSUB
) == EQN_TOK_EOF
) {
572 mandoc_msg(MANDOCERR_REQ_EMPTY
, ep
->parse
,
573 ep
->node
->line
, ep
->node
->pos
, "undef");
576 if ((def
= eqn_def_find(ep
)) == NULL
)
580 def
->key
= def
->val
= NULL
;
581 def
->keysz
= def
->valsz
= 0;
585 eqn_def(struct eqn_node
*ep
)
590 if (eqn_next(ep
, MODE_NOSUB
) == EQN_TOK_EOF
) {
591 mandoc_msg(MANDOCERR_REQ_EMPTY
, ep
->parse
,
592 ep
->node
->line
, ep
->node
->pos
, "define");
597 * Search for a key that already exists.
598 * Create a new key if none is found.
600 if ((def
= eqn_def_find(ep
)) == NULL
) {
601 /* Find holes in string array. */
602 for (i
= 0; i
< (int)ep
->defsz
; i
++)
603 if (0 == ep
->defs
[i
].keysz
)
606 if (i
== (int)ep
->defsz
) {
608 ep
->defs
= mandoc_reallocarray(ep
->defs
,
609 ep
->defsz
, sizeof(struct eqn_def
));
610 ep
->defs
[i
].key
= ep
->defs
[i
].val
= NULL
;
615 def
->key
= mandoc_strndup(ep
->start
, ep
->toksz
);
616 def
->keysz
= ep
->toksz
;
619 if (eqn_next(ep
, MODE_QUOTED
) == EQN_TOK_EOF
) {
620 mandoc_vmsg(MANDOCERR_REQ_EMPTY
, ep
->parse
,
621 ep
->node
->line
, ep
->node
->pos
, "define %s", def
->key
);
624 def
->key
= def
->val
= NULL
;
625 def
->keysz
= def
->valsz
= 0;
629 def
->val
= mandoc_strndup(ep
->start
, ep
->toksz
);
630 def
->valsz
= ep
->toksz
;
634 eqn_parse(struct eqn_node
*ep
)
636 struct eqn_box
*cur
, *nbox
, *parent
, *split
;
637 const char *cp
, *cpn
;
640 enum { CCL_LET
, CCL_DIG
, CCL_PUN
} ccl
, ccln
;
643 parent
= ep
->node
->eqn
;
644 assert(parent
!= NULL
);
648 * Do not add it to the high-level syntax tree.
651 if (ep
->data
== NULL
)
654 ep
->start
= ep
->end
= ep
->data
+ strspn(ep
->data
, " ^~");
657 tok
= eqn_next(ep
, MODE_TOK
);
662 case EQN_TOK_NDEFINE
:
666 case EQN_TOK_TDEFINE
:
667 if (eqn_next(ep
, MODE_NOSUB
) == EQN_TOK_EOF
||
668 eqn_next(ep
, MODE_QUOTED
) == EQN_TOK_EOF
)
669 mandoc_msg(MANDOCERR_REQ_EMPTY
, ep
->parse
,
670 ep
->node
->line
, ep
->node
->pos
, "tdefine");
676 if (eqn_next(ep
, MODE_SUB
) == EQN_TOK_EOF
)
677 mandoc_msg(MANDOCERR_REQ_EMPTY
, ep
->parse
,
678 ep
->node
->line
, ep
->node
->pos
, eqn_toks
[tok
]);
692 if (parent
->last
== NULL
) {
693 mandoc_msg(MANDOCERR_EQN_NOBOX
, ep
->parse
,
694 ep
->node
->line
, ep
->node
->pos
, eqn_toks
[tok
]);
695 cur
= eqn_box_alloc(ep
, parent
);
696 cur
->type
= EQN_TEXT
;
697 cur
->text
= mandoc_strdup("");
699 parent
= eqn_box_makebinary(ep
, parent
);
700 parent
->type
= EQN_LIST
;
701 parent
->expectargs
= 1;
702 parent
->font
= EQNFONT_ROMAN
;
705 parent
->top
= mandoc_strdup("\\[ad]");
708 parent
->top
= mandoc_strdup("\\[->]");
711 parent
->top
= mandoc_strdup("\\[<>]");
714 parent
->top
= mandoc_strdup("\\[a~]");
717 parent
->bottom
= mandoc_strdup("\\[ul]");
720 parent
->top
= mandoc_strdup("\\[rn]");
723 parent
->top
= mandoc_strdup("\\[a.]");
726 parent
->top
= mandoc_strdup("\\[ha]");
731 parent
= parent
->parent
;
737 if (eqn_next(ep
, MODE_SUB
) == EQN_TOK_EOF
)
738 mandoc_msg(MANDOCERR_REQ_EMPTY
, ep
->parse
,
739 ep
->node
->line
, ep
->node
->pos
, eqn_toks
[tok
]);
745 while (parent
->args
== parent
->expectargs
)
746 parent
= parent
->parent
;
748 * These values apply to the next word or sequence of
749 * words; thus, we mark that we'll have a child with
750 * exactly one of those.
752 parent
= eqn_box_alloc(ep
, parent
);
753 parent
->type
= EQN_LIST
;
754 parent
->expectargs
= 1;
757 parent
->font
= EQNFONT_FAT
;
760 parent
->font
= EQNFONT_ROMAN
;
763 parent
->font
= EQNFONT_ITALIC
;
766 parent
->font
= EQNFONT_BOLD
;
774 /* Accept two values: integral size and a single. */
775 if (eqn_next(ep
, MODE_SUB
) == EQN_TOK_EOF
) {
776 mandoc_msg(MANDOCERR_REQ_EMPTY
, ep
->parse
,
777 ep
->node
->line
, ep
->node
->pos
, eqn_toks
[tok
]);
780 size
= mandoc_strntoi(ep
->start
, ep
->toksz
, 10);
782 mandoc_msg(MANDOCERR_IT_NONUM
, ep
->parse
,
783 ep
->node
->line
, ep
->node
->pos
, eqn_toks
[tok
]);
786 if (EQN_TOK_GSIZE
== tok
) {
790 while (parent
->args
== parent
->expectargs
)
791 parent
= parent
->parent
;
792 parent
= eqn_box_alloc(ep
, parent
);
793 parent
->type
= EQN_LIST
;
794 parent
->expectargs
= 1;
802 * We have a left-right-associative expression.
803 * Repivot under a positional node, open a child scope
804 * and keep on reading.
806 if (parent
->last
== NULL
) {
807 mandoc_msg(MANDOCERR_EQN_NOBOX
, ep
->parse
,
808 ep
->node
->line
, ep
->node
->pos
, eqn_toks
[tok
]);
809 cur
= eqn_box_alloc(ep
, parent
);
810 cur
->type
= EQN_TEXT
;
811 cur
->text
= mandoc_strdup("");
813 while (parent
->expectargs
== 1 && parent
->args
== 1)
814 parent
= parent
->parent
;
815 if (tok
== EQN_TOK_FROM
|| tok
== EQN_TOK_TO
) {
816 for (cur
= parent
; cur
!= NULL
; cur
= cur
->parent
)
817 if (cur
->pos
== EQNPOS_SUB
||
818 cur
->pos
== EQNPOS_SUP
||
819 cur
->pos
== EQNPOS_SUBSUP
||
820 cur
->pos
== EQNPOS_SQRT
||
821 cur
->pos
== EQNPOS_OVER
)
824 parent
= cur
->parent
;
826 if (tok
== EQN_TOK_SUP
&& parent
->pos
== EQNPOS_SUB
) {
827 parent
->expectargs
= 3;
828 parent
->pos
= EQNPOS_SUBSUP
;
831 if (tok
== EQN_TOK_TO
&& parent
->pos
== EQNPOS_FROM
) {
832 parent
->expectargs
= 3;
833 parent
->pos
= EQNPOS_FROMTO
;
836 parent
= eqn_box_makebinary(ep
, parent
);
839 parent
->pos
= EQNPOS_FROM
;
842 parent
->pos
= EQNPOS_TO
;
845 parent
->pos
= EQNPOS_SUP
;
848 parent
->pos
= EQNPOS_SUB
;
855 while (parent
->args
== parent
->expectargs
)
856 parent
= parent
->parent
;
858 * Accept a left-right-associative set of arguments just
859 * like sub and sup and friends but without rebalancing
862 parent
= eqn_box_alloc(ep
, parent
);
863 parent
->type
= EQN_SUBEXPR
;
864 parent
->pos
= EQNPOS_SQRT
;
865 parent
->expectargs
= 1;
869 * We have a right-left-associative fraction.
870 * Close out anything that's currently open, then
871 * rebalance and continue reading.
873 if (parent
->last
== NULL
) {
874 mandoc_msg(MANDOCERR_EQN_NOBOX
, ep
->parse
,
875 ep
->node
->line
, ep
->node
->pos
, eqn_toks
[tok
]);
876 cur
= eqn_box_alloc(ep
, parent
);
877 cur
->type
= EQN_TEXT
;
878 cur
->text
= mandoc_strdup("");
880 while (parent
->args
== parent
->expectargs
)
881 parent
= parent
->parent
;
882 while (EQN_SUBEXPR
== parent
->type
)
883 parent
= parent
->parent
;
884 parent
= eqn_box_makebinary(ep
, parent
);
885 parent
->pos
= EQNPOS_OVER
;
888 case EQN_TOK_BRACE_CLOSE
:
890 * Close out the existing brace.
891 * FIXME: this is a shitty sentinel: we should really
892 * have a native EQN_BRACE type or whatnot.
894 for (cur
= parent
; cur
!= NULL
; cur
= cur
->parent
)
895 if (cur
->type
== EQN_LIST
&&
896 cur
->expectargs
> 1 &&
897 (tok
== EQN_TOK_BRACE_CLOSE
||
901 mandoc_msg(MANDOCERR_BLK_NOTOPEN
, ep
->parse
,
902 ep
->node
->line
, ep
->node
->pos
, eqn_toks
[tok
]);
906 if (EQN_TOK_RIGHT
== tok
) {
907 if (eqn_next(ep
, MODE_SUB
) == EQN_TOK_EOF
) {
908 mandoc_msg(MANDOCERR_REQ_EMPTY
,
909 ep
->parse
, ep
->node
->line
,
910 ep
->node
->pos
, eqn_toks
[tok
]);
913 /* Handling depends on right/left. */
914 if (STRNEQ(ep
->start
, ep
->toksz
, "ceiling", 7))
915 parent
->right
= mandoc_strdup("\\[rc]");
916 else if (STRNEQ(ep
->start
, ep
->toksz
, "floor", 5))
917 parent
->right
= mandoc_strdup("\\[rf]");
920 mandoc_strndup(ep
->start
, ep
->toksz
);
922 parent
= parent
->parent
;
923 if (tok
== EQN_TOK_BRACE_CLOSE
&&
924 (parent
->type
== EQN_PILE
||
925 parent
->type
== EQN_MATRIX
))
926 parent
= parent
->parent
;
927 /* Close out any "singleton" lists. */
928 while (parent
->type
== EQN_LIST
&&
929 parent
->expectargs
== 1 &&
931 parent
= parent
->parent
;
933 case EQN_TOK_BRACE_OPEN
:
936 * If we already have something in the stack and we're
937 * in an expression, then rewind til we're not any more
938 * (just like with the text node).
940 while (parent
->args
== parent
->expectargs
)
941 parent
= parent
->parent
;
942 if (EQN_TOK_LEFT
== tok
&&
943 eqn_next(ep
, MODE_SUB
) == EQN_TOK_EOF
) {
944 mandoc_msg(MANDOCERR_REQ_EMPTY
, ep
->parse
,
945 ep
->node
->line
, ep
->node
->pos
, eqn_toks
[tok
]);
948 parent
= eqn_box_alloc(ep
, parent
);
949 parent
->type
= EQN_LIST
;
950 if (EQN_TOK_LEFT
== tok
) {
951 if (STRNEQ(ep
->start
, ep
->toksz
, "ceiling", 7))
952 parent
->left
= mandoc_strdup("\\[lc]");
953 else if (STRNEQ(ep
->start
, ep
->toksz
, "floor", 5))
954 parent
->left
= mandoc_strdup("\\[lf]");
957 mandoc_strndup(ep
->start
, ep
->toksz
);
967 while (parent
->args
== parent
->expectargs
)
968 parent
= parent
->parent
;
969 parent
= eqn_box_alloc(ep
, parent
);
970 parent
->type
= EQN_PILE
;
971 parent
->expectargs
= 1;
974 for (cur
= parent
; cur
!= NULL
; cur
= cur
->parent
)
975 if (cur
->type
== EQN_PILE
)
978 mandoc_msg(MANDOCERR_IT_STRAY
, ep
->parse
,
979 ep
->node
->line
, ep
->node
->pos
, eqn_toks
[tok
]);
982 parent
= eqn_box_alloc(ep
, cur
);
983 parent
->type
= EQN_LIST
;
986 while (parent
->args
== parent
->expectargs
)
987 parent
= parent
->parent
;
988 parent
= eqn_box_alloc(ep
, parent
);
989 parent
->type
= EQN_MATRIX
;
990 parent
->expectargs
= 1;
1001 * If we already have something in the stack and we're
1002 * in an expression, then rewind til we're not any more.
1004 while (parent
->args
== parent
->expectargs
)
1005 parent
= parent
->parent
;
1006 cur
= eqn_box_alloc(ep
, parent
);
1007 cur
->type
= EQN_TEXT
;
1011 cur
->font
= EQNFONT_ROMAN
;
1013 case EQN_TOK_QUOTED
:
1014 if (cur
->font
== EQNFONT_NONE
)
1015 cur
->font
= EQNFONT_ITALIC
;
1020 if (cur
->font
!= EQNFONT_NONE
|| *p
== '\0')
1026 /* Advance to next character. */
1029 ccln
= isalpha((unsigned char)*cpn
) ? CCL_LET
:
1030 isdigit((unsigned char)*cpn
) ||
1031 (*cpn
== '.' && (ccl
== CCL_DIG
||
1032 isdigit((unsigned char)cpn
[1]))) ?
1034 /* No boundary before first character. */
1037 cur
->font
= ccl
== CCL_LET
?
1038 EQNFONT_ITALIC
: EQNFONT_ROMAN
;
1040 mandoc_escape(&cpn
, NULL
, NULL
);
1041 /* No boundary after last character. */
1044 if (ccln
== ccl
&& *cp
!= ',' && *cpn
!= ',')
1046 /* Boundary found, split the text. */
1047 if (parent
->args
== parent
->expectargs
) {
1048 /* Remove the text from the tree. */
1049 if (cur
->prev
== NULL
)
1050 parent
->first
= cur
->next
;
1052 cur
->prev
->next
= NULL
;
1053 parent
->last
= cur
->prev
;
1055 /* Set up a list instead. */
1056 split
= eqn_box_alloc(ep
, parent
);
1057 split
->type
= EQN_LIST
;
1058 /* Insert the word into the list. */
1059 split
->first
= split
->last
= cur
;
1060 cur
->parent
= split
;
1064 /* Append a new text box. */
1065 nbox
= eqn_box_alloc(ep
, parent
);
1066 nbox
->type
= EQN_TEXT
;
1067 nbox
->text
= mandoc_strdup(cpn
);
1068 /* Truncate the old box. */
1069 p
= mandoc_strndup(cur
->text
,
1073 /* Setup to process the new box. */
1080 parent
= split
->parent
;
1091 eqn_free(struct eqn_node
*p
)
1095 for (i
= 0; i
< (int)p
->defsz
; i
++) {
1096 free(p
->defs
[i
].key
);
1097 free(p
->defs
[i
].val
);