Annotation of mandoc/validate.c, Revision 1.56
1.56 ! kristaps 1: /* $Id: validate.c,v 1.55 2009/02/24 12:20:52 kristaps Exp $ */
1.1 kristaps 2: /*
3: * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the
7: * above copyright notice and this permission notice appear in all
8: * copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11: * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12: * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13: * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14: * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15: * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16: * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17: * PERFORMANCE OF THIS SOFTWARE.
18: */
19: #include <assert.h>
1.56 ! kristaps 20: #include <ctype.h>
1.8 kristaps 21: #include <stdlib.h>
1.1 kristaps 22:
23: #include "private.h"
24:
1.44 kristaps 25: /*
26: * Pre- and post-validate macros as they're parsed. Pre-validation
27: * occurs when the macro has been detected and its arguments parsed.
28: * Post-validation occurs when all child macros have also been parsed.
29: * In the ELEMENT case, this is simply the parameters of the macro; in
30: * the BLOCK case, this is the HEAD, BODY, TAIL and so on.
31: */
32:
1.55 kristaps 33: #define PRE_ARGS struct mdoc *mdoc, const struct mdoc_node *n
34: #define POST_ARGS struct mdoc *mdoc
35:
36: typedef int (*v_pre)(PRE_ARGS);
37: typedef int (*v_post)(POST_ARGS);
1.14 kristaps 38:
1.36 kristaps 39: /* FIXME: some sections should only occur in specific msecs. */
40: /* FIXME: ignoring Pp. */
41: /* FIXME: math symbols. */
1.51 kristaps 42: /* FIXME: .Fd only in synopsis section. */
1.36 kristaps 43:
1.14 kristaps 44: struct valids {
1.24 kristaps 45: v_pre *pre;
1.17 kristaps 46: v_post *post;
1.14 kristaps 47: };
1.1 kristaps 48:
1.37 kristaps 49: /* Utility checks. */
50:
1.55 kristaps 51: static int check_parent(PRE_ARGS, int, enum mdoc_type);
52: static int check_msec(PRE_ARGS, int, enum mdoc_msec *);
53: static int check_stdarg(PRE_ARGS);
54:
55: static int check_text(struct mdoc *,
56: size_t, size_t, const char *);
57:
1.51 kristaps 58: static int err_child_lt(struct mdoc *, const char *, int);
59: static int err_child_gt(struct mdoc *, const char *, int);
60: static int warn_child_gt(struct mdoc *, const char *, int);
61: static int err_child_eq(struct mdoc *, const char *, int);
62: static int warn_child_eq(struct mdoc *, const char *, int);
63:
64: /* Utility auxiliaries. */
65:
66: static inline int count_child(struct mdoc *);
67: static inline int warn_count(struct mdoc *, const char *,
68: int, const char *, int);
69: static inline int err_count(struct mdoc *, const char *,
70: int, const char *, int);
1.11 kristaps 71:
1.37 kristaps 72: /* Specific pre-child-parse routines. */
73:
1.55 kristaps 74: static int pre_display(PRE_ARGS);
75: static int pre_sh(PRE_ARGS);
76: static int pre_ss(PRE_ARGS);
77: static int pre_bd(PRE_ARGS);
78: static int pre_bl(PRE_ARGS);
79: static int pre_it(PRE_ARGS);
80: static int pre_cd(PRE_ARGS);
81: static int pre_er(PRE_ARGS);
82: static int pre_ex(PRE_ARGS);
83: static int pre_rv(PRE_ARGS);
84: static int pre_an(PRE_ARGS);
85: static int pre_st(PRE_ARGS);
86: static int pre_prologue(PRE_ARGS);
87: static int pre_prologue(PRE_ARGS);
88: static int pre_prologue(PRE_ARGS);
1.24 kristaps 89:
1.37 kristaps 90: /* Specific post-child-parse routines. */
91:
1.55 kristaps 92: static int herr_ge1(POST_ARGS);
93: static int herr_le1(POST_ARGS);
94: static int herr_eq0(POST_ARGS);
95: static int eerr_eq0(POST_ARGS);
96: static int eerr_le1(POST_ARGS);
97: static int eerr_le2(POST_ARGS);
98: static int eerr_eq1(POST_ARGS);
99: static int eerr_ge1(POST_ARGS);
100: static int ewarn_eq0(POST_ARGS);
101: static int ewarn_eq1(POST_ARGS);
102: static int bwarn_ge1(POST_ARGS);
103: static int ewarn_ge1(POST_ARGS);
104: static int ebool(POST_ARGS);
105:
106: static int post_sh(POST_ARGS);
107: static int post_sh_body(POST_ARGS);
108: static int post_sh_head(POST_ARGS);
109: static int post_bl(POST_ARGS);
110: static int post_it(POST_ARGS);
111: static int post_ex(POST_ARGS);
112: static int post_an(POST_ARGS);
113: static int post_at(POST_ARGS);
114: static int post_xr(POST_ARGS);
115: static int post_nm(POST_ARGS);
116: static int post_bf(POST_ARGS);
117: static int post_root(POST_ARGS);
1.37 kristaps 118:
119: /* Collections of pre-child-parse routines. */
1.17 kristaps 120:
1.24 kristaps 121: static v_pre pres_prologue[] = { pre_prologue, NULL };
122: static v_pre pres_d1[] = { pre_display, NULL };
123: static v_pre pres_bd[] = { pre_display, pre_bd, NULL };
124: static v_pre pres_bl[] = { pre_bl, NULL };
1.25 kristaps 125: static v_pre pres_it[] = { pre_it, NULL };
1.33 kristaps 126: static v_pre pres_ss[] = { pre_ss, NULL };
127: static v_pre pres_sh[] = { pre_sh, NULL };
128: static v_pre pres_cd[] = { pre_cd, NULL };
129: static v_pre pres_er[] = { pre_er, NULL };
130: static v_pre pres_ex[] = { pre_ex, NULL };
1.36 kristaps 131: static v_pre pres_rv[] = { pre_rv, NULL };
1.35 kristaps 132: static v_pre pres_an[] = { pre_an, NULL };
1.36 kristaps 133: static v_pre pres_st[] = { pre_st, NULL };
1.24 kristaps 134:
1.37 kristaps 135: /* Collections of post-child-parse routines. */
136:
137: static v_post posts_bool[] = { eerr_eq1, ebool, NULL };
138: static v_post posts_bd[] = { herr_eq0, bwarn_ge1, NULL };
139: static v_post posts_text[] = { eerr_ge1, NULL };
140: static v_post posts_wtext[] = { ewarn_ge1, NULL };
141: static v_post posts_notext[] = { eerr_eq0, NULL };
1.43 kristaps 142: static v_post posts_wline[] = { bwarn_ge1, herr_eq0, NULL };
1.37 kristaps 143: static v_post posts_sh[] = { herr_ge1, bwarn_ge1, post_sh, NULL };
144: static v_post posts_bl[] = { herr_eq0, bwarn_ge1, post_bl, NULL };
1.25 kristaps 145: static v_post posts_it[] = { post_it, NULL };
1.39 kristaps 146: static v_post posts_in[] = { ewarn_eq1, NULL };
1.37 kristaps 147: static v_post posts_ss[] = { herr_ge1, NULL };
1.52 kristaps 148: static v_post posts_pf[] = { eerr_eq1, NULL };
1.37 kristaps 149: static v_post posts_pp[] = { ewarn_eq0, NULL };
150: static v_post posts_ex[] = { eerr_le1, post_ex, NULL };
1.35 kristaps 151: static v_post posts_an[] = { post_an, NULL };
1.37 kristaps 152: static v_post posts_at[] = { post_at, NULL };
153: static v_post posts_xr[] = { eerr_ge1, eerr_le2, post_xr, NULL };
154: static v_post posts_nm[] = { post_nm, NULL };
1.41 kristaps 155: static v_post posts_bf[] = { herr_le1, post_bf, NULL };
1.45 kristaps 156: static v_post posts_rs[] = { herr_eq0, bwarn_ge1, NULL };
157: static v_post posts_fo[] = { bwarn_ge1, NULL };
158: static v_post posts_bk[] = { herr_eq0, bwarn_ge1, NULL };
1.9 kristaps 159:
1.37 kristaps 160: /* Per-macro pre- and post-child-check routine collections. */
1.12 kristaps 161:
1.9 kristaps 162: const struct valids mdoc_valids[MDOC_MAX] = {
1.51 kristaps 163: { NULL, NULL }, /* \" */
164: { pres_prologue, posts_text }, /* Dd */
165: { pres_prologue, NULL }, /* Dt */
166: { pres_prologue, NULL }, /* Os */
167: { pres_sh, posts_sh }, /* Sh */
168: { pres_ss, posts_ss }, /* Ss */
169: { NULL, posts_pp }, /* Pp */
170: { pres_d1, posts_wline }, /* D1 */
171: { pres_d1, posts_wline }, /* Dl */
172: { pres_bd, posts_bd }, /* Bd */
173: { NULL, NULL }, /* Ed */
174: { pres_bl, posts_bl }, /* Bl */
175: { NULL, NULL }, /* El */
176: { pres_it, posts_it }, /* It */
177: { NULL, posts_text }, /* Ad */
178: { pres_an, posts_an }, /* An */
179: { NULL, NULL }, /* Ar */
180: { pres_cd, posts_text }, /* Cd */
181: { NULL, NULL }, /* Cm */
182: { NULL, posts_text }, /* Dv */
183: { pres_er, posts_text }, /* Er */
184: { NULL, posts_text }, /* Ev */
185: { pres_ex, posts_ex }, /* Ex */
186: { NULL, posts_text }, /* Fa */
187: { NULL, posts_wtext }, /* Fd */
188: { NULL, NULL }, /* Fl */
189: { NULL, posts_text }, /* Fn */
190: { NULL, posts_wtext }, /* Ft */
191: { NULL, posts_text }, /* Ic */
192: { NULL, posts_in }, /* In */
193: { NULL, posts_text }, /* Li */
194: { NULL, posts_wtext }, /* Nd */
195: { NULL, posts_nm }, /* Nm */
196: { NULL, posts_wline }, /* Op */
197: { NULL, NULL }, /* Ot */
198: { NULL, NULL }, /* Pa */
199: { pres_rv, posts_notext }, /* Rv */
200: { pres_st, posts_notext }, /* St */
201: { NULL, posts_text }, /* Va */
202: { NULL, posts_text }, /* Vt */
203: { NULL, posts_xr }, /* Xr */
204: { NULL, posts_text }, /* %A */
205: { NULL, posts_text }, /* %B */
206: { NULL, posts_text }, /* %D */
207: { NULL, posts_text }, /* %I */
208: { NULL, posts_text }, /* %J */
209: { NULL, posts_text }, /* %N */
210: { NULL, posts_text }, /* %O */
211: { NULL, posts_text }, /* %P */
212: { NULL, posts_text }, /* %R */
213: { NULL, posts_text }, /* %T */
214: { NULL, posts_text }, /* %V */
215: { NULL, NULL }, /* Ac */
216: { NULL, NULL }, /* Ao */
217: { NULL, posts_wline }, /* Aq */
218: { NULL, posts_at }, /* At */
219: { NULL, NULL }, /* Bc */
220: { NULL, posts_bf }, /* Bf */
221: { NULL, NULL }, /* Bo */
222: { NULL, posts_wline }, /* Bq */
223: { NULL, NULL }, /* Bsx */
224: { NULL, NULL }, /* Bx */
225: { NULL, posts_bool }, /* Db */
226: { NULL, NULL }, /* Dc */
227: { NULL, NULL }, /* Do */
228: { NULL, posts_wline }, /* Dq */
229: { NULL, NULL }, /* Ec */
230: { NULL, NULL }, /* Ef */
231: { NULL, posts_text }, /* Em */
232: { NULL, NULL }, /* Eo */
233: { NULL, NULL }, /* Fx */
234: { NULL, posts_text }, /* Ms */
235: { NULL, posts_notext }, /* No */
236: { NULL, posts_notext }, /* Ns */
237: { NULL, NULL }, /* Nx */
238: { NULL, NULL }, /* Ox */
239: { NULL, NULL }, /* Pc */
1.52 kristaps 240: { NULL, posts_pf }, /* Pf */
1.51 kristaps 241: { NULL, NULL }, /* Po */
242: { NULL, posts_wline }, /* Pq */
243: { NULL, NULL }, /* Qc */
244: { NULL, posts_wline }, /* Ql */
245: { NULL, NULL }, /* Qo */
246: { NULL, posts_wline }, /* Qq */
247: { NULL, NULL }, /* Re */
248: { NULL, posts_rs }, /* Rs */
249: { NULL, NULL }, /* Sc */
250: { NULL, NULL }, /* So */
251: { NULL, posts_wline }, /* Sq */
252: { NULL, posts_bool }, /* Sm */
253: { NULL, posts_text }, /* Sx */
254: { NULL, posts_text }, /* Sy */
255: { NULL, posts_text }, /* Tn */
256: { NULL, NULL }, /* Ux */
257: { NULL, NULL }, /* Xc */
258: { NULL, NULL }, /* Xo */
259: { NULL, posts_fo }, /* Fo */
260: { NULL, NULL }, /* Fc */
261: { NULL, NULL }, /* Oo */
262: { NULL, NULL }, /* Oc */
263: { NULL, posts_bk }, /* Bk */
264: { NULL, NULL }, /* Ek */
265: { NULL, posts_notext }, /* Bt */
266: { NULL, NULL }, /* Hf */
267: { NULL, NULL }, /* Fr */
268: { NULL, posts_notext }, /* Ud */
1.9 kristaps 269: };
1.6 kristaps 270:
271:
1.51 kristaps 272: static inline int
273: warn_count(struct mdoc *m, const char *k,
274: int want, const char *v, int has)
275: {
276:
1.55 kristaps 277: return(mdoc_warn(m, WARN_SYNTAX,
278: "suggests %s %d %s (has %d)",
279: v, want, k, has));
1.51 kristaps 280: }
281:
282:
283: static inline int
284: err_count(struct mdoc *m, const char *k,
285: int want, const char *v, int has)
286: {
287:
288: return(mdoc_err(m, "requires %s %d %s (has %d)",
289: v, want, k, has));
290: }
291:
292:
293: static inline int
294: count_child(struct mdoc *mdoc)
1.36 kristaps 295: {
1.51 kristaps 296: int i;
1.36 kristaps 297: struct mdoc_node *n;
298:
299: for (i = 0, n = mdoc->last->child; n; n = n->next, i++)
300: /* Do nothing */ ;
1.53 kristaps 301:
1.36 kristaps 302: return(i);
303: }
304:
305:
1.53 kristaps 306: /*
307: * Build these up with macros because they're basically the same check
308: * for different inequalities. Yes, this could be done with functions,
309: * but this is reasonable for now.
310: */
1.36 kristaps 311:
1.53 kristaps 312: #define CHECK_CHILD_DEFN(lvl, name, ineq) \
313: static int \
314: lvl##_child_##name(struct mdoc *mdoc, const char *p, int sz) \
315: { \
316: int i; \
317: if ((i = count_child(mdoc)) ineq sz) \
318: return(1); \
319: return(lvl##_count(mdoc, #ineq, sz, p, i)); \
320: }
321:
322: #define CHECK_BODY_DEFN(name, lvl, func, num) \
323: static int \
1.55 kristaps 324: b##lvl##_##name(POST_ARGS) \
1.53 kristaps 325: { \
326: if (MDOC_BODY != mdoc->last->type) \
327: return(1); \
328: return(func(mdoc, "multiline parameters", (num))); \
329: }
330:
331: #define CHECK_ELEM_DEFN(name, lvl, func, num) \
332: static int \
1.55 kristaps 333: e##lvl##_##name(POST_ARGS) \
1.53 kristaps 334: { \
335: assert(MDOC_ELEM == mdoc->last->type); \
336: return(func(mdoc, "line parameters", (num))); \
337: }
338:
339: #define CHECK_HEAD_DEFN(name, lvl, func, num) \
340: static int \
1.55 kristaps 341: h##lvl##_##name(POST_ARGS) \
1.53 kristaps 342: { \
343: if (MDOC_HEAD != mdoc->last->type) \
344: return(1); \
345: return(func(mdoc, "multiline parameters", (num))); \
1.36 kristaps 346: }
347:
348:
1.53 kristaps 349: CHECK_CHILD_DEFN(warn, gt, >) /* warn_child_gt() */
350: CHECK_CHILD_DEFN(err, gt, >) /* err_child_gt() */
351: CHECK_CHILD_DEFN(warn, eq, ==) /* warn_child_eq() */
352: CHECK_CHILD_DEFN(err, eq, ==) /* err_child_eq() */
353: CHECK_CHILD_DEFN(err, lt, <) /* err_child_lt() */
354: CHECK_BODY_DEFN(ge1, warn, warn_child_gt, 0) /* bwarn_ge1() */
355: CHECK_ELEM_DEFN(eq1, warn, warn_child_eq, 1) /* ewarn_eq1() */
356: CHECK_ELEM_DEFN(eq0, warn, warn_child_eq, 0) /* ewarn_eq0() */
357: CHECK_ELEM_DEFN(ge1, warn, warn_child_gt, 0) /* ewarn_gt1() */
358: CHECK_ELEM_DEFN(eq1, err, err_child_eq, 1) /* eerr_eq1() */
359: CHECK_ELEM_DEFN(le2, err, err_child_lt, 3) /* eerr_le2() */
360: CHECK_ELEM_DEFN(le1, err, err_child_lt, 2) /* eerr_le1() */
361: CHECK_ELEM_DEFN(eq0, err, err_child_eq, 0) /* eerr_eq0() */
362: CHECK_ELEM_DEFN(ge1, err, err_child_gt, 0) /* eerr_ge1() */
363: CHECK_HEAD_DEFN(eq0, err, err_child_eq, 0) /* herr_eq0() */
364: CHECK_HEAD_DEFN(le1, err, err_child_lt, 2) /* herr_le1() */
365: CHECK_HEAD_DEFN(ge1, err, err_child_gt, 0) /* herr_ge1() */
1.36 kristaps 366:
367:
368: static int
1.55 kristaps 369: check_stdarg(PRE_ARGS)
1.36 kristaps 370: {
371:
1.55 kristaps 372: if (MDOC_Std == n->data.elem.argv[0].arg &&
373: 1 == n->data.elem.argc)
1.36 kristaps 374: return(1);
1.51 kristaps 375:
1.55 kristaps 376: return(mdoc_nwarn(mdoc, n, WARN_COMPAT,
1.53 kristaps 377: "one argument suggested"));
1.36 kristaps 378: }
379:
380:
381: static int
1.55 kristaps 382: check_msec(PRE_ARGS, int sz, enum mdoc_msec *msecs)
1.33 kristaps 383: {
384: int i;
385:
386: for (i = 0; i < sz; i++)
387: if (msecs[i] == mdoc->meta.msec)
388: return(1);
1.55 kristaps 389: return(mdoc_nwarn(mdoc, n, WARN_COMPAT,
390: "invalid manual section"));
391: }
392:
393:
394: static int
395: check_text(struct mdoc *mdoc, size_t line, size_t pos, const char *p)
396: {
397: size_t c;
398:
399: for ( ; *p; p++) {
1.56 ! kristaps 400: if ( ! isprint(*p) && '\t' != *p)
! 401: return(mdoc_perr(mdoc, line, pos,
! 402: "invalid characters"));
1.55 kristaps 403: if ('\\' != *p)
404: continue;
405: if ((c = mdoc_isescape(p))) {
406: p += (c - 1);
407: continue;
408: }
409: return(mdoc_perr(mdoc, line, pos,
1.56 ! kristaps 410: "invalid escape sequence"));
1.55 kristaps 411: }
412:
413: return(1);
1.14 kristaps 414: }
415:
416:
1.55 kristaps 417:
418:
1.14 kristaps 419: static int
1.55 kristaps 420: check_parent(PRE_ARGS, int tok, enum mdoc_type t)
1.54 kristaps 421: {
422:
423: assert(n->parent);
424: if ((MDOC_ROOT == t || tok == n->parent->tok) &&
425: (t == n->parent->type))
426: return(1);
427:
428: return(mdoc_nerr(mdoc, n, "require parent %s",
429: MDOC_ROOT == t ? "<root>" : mdoc_macronames[tok]));
430: }
431:
432:
433:
434: static int
1.55 kristaps 435: pre_display(PRE_ARGS)
1.23 kristaps 436: {
1.55 kristaps 437: struct mdoc_node *node;
1.23 kristaps 438:
1.53 kristaps 439: /* Display elements (`Bd', `D1'...) cannot be nested. */
440:
1.55 kristaps 441: if (MDOC_BLOCK != n->type)
1.24 kristaps 442: return(1);
443:
1.38 kristaps 444: /* LINTED */
1.55 kristaps 445: for (node = mdoc->last->parent; node; node = node->parent)
446: if (MDOC_BLOCK == node->type)
447: if (MDOC_Bd == node->tok)
1.23 kristaps 448: break;
1.55 kristaps 449: if (NULL == node)
1.23 kristaps 450: return(1);
1.53 kristaps 451:
1.55 kristaps 452: return(mdoc_nerr(mdoc, n, "displays may not be nested"));
1.23 kristaps 453: }
454:
455:
456: static int
1.55 kristaps 457: pre_bl(PRE_ARGS)
1.24 kristaps 458: {
1.53 kristaps 459: int type, err, i;
1.24 kristaps 460: struct mdoc_arg *argv;
1.53 kristaps 461: size_t argc;
1.24 kristaps 462:
1.55 kristaps 463: if (MDOC_BLOCK != n->type)
1.24 kristaps 464: return(1);
465:
1.55 kristaps 466: argc = n->data.block.argc;
1.24 kristaps 467:
1.53 kristaps 468: /* Make sure that only one type of list is specified. */
469:
1.38 kristaps 470: /* LINTED */
1.53 kristaps 471: for (i = 0, type = err = 0; i < (int)argc; i++) {
1.55 kristaps 472: argv = &n->data.block.argv[i];
1.53 kristaps 473:
1.24 kristaps 474: switch (argv->arg) {
475: case (MDOC_Bullet):
476: /* FALLTHROUGH */
477: case (MDOC_Dash):
478: /* FALLTHROUGH */
479: case (MDOC_Enum):
480: /* FALLTHROUGH */
481: case (MDOC_Hyphen):
482: /* FALLTHROUGH */
483: case (MDOC_Item):
484: /* FALLTHROUGH */
485: case (MDOC_Tag):
486: /* FALLTHROUGH */
487: case (MDOC_Diag):
488: /* FALLTHROUGH */
489: case (MDOC_Hang):
490: /* FALLTHROUGH */
491: case (MDOC_Ohang):
492: /* FALLTHROUGH */
493: case (MDOC_Inset):
1.26 kristaps 494: /* FALLTHROUGH */
495: case (MDOC_Column):
1.53 kristaps 496: if (0 == type++)
497: break;
498: return(mdoc_perr(mdoc, argv->line, argv->pos,
499: "multiple types specified"));
1.24 kristaps 500: default:
501: break;
502: }
503: }
1.53 kristaps 504:
505: if (type)
506: return(1);
507: return(mdoc_err(mdoc, "no type specified"));
1.24 kristaps 508: }
509:
510:
511: static int
1.55 kristaps 512: pre_bd(PRE_ARGS)
1.24 kristaps 513: {
1.53 kristaps 514: int type, err, i;
1.24 kristaps 515: struct mdoc_arg *argv;
1.53 kristaps 516: size_t argc;
1.24 kristaps 517:
1.55 kristaps 518: if (MDOC_BLOCK != n->type)
1.24 kristaps 519: return(1);
520:
1.55 kristaps 521: argc = n->data.block.argc;
1.24 kristaps 522:
1.53 kristaps 523: /* Make sure that only one type of display is specified. */
524:
1.38 kristaps 525: /* LINTED */
1.53 kristaps 526: for (i = 0, err = type = 0; ! err && i < (int)argc; i++) {
1.55 kristaps 527: argv = &n->data.block.argv[i];
1.53 kristaps 528:
1.24 kristaps 529: switch (argv->arg) {
530: case (MDOC_Ragged):
531: /* FALLTHROUGH */
532: case (MDOC_Unfilled):
533: /* FALLTHROUGH */
1.29 kristaps 534: case (MDOC_Filled):
535: /* FALLTHROUGH */
1.24 kristaps 536: case (MDOC_Literal):
537: /* FALLTHROUGH */
538: case (MDOC_File):
1.53 kristaps 539: if (0 == type++)
540: break;
541: return(mdoc_perr(mdoc, argv->line, argv->pos,
542: "multiple types specified"));
1.24 kristaps 543: default:
544: break;
545: }
546: }
1.53 kristaps 547:
548: if (type)
549: return(1);
550: return(mdoc_err(mdoc, "no type specified"));
1.24 kristaps 551: }
552:
553:
554: static int
1.55 kristaps 555: pre_ss(PRE_ARGS)
1.33 kristaps 556: {
557:
1.55 kristaps 558: if (MDOC_BLOCK != n->type)
1.33 kristaps 559: return(1);
1.55 kristaps 560: return(check_parent(mdoc, n, MDOC_Sh, MDOC_BODY));
1.33 kristaps 561: }
562:
563:
564: static int
1.55 kristaps 565: pre_sh(PRE_ARGS)
1.33 kristaps 566: {
567:
1.55 kristaps 568: if (MDOC_BLOCK != n->type)
1.33 kristaps 569: return(1);
1.55 kristaps 570: return(check_parent(mdoc, n, -1, MDOC_ROOT));
1.33 kristaps 571: }
572:
573:
574: static int
1.55 kristaps 575: pre_it(PRE_ARGS)
1.53 kristaps 576: {
577:
578: /* TODO: -width attribute must be specified for -tag. */
579: /* TODO: children too big for -width? */
580:
1.55 kristaps 581: if (MDOC_BLOCK != n->type)
1.53 kristaps 582: return(1);
1.55 kristaps 583: return(check_parent(mdoc, n, MDOC_Bl, MDOC_BODY));
1.53 kristaps 584: }
585:
586:
587: static int
1.55 kristaps 588: pre_st(PRE_ARGS)
1.36 kristaps 589: {
590:
1.55 kristaps 591: if (1 == n->data.elem.argc)
1.36 kristaps 592: return(1);
1.55 kristaps 593: return(mdoc_nerr(mdoc, n, "one argument required"));
1.36 kristaps 594: }
595:
596:
597: static int
1.55 kristaps 598: pre_an(PRE_ARGS)
1.35 kristaps 599: {
1.36 kristaps 600:
1.55 kristaps 601: if (1 >= n->data.elem.argc)
1.35 kristaps 602: return(1);
1.55 kristaps 603: return(mdoc_nerr(mdoc, n, "one argument allowed"));
1.35 kristaps 604: }
605:
606:
607: static int
1.55 kristaps 608: pre_rv(PRE_ARGS)
1.36 kristaps 609: {
1.53 kristaps 610: enum mdoc_msec msecs[] = { MSEC_2, MSEC_3 };
1.36 kristaps 611:
1.55 kristaps 612: if ( ! check_msec(mdoc, n, 2, msecs))
1.36 kristaps 613: return(0);
1.55 kristaps 614: return(check_stdarg(mdoc, n));
1.36 kristaps 615: }
616:
617:
618: static int
1.55 kristaps 619: pre_ex(PRE_ARGS)
1.33 kristaps 620: {
1.53 kristaps 621: enum mdoc_msec msecs[] = { MSEC_1, MSEC_6, MSEC_8 };
1.35 kristaps 622:
1.55 kristaps 623: if ( ! check_msec(mdoc, n, 3, msecs))
1.35 kristaps 624: return(0);
1.55 kristaps 625: return(check_stdarg(mdoc, n));
1.33 kristaps 626: }
627:
628:
629: static int
1.55 kristaps 630: pre_er(PRE_ARGS)
1.33 kristaps 631: {
1.53 kristaps 632: enum mdoc_msec msecs[] = { MSEC_2 };
1.33 kristaps 633:
1.55 kristaps 634: return(check_msec(mdoc, n, 1, msecs));
1.33 kristaps 635: }
636:
637:
638: static int
1.55 kristaps 639: pre_cd(PRE_ARGS)
1.33 kristaps 640: {
1.53 kristaps 641: enum mdoc_msec msecs[] = { MSEC_4 };
1.33 kristaps 642:
1.55 kristaps 643: return(check_msec(mdoc, n, 1, msecs));
1.33 kristaps 644: }
645:
646:
647: static int
1.55 kristaps 648: pre_prologue(PRE_ARGS)
1.20 kristaps 649: {
650:
1.46 kristaps 651: if (SEC_PROLOGUE != mdoc->lastnamed)
1.55 kristaps 652: return(mdoc_nerr(mdoc, n, "prologue only"));
1.20 kristaps 653:
654: /* Check for ordering. */
655:
1.55 kristaps 656: switch (n->tok) {
1.20 kristaps 657: case (MDOC_Os):
1.36 kristaps 658: if (mdoc->meta.title && mdoc->meta.date)
1.20 kristaps 659: break;
1.55 kristaps 660: return(mdoc_nerr(mdoc, n, "prologue out-of-order"));
1.20 kristaps 661: case (MDOC_Dt):
1.36 kristaps 662: if (NULL == mdoc->meta.title && mdoc->meta.date)
1.20 kristaps 663: break;
1.55 kristaps 664: return(mdoc_nerr(mdoc, n, "prologue out-of-order"));
1.20 kristaps 665: case (MDOC_Dd):
1.36 kristaps 666: if (NULL == mdoc->meta.title && 0 == mdoc->meta.date)
1.20 kristaps 667: break;
1.55 kristaps 668: return(mdoc_nerr(mdoc, n, "prologue out-of-order"));
1.20 kristaps 669: default:
670: abort();
671: /* NOTREACHED */
672: }
673:
674: /* Check for repetition. */
675:
1.55 kristaps 676: switch (n->tok) {
1.20 kristaps 677: case (MDOC_Os):
1.36 kristaps 678: if (NULL == mdoc->meta.os)
1.20 kristaps 679: return(1);
680: break;
681: case (MDOC_Dd):
682: if (0 == mdoc->meta.date)
683: return(1);
684: break;
685: case (MDOC_Dt):
1.36 kristaps 686: if (NULL == mdoc->meta.title)
1.20 kristaps 687: return(1);
688: break;
689: default:
690: abort();
691: /* NOTREACHED */
692: }
693:
1.55 kristaps 694: return(mdoc_nerr(mdoc, n, "prologue repetition"));
1.20 kristaps 695: }
696:
697:
1.35 kristaps 698: static int
1.55 kristaps 699: post_bf(POST_ARGS)
1.41 kristaps 700: {
701: char *p;
702: struct mdoc_node *head;
703:
704: if (MDOC_BLOCK != mdoc->last->type)
705: return(1);
1.53 kristaps 706:
1.41 kristaps 707: head = mdoc->last->data.block.head;
708:
709: if (0 == mdoc->last->data.block.argc) {
1.53 kristaps 710: if (NULL == head->child)
711: return(mdoc_err(mdoc, "argument expected"));
712:
713: p = head->child->data.text.string;
714: if (xstrcmp(p, "Em"))
715: return(1);
716: else if (xstrcmp(p, "Li"))
717: return(1);
718: else if (xstrcmp(p, "Sm"))
719: return(1);
720: return(mdoc_nerr(mdoc, head->child, "invalid font"));
1.41 kristaps 721: }
1.53 kristaps 722:
1.41 kristaps 723: if (head->child)
1.53 kristaps 724: return(mdoc_err(mdoc, "argument expected"));
725:
1.41 kristaps 726: if (1 == mdoc->last->data.block.argc)
727: return(1);
1.53 kristaps 728: return(mdoc_err(mdoc, "argument expected"));
1.41 kristaps 729: }
730:
731:
732: static int
1.55 kristaps 733: post_nm(POST_ARGS)
1.37 kristaps 734: {
735:
736: if (mdoc->last->child)
737: return(1);
738: if (mdoc->meta.name)
739: return(1);
1.53 kristaps 740: return(mdoc_err(mdoc, "not yet invoked with name"));
1.37 kristaps 741: }
742:
743:
744: static int
1.55 kristaps 745: post_xr(POST_ARGS)
1.36 kristaps 746: {
747: struct mdoc_node *n;
748:
749: if (NULL == (n = mdoc->last->child->next))
750: return(1);
751: if (MSEC_DEFAULT != mdoc_atomsec(n->data.text.string))
752: return(1);
753: return(mdoc_nerr(mdoc, n, "invalid manual section"));
754: }
755:
756:
757: static int
1.55 kristaps 758: post_at(POST_ARGS)
1.36 kristaps 759: {
760:
1.37 kristaps 761: if (NULL == mdoc->last->child)
762: return(1);
1.36 kristaps 763: if (ATT_DEFAULT != mdoc_atoatt(mdoc->last->child->data.text.string))
764: return(1);
1.53 kristaps 765: return(mdoc_err(mdoc, "require valid symbol"));
1.36 kristaps 766: }
767:
768:
769: static int
1.55 kristaps 770: post_an(POST_ARGS)
1.35 kristaps 771: {
772:
773: if (0 != mdoc->last->data.elem.argc) {
774: if (NULL == mdoc->last->child)
775: return(1);
1.53 kristaps 776: return(mdoc_err(mdoc, "argument(s) expected"));
1.35 kristaps 777: }
778:
779: if (mdoc->last->child)
780: return(1);
1.53 kristaps 781: return(mdoc_err(mdoc, "argument(s) expected"));
1.35 kristaps 782: }
783:
784:
785: static int
1.55 kristaps 786: post_ex(POST_ARGS)
1.35 kristaps 787: {
788:
789: if (0 == mdoc->last->data.elem.argc) {
790: if (mdoc->last->child)
791: return(1);
1.53 kristaps 792: return(mdoc_err(mdoc, "argument(s) expected"));
1.35 kristaps 793: }
794: if (mdoc->last->child)
1.53 kristaps 795: return(mdoc_err(mdoc, "argument(s) expected"));
1.35 kristaps 796: if (1 != mdoc->last->data.elem.argc)
1.53 kristaps 797: return(mdoc_err(mdoc, "argument(s) expected"));
1.35 kristaps 798: if (MDOC_Std != mdoc->last->data.elem.argv[0].arg)
1.53 kristaps 799: return(mdoc_err(mdoc, "argument(s) expected"));
800:
1.35 kristaps 801: return(1);
802: }
803:
804:
1.25 kristaps 805: static int
1.55 kristaps 806: post_it(POST_ARGS)
1.25 kristaps 807: {
1.53 kristaps 808: int type, sv, i;
1.25 kristaps 809: #define TYPE_NONE (0)
810: #define TYPE_BODY (1)
811: #define TYPE_HEAD (2)
1.47 kristaps 812: #define TYPE_OHEAD (3)
1.53 kristaps 813: size_t argc;
1.25 kristaps 814: struct mdoc_node *n;
815:
816: if (MDOC_BLOCK != mdoc->last->type)
817: return(1);
818:
1.53 kristaps 819: n = mdoc->last->parent->parent;
1.25 kristaps 820:
821: argc = n->data.block.argc;
822: type = TYPE_NONE;
1.38 kristaps 823: sv = -1;
1.26 kristaps 824:
825: /* Some types require block-head, some not. */
1.25 kristaps 826:
1.38 kristaps 827: /* LINTED */
1.53 kristaps 828: for (i = 0; TYPE_NONE == type && i < (int)argc; i++)
829: switch (n->data.block.argv[i].arg) {
1.25 kristaps 830: case (MDOC_Tag):
831: /* FALLTHROUGH */
832: case (MDOC_Diag):
833: /* FALLTHROUGH */
834: case (MDOC_Hang):
835: /* FALLTHROUGH */
836: case (MDOC_Ohang):
837: /* FALLTHROUGH */
838: case (MDOC_Inset):
839: type = TYPE_HEAD;
1.53 kristaps 840: sv = n->data.block.argv[i].arg;
1.25 kristaps 841: break;
842: case (MDOC_Bullet):
843: /* FALLTHROUGH */
844: case (MDOC_Dash):
845: /* FALLTHROUGH */
846: case (MDOC_Enum):
847: /* FALLTHROUGH */
848: case (MDOC_Hyphen):
849: /* FALLTHROUGH */
850: case (MDOC_Item):
1.47 kristaps 851: type = TYPE_BODY;
1.53 kristaps 852: sv = n->data.block.argv[i].arg;
1.47 kristaps 853: break;
1.25 kristaps 854: case (MDOC_Column):
1.47 kristaps 855: type = TYPE_OHEAD;
1.53 kristaps 856: sv = n->data.block.argv[i].arg;
1.25 kristaps 857: break;
858: default:
859: break;
860: }
861:
862: assert(TYPE_NONE != type);
863:
1.47 kristaps 864: n = mdoc->last->data.block.head;
865:
1.25 kristaps 866: if (TYPE_HEAD == type) {
1.33 kristaps 867: if (NULL == n->child)
1.53 kristaps 868: if ( ! mdoc_warn(mdoc, WARN_SYNTAX,
869: "argument(s) suggested"))
1.25 kristaps 870: return(0);
871:
1.33 kristaps 872: n = mdoc->last->data.block.body;
873: if (NULL == n->child)
1.53 kristaps 874: if ( ! mdoc_warn(mdoc, WARN_SYNTAX,
875: "multiline body suggested"))
1.25 kristaps 876: return(0);
877:
1.47 kristaps 878: } else if (TYPE_BODY == type) {
879: if (n->child)
1.53 kristaps 880: if ( ! mdoc_warn(mdoc, WARN_SYNTAX,
881: "no argument suggested"))
1.47 kristaps 882: return(0);
883:
884: n = mdoc->last->data.block.body;
885: if (NULL == n->child)
1.53 kristaps 886: if ( ! mdoc_warn(mdoc, WARN_SYNTAX,
887: "multiline body suggested"))
1.47 kristaps 888: return(0);
889: } else {
890: if (NULL == n->child)
1.53 kristaps 891: if ( ! mdoc_warn(mdoc, WARN_SYNTAX,
892: "argument(s) suggested"))
1.47 kristaps 893: return(0);
894:
895: n = mdoc->last->data.block.body;
896: if (n->child)
1.53 kristaps 897: if ( ! mdoc_warn(mdoc, WARN_SYNTAX,
898: "no multiline body suggested"))
1.47 kristaps 899: return(0);
1.25 kristaps 900: }
901:
1.47 kristaps 902: if (MDOC_Column != sv)
1.26 kristaps 903: return(1);
904:
1.38 kristaps 905: argc = mdoc->last->parent->parent->data.block.argv->sz;
1.26 kristaps 906: n = mdoc->last->data.block.head->child;
1.25 kristaps 907:
1.26 kristaps 908: for (i = 0; n; n = n->next)
909: i++;
910:
1.53 kristaps 911: if (i == (int)argc)
1.26 kristaps 912: return(1);
1.53 kristaps 913:
914: return(mdoc_err(mdoc, "need %zu columns (have %d)", argc, i));
1.25 kristaps 915: #undef TYPE_NONE
916: #undef TYPE_BODY
917: #undef TYPE_HEAD
1.47 kristaps 918: #undef TYPE_OHEAD
1.25 kristaps 919: }
920:
921:
1.24 kristaps 922: static int
1.55 kristaps 923: post_bl(POST_ARGS)
1.24 kristaps 924: {
1.53 kristaps 925: struct mdoc_node *n;
1.24 kristaps 926:
927: if (MDOC_BODY != mdoc->last->type)
928: return(1);
929:
1.38 kristaps 930: /* LINTED */
1.24 kristaps 931: for (n = mdoc->last->child; n; n = n->next) {
932: if (MDOC_BLOCK == n->type)
1.25 kristaps 933: if (MDOC_It == n->tok)
1.24 kristaps 934: continue;
935: break;
936: }
1.53 kristaps 937:
1.24 kristaps 938: if (NULL == n)
939: return(1);
1.53 kristaps 940:
941: return(mdoc_nerr(mdoc, n, "bad child of parent list"));
1.24 kristaps 942: }
943:
944:
1.34 kristaps 945: static int
1.37 kristaps 946: ebool(struct mdoc *mdoc)
1.34 kristaps 947: {
948: struct mdoc_node *n;
949:
1.38 kristaps 950: /* LINTED */
1.34 kristaps 951: for (n = mdoc->last->child; n; n = n->next) {
952: if (MDOC_TEXT != n->type)
953: break;
954: if (xstrcmp(n->data.text.string, "on"))
955: continue;
956: if (xstrcmp(n->data.text.string, "off"))
957: continue;
958: break;
959: }
1.53 kristaps 960:
1.34 kristaps 961: if (NULL == n)
962: return(1);
1.53 kristaps 963: return(mdoc_nerr(mdoc, n, "expected boolean"));
1.37 kristaps 964: }
965:
966:
967: static int
1.55 kristaps 968: post_root(POST_ARGS)
1.37 kristaps 969: {
970:
1.46 kristaps 971: if (NULL == mdoc->first->child)
1.53 kristaps 972: return(mdoc_err(mdoc, "document lacks data"));
1.46 kristaps 973: if (SEC_PROLOGUE == mdoc->lastnamed)
1.53 kristaps 974: return(mdoc_err(mdoc, "document lacks prologue"));
975:
1.46 kristaps 976: if (MDOC_BLOCK != mdoc->first->child->type)
1.54 kristaps 977: return(mdoc_err(mdoc, "lacking post-prologue %s",
1.53 kristaps 978: mdoc_macronames[MDOC_Sh]));
1.46 kristaps 979: if (MDOC_Sh != mdoc->first->child->tok)
1.54 kristaps 980: return(mdoc_err(mdoc, "lacking post-prologue %s",
1.53 kristaps 981: mdoc_macronames[MDOC_Sh]));
982:
1.37 kristaps 983: return(1);
1.34 kristaps 984: }
985:
986:
1.20 kristaps 987: static int
1.55 kristaps 988: post_sh(POST_ARGS)
1.14 kristaps 989: {
1.46 kristaps 990:
991: if (MDOC_HEAD == mdoc->last->type)
992: return(post_sh_head(mdoc));
993: if (MDOC_BODY == mdoc->last->type)
994: return(post_sh_body(mdoc));
1.53 kristaps 995:
1.46 kristaps 996: return(1);
997: }
998:
999:
1000: static int
1.55 kristaps 1001: post_sh_body(POST_ARGS)
1.46 kristaps 1002: {
1003: struct mdoc_node *n;
1004:
1005: if (SEC_NAME != mdoc->lastnamed)
1006: return(1);
1007:
1.51 kristaps 1008: /*
1009: * Warn if the NAME section doesn't contain the `Nm' and `Nd'
1010: * macros (can have multiple `Nm' and one `Nd'). Note that the
1011: * children of the BODY declaration can also be "text".
1012: */
1013:
1.46 kristaps 1014: if (NULL == (n = mdoc->last->child))
1.54 kristaps 1015: return(mdoc_warn(mdoc, WARN_SYNTAX,
1016: "section should have %s and %s",
1.51 kristaps 1017: mdoc_macronames[MDOC_Nm],
1018: mdoc_macronames[MDOC_Nd]));
1019:
1020: for ( ; n && n->next; n = n->next) {
1021: if (MDOC_ELEM == n->type && MDOC_Nm == n->tok)
1022: continue;
1023: if (MDOC_TEXT == n->type)
1024: continue;
1.54 kristaps 1025: if ( ! (mdoc_nwarn(mdoc, n, WARN_SYNTAX,
1026: "section should have %s first",
1.51 kristaps 1027: mdoc_macronames[MDOC_Nm])))
1028: return(0);
1029: }
1030:
1031: if (MDOC_ELEM == n->type && MDOC_Nd == n->tok)
1.46 kristaps 1032: return(1);
1033:
1.54 kristaps 1034: return(mdoc_warn(mdoc, WARN_SYNTAX,
1035: "section should have %s last",
1.51 kristaps 1036: mdoc_macronames[MDOC_Nd]));
1.46 kristaps 1037: }
1038:
1039:
1040: static int
1.55 kristaps 1041: post_sh_head(POST_ARGS)
1.46 kristaps 1042: {
1.36 kristaps 1043: char buf[64];
1.21 kristaps 1044: enum mdoc_sec sec;
1045:
1.25 kristaps 1046: assert(MDOC_Sh == mdoc->last->tok);
1.21 kristaps 1047:
1.54 kristaps 1048: if ( ! xstrlcats(buf, mdoc->last->child, sizeof(buf)))
1049: return(mdoc_err(mdoc, "argument too long"));
1.14 kristaps 1050:
1.46 kristaps 1051: sec = mdoc_atosec(buf);
1052:
1053: if (SEC_BODY == mdoc->lastnamed && SEC_NAME != sec)
1.54 kristaps 1054: return(mdoc_warn(mdoc, WARN_SYNTAX,
1055: "section NAME should be first"));
1.46 kristaps 1056: if (SEC_CUSTOM == sec)
1.21 kristaps 1057: return(1);
1.46 kristaps 1058: if (sec == mdoc->lastnamed)
1.54 kristaps 1059: return(mdoc_warn(mdoc, WARN_SYNTAX,
1060: "section repeated"));
1.46 kristaps 1061: if (sec < mdoc->lastnamed)
1.54 kristaps 1062: return(mdoc_warn(mdoc, WARN_SYNTAX,
1063: "section out of order"));
1.46 kristaps 1064:
1065: return(1);
1.11 kristaps 1066: }
1067:
1068:
1.17 kristaps 1069: int
1.55 kristaps 1070: mdoc_valid_pre(struct mdoc *mdoc,
1071: const struct mdoc_node *node)
1.11 kristaps 1072: {
1.24 kristaps 1073: v_pre *p;
1.55 kristaps 1074: struct mdoc_arg *argv;
1075: size_t argc, i, j, line, pos;
1076: const char *tp;
1.18 kristaps 1077:
1.55 kristaps 1078: if (MDOC_TEXT == node->type) {
1079: tp = node->data.text.string;
1080: line = node->line;
1081: pos = node->pos;
1082: return(check_text(mdoc, line, pos, tp));
1083: }
1084:
1085: if (MDOC_BLOCK == node->type || MDOC_ELEM == node->type) {
1086: argv = MDOC_BLOCK == node->type ?
1087: node->data.block.argv :
1088: node->data.elem.argv;
1089: argc = MDOC_BLOCK == node->type ?
1090: node->data.block.argc :
1091: node->data.elem.argc;
1092:
1093: for (i = 0; i < argc; i++) {
1094: if (0 == argv[i].sz)
1095: continue;
1096: for (j = 0; j < argv[i].sz; j++) {
1097: tp = argv[i].value[j];
1098: line = argv[i].line;
1099: pos = argv[i].pos;
1100: if ( ! check_text(mdoc, line, pos, tp))
1101: return(0);
1102: }
1103: }
1104: }
1.11 kristaps 1105:
1.25 kristaps 1106: if (NULL == mdoc_valids[node->tok].pre)
1.11 kristaps 1107: return(1);
1.25 kristaps 1108: for (p = mdoc_valids[node->tok].pre; *p; p++)
1.24 kristaps 1109: if ( ! (*p)(mdoc, node))
1110: return(0);
1111: return(1);
1.11 kristaps 1112: }
1113:
1114:
1.17 kristaps 1115: int
1.18 kristaps 1116: mdoc_valid_post(struct mdoc *mdoc)
1.11 kristaps 1117: {
1.17 kristaps 1118: v_post *p;
1.11 kristaps 1119:
1.54 kristaps 1120: /*
1121: * This check occurs after the macro's children have been filled
1122: * in: postfix validation. Since this happens when we're
1123: * rewinding the scope tree, it's possible to have multiple
1124: * invocations (as by design, for now), we set bit MDOC_VALID to
1125: * indicate that we've validated.
1126: */
1127:
1.39 kristaps 1128: if (MDOC_VALID & mdoc->last->flags)
1129: return(1);
1130: mdoc->last->flags |= MDOC_VALID;
1131:
1.25 kristaps 1132: if (MDOC_TEXT == mdoc->last->type)
1133: return(1);
1.37 kristaps 1134: if (MDOC_ROOT == mdoc->last->type)
1135: return(post_root(mdoc));
1.14 kristaps 1136:
1.25 kristaps 1137: if (NULL == mdoc_valids[mdoc->last->tok].post)
1.9 kristaps 1138: return(1);
1.25 kristaps 1139: for (p = mdoc_valids[mdoc->last->tok].post; *p; p++)
1.18 kristaps 1140: if ( ! (*p)(mdoc))
1.17 kristaps 1141: return(0);
1.11 kristaps 1142:
1.14 kristaps 1143: return(1);
1.11 kristaps 1144: }
1.14 kristaps 1145:
CVSweb