Blame src/main.c

Stephen Gregoratto cfce167
#define _XOPEN_SOURCE 600
a419394
#include <assert.h>
a419394
#include <ctype.h>
Silvan Jegen 599a615
#include <errno.h>
Silvan Jegen 599a615
#include <limits.h>
a4c7cc6
#include <stdbool.h>
a419394
#include <stdio.h>
a419394
#include <stdlib.h>
309ff46
#include <string.h>
a419394
#include <time.h>
a419394
#include <unistd.h>
309ff46
#include "str.h"
a419394
#include "unicode.h"
a419394
#include "util.h"
a419394
e5dec0c
char *strstr(const char *haystack, const char *needle);
Silvan Jegen 599a615
char *strerror(int errnum);
e5dec0c
Damien Tardy-Panis baaebab
static struct str *parse_section(struct parser *p) {
e2794c9
	struct str *section = str_create();
a419394
	uint32_t ch;
Damien Tardy-Panis baaebab
	char *subsection;
a419394
	while ((ch = parser_getch(p)) != UTF8_INVALID) {
Damien Tardy-Panis baaebab
		if (ch < 0x80 && isalnum(ch)) {
cfbf213
			int ret = str_append_ch(section, ch);
cfbf213
			assert(ret != -1);
a419394
		} else if (ch == ')') {
Damien Tardy-Panis 656484e
			if (section->len == 0) {
a419394
				break;
a419394
			}
Damien Tardy-Panis baaebab
			int sec = strtol(section->str, &subsection, 10);
Damien Tardy-Panis baaebab
			if (section->str == subsection) {
Damien Tardy-Panis baaebab
				parser_fatal(p, "Expected section digit");
Damien Tardy-Panis baaebab
				break;
Damien Tardy-Panis baaebab
			}
199998a
			if (sec < 0 || sec > 9) {
199998a
				parser_fatal(p, "Expected section between 0 and 9");
a419394
				break;
a419394
			}
Damien Tardy-Panis baaebab
			return section;
a419394
		} else {
Damien Tardy-Panis baaebab
			parser_fatal(p, "Expected alphanumerical character or )");
a419394
			break;
a419394
		}
a419394
	};
a419394
	parser_fatal(p, "Expected manual section");
Damien Tardy-Panis baaebab
	return NULL;
a419394
}
a419394
e2794c9
static struct str *parse_extra(struct parser *p) {
e2794c9
	struct str *extra = str_create();
Richard Bradfield 0fabc9b
	int ret = str_append_ch(extra, '"');
Richard Bradfield 0fabc9b
	assert(ret != -1);
Richard Bradfield 0fabc9b
	uint32_t ch;
Richard Bradfield 0fabc9b
	while ((ch = parser_getch(p)) != UTF8_INVALID) {
Richard Bradfield 0fabc9b
		if (ch == '"') {
Richard Bradfield 0fabc9b
			ret = str_append_ch(extra, ch);
Richard Bradfield 0fabc9b
			assert(ret != -1);
Richard Bradfield 0fabc9b
			return extra;
Richard Bradfield 0fabc9b
		} else if (ch == '\n') {
Richard Bradfield 0fabc9b
			parser_fatal(p, "Unclosed extra preamble field");
Richard Bradfield 0fabc9b
			break;
Richard Bradfield 0fabc9b
		} else {
Richard Bradfield 0fabc9b
			ret = str_append_ch(extra, ch);
Richard Bradfield 0fabc9b
			assert(ret != -1);
Richard Bradfield 0fabc9b
		}
Richard Bradfield 0fabc9b
	}
random human d3030d8
	str_free(extra);
Richard Bradfield 0fabc9b
	return NULL;
Richard Bradfield 0fabc9b
}
Richard Bradfield 0fabc9b
a419394
static void parse_preamble(struct parser *p) {
e2794c9
	struct str *name = str_create();
Richard Bradfield 0fabc9b
	int ex = 0;
e2794c9
	struct str *extras[2] = { NULL };
Damien Tardy-Panis baaebab
	struct str *section = NULL;
a419394
	uint32_t ch;
Silvan Jegen 599a615
	time_t date_time;
688923f
	char date[256];
Michael Weiss baabb97
	char *source_date_epoch = getenv("SOURCE_DATE_EPOCH");
Michael Weiss baabb97
	if (source_date_epoch != NULL) {
Silvan Jegen 599a615
		unsigned long long epoch;
Silvan Jegen 599a615
		char *endptr;
Silvan Jegen 599a615
		errno = 0;
Silvan Jegen 599a615
		epoch = strtoull(source_date_epoch, &endptr, 10);
Silvan Jegen 599a615
		if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0))
Silvan Jegen 599a615
				|| (errno != 0 && epoch == 0)) {
Silvan Jegen 599a615
			fprintf(stderr, "$SOURCE_DATE_EPOCH: strtoull: %s\n",
Silvan Jegen 599a615
					strerror(errno));
Silvan Jegen 599a615
			exit(EXIT_FAILURE);
Michael Weiss baabb97
		}
Silvan Jegen 599a615
		if (endptr == source_date_epoch) {
Silvan Jegen 599a615
			fprintf(stderr, "$SOURCE_DATE_EPOCH: No digits were found: %s\n",
Silvan Jegen 599a615
					endptr);
Silvan Jegen 599a615
			exit(EXIT_FAILURE);
Silvan Jegen 599a615
		}
Silvan Jegen 599a615
		if (*endptr != '\0') {
Silvan Jegen 599a615
			fprintf(stderr, "$SOURCE_DATE_EPOCH: Trailing garbage: %s\n",
Silvan Jegen 599a615
					endptr);
Silvan Jegen 599a615
			exit(EXIT_FAILURE);
Silvan Jegen 599a615
		}
Silvan Jegen 599a615
		if (epoch > ULONG_MAX) {
Silvan Jegen 599a615
			fprintf(stderr, "$SOURCE_DATE_EPOCH: value must be smaller than or "
Silvan Jegen 599a615
					"equal to %lu but was found to be: %llu \n",
Silvan Jegen 599a615
					ULONG_MAX, epoch);
Silvan Jegen 599a615
			exit(EXIT_FAILURE);
Silvan Jegen 599a615
		}
Silvan Jegen 599a615
		date_time = epoch;
Michael Weiss baabb97
	} else {
Silvan Jegen 599a615
		date_time = time(NULL);
Michael Weiss baabb97
	}
Birger Schacht 74ab656
	struct tm *date_tm = gmtime(&date_time);
Silvan Jegen 599a615
	strftime(date, sizeof(date), "%F", date_tm);
9f74b6a
	while ((ch = parser_getch(p)) != UTF8_INVALID) {
316e53b
		if ((ch < 0x80 && isalnum(ch)) || ch == '_' || ch == '-' || ch == '.') {
cfbf213
			int ret = str_append_ch(name, ch);
cfbf213
			assert(ret != -1);
a419394
		} else if (ch == '(') {
a419394
			section = parse_section(p);
Richard Bradfield 0fabc9b
		} else if (ch == '"') {
Richard Bradfield 0fabc9b
			if (ex == 2) {
Richard Bradfield 0fabc9b
				parser_fatal(p, "Too many extra preamble fields");
Richard Bradfield 0fabc9b
			}
Richard Bradfield 0fabc9b
			extras[ex++] = parse_extra(p);
a419394
		} else if (ch == '\n') {
a419394
			if (name->len == 0) {
a419394
				parser_fatal(p, "Expected preamble");
a419394
			}
Damien Tardy-Panis baaebab
			if (section == NULL) {
a419394
				parser_fatal(p, "Expected manual section");
a419394
			}
Richard Bradfield 0fabc9b
			char *ex2 = extras[0] != NULL ? extras[0]->str : NULL;
Richard Bradfield 0fabc9b
			char *ex3 = extras[1] != NULL ? extras[1]->str : NULL;
Damien Tardy-Panis baaebab
			fprintf(p->output, ".TH \"%s\" \"%s\" \"%s\"", name->str, section->str, date);
Richard Bradfield 0fabc9b
			/* ex2 and ex3 are already double-quoted */
Richard Bradfield 0fabc9b
			if (ex2) {
Richard Bradfield 0fabc9b
				fprintf(p->output, " %s", ex2);
Richard Bradfield 0fabc9b
			}
Richard Bradfield 0fabc9b
			if (ex3) {
Richard Bradfield 0fabc9b
				fprintf(p->output, " %s", ex3);
Richard Bradfield 0fabc9b
			}
Richard Bradfield 0fabc9b
			fprintf(p->output, "\n");
a419394
			break;
Damien Tardy-Panis baaebab
		} else if (section == NULL) {
Zandr Martin f7fb070
			parser_fatal(p, "Name characters must be A-Z, a-z, 0-9, `-`, `_`, or `.`");
a419394
		}
9f74b6a
	}
a419394
	str_free(name);
Richard Bradfield 0fabc9b
	for (int i = 0; i < 2; ++i) {
Richard Bradfield 0fabc9b
		if (extras[i] != NULL) {
Richard Bradfield 0fabc9b
			str_free(extras[i]);
Richard Bradfield 0fabc9b
		}
Richard Bradfield 0fabc9b
	}
a419394
}
a419394
10e1cf6
static void parse_format(struct parser *p, enum formatting fmt) {
10e1cf6
	char formats[FORMAT_LAST] = {
10e1cf6
		[FORMAT_BOLD] = 'B',
10e1cf6
		[FORMAT_UNDERLINE] = 'I',
10e1cf6
	};
2822228
	char error[512];
10e1cf6
	if (p->flags) {
10e1cf6
		if ((p->flags & ~fmt)) {
2822228
			snprintf(error, sizeof(error), "Cannot nest inline formatting "
2822228
						"(began with %c at %d:%d)",
2822228
					p->flags == FORMAT_BOLD ? '*' : '_',
2822228
					p->fmt_line, p->fmt_col);
2822228
			parser_fatal(p, error);
10e1cf6
		}
10e1cf6
		fprintf(p->output, "\\fR");
10e1cf6
	} else {
10e1cf6
		fprintf(p->output, "\\f%c", formats[fmt]);
2822228
		p->fmt_line = p->line;
2822228
		p->fmt_col = p->col;
10e1cf6
	}
10e1cf6
	p->flags ^= fmt;
10e1cf6
}
10e1cf6
da3cd52
static void parse_linebreak(struct parser *p) {
da3cd52
	uint32_t plus = parser_getch(p);
da3cd52
	if (plus != '+') {
da3cd52
		fprintf(p->output, "+");
da3cd52
		parser_pushch(p, plus);
da3cd52
		return;
da3cd52
	}
da3cd52
	uint32_t lf = parser_getch(p);
da3cd52
	if (lf != '\n') {
da3cd52
		fprintf(p->output, "+");
da3cd52
		parser_pushch(p, plus);
da3cd52
		parser_pushch(p, '\n');
da3cd52
		return;
da3cd52
	}
da3cd52
	uint32_t ch = parser_getch(p);
da3cd52
	if (ch == '\n') {
da3cd52
		parser_fatal(
da3cd52
				p, "Explicit line breaks cannot be followed by a blank line");
da3cd52
	}
da3cd52
	parser_pushch(p, ch);
da3cd52
	fprintf(p->output, "\n.br\n");
da3cd52
}
da3cd52
9f74b6a
static void parse_text(struct parser *p) {
Carlo Abelli f1db2e1
	uint32_t ch, next, last = ' ';
35028f2
	int i = 0;
9f74b6a
	while ((ch = parser_getch(p)) != UTF8_INVALID) {
9f74b6a
		switch (ch) {
9f74b6a
		case '\\':
49e39ef
			ch = parser_getch(p);
49e39ef
			if (ch == UTF8_INVALID) {
49e39ef
				parser_fatal(p, "Unexpected EOF");
49e39ef
			} else if (ch == '\\') {
49e39ef
				fprintf(p->output, "\\\\");
49e39ef
			} else {
49e39ef
				utf8_fputch(p->output, ch);
49e39ef
			}
9f74b6a
			break;
10e1cf6
		case '*':
10e1cf6
			parse_format(p, FORMAT_BOLD);
10e1cf6
			break;
10e1cf6
		case '_':
Carlo Abelli f1db2e1
			next = parser_getch(p);
Carlo Abelli f1db2e1
			if (!isalnum(last) || ((p->flags & FORMAT_UNDERLINE) && !isalnum(next))) {
7f29817
				parse_format(p, FORMAT_UNDERLINE);
7f29817
			} else {
7f29817
				utf8_fputch(p->output, ch);
7f29817
			}
Brian Ashworth 0d731c6
			if (next == UTF8_INVALID) {
Brian Ashworth 0d731c6
				return;
Brian Ashworth 0d731c6
			}
Carlo Abelli f1db2e1
			parser_pushch(p, next);
10e1cf6
			break;
da3cd52
		case '+':
da3cd52
			parse_linebreak(p);
da3cd52
			break;
a4c7cc6
		case '\n':
a4c7cc6
			utf8_fputch(p->output, ch);
a4c7cc6
			return;
35028f2
		case '.':
35028f2
			if (!i) {
35028f2
				// Escape . if it's the first character
35028f2
				fprintf(p->output, "\\&.");
35028f2
				break;
35028f2
			}
35028f2
			/* fallthrough */
9f74b6a
		default:
7f29817
			last = ch;
9f74b6a
			utf8_fputch(p->output, ch);
9f74b6a
			break;
9f74b6a
		}
35028f2
		++i;
9f74b6a
	}
9f74b6a
}
9f74b6a
9f74b6a
static void parse_heading(struct parser *p) {
9f74b6a
	uint32_t ch;
9f74b6a
	int level = 1;
9f74b6a
	while ((ch = parser_getch(p)) != UTF8_INVALID) {
9f74b6a
		if (ch == '#') {
9f74b6a
			++level;
9f74b6a
		} else if (ch == ' ') {
9f74b6a
			break;
9f74b6a
		} else {
9f74b6a
			parser_fatal(p, "Invalid start of heading (probably needs a space)");
9f74b6a
		}
9f74b6a
	}
9f74b6a
	switch (level) {
9f74b6a
	case 1:
9f74b6a
		fprintf(p->output, ".SH ");
9f74b6a
		break;
9f74b6a
	case 2:
9f74b6a
		fprintf(p->output, ".SS ");
9f74b6a
		break;
9f74b6a
	default:
9f74b6a
		parser_fatal(p, "Only headings up to two levels deep are permitted");
9f74b6a
		break;
9f74b6a
	}
9f74b6a
	while ((ch = parser_getch(p)) != UTF8_INVALID) {
9f74b6a
		utf8_fputch(p->output, ch);
9f74b6a
		if (ch == '\n') {
9f74b6a
			break;
9f74b6a
		}
9f74b6a
	}
9f74b6a
}
9f74b6a
5f44aae
static int parse_indent(struct parser *p, int *indent, bool write) {
35028f2
	int i = 0;
35028f2
	uint32_t ch;
35028f2
	while ((ch = parser_getch(p)) == '\t') {
35028f2
		++i;
35028f2
	}
35028f2
	parser_pushch(p, ch);
Zandr Martin 0c26abe
	if ((ch == '\n' || ch == UTF8_INVALID) && *indent != 0) {
Zandr Martin 0c26abe
		// Don't change indent when we encounter empty lines or EOF
175c566
		return *indent;
175c566
	}
5f44aae
	if (write) {
Zandr Martin 0c26abe
		if ((i - *indent) > 1) {
Zandr Martin 0c26abe
			parser_fatal(p, "Indented by an amount greater than 1");
Zandr Martin 0c26abe
		} else if (i < *indent) {
175c566
			for (int j = *indent; i < j; --j) {
175c566
				roff_macro(p, "RE", NULL);
175c566
			}
5f44aae
		} else if (i == *indent + 1) {
7d6ea06
			fprintf(p->output, ".RS 4\n");
5f44aae
		}
a4c7cc6
	}
14ef2b3
	*indent = i;
35028f2
	return i;
35028f2
}
35028f2
ac51eba
static void list_header(struct parser *p, int *num) {
7d6ea06
	fprintf(p->output, ".RS 4\n");
84e46de
	fprintf(p->output, ".ie n \\{\\\n");
ac51eba
	if (*num == -1) {
ac51eba
		fprintf(p->output, "\\h'-0%d'%s\\h'+03'\\c\n",
ac51eba
				*num >= 10 ? 5 : 4, "\\(bu");
ac51eba
	} else {
ac51eba
		fprintf(p->output, "\\h'-0%d'%d.\\h'+03'\\c\n",
ac51eba
				*num >= 10 ? 5 : 4, *num);
ac51eba
	}
84e46de
	fprintf(p->output, ".\\}\n");
84e46de
	fprintf(p->output, ".el \\{\\\n");
ac51eba
	if (*num == -1) {
ac51eba
		fprintf(p->output, ".IP %s 4\n", "\\(bu");
ac51eba
	} else {
ac51eba
		fprintf(p->output, ".IP %d. 4\n", *num);
ac51eba
		*num = *num + 1;
ac51eba
	}
84e46de
	fprintf(p->output, ".\\}\n");
84e46de
}
84e46de
ac51eba
static void parse_list(struct parser *p, int *indent, int num) {
9f74b6a
	uint32_t ch;
a4c7cc6
	if ((ch = parser_getch(p)) != ' ') {
a4c7cc6
		parser_fatal(p, "Expected space before start of list entry");
a4c7cc6
	}
ac51eba
	list_header(p, &num);
a4c7cc6
	parse_text(p);
35028f2
	do {
5f44aae
		parse_indent(p, indent, true);
a4c7cc6
		if ((ch = parser_getch(p)) == UTF8_INVALID) {
35028f2
			break;
35028f2
		}
a4c7cc6
		switch (ch) {
a4c7cc6
		case ' ':
6707a05
			if ((ch = parser_getch(p)) != ' ') {
6707a05
				parser_fatal(p, "Expected two spaces for list entry continuation");
6707a05
			}
6707a05
			parse_text(p);
a4c7cc6
			break;
a4c7cc6
		case '-':
ac51eba
		case '.':
a4c7cc6
			if ((ch = parser_getch(p)) != ' ') {
a4c7cc6
				parser_fatal(p, "Expected space before start of list entry");
a4c7cc6
			}
Zandr Martin 053c770
			roff_macro(p, "RE", NULL);
ac51eba
			list_header(p, &num);
49e39ef
			parse_text(p);
a4c7cc6
			break;
a4c7cc6
		default:
a4c7cc6
			fprintf(p->output, "\n");
a4c7cc6
			parser_pushch(p, ch);
14ef2b3
			goto ret;
49e39ef
		}
a4c7cc6
	} while (ch != UTF8_INVALID);
14ef2b3
ret:
Zandr Martin 053c770
	roff_macro(p, "RE", NULL);
a4c7cc6
}
49e39ef
5f44aae
static void parse_literal(struct parser *p, int *indent) {
5f44aae
	uint32_t ch;
5f44aae
	if ((ch = parser_getch(p)) != '`' ||
5f44aae
		(ch = parser_getch(p)) != '`' ||
5f44aae
		(ch = parser_getch(p)) != '\n') {
5f44aae
		parser_fatal(p, "Expected ``` and a newline to begin literal block");
5f44aae
	}
5f44aae
	int stops = 0;
5f44aae
	roff_macro(p, "nf", NULL);
7d6ea06
	fprintf(p->output, ".RS 4\n");
Zandr Martin 5c782cd
	bool check_indent = true;
5f44aae
	do {
Zandr Martin 5c782cd
		if (check_indent) {
Zandr Martin 5c782cd
			int _indent = *indent;
Zandr Martin 5c782cd
			parse_indent(p, &_indent, false);
Zandr Martin 5c782cd
			if (_indent < *indent) {
Zandr Martin 5c782cd
				parser_fatal(p, "Cannot deindent in literal block");
Zandr Martin 5c782cd
			}
Zandr Martin 5c782cd
			while (_indent > *indent) {
Zandr Martin 5c782cd
				--_indent;
Zandr Martin 5c782cd
				fprintf(p->output, "\t");
Zandr Martin 5c782cd
			}
Zandr Martin 5c782cd
			check_indent = false;
5f44aae
		}
5f44aae
		if ((ch = parser_getch(p)) == UTF8_INVALID) {
5f44aae
			break;
5f44aae
		}
5f44aae
		if (ch == '`') {
5f44aae
			if (++stops == 3) {
5f44aae
				if ((ch = parser_getch(p)) != '\n') {
5f44aae
					parser_fatal(p, "Expected literal block to end with newline");
5f44aae
				}
5f44aae
				roff_macro(p, "fi", NULL);
5f44aae
				roff_macro(p, "RE", NULL);
5f44aae
				return;
5f44aae
			}
5f44aae
		} else {
44c6182
			while (stops != 0) {
44c6182
				fputc('`', p->output);
44c6182
				--stops;
44c6182
			}
5f44aae
			switch (ch) {
5f44aae
			case '.':
5f44aae
				fprintf(p->output, "\\&.");
5f44aae
				break;
5f44aae
			case '\\':
5f44aae
				ch = parser_getch(p);
5f44aae
				if (ch == UTF8_INVALID) {
5f44aae
					parser_fatal(p, "Unexpected EOF");
5f44aae
				} else if (ch == '\\') {
5f44aae
					fprintf(p->output, "\\\\");
5f44aae
				} else {
5f44aae
					utf8_fputch(p->output, ch);
5f44aae
				}
5f44aae
				break;
Zandr Martin 5c782cd
			case '\n':
Zandr Martin 5c782cd
				check_indent = true;
Zandr Martin 5c782cd
				/* fallthrough */
5f44aae
			default:
5f44aae
				utf8_fputch(p->output, ch);
5f44aae
				break;
5f44aae
			}
5f44aae
		}
5f44aae
	} while (ch != UTF8_INVALID);
5f44aae
}
5f44aae
e5dec0c
enum table_align {
e5dec0c
	ALIGN_LEFT,
e5dec0c
	ALIGN_CENTER,
e5dec0c
	ALIGN_RIGHT,
4b29f37
	ALIGN_LEFT_EXPAND,
4b29f37
	ALIGN_CENTER_EXPAND,
4b29f37
	ALIGN_RIGHT_EXPAND,
e5dec0c
};
e5dec0c
e5dec0c
struct table_row {
e5dec0c
	struct table_cell *cell;
e5dec0c
	struct table_row *next;
e5dec0c
};
e5dec0c
e5dec0c
struct table_cell {
e5dec0c
	enum table_align align;
e2794c9
	struct str *contents;
e5dec0c
	struct table_cell *next;
e5dec0c
};
e5dec0c
e5dec0c
static void parse_table(struct parser *p, uint32_t style) {
e5dec0c
	struct table_row *table = NULL;
e5dec0c
	struct table_row *currow = NULL, *prevrow = NULL;
e5dec0c
	struct table_cell *curcell = NULL;
e5dec0c
	int column = 0;
e5dec0c
	uint32_t ch;
e5dec0c
	parser_pushch(p, '|');
e5dec0c
e5dec0c
	do {
e5dec0c
		if ((ch = parser_getch(p)) == UTF8_INVALID) {
e5dec0c
			break;
e5dec0c
		}
e5dec0c
		switch (ch) {
e5dec0c
		case '\n':
e5dec0c
			goto commit_table;
e5dec0c
		case '|':
e5dec0c
			prevrow = currow;
e5dec0c
			currow = calloc(1, sizeof(struct table_row));
e5dec0c
			if (prevrow) {
e5dec0c
				// TODO: Verify the number of columns match
e5dec0c
				prevrow->next = currow;
e5dec0c
			}
e5dec0c
			curcell = calloc(1, sizeof(struct table_cell));
e5dec0c
			currow->cell = curcell;
e5dec0c
			column = 0;
e5dec0c
			if (!table) {
e5dec0c
				table = currow;
e5dec0c
			}
e5dec0c
			break;
e5dec0c
		case ':':
e5dec0c
			if (!currow) {
e5dec0c
				parser_fatal(p, "Cannot start a column without "
e5dec0c
						"starting a row first");
e5dec0c
			} else {
e5dec0c
				struct table_cell *prev = curcell;
e5dec0c
				curcell = calloc(1, sizeof(struct table_cell));
e5dec0c
				if (prev) {
e5dec0c
					prev->next = curcell;
e5dec0c
				}
e5dec0c
				++column;
e5dec0c
			}
e5dec0c
			break;
71a5794
		case ' ':
71a5794
			goto continue_cell;
e5dec0c
		default:
e5dec0c
			parser_fatal(p, "Expected either '|' or ':'");
e5dec0c
			break;
e5dec0c
		}
e5dec0c
		if ((ch = parser_getch(p)) == UTF8_INVALID) {
e5dec0c
			break;
e5dec0c
		}
e5dec0c
		switch (ch) {
e5dec0c
		case '[':
e5dec0c
			curcell->align = ALIGN_LEFT;
e5dec0c
			break;
e5dec0c
		case '-':
e5dec0c
			curcell->align = ALIGN_CENTER;
e5dec0c
			break;
e5dec0c
		case ']':
e5dec0c
			curcell->align = ALIGN_RIGHT;
e5dec0c
			break;
4b29f37
		case '<':
4b29f37
			curcell->align = ALIGN_LEFT_EXPAND;
4b29f37
			break;
4b29f37
		case '=':
4b29f37
			curcell->align = ALIGN_CENTER_EXPAND;
4b29f37
			break;
4b29f37
		case '>':
4b29f37
			curcell->align = ALIGN_RIGHT_EXPAND;
4b29f37
			break;
e5dec0c
		case ' ':
e5dec0c
			if (prevrow) {
e5dec0c
				struct table_cell *pcell = prevrow->cell;
e5dec0c
				for (int i = 0; i <= column && pcell; ++i, pcell = pcell->next) {
e5dec0c
					if (i == column) {
e5dec0c
						curcell->align = pcell->align;
e5dec0c
						break;
e5dec0c
					}
e5dec0c
				}
e5dec0c
			} else {
e5dec0c
				parser_fatal(p, "No previous row to infer alignment from");
e5dec0c
			}
e5dec0c
			break;
e5dec0c
		default:
e5dec0c
			parser_fatal(p, "Expected one of '[', '-', ']', or ' '");
e5dec0c
			break;
e5dec0c
		}
e5dec0c
		curcell->contents = str_create();
71a5794
continue_cell:
Jakub Kądziołka 56b882d
		switch (ch = parser_getch(p)) {
Jakub Kądziołka 56b882d
		case ' ':
Jakub Kądziołka 56b882d
			// Read out remainder of the text
Jakub Kądziołka 56b882d
			while ((ch = parser_getch(p)) != UTF8_INVALID) {
Jakub Kądziołka 56b882d
				switch (ch) {
Jakub Kądziołka 56b882d
				case '\n':
Jakub Kądziołka 56b882d
					goto commit_cell;
Jakub Kądziołka 56b882d
				default:;
Jakub Kądziołka 56b882d
					int ret = str_append_ch(curcell->contents, ch);
Jakub Kądziołka 56b882d
					assert(ret != -1);
Jakub Kądziołka 56b882d
					break;
Jakub Kądziołka 56b882d
				}
e5dec0c
			}
Jakub Kądziołka 56b882d
			break;
Jakub Kądziołka 56b882d
		case '\n':
Jakub Kądziołka 56b882d
			goto commit_cell;
Jakub Kądziołka 56b882d
		default:
Jakub Kądziołka 56b882d
			parser_fatal(p, "Expected ' ' or a newline");
Jakub Kądziołka 56b882d
			break;
e5dec0c
		}
e5dec0c
commit_cell:
e5dec0c
		if (strstr(curcell->contents->str, "T{")
e5dec0c
				|| strstr(curcell->contents->str, "T}")) {
e5dec0c
			parser_fatal(p, "Cells cannot contain T{ or T} "
e5dec0c
					"due to roff limitations");
e5dec0c
		}
e5dec0c
	} while (ch != UTF8_INVALID);
e5dec0c
commit_table:
e5dec0c
e5dec0c
	if (ch == UTF8_INVALID) {
e5dec0c
		return;
e5dec0c
	}
e5dec0c
e5dec0c
	roff_macro(p, "TS", NULL);
e5dec0c
e5dec0c
	switch (style) {
e5dec0c
	case '[':
Ivan Tham da6ddfe
		fprintf(p->output, "allbox;");
e5dec0c
		break;
e5dec0c
	case ']':
Ivan Tham da6ddfe
		fprintf(p->output, "box;");
e5dec0c
		break;
e5dec0c
	}
e5dec0c
e5dec0c
	// Print alignments first
e5dec0c
	currow = table;
e5dec0c
	while (currow) {
e5dec0c
		curcell = currow->cell;
e5dec0c
		while (curcell) {
4b29f37
			char *align = "";
4b29f37
			switch (curcell->align) {
4b29f37
			case ALIGN_LEFT:
4b29f37
				align = "l";
4b29f37
				break;
4b29f37
			case ALIGN_CENTER:
4b29f37
				align = "c";
4b29f37
				break;
4b29f37
			case ALIGN_RIGHT:
4b29f37
				align = "r";
4b29f37
				break;
4b29f37
			case ALIGN_LEFT_EXPAND:
4b29f37
				align = "lx";
4b29f37
				break;
4b29f37
			case ALIGN_CENTER_EXPAND:
4b29f37
				align = "cx";
4b29f37
				break;
4b29f37
			case ALIGN_RIGHT_EXPAND:
4b29f37
				align = "rx";
4b29f37
				break;
4b29f37
			}
4b29f37
			fprintf(p->output, "%s%s", align, curcell->next ? " " : "");
e5dec0c
			curcell = curcell->next;
e5dec0c
		}
e5dec0c
		fprintf(p->output, "%s\n", currow->next ? "" : ".");
e5dec0c
		currow = currow->next;
e5dec0c
	}
e5dec0c
e5dec0c
	// Then contents
e5dec0c
	currow = table;
e5dec0c
	while (currow) {
e5dec0c
		curcell = currow->cell;
e5dec0c
		fprintf(p->output, "T{\n");
e5dec0c
		while (curcell) {
e5dec0c
			parser_pushstr(p, curcell->contents->str);
e5dec0c
			parse_text(p);
e5dec0c
			if (curcell->next) {
Ivan Tham da6ddfe
				fprintf(p->output, "\nT}\tT{\n");
e5dec0c
			} else {
e5dec0c
				fprintf(p->output, "\nT}");
e5dec0c
			}
e5dec0c
			struct table_cell *prev = curcell;
e5dec0c
			curcell = curcell->next;
e5dec0c
			str_free(prev->contents);
e5dec0c
			free(prev);
e5dec0c
		}
e5dec0c
		fprintf(p->output, "\n");
e5dec0c
		struct table_row *prev = currow;
e5dec0c
		currow = currow->next;
e5dec0c
		free(prev);
e5dec0c
	}
e5dec0c
e5dec0c
	roff_macro(p, "TE", NULL);
0c25f93
	fprintf(p->output, ".sp 1\n");
e5dec0c
}
e5dec0c
a4c7cc6
static void parse_document(struct parser *p) {
a4c7cc6
	uint32_t ch;
a4c7cc6
	int indent = 0;
a4c7cc6
	do {
5f44aae
		parse_indent(p, &indent, true);
a4c7cc6
		if ((ch = parser_getch(p)) == UTF8_INVALID) {
a4c7cc6
			break;
a4c7cc6
		}
9f74b6a
		switch (ch) {
9744a53
		case ';':
9744a53
			if ((ch = parser_getch(p)) != ' ') {
9744a53
				parser_fatal(p, "Expected space after ; to begin comment");
9744a53
			}
9744a53
			do {
9744a53
				ch = parser_getch(p);
9744a53
			} while (ch != UTF8_INVALID && ch != '\n');
9744a53
			break;
9f74b6a
		case '#':
a4c7cc6
			if (indent != 0) {
a4c7cc6
				parser_pushch(p, ch);
a4c7cc6
				parse_text(p);
a4c7cc6
				break;
a4c7cc6
			}
9f74b6a
			parse_heading(p);
9f74b6a
			break;
a4c7cc6
		case '-':
ac51eba
			parse_list(p, &indent, -1);
ac51eba
			break;
ac51eba
		case '.':
ad035a3
			if ((ch = parser_getch(p)) == ' ') {
ad035a3
				parser_pushch(p, ch);
ad035a3
				parse_list(p, &indent, 1);
ad035a3
			} else {
ad035a3
				parser_pushch(p, ch);
ad035a3
				parse_text(p);
ad035a3
			}
a4c7cc6
			break;
5f44aae
		case '`':
5f44aae
			parse_literal(p, &indent);
5f44aae
			break;
e5dec0c
		case '[':
e5dec0c
		case '|':
e5dec0c
		case ']':
e5dec0c
			if (indent != 0) {
e5dec0c
				parser_fatal(p, "Tables cannot be indented");
e5dec0c
			}
e5dec0c
			parse_table(p, ch);
e5dec0c
			break;
35028f2
		case ' ':
35028f2
			parser_fatal(p, "Tabs are required for indentation");
35028f2
			break;
49e39ef
		case '\n':
1c03e57
			if (p->flags) {
1c03e57
				char error[512];
1c03e57
				snprintf(error, sizeof(error), "Expected %c before starting "
1c03e57
						"new paragraph (began with %c at %d:%d)",
1c03e57
						p->flags == FORMAT_BOLD ? '*' : '_',
1c03e57
						p->flags == FORMAT_BOLD ? '*' : '_',
1c03e57
						p->fmt_line, p->fmt_col);
1c03e57
				parser_fatal(p, error);
1c03e57
			}
49e39ef
			roff_macro(p, "P", NULL);
49e39ef
			break;
9f74b6a
		default:
35028f2
			parser_pushch(p, ch);
9f74b6a
			parse_text(p);
9f74b6a
			break;
9f74b6a
		}
35028f2
	} while (ch != UTF8_INVALID);
9f74b6a
}
9f74b6a
9f74b6a
static void output_scdoc_preamble(struct parser *p) {
d4b6ef1
	fprintf(p->output, ".\\\" Generated by scdoc " VERSION "\n");
8dfd30c
	fprintf(p->output, ".\\\" Complete documentation for this program is not "
8dfd30c
			"available as a GNU info page\n");
a035feb
	// Fix weird quotation marks
a035feb
	// http://bugs.debian.org/507673
a035feb
	// http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
a419394
	fprintf(p->output, ".ie \\n(.g .ds Aq \\(aq\n");
a419394
	fprintf(p->output, ".el       .ds Aq '\n");
a035feb
	// Disable hyphenation:
a419394
	roff_macro(p, "nh", NULL);
a035feb
	// Disable justification:
05694b0
	roff_macro(p, "ad l", NULL);
3320c97
	fprintf(p->output, ".\\\" Begin generated content:\n");
a419394
}
a419394
a419394
int main(int argc, char **argv) {
309ff46
	if (argc == 2 && strcmp(argv[1], "-v") == 0) {
309ff46
		printf("scdoc " VERSION "\n");
309ff46
		return 0;
309ff46
	} else if (argc > 1) {
6cc8714
		fprintf(stderr, "Usage: scdoc < input.scd > output.roff\n");
a419394
		return 1;
a419394
	}
a419394
	struct parser p = {
a419394
		.input = stdin,
a419394
		.output = stdout,
a419394
		.line = 1,
a419394
		.col = 1
a419394
	};
9f74b6a
	output_scdoc_preamble(&p);
a419394
	parse_preamble(&p);
9f74b6a
	parse_document(&p);
a419394
	return 0;
a419394
}