fmt: start a paragraph only after a blank line
[ctxt.git] / fmt.c
blob70a646751280959c0be3e9a1cd44d9e872224d49
1 #include <ctype.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include "ctxt.h"
7 #define MAXLINE 1024
8 #define LENGTH(vars) (sizeof(vars) / sizeof(vars[0]))
10 static char *markers[] = {"**", "$$", "%%", "[]", "||", "//", "''", "``"};
11 struct block {
12 char *beg;
13 char *end;
14 int txt; /* process inline markups in the block */
15 } blocks[] = {
16 {".#1", ".#2", 1}, {".!1", ".!2"},
17 {".TS", ".TE", 1}, {".DS", ".DE", 1}, {".G1", ".G2"},
18 {".EQ", ".EN"}, {".PS", ".PE"}, {".[", ".]"},
21 struct fmt {
22 struct doc *doc;
23 struct txt *txt;
24 struct fmt_ops *ops;
25 int level;
28 static struct fmt *fmt_alloc(struct doc *doc, struct txt *txt,
29 struct fmt_ops *ops)
31 struct fmt *fmt = xmalloc(sizeof(struct fmt));
32 fmt->doc = doc;
33 fmt->txt = txt;
34 fmt->ops = ops;
35 fmt->level = 0;
36 return fmt;
39 static void fmt_free(struct fmt *fmt)
41 free(fmt);
44 static char *fmt_line(struct fmt *fmt, int line)
46 char *s = txt_line(fmt->txt, line);
47 int i;
48 for (i = 0; i < fmt->level && s && *s; i++)
49 s++;
50 return s;
53 static char *marker_char(char c, char p, char n)
55 int i;
56 if (p == '\\' || (p && !isspace(p)) || isspace(n))
57 return NULL;
58 for (i = 0; i < LENGTH(markers); i++)
59 if (markers[i][0] == c)
60 return markers[i];
61 return NULL;
64 static char *fillbuf(char *beg, char *end)
66 static char buf[MAXLINE];
67 memcpy(buf, beg, end - beg);
68 buf[end - beg] = '\0';
69 return buf;
72 static void put_text(struct fmt *fmt, char *s)
74 char *_s = s;
75 char *o = s;
76 while (*s) {
77 char *r, *m;
78 while (*s && !marker_char(*s, s == _s ? 0 : s[-1], s[1]))
79 s++;
80 if (!*s)
81 break;
82 m = marker_char(*s, s == _s ? 0 : s[-1], s[1]);
83 r = strchr(s + 1, m[1]);
84 if (r) {
85 fmt->ops->put(fmt->doc, fillbuf(o, s));
86 fmt->ops->put_txt(fmt->doc, fillbuf(s + 1, r), m);
87 o = r + 1;
88 s = r + 1;
89 } else {
90 s++;
93 fmt->ops->put(fmt->doc, o);
96 static void raw_line(struct fmt *fmt, char *s)
98 fmt->ops->put(fmt->doc, s);
101 static void put_line(struct fmt *fmt, int n)
103 put_text(fmt, fmt_line(fmt, n));
104 put_text(fmt, "\n");
107 static void put_lines(struct fmt *fmt, int beg, int end)
109 int i;
110 for (i = beg; i < end; i++)
111 put_line(fmt, i);
114 static void raw_lines(struct fmt *fmt, int beg, int end)
116 int i;
117 for (i = beg; i < end; i++) {
118 raw_line(fmt, fmt_line(fmt, i));
119 raw_line(fmt, "\n");
123 static int indents(char *s)
125 char *r = s;
126 while (isspace(*r))
127 r++;
128 return r - s;
131 static int islist(char *first, char *line)
133 char *signs = "*-+";
134 if (!line) {
135 if (!first || strlen(first) < 2)
136 return 0;
137 if (first[1] != ' ' || !strchr(signs, first[0]))
138 return 0;
139 return 1;
141 if (strlen(line) < 2)
142 return 0;
143 return line[0] == first[0] && line[1] == first[1];
146 static void fmt_handle(struct fmt *fmt, int beg, int end, int level);
148 static int fmt_head(struct fmt *fmt, int beg, int end)
150 char *line = fmt_line(fmt, beg);
151 char *next = fmt_line(fmt, beg + 1);
152 char c;
153 char *s = next;
154 char *signs = "=-~";
155 if (!next || !*line || beg == end || fmt->level)
156 return 0;
157 c = *next;
158 if (!c || !strchr(signs, *next))
159 return 0;
160 while (*++s)
161 if (*s != c)
162 return 0;
163 fmt->ops->head_beg(fmt->doc, strchr(signs, *next) - signs);
164 put_text(fmt, line);
165 fmt->ops->head_end(fmt->doc, strchr(signs, *next) - signs);
166 return 2;
169 static int cmd_line(struct fmt *fmt, int i)
171 return !fmt->level && *fmt_line(fmt, i) == '.';
174 static int par_end(struct fmt *fmt, int beg, int end)
176 int i = beg;
177 char *line;
178 while (i < end && (line = fmt_line(fmt, i))) {
179 if (!*line || indents(line) || islist(line, NULL) || cmd_line(fmt, i))
180 break;
181 i++;
183 return i;
186 static int fmt_par(struct fmt *fmt, int beg, int end)
188 int i;
189 if (indents(fmt_line(fmt, beg)) || cmd_line(fmt, beg))
190 return 0;
191 fmt->ops->par_beg(fmt->doc);
192 i = par_end(fmt, beg, end);
193 put_lines(fmt, beg, i);
194 fmt->ops->par_end(fmt->doc);
195 return i - beg;
198 static int fmt_rawline(struct fmt *fmt, int beg, int end)
200 if (*fmt_line(fmt, beg) != '.')
201 return 0;
202 raw_lines(fmt, beg, beg + 1);
203 return 1;
206 static int min(int a, int b)
208 return a < b ? a : b;
211 static int fmt_deindent(struct fmt *fmt, int n, int indent)
213 char *line;
214 int result = n;
215 while ((line = fmt_line(fmt, n))) {
216 if (*line && indents(line) < indent)
217 break;
218 n++;
219 if (*line)
220 result = n;
222 return result;
225 static int fmt_pre(struct fmt *fmt, int beg, int end)
227 int level = indents(fmt_line(fmt, beg));
228 int next = min(end, fmt_deindent(fmt, beg + 1, level));
229 fmt->level += level;
230 fmt->ops->block_beg(fmt->doc, NULL);
231 raw_lines(fmt, beg, next);
232 fmt->ops->block_end(fmt->doc, NULL);
233 fmt->level -= level;
234 return next - beg;
237 static int fmt_block(struct fmt *fmt, int beg, int end)
239 struct block *blk = NULL;
240 int next;
241 char *line;
242 int depth = 1;
243 int i;
244 line = fmt_line(fmt, beg);
245 if (indents(line))
246 return fmt_pre(fmt, beg, end);
247 for (i = 0; i < LENGTH(blocks); i++)
248 if (!strncmp(blocks[i].beg, line, strlen(blocks[i].beg)))
249 blk = &blocks[i];
250 if (!blk)
251 return 0;
252 next = beg + 1;
253 while (next < end && depth > 0) {
254 line = fmt_line(fmt, next++);
255 if (!strncmp(blk->beg, line, strlen(blk->beg)))
256 depth++;
257 if (!strncmp(blk->end, line, strlen(blk->end)))
258 depth--;
260 fmt->ops->block_beg(fmt->doc, fmt_line(fmt, beg));
261 if (beg + 1 < next) {
262 if (blk->txt)
263 put_lines(fmt, beg + 1, next - 1);
264 else
265 raw_lines(fmt, beg + 1, next - 1);
267 fmt->ops->block_end(fmt->doc, fmt_line(fmt, next - 1));
268 return next - beg;
271 static char *listhead(char *s, char *head)
273 if (s[0] != '+')
274 return NULL;
275 s += 2;
276 while (*s && *s != ':')
277 *head++ = *s++;
278 if (*s == ':')
279 s++;
280 while (isspace(*s))
281 s++;
282 *head = '\0';
283 return s;
286 static int fmt_list(struct fmt *fmt, int beg, int end)
288 int i = beg;
289 char *first = fmt_line(fmt, i);
290 char *line = first;
291 if (!islist(first, NULL))
292 return 0;
293 fmt->ops->list_beg(fmt->doc, line[0]);
294 while ((line = fmt_line(fmt, i)) && islist(first, line)) {
295 int next = min(end, fmt_deindent(fmt, i + 1, 2));
296 int head = i;
297 char lhead[MAXLINE];
298 if (line[0] == '+') {
299 line = listhead(line, lhead);
300 fmt->ops->item_beg(fmt->doc, lhead);
301 put_text(fmt, line);
302 put_text(fmt, "\n");
303 head++;
304 } else {
305 fmt->ops->item_beg(fmt->doc, NULL);
308 fmt->level += 2;
309 i = par_end(fmt, i, next);
310 put_lines(fmt, head, i);
311 fmt->level -= 2;
313 fmt_handle(fmt, i, next, 2);
314 i = next;
315 fmt->ops->item_end(fmt->doc);
316 if (i == end)
317 break;
319 fmt->ops->list_end(fmt->doc);
320 return i - beg;
323 /* .T1/.T2 tables */
324 static int fmt_table(struct fmt *fmt, int beg, int end)
326 char *hdr = fmt_line(fmt, beg);
327 int i;
328 int row = 0, col = 0;
329 if (hdr[0] != '.' || hdr[1] != 'T' || hdr[2] != '1')
330 return 0;
331 fmt->ops->table_beg(fmt->doc, 0);
332 for (i = beg + 1; i < end; i++) {
333 char *cur = fmt_line(fmt, i);
334 if (cur[0] == '.' && cur[1] == 'T' && cur[2] == '2')
335 break;
336 if (cur[0] == '%') {
337 raw_line(fmt, cur + 2);
338 raw_line(fmt, "\n");
339 continue;
341 if (strchr("_=\n", cur[0])) {
342 if (col)
343 fmt->ops->entry_end(fmt->doc);
344 if (row)
345 fmt->ops->row_end(fmt->doc);
346 if (cur[0] != '\n') {
347 raw_line(fmt, cur);
348 raw_line(fmt, "\n");
350 col = 0;
351 continue;
353 if (strchr("#*-+", cur[0])) {
354 if (!col) {
355 fmt->ops->row_beg(fmt->doc);
356 row++;
357 } else {
358 fmt->ops->entry_end(fmt->doc);
360 fmt->ops->entry_beg(fmt->doc);
361 col++;
363 fmt->level += 2;
364 put_line(fmt, i);
365 fmt->level -= 2;
367 if (col)
368 fmt->ops->entry_end(fmt->doc);
369 if (row)
370 fmt->ops->row_end(fmt->doc);
371 fmt->ops->table_end(fmt->doc);
372 return i - beg + 1;
375 /* simple tables */
377 static int table_columns(char *line)
379 int n;
380 for (n = 0; *line; n++) {
381 while (*line == '\t')
382 line++;
383 while (*line && *line != '\t')
384 line++;
386 return n;
389 static void table_row(struct fmt *fmt, char *s)
391 if (*s == '=' || *s == '-')
392 return;
393 fmt->ops->row_beg(fmt->doc);
394 while (*s) {
395 char *r = s;
396 while (*s && *s != '\t')
397 s++;
398 fmt->ops->entry_beg(fmt->doc);
399 put_text(fmt, fillbuf(r, s));
400 fmt->ops->entry_end(fmt->doc);
401 while (*s == '\t')
402 s++;
404 fmt->ops->row_end(fmt->doc);
407 static int fmt_tableascii(struct fmt *fmt, int beg, int end)
409 int i;
410 int n;
411 if (*fmt_line(fmt, beg) != '=')
412 return 0;
413 n = table_columns(fmt_line(fmt, beg + 1));
414 fmt->ops->table_beg(fmt->doc, n);
415 for (i = beg + 1; i < end; i++) {
416 if (!fmt->level && !*fmt_line(fmt, i))
417 break;
418 table_row(fmt, fmt_line(fmt, i));
420 fmt->ops->table_end(fmt->doc);
421 return i - beg;
424 /* parsing text */
426 static int (*parts[])(struct fmt *fmt, int beg, int end) = {
427 fmt_head, fmt_list, fmt_table, fmt_tableascii,
428 fmt_block, fmt_rawline
431 static void fmt_handle(struct fmt *fmt, int beg, int end, int level)
433 int line = beg;
434 int i;
435 fmt->level += level;
436 while (line < end) {
437 int c = 0;
438 if (!*fmt_line(fmt, line)) {
439 put_line(fmt, line++);
440 continue;
442 for (i = 0; i < LENGTH(parts); i++)
443 if ((c = parts[i](fmt, line, end)))
444 break;
445 /* start a paragraph only after a blank line */
446 if (!c && (line == beg || !*fmt_line(fmt, line - 1)))
447 c = fmt_par(fmt, line, end);
448 line += c;
449 if (!c)
450 put_line(fmt, line++);
452 fmt->level -= level;
455 void format(struct doc *doc, struct txt *txt, struct fmt_ops *ops)
457 struct fmt *fmt = fmt_alloc(doc, txt, ops);
458 fmt->ops->doc_beg(doc);
459 fmt_handle(fmt, 0, txt->n, 0);
460 fmt->ops->doc_end(doc);
461 fmt_free(fmt);