feat(parser): starting to believe in lists
parent
82d5aa6078
commit
f65942697e
|
@ -50,6 +50,12 @@ mrk_err mrk_parser_eat(mrk_token *out, mrk_parser *parser, mrk_token_type type);
|
||||||
*/
|
*/
|
||||||
void mrk_parser_indent(mrk_parser *parser);
|
void mrk_parser_indent(mrk_parser *parser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse all elements on the parser's current indentation level. If the level is
|
||||||
|
* 0, this will parse the entire file.
|
||||||
|
*/
|
||||||
|
mrk_err mrk_parser_parse_indent_block(mrk_parser *parser, mrk_ast_node *parent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse an entire header block.
|
* Parse an entire header block.
|
||||||
*/
|
*/
|
||||||
|
@ -69,6 +75,13 @@ mrk_err mrk_parser_parse_paragraph(mrk_ast_node *out, mrk_parser *parser);
|
||||||
*/
|
*/
|
||||||
mrk_err mrk_parser_parse_text(mrk_ast_node *out, mrk_parser *parser);
|
mrk_err mrk_parser_parse_text(mrk_ast_node *out, mrk_parser *parser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse any tokens that have the same meaning no matter the context and append
|
||||||
|
* them to the parent. This includes text tokens and links. Only tokens within
|
||||||
|
* the same indentation level are matched.
|
||||||
|
*/
|
||||||
|
mrk_err mrk_parser_parse_common(mrk_parser *parser, mrk_ast_node *parent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a link construct
|
* Parse a link construct
|
||||||
*/
|
*/
|
||||||
|
@ -79,4 +92,6 @@ mrk_err mrk_parser_parse_link(mrk_ast_node *out, mrk_parser *parser);
|
||||||
*/
|
*/
|
||||||
mrk_err mrk_parser_parse_list(mrk_ast_node *out, mrk_parser *parser);
|
mrk_err mrk_parser_parse_list(mrk_ast_node *out, mrk_parser *parser);
|
||||||
|
|
||||||
|
mrk_err mrk_parser_parse_list_item(mrk_ast_node *out, mrk_parser *parser);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -17,12 +17,24 @@ void mrk_parser_open(mrk_parser *parser, mrk_lexer *lexer) {
|
||||||
mrk_err mrk_parser_parse(mrk_ast_node **out, mrk_parser *parser) {
|
mrk_err mrk_parser_parse(mrk_ast_node **out, mrk_parser *parser) {
|
||||||
mrk_ast_node *root;
|
mrk_ast_node *root;
|
||||||
MRK_RES(mrk_ast_node_init(&root));
|
MRK_RES(mrk_ast_node_init(&root));
|
||||||
|
mrk_parser_indent(parser);
|
||||||
|
|
||||||
while (!mrk_parser_done(parser)) {
|
while (!mrk_parser_done(parser)) {
|
||||||
mrk_parser_indent(parser);
|
MRK_RES(mrk_parser_parse_indent_block(parser, root));
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = root;
|
||||||
|
|
||||||
|
return mrk_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrk_err mrk_parser_parse_indent_block(mrk_parser *parser,
|
||||||
|
mrk_ast_node *parent) {
|
||||||
|
size_t indent = parser->indent;
|
||||||
|
|
||||||
|
while (!mrk_parser_done(parser) && parser->indent == indent) {
|
||||||
mrk_ast_node *child;
|
mrk_ast_node *child;
|
||||||
MRK_RES(mrk_ast_node_child_append(&child, root));
|
MRK_RES(mrk_ast_node_child_append(&child, parent));
|
||||||
|
|
||||||
switch (mrk_parser_peek(parser).type) {
|
switch (mrk_parser_peek(parser).type) {
|
||||||
case mrk_token_type_header_start:
|
case mrk_token_type_header_start:
|
||||||
|
@ -32,11 +44,15 @@ mrk_err mrk_parser_parse(mrk_ast_node **out, mrk_parser *parser) {
|
||||||
case mrk_token_type_left_bracket:
|
case mrk_token_type_left_bracket:
|
||||||
MRK_RES(mrk_parser_parse_paragraph(child, parser));
|
MRK_RES(mrk_parser_parse_paragraph(child, parser));
|
||||||
break;
|
break;
|
||||||
|
case mrk_token_type_list_item_unordered:
|
||||||
|
case mrk_token_type_list_item_checked:
|
||||||
|
case mrk_token_type_list_item_unchecked:
|
||||||
|
case mrk_token_type_list_item_ordered:
|
||||||
|
MRK_RES(mrk_parser_parse_list(child, parser));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*out = root;
|
|
||||||
|
|
||||||
return mrk_err_ok;
|
return mrk_err_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,35 +63,11 @@ mrk_err mrk_parser_parse_header(mrk_ast_node *out, mrk_parser *parser) {
|
||||||
out->type = mrk_ast_node_type_header;
|
out->type = mrk_ast_node_type_header;
|
||||||
out->d.header.depth = mrk_token_len(header_token);
|
out->d.header.depth = mrk_token_len(header_token);
|
||||||
|
|
||||||
size_t header_indent = parser->indent;
|
// All continuation lines of the header should be one indentation deeper than
|
||||||
|
// the definition of the header
|
||||||
|
parser->indent++;
|
||||||
|
|
||||||
// Parse subsections of header
|
MRK_RES(mrk_parser_parse_common(parser, out));
|
||||||
while (!mrk_parser_done(parser) && parser->indent == header_indent) {
|
|
||||||
mrk_ast_node *child;
|
|
||||||
|
|
||||||
switch (mrk_parser_peek(parser).type) {
|
|
||||||
case mrk_token_type_text:
|
|
||||||
MRK_RES(mrk_ast_node_child_append(&child, out));
|
|
||||||
MRK_RES(mrk_parser_parse_text(child, parser));
|
|
||||||
break;
|
|
||||||
// Newlines are interpreted as spaces
|
|
||||||
case mrk_token_type_newline:
|
|
||||||
MRK_RES(mrk_ast_node_child_append(&child, out));
|
|
||||||
child->type = mrk_ast_node_type_space;
|
|
||||||
mrk_parser_advance(parser);
|
|
||||||
mrk_parser_indent(parser);
|
|
||||||
break;
|
|
||||||
case mrk_token_type_left_bracket:
|
|
||||||
MRK_RES(mrk_ast_node_child_append(&child, out));
|
|
||||||
MRK_RES(mrk_parser_parse_link(child, parser));
|
|
||||||
break;
|
|
||||||
// Header definition ends at newline
|
|
||||||
case mrk_token_type_blank_line:
|
|
||||||
mrk_parser_advance(parser);
|
|
||||||
mrk_parser_indent(parser);
|
|
||||||
return mrk_err_ok;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mrk_err_ok;
|
return mrk_err_ok;
|
||||||
}
|
}
|
||||||
|
@ -91,6 +83,40 @@ mrk_err mrk_parser_parse_text(mrk_ast_node *out, mrk_parser *parser) {
|
||||||
return mrk_err_ok;
|
return mrk_err_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mrk_err mrk_parser_parse_common(mrk_parser *parser, mrk_ast_node *parent) {
|
||||||
|
size_t indent = parser->indent;
|
||||||
|
|
||||||
|
while (!mrk_parser_done(parser) && parser->indent == indent) {
|
||||||
|
mrk_ast_node *child;
|
||||||
|
|
||||||
|
switch (mrk_parser_peek(parser).type) {
|
||||||
|
case mrk_token_type_text:
|
||||||
|
MRK_RES(mrk_ast_node_child_append(&child, parent));
|
||||||
|
MRK_RES(mrk_parser_parse_text(child, parser));
|
||||||
|
break;
|
||||||
|
case mrk_token_type_left_bracket:
|
||||||
|
MRK_RES(mrk_ast_node_child_append(&child, parent));
|
||||||
|
MRK_RES(mrk_parser_parse_link(child, parser));
|
||||||
|
break;
|
||||||
|
case mrk_token_type_newline:
|
||||||
|
MRK_RES(mrk_ast_node_child_append(&child, parent));
|
||||||
|
child->type = mrk_ast_node_type_space;
|
||||||
|
mrk_parser_advance(parser);
|
||||||
|
mrk_parser_indent(parser);
|
||||||
|
break;
|
||||||
|
case mrk_token_type_blank_line:
|
||||||
|
mrk_parser_advance(parser);
|
||||||
|
mrk_parser_indent(parser);
|
||||||
|
return mrk_err_ok;
|
||||||
|
// Any other tokens aren't part of the common section so we just exit
|
||||||
|
default:
|
||||||
|
return mrk_err_ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mrk_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
mrk_err mrk_parser_parse_link(mrk_ast_node *out, mrk_parser *parser) {
|
mrk_err mrk_parser_parse_link(mrk_ast_node *out, mrk_parser *parser) {
|
||||||
out->type = mrk_ast_node_type_link;
|
out->type = mrk_ast_node_type_link;
|
||||||
|
|
||||||
|
@ -132,29 +158,56 @@ mrk_err mrk_parser_parse_link(mrk_ast_node *out, mrk_parser *parser) {
|
||||||
mrk_err mrk_parser_parse_paragraph(mrk_ast_node *out, mrk_parser *parser) {
|
mrk_err mrk_parser_parse_paragraph(mrk_ast_node *out, mrk_parser *parser) {
|
||||||
out->type = mrk_ast_node_type_paragraph;
|
out->type = mrk_ast_node_type_paragraph;
|
||||||
|
|
||||||
|
MRK_RES(mrk_parser_parse_common(parser, out));
|
||||||
|
|
||||||
|
return mrk_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrk_err mrk_parser_parse_list(mrk_ast_node *out, mrk_parser *parser) {
|
||||||
|
out->type = mrk_ast_node_type_list;
|
||||||
|
|
||||||
size_t indent = parser->indent;
|
size_t indent = parser->indent;
|
||||||
|
|
||||||
while (!mrk_parser_done(parser) && parser->indent == indent) {
|
switch (mrk_parser_peek(parser).type) {
|
||||||
mrk_ast_node *child;
|
case mrk_token_type_list_item_unordered:
|
||||||
|
case mrk_token_type_list_item_checked:
|
||||||
|
case mrk_token_type_list_item_unchecked:
|
||||||
|
out->d.list.ordered = false;
|
||||||
|
break;
|
||||||
|
case mrk_token_type_list_item_ordered:
|
||||||
|
out->d.list.ordered = true;
|
||||||
|
break;
|
||||||
|
// TODO error on default
|
||||||
|
}
|
||||||
|
|
||||||
|
mrk_ast_node *child;
|
||||||
|
MRK_RES(mrk_ast_node_child_append(&child, out));
|
||||||
|
MRK_RES(mrk_parser_parse_list_item(child, parser));
|
||||||
|
|
||||||
|
while (!mrk_parser_done(parser) && parser->indent == indent) {
|
||||||
switch (mrk_parser_peek(parser).type) {
|
switch (mrk_parser_peek(parser).type) {
|
||||||
case mrk_token_type_text:
|
case mrk_token_type_list_item_unordered:
|
||||||
|
case mrk_token_type_list_item_checked:
|
||||||
|
case mrk_token_type_list_item_unchecked:
|
||||||
|
// Two lists of different types can follow up on each other
|
||||||
|
if (out->d.list.ordered) {
|
||||||
|
return mrk_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
MRK_RES(mrk_ast_node_child_append(&child, out));
|
MRK_RES(mrk_ast_node_child_append(&child, out));
|
||||||
MRK_RES(mrk_parser_parse_text(child, parser));
|
MRK_RES(mrk_parser_parse_list_item(child, parser));
|
||||||
break;
|
break;
|
||||||
case mrk_token_type_left_bracket:
|
case mrk_token_type_list_item_ordered:
|
||||||
|
// Two lists of different types can follow up on each other
|
||||||
|
if (!out->d.list.ordered) {
|
||||||
|
return mrk_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
MRK_RES(mrk_ast_node_child_append(&child, out));
|
MRK_RES(mrk_ast_node_child_append(&child, out));
|
||||||
MRK_RES(mrk_parser_parse_link(child, parser));
|
MRK_RES(mrk_parser_parse_list_item(child, parser));
|
||||||
break;
|
break;
|
||||||
case mrk_token_type_newline:
|
// Other tokens imply end of list
|
||||||
MRK_RES(mrk_ast_node_child_append(&child, out));
|
default:
|
||||||
child->type = mrk_ast_node_type_space;
|
|
||||||
mrk_parser_advance(parser);
|
|
||||||
mrk_parser_indent(parser);
|
|
||||||
break;
|
|
||||||
case mrk_token_type_blank_line:
|
|
||||||
mrk_parser_advance(parser);
|
|
||||||
mrk_parser_indent(parser);
|
|
||||||
return mrk_err_ok;
|
return mrk_err_ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,6 +215,39 @@ mrk_err mrk_parser_parse_paragraph(mrk_ast_node *out, mrk_parser *parser) {
|
||||||
return mrk_err_ok;
|
return mrk_err_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
mrk_err mrk_parser_parse_list(mrk_ast_node *out, mrk_parser *parser) {
|
mrk_err mrk_parser_parse_list_item(mrk_ast_node *out, mrk_parser *parser) {
|
||||||
return mrk_err_invalid;
|
out->type = mrk_ast_node_type_list_item;
|
||||||
|
|
||||||
|
size_t indent = parser->indent;
|
||||||
|
|
||||||
|
switch (mrk_parser_peek(parser).type) {
|
||||||
|
case mrk_token_type_list_item_unordered:
|
||||||
|
case mrk_token_type_list_item_ordered:
|
||||||
|
out->d.list_item.checkbox = false;
|
||||||
|
out->d.list_item.checked = false;
|
||||||
|
break;
|
||||||
|
case mrk_token_type_list_item_checked:
|
||||||
|
out->d.list_item.checkbox = true;
|
||||||
|
out->d.list_item.checked = true;
|
||||||
|
break;
|
||||||
|
case mrk_token_type_list_item_unchecked:
|
||||||
|
out->d.list_item.checkbox = true;
|
||||||
|
out->d.list_item.checked = false;
|
||||||
|
break;
|
||||||
|
// This path should never be taken
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrk_parser_advance(parser);
|
||||||
|
|
||||||
|
// Parse the textual part of the list
|
||||||
|
parser->indent++;
|
||||||
|
MRK_RES(mrk_parser_parse_common(parser, out));
|
||||||
|
|
||||||
|
// This allows lists to be arbitrarily nested
|
||||||
|
if (!mrk_parser_done(parser) && parser->indent > indent) {
|
||||||
|
MRK_RES(mrk_parser_parse_indent_block(parser, out));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mrk_err_ok;
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ void test_parse_link() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_parse_unordered_list() {
|
void test_parse_unordered_list() {
|
||||||
const char *buf = "* element one\n* element two\n paragraph in element two";
|
const char *buf = "* element one\n* element two\n\n paragraph in element two";
|
||||||
PARSER_OPEN(buf);
|
PARSER_OPEN(buf);
|
||||||
|
|
||||||
mrk_ast_node *list;
|
mrk_ast_node *list;
|
||||||
|
@ -94,8 +94,8 @@ void test_parse_unordered_list() {
|
||||||
TEST_CHECK(subchild->children.len == 1);
|
TEST_CHECK(subchild->children.len == 1);
|
||||||
|
|
||||||
TEST_CHECK(subchild->children.arr[0]->type == mrk_ast_node_type_text);
|
TEST_CHECK(subchild->children.arr[0]->type == mrk_ast_node_type_text);
|
||||||
TEST_CHECK(subchild->children.arr[0]->d.text.start == 31);
|
TEST_CHECK(subchild->children.arr[0]->d.text.start == 33);
|
||||||
TEST_CHECK(subchild->children.arr[0]->d.text.end == 55);
|
TEST_CHECK(subchild->children.arr[0]->d.text.end == 57);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_LIST = {
|
TEST_LIST = {
|
||||||
|
|
Loading…
Reference in New Issue