~sircmpwn/scdoc

e5dec0cbd0b022fbe020c374a9f1f8d649657437 — Drew DeVault 3 years ago 8ed8b6b 1.3.0
Implement tables
5 files changed, 244 insertions(+), 1 deletions(-)

M Makefile
M include/util.h
M scdoc.1.scd
M src/main.c
M src/util.c
M Makefile => Makefile +1 -1
@@ 1,5 1,5 @@
VERSION=1.2.3
CFLAGS=-DVERSION='"$(VERSION)"' -Wall -Wextra -Werror -Wno-unused-parameter
CFLAGS=-g -DVERSION='"$(VERSION)"' -Wall -Wextra -Werror -Wno-unused-parameter
LDFLAGS=-static
INCLUDE=-Iinclude
PREFIX=/usr/local

M include/util.h => include/util.h +2 -0
@@ 10,6 10,7 @@ struct parser {
	int qhead;
	uint32_t queue[32];
	uint32_t flags;
	const char *str;
};

enum formatting {


@@ 21,6 22,7 @@ enum formatting {
void parser_fatal(struct parser *parser, const char *err);
uint32_t parser_getch(struct parser *parser);
void parser_pushch(struct parser *parser, uint32_t ch);
void parser_pushstr(struct parser *parser, const char *str);
int roff_macro(struct parser *p, char *cmd, ...);

#endif

M scdoc.1.scd => scdoc.1.scd +39 -0
@@ 112,6 112,45 @@ of dashes (-), like so:
. Item 3,
  with multiple lines

## TABLES

To begin a table, add an empty line followed by any number of rows.

Each line of a table should start with | or : to start a new row or column
respectively, followed by [ or - or ] to align the contents to the left,
center, or right, followed by a space and the contents of that cell.  You may
use a space instead of an alignment specifier to inherit the alignment of the
same column in the previous row.

The first character of the first row is not limited to | and has special
meaning. [ will produce a table with borders around each cell. | will produce a
table with no borders. ] will produce a table with one border around the whole
table.

To conclude your table, add an empty line after the last row.

```
[[ *Foo*
:- _Bar_
:- _Baz_
|  *Row 1*
:  Hello
:] world!
|  *Row 2*
:  こんにちは
:  世界
```

[[ *Foo*
:- _Bar_
:- _Baz_
|  *Row 1*
:  Hello
:] world!
|  *Row 2*
:  こんにちは
:  世界

## LITERAL TEXT

You may turn off scdoc formatting and output literal text with escape codes and

M src/main.c => src/main.c +190 -0
@@ 9,6 9,8 @@
#include "unicode.h"
#include "util.h"

char *strstr(const char *haystack, const char *needle);

static int parse_section(struct parser *p) {
	str_t *section = str_create();
	uint32_t ch;


@@ 300,6 302,186 @@ static void parse_literal(struct parser *p, int *indent) {
	} while (ch != UTF8_INVALID);
}

enum table_align {
	ALIGN_LEFT,
	ALIGN_CENTER,
	ALIGN_RIGHT,
};

struct table_row {
	struct table_cell *cell;
	struct table_row *next;
};

struct table_cell {
	enum table_align align;
	str_t *contents;
	struct table_cell *next;
};

static void parse_table(struct parser *p, uint32_t style) {
	struct table_row *table = NULL;
	struct table_row *currow = NULL, *prevrow = NULL;
	struct table_cell *curcell = NULL;
	int column = 0;
	uint32_t ch;
	parser_pushch(p, '|');

	do {
		if ((ch = parser_getch(p)) == UTF8_INVALID) {
			break;
		}
		switch (ch) {
		case '\n':
			goto commit_table;
		case '|':
			prevrow = currow;
			currow = calloc(1, sizeof(struct table_row));
			if (prevrow) {
				// TODO: Verify the number of columns match
				prevrow->next = currow;
			}
			curcell = calloc(1, sizeof(struct table_cell));
			currow->cell = curcell;
			column = 0;
			if (!table) {
				table = currow;
			}
			break;
		case ':':
			if (!currow) {
				parser_fatal(p, "Cannot start a column without "
						"starting a row first");
			} else {
				struct table_cell *prev = curcell;
				curcell = calloc(1, sizeof(struct table_cell));
				if (prev) {
					prev->next = curcell;
				}
				++column;
			}
			break;
		default:
			parser_fatal(p, "Expected either '|' or ':'");
			break;
		}
		if ((ch = parser_getch(p)) == UTF8_INVALID) {
			break;
		}
		switch (ch) {
		case '[':
			curcell->align = ALIGN_LEFT;
			break;
		case '-':
			curcell->align = ALIGN_CENTER;
			break;
		case ']':
			curcell->align = ALIGN_RIGHT;
			break;
		case ' ':
			if (prevrow) {
				struct table_cell *pcell = prevrow->cell;
				for (int i = 0; i <= column && pcell; ++i, pcell = pcell->next) {
					if (i == column) {
						curcell->align = pcell->align;
						break;
					}
				}
			} else {
				parser_fatal(p, "No previous row to infer alignment from");
			}
			break;
		default:
			parser_fatal(p, "Expected one of '[', '-', ']', or ' '");
			break;
		}
		if ((ch = parser_getch(p)) != ' ') {
			parser_fatal(p, "Expected ' '");
			break;
		}
		// Read out remainder of the text
		curcell->contents = str_create();
		while ((ch = parser_getch(p)) != UTF8_INVALID) {
			switch (ch) {
			case '\n':
				goto commit_cell;
			default:
				assert(str_append_ch(curcell->contents, ch) != -1);
				break;
			}
		}
commit_cell:
		if (strstr(curcell->contents->str, "T{")
				|| strstr(curcell->contents->str, "T}")) {
			parser_fatal(p, "Cells cannot contain T{ or T} "
					"due to roff limitations");
		}
	} while (ch != UTF8_INVALID);
commit_table:

	if (ch == UTF8_INVALID) {
		return;
	}

	roff_macro(p, "TS", NULL);

	const char *_style = NULL;
	switch (style) {
	case '[':
		_style = "allbox ";
		break;
	case '|':
		_style = "";
		break;
	case ']':
		_style = "box ";
		break;
	}

	fprintf(p->output, "%s tab(:);\n", _style);

	// Print alignments first
	currow = table;
	while (currow) {
		curcell = currow->cell;
		while (curcell) {
			fprintf(p->output, "%c%s", "lcr"[curcell->align],
					curcell->next ? " " : "");
			curcell = curcell->next;
		}
		fprintf(p->output, "%s\n", currow->next ? "" : ".");
		currow = currow->next;
	}

	// Then contents
	currow = table;
	while (currow) {
		curcell = currow->cell;
		fprintf(p->output, "T{\n");
		while (curcell) {
			parser_pushstr(p, curcell->contents->str);
			parse_text(p);
			if (curcell->next) {
				fprintf(p->output, "\nT}:T{\n");
			} else {
				fprintf(p->output, "\nT}");
			}
			struct table_cell *prev = curcell;
			curcell = curcell->next;
			str_free(prev->contents);
			free(prev);
		}
		fprintf(p->output, "\n");
		struct table_row *prev = currow;
		currow = currow->next;
		free(prev);
	}

	roff_macro(p, "TE", NULL);
	roff_macro(p, "sp", "1", NULL);
	roff_macro(p, "RE", NULL);
}

static void parse_document(struct parser *p) {
	uint32_t ch;
	int indent = 0;


@@ 332,6 514,14 @@ static void parse_document(struct parser *p) {
		case '`':
			parse_literal(p, &indent);
			break;
		case '[':
		case '|':
		case ']':
			if (indent != 0) {
				parser_fatal(p, "Tables cannot be indented");
			}
			parse_table(p, ch);
			break;
		case ' ':
			parser_fatal(p, "Tabs are required for indentation");
			break;

M src/util.c => src/util.c +12 -0
@@ 17,6 17,14 @@ uint32_t parser_getch(struct parser *parser) {
	if (parser->qhead) {
		return parser->queue[--parser->qhead];
	}
	if (parser->str) {
		uint32_t ch = utf8_decode(&parser->str);
		if (!ch || ch == UTF8_INVALID) {
			parser->str = NULL;
			return UTF8_INVALID;
		}
		return ch;
	}
	uint32_t ch = utf8_fgetch(parser->input);
	if (ch == '\n') {
		parser->col = 0;


@@ 33,6 41,10 @@ void parser_pushch(struct parser *parser, uint32_t ch) {
	}
}

void parser_pushstr(struct parser *parser, const char *str) {
	parser->str = str;
}

int roff_macro(struct parser *p, char *cmd, ...) {
	FILE *f = p->output;
	int l = fprintf(f, ".%s", cmd);