commit e5dec0cbd0b022fbe020c374a9f1f8d649657437
parent 8ed8b6bd77d02c94a37dd7bca9f788c86596380f
Author: Drew DeVault <sir@cmpwn.com>
Date: Thu, 10 May 2018 22:48:27 -0400
Implement tables
Diffstat:
5 files changed, 244 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
@@ -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
diff --git a/include/util.h b/include/util.h
@@ -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
diff --git a/scdoc.1.scd b/scdoc.1.scd
@@ -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
diff --git a/src/main.c b/src/main.c
@@ -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;
diff --git a/src/util.c b/src/util.c
@@ -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);