Implement tables

master 1.3.0
Drew DeVault 7 years ago
parent 8ed8b6bd77
commit e5dec0cbd0
  1. 2
      Makefile
  2. 2
      include/util.h
  3. 39
      scdoc.1.scd
  4. 190
      src/main.c
  5. 12
      src/util.c

@ -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

@ -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

@ -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

@ -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;

@ -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);

Loading…
Cancel
Save