diff options
-rw-r--r-- | src/blocks.c | 337 | ||||
-rw-r--r-- | src/buffer.c | 2 | ||||
-rw-r--r-- | src/buffer.h | 2 | ||||
-rw-r--r-- | src/cmark.h | 35 | ||||
-rw-r--r-- | src/main.c | 70 |
5 files changed, 262 insertions, 184 deletions
diff --git a/src/blocks.c b/src/blocks.c index 604b933..568af0a 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -10,13 +10,10 @@ #include "inlines.h" #include "html/houdini.h" #include "buffer.h" -#include "bench.h" +#include "debug.h" #define peek_at(i, n) (i)->data[n] -static void incorporate_line(strbuf *ln, int line_number, node_block** curptr); -static void finalize(node_block* b, int line_number); - static node_block* make_block(int tag, int start_line, int start_column) { node_block* e; @@ -44,18 +41,42 @@ static node_block* make_document() return e; } +cmark_doc_parser *cmark_new_doc_parser() +{ + cmark_doc_parser *parser = (cmark_doc_parser*)malloc(sizeof(cmark_doc_parser)); + node_block *document = make_document(); + strbuf *line = (strbuf*)malloc(sizeof(strbuf)); + cmark_strbuf_init(line, 256); + + parser->head = document; + parser->current = document; + parser->line_number = 0; + parser->curline = line; + + return parser; +} + +void cmark_free_doc_parser(cmark_doc_parser *parser) +{ + cmark_strbuf_free(parser->curline); + free(parser->curline); + free(parser); +} + +static void finalize(node_block* b, int line_number); + // Returns true if line has only space characters, else false. static bool is_blank(strbuf *s, int offset) { while (offset < s->size) { switch (s->ptr[offset]) { - case '\n': - return true; - case ' ': - offset++; - break; - default: - return false; + case '\n': + return true; + case ' ': + offset++; + break; + default: + return false; } } @@ -65,17 +86,17 @@ static bool is_blank(strbuf *s, int offset) static inline bool can_contain(int parent_type, int child_type) { return ( parent_type == BLOCK_DOCUMENT || - parent_type == BLOCK_BQUOTE || - parent_type == BLOCK_LIST_ITEM || - (parent_type == BLOCK_LIST && child_type == BLOCK_LIST_ITEM) ); + parent_type == BLOCK_BQUOTE || + parent_type == BLOCK_LIST_ITEM || + (parent_type == BLOCK_LIST && child_type == BLOCK_LIST_ITEM) ); } static inline bool accepts_lines(int block_type) { return (block_type == BLOCK_PARAGRAPH || - block_type == BLOCK_ATX_HEADER || - block_type == BLOCK_INDENTED_CODE || - block_type == BLOCK_FENCED_CODE); + block_type == BLOCK_ATX_HEADER || + block_type == BLOCK_INDENTED_CODE || + block_type == BLOCK_FENCED_CODE); } static void add_line(node_block* node_block, chunk *ch, int offset) @@ -158,77 +179,77 @@ static void finalize(node_block* b, int line_number) } switch (b->tag) { - case BLOCK_PARAGRAPH: - pos = 0; - while (strbuf_at(&b->string_content, 0) == '[' && - (pos = parse_reference_inline(&b->string_content, b->top->as.document.refmap))) { + case BLOCK_PARAGRAPH: + pos = 0; + while (strbuf_at(&b->string_content, 0) == '[' && + (pos = parse_reference_inline(&b->string_content, b->top->as.document.refmap))) { - strbuf_drop(&b->string_content, pos); - } - if (is_blank(&b->string_content, 0)) { - b->tag = BLOCK_REFERENCE_DEF; - } - break; - - case BLOCK_INDENTED_CODE: - remove_trailing_blank_lines(&b->string_content); - strbuf_putc(&b->string_content, '\n'); - break; - - case BLOCK_FENCED_CODE: - // first line of contents becomes info - firstlinelen = strbuf_strchr(&b->string_content, '\n', 0); - - strbuf_init(&b->as.code.info, 0); - houdini_unescape_html_f( - &b->as.code.info, - b->string_content.ptr, - firstlinelen - ); - - strbuf_drop(&b->string_content, firstlinelen + 1); - - strbuf_trim(&b->as.code.info); - strbuf_unescape(&b->as.code.info); - break; - - case BLOCK_LIST: // determine tight/loose status - b->as.list.tight = true; // tight by default - item = b->children; - - while (item) { - // check for non-final non-empty list item ending with blank line: - if (item->last_line_blank && item->next) { - b->as.list.tight = false; - break; + strbuf_drop(&b->string_content, pos); } - // recurse into children of list item, to see if there are - // spaces between them: - subitem = item->children; - while (subitem) { - if (ends_with_blank_line(subitem) && - (item->next || subitem->next)) { + if (is_blank(&b->string_content, 0)) { + b->tag = BLOCK_REFERENCE_DEF; + } + break; + + case BLOCK_INDENTED_CODE: + remove_trailing_blank_lines(&b->string_content); + strbuf_putc(&b->string_content, '\n'); + break; + + case BLOCK_FENCED_CODE: + // first line of contents becomes info + firstlinelen = strbuf_strchr(&b->string_content, '\n', 0); + + strbuf_init(&b->as.code.info, 0); + houdini_unescape_html_f( + &b->as.code.info, + b->string_content.ptr, + firstlinelen + ); + + strbuf_drop(&b->string_content, firstlinelen + 1); + + strbuf_trim(&b->as.code.info); + strbuf_unescape(&b->as.code.info); + break; + + case BLOCK_LIST: // determine tight/loose status + b->as.list.tight = true; // tight by default + item = b->children; + + while (item) { + // check for non-final non-empty list item ending with blank line: + if (item->last_line_blank && item->next) { b->as.list.tight = false; break; } - subitem = subitem->next; - } - if (!(b->as.list.tight)) { - break; + // recurse into children of list item, to see if there are + // spaces between them: + subitem = item->children; + while (subitem) { + if (ends_with_blank_line(subitem) && + (item->next || subitem->next)) { + b->as.list.tight = false; + break; + } + subitem = subitem->next; + } + if (!(b->as.list.tight)) { + break; + } + item = item->next; } - item = item->next; - } - break; + break; - default: - break; + default: + break; } } // Add a node_block as child of another. Return pointer to child. static node_block* add_child(node_block* parent, - int block_type, int start_line, int start_column) + int block_type, int start_line, int start_column) { assert(parent); @@ -269,14 +290,14 @@ static void process_inlines(node_block* cur, reference_map *refmap) while (cur != NULL) { switch (cur->tag) { - case BLOCK_PARAGRAPH: - case BLOCK_ATX_HEADER: - case BLOCK_SETEXT_HEADER: - cur->inline_content = parse_inlines(&cur->string_content, refmap); - break; + case BLOCK_PARAGRAPH: + case BLOCK_ATX_HEADER: + case BLOCK_SETEXT_HEADER: + cur->inline_content = parse_inlines(&cur->string_content, refmap); + break; - default: - break; + default: + break; } if (cur->children) { @@ -373,14 +394,13 @@ static int parse_list_marker(chunk *input, int pos, struct ListData ** dataptr) static int lists_match(struct ListData *list_data, struct ListData *item_data) { return (list_data->list_type == item_data->list_type && - list_data->delimiter == item_data->delimiter && - // list_data->marker_offset == item_data.marker_offset && - list_data->bullet_char == item_data->bullet_char); + list_data->delimiter == item_data->delimiter && + // list_data->marker_offset == item_data.marker_offset && + list_data->bullet_char == item_data->bullet_char); } static node_block *finalize_document(node_block *document, int linenum) { - start_timer(); while (document != document->top) { finalize(document, linenum); document = document->parent; @@ -388,56 +408,46 @@ static node_block *finalize_document(node_block *document, int linenum) finalize(document, linenum); process_inlines(document, document->as.document.refmap); - end_timer("finalize_document"); return document; } extern node_block *cmark_parse_file(FILE *f) { - strbuf line = GH_BUF_INIT; unsigned char buffer[4096]; - int linenum = 1; - node_block *document = make_document(); + cmark_doc_parser *parser = cmark_new_doc_parser(); + size_t offset; + node_block *document; - start_timer(); while (fgets((char *)buffer, sizeof(buffer), f)) { - utf8proc_detab(&line, buffer, strlen((char *)buffer)); - incorporate_line(&line, linenum, &document); - strbuf_clear(&line); - linenum++; + offset = strlen((char *)buffer); + cmark_process_line(parser, buffer, offset); } - end_timer("incorporate_line(s)"); - strbuf_free(&line); - return finalize_document(document, linenum); + document = cmark_finish(parser); + cmark_free_doc_parser(parser); + return document; } extern node_block *cmark_parse_document(const unsigned char *buffer, size_t len) { - strbuf line = GH_BUF_INIT; int linenum = 1; const unsigned char *end = buffer + len; - node_block *document = make_document(); + size_t offset; + cmark_doc_parser *parser = cmark_new_doc_parser(); + node_block *document; while (buffer < end) { const unsigned char *eol = memchr(buffer, '\n', end - buffer); - - if (!eol) { - utf8proc_detab(&line, buffer, end - buffer); - buffer = end; - } else { - utf8proc_detab(&line, buffer, (eol - buffer) + 1); - buffer += (eol - buffer) + 1; - } - - incorporate_line(&line, linenum, &document); - strbuf_clear(&line); + offset = eol ? (eol - buffer) + 1 : eol - buffer; + cmark_process_line(parser, buffer, offset); + buffer += offset; linenum++; } - strbuf_free(&line); - return finalize_document(document, linenum); + document = cmark_finish(parser); + cmark_free_doc_parser(parser); + return document; } static void chop_trailing_hashtags(chunk *ch) @@ -458,8 +468,8 @@ static void chop_trailing_hashtags(chunk *ch) } } -// Process one line at a time, modifying a node_block. -static void incorporate_line(strbuf *line, int line_number, node_block** curptr) +void cmark_process_line(cmark_doc_parser *parser, const unsigned char *buffer, + size_t bytes) { node_block* last_matched_container; int offset = 0; @@ -469,22 +479,27 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) struct ListData * data = NULL; bool all_matched = true; node_block* container; - node_block* cur = *curptr; + node_block* cur = parser->current; bool blank = false; int first_nonspace; int indent; chunk input; + utf8proc_detab(parser->curline, buffer, bytes); + // Add a newline to the end if not present: - if (line->ptr[line->size - 1] != '\n') { - strbuf_putc(line, '\n'); + // TODO this breaks abstraction: + if (parser->curline->ptr[parser->curline->size - 1] != '\n') { + strbuf_putc(parser->curline, '\n'); } - input.data = line->ptr; - input.len = line->size; + input.data = parser->curline->ptr; + input.len = parser->curline->size; // container starts at the document root. container = cur->top; + parser->line_number++; + // for each containing node_block, try to parse the associated line start. // bail out on failure: container will point to the last matching node_block. @@ -512,7 +527,7 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) } else if (container->tag == BLOCK_LIST_ITEM) { if (indent >= container->as.list.marker_offset + - container->as.list.padding) { + container->as.list.padding) { offset += container->as.list.marker_offset + container->as.list.padding; } else if (blank) { @@ -532,7 +547,7 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) } } else if (container->tag == BLOCK_ATX_HEADER || - container->tag == BLOCK_SETEXT_HEADER) { + container->tag == BLOCK_SETEXT_HEADER) { // a header can never contain more than one line all_matched = false; @@ -571,12 +586,12 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) // check to see if we've hit 2nd blank line, break out of list: if (blank && container->last_line_blank) { - break_out_of_lists(&container, line_number); + break_out_of_lists(&container, parser->line_number); } // unless last matched container is code node_block, try new container starts: while (container->tag != BLOCK_FENCED_CODE && container->tag != BLOCK_INDENTED_CODE && - container->tag != BLOCK_HTML) { + container->tag != BLOCK_HTML) { first_nonspace = offset; while (peek_at(&input, first_nonspace) == ' ') @@ -588,7 +603,7 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) if (indent >= CODE_INDENT) { if (cur->tag != BLOCK_PARAGRAPH && !blank) { offset += CODE_INDENT; - container = add_child(container, BLOCK_INDENTED_CODE, line_number, offset + 1); + container = add_child(container, BLOCK_INDENTED_CODE, parser->line_number, offset + 1); } else { // indent > 4 in lazy line break; } @@ -599,12 +614,12 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) // optional following character if (peek_at(&input, offset) == ' ') offset++; - container = add_child(container, BLOCK_BQUOTE, line_number, offset + 1); + container = add_child(container, BLOCK_BQUOTE, parser->line_number, offset + 1); } else if ((matched = scan_atx_header_start(&input, first_nonspace))) { offset = first_nonspace + matched; - container = add_child(container, BLOCK_ATX_HEADER, line_number, offset + 1); + container = add_child(container, BLOCK_ATX_HEADER, parser->line_number, offset + 1); int hashpos = chunk_strchr(&input, '#', first_nonspace); int level = 0; @@ -617,7 +632,7 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) } else if ((matched = scan_open_code_fence(&input, first_nonspace))) { - container = add_child(container, BLOCK_FENCED_CODE, line_number, first_nonspace + 1); + container = add_child(container, BLOCK_FENCED_CODE, parser->line_number, first_nonspace + 1); container->as.code.fence_char = peek_at(&input, first_nonspace); container->as.code.fence_length = matched; container->as.code.fence_offset = first_nonspace - offset; @@ -625,25 +640,25 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) } else if ((matched = scan_html_block_tag(&input, first_nonspace))) { - container = add_child(container, BLOCK_HTML, line_number, first_nonspace + 1); + container = add_child(container, BLOCK_HTML, parser->line_number, first_nonspace + 1); // note, we don't adjust offset because the tag is part of the text } else if (container->tag == BLOCK_PARAGRAPH && - (lev = scan_setext_header_line(&input, first_nonspace)) && - // check that there is only one line in the paragraph: - strbuf_strrchr(&container->string_content, '\n', - strbuf_len(&container->string_content) - 2) < 0) { + (lev = scan_setext_header_line(&input, first_nonspace)) && + // check that there is only one line in the paragraph: + strbuf_strrchr(&container->string_content, '\n', + strbuf_len(&container->string_content) - 2) < 0) { container->tag = BLOCK_SETEXT_HEADER; container->as.header.level = lev; offset = input.len - 1; } else if (!(container->tag == BLOCK_PARAGRAPH && !all_matched) && - (matched = scan_hrule(&input, first_nonspace))) { + (matched = scan_hrule(&input, first_nonspace))) { // it's only now that we know the line is not part of a setext header: - container = add_child(container, BLOCK_HRULE, line_number, first_nonspace + 1); - finalize(container, line_number); + container = add_child(container, BLOCK_HRULE, parser->line_number, first_nonspace + 1); + finalize(container, parser->line_number); container = container->parent; offset = input.len - 1; @@ -672,16 +687,16 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) data->marker_offset = indent; if (container->tag != BLOCK_LIST || - !lists_match(&container->as.list, data)) { - container = add_child(container, BLOCK_LIST, line_number, - first_nonspace + 1); + !lists_match(&container->as.list, data)) { + container = add_child(container, BLOCK_LIST, parser->line_number, + first_nonspace + 1); memcpy(&container->as.list, data, sizeof(*data)); } // add the list item - container = add_child(container, BLOCK_LIST_ITEM, line_number, - first_nonspace + 1); + container = add_child(container, BLOCK_LIST_ITEM, parser->line_number, + first_nonspace + 1); /* TODO: static */ memcpy(&container->as.list, data, sizeof(*data)); free(data); @@ -710,11 +725,11 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) // lists or breaking out of lists. we also don't set last_line_blank // on an empty list item. container->last_line_blank = (blank && - container->tag != BLOCK_BQUOTE && - container->tag != BLOCK_FENCED_CODE && - !(container->tag == BLOCK_LIST_ITEM && - container->children == NULL && - container->start_line == line_number)); + container->tag != BLOCK_BQUOTE && + container->tag != BLOCK_FENCED_CODE && + !(container->tag == BLOCK_LIST_ITEM && + container->children == NULL && + container->start_line == parser->line_number)); node_block *cont = container; while (cont->parent) { @@ -723,10 +738,10 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) } if (cur != last_matched_container && - container == last_matched_container && - !blank && - cur->tag == BLOCK_PARAGRAPH && - strbuf_len(&cur->string_content) > 0) { + container == last_matched_container && + !blank && + cur->tag == BLOCK_PARAGRAPH && + strbuf_len(&cur->string_content) > 0) { add_line(cur, &input, offset); @@ -734,7 +749,7 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) // finalize any blocks that were not matched and set cur to container: while (cur != last_matched_container) { - finalize(cur, line_number); + finalize(cur, parser->line_number); cur = cur->parent; assert(cur != NULL); } @@ -747,7 +762,7 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) matched = 0; if (indent <= 3 && - peek_at(&input, first_nonspace) == container->as.code.fence_char) { + peek_at(&input, first_nonspace) == container->as.code.fence_char) { int fence_len = scan_close_code_fence(&input, first_nonspace); if (fence_len > container->as.code.fence_length) matched = 1; @@ -755,7 +770,7 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) if (matched) { // if closing fence, don't add line to container; instead, close it: - finalize(container, line_number); + finalize(container, parser->line_number); container = container->parent; // back up to parent } else { add_line(container, &input, offset); @@ -773,7 +788,7 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) chop_trailing_hashtags(&input); add_line(container, &input, first_nonspace); - finalize(container, line_number); + finalize(container, parser->line_number); container = container->parent; } else if (accepts_lines(container->tag)) { @@ -783,13 +798,23 @@ static void incorporate_line(strbuf *line, int line_number, node_block** curptr) } else if (container->tag != BLOCK_HRULE && container->tag != BLOCK_SETEXT_HEADER) { // create paragraph container for line - container = add_child(container, BLOCK_PARAGRAPH, line_number, first_nonspace + 1); + container = add_child(container, BLOCK_PARAGRAPH, parser->line_number, first_nonspace + 1); add_line(container, &input, first_nonspace); } else { assert(false); } - *curptr = container; + parser->current = container; } + strbuf_clear(parser->curline); + } + +node_block *cmark_finish(cmark_doc_parser *parser) +{ + finalize_document(parser->current, parser->line_number); + strbuf_free(parser->curline); + return parser->head; +} + diff --git a/src/buffer.c b/src/buffer.c index 6219935..6bc403e 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -205,7 +205,7 @@ int cmark_strbuf_printf(strbuf *buf, const char *format, ...) return r; } -static inline const char *cmark_strbuf_cstr(const cmark_strbuf *buf) +inline const char *cmark_strbuf_cstr(const cmark_strbuf *buf) { return (char *)buf->ptr; } diff --git a/src/buffer.h b/src/buffer.h index fc0a6c2..7dfc600 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -81,6 +81,8 @@ void cmark_strbuf_attach(cmark_strbuf *buf, unsigned char *ptr, int asize); CMARK_EXPORT unsigned char *cmark_strbuf_detach(cmark_strbuf *buf); CMARK_EXPORT +inline const char *cmark_strbuf_cstr(const cmark_strbuf *buf); +CMARK_EXPORT void cmark_strbuf_copy_cstr(char *data, int datasize, const cmark_strbuf *buf); #define cmark_strbuf_at(buf, n) ((buf)->ptr[n]) diff --git a/src/cmark.h b/src/cmark.h index cb9ea73..ef9bdfb 100644 --- a/src/cmark.h +++ b/src/cmark.h @@ -111,6 +111,15 @@ struct cmark_node_block { typedef struct cmark_node_block cmark_node_block; +struct cmark_doc_parser { + cmark_node_block* head; + cmark_node_block* current; + int line_number; + cmark_strbuf *curline; +}; + +typedef struct cmark_doc_parser cmark_doc_parser; + CMARK_EXPORT void cmark_free_blocks(cmark_node_block *e); @@ -148,6 +157,21 @@ cmark_node_inl* cmark_make_simple(int t); #define cmark_make_strong(contents) cmark_make_inlines(INL_STRONG, contents) CMARK_EXPORT +cmark_doc_parser *cmark_new_doc_parser(); + +CMARK_EXPORT +void cmark_free_doc_parser(cmark_doc_parser *parser); + +CMARK_EXPORT +cmark_node_block *cmark_finish(cmark_doc_parser *parser); + +CMARK_EXPORT +void cmark_process_line(cmark_doc_parser *parser, const unsigned char *buffer, size_t bytes); + +CMARK_EXPORT +cmark_node_block *cmark_finish(cmark_doc_parser *parser); + +CMARK_EXPORT cmark_node_block *cmark_parse_document(const unsigned char *buffer, size_t len); CMARK_EXPORT @@ -203,9 +227,14 @@ unsigned char *cmark_markdown_to_html(unsigned char *text, int len); #define make_softbreak cmark_make_softbreak #define make_emph cmark_make_emph #define make_strong cmark_make_strong - #define make_simple cmark_make_simple - #define make_simple cmark_make_simple - #define make_simple cmark_make_simple + #define make_simple cmark_make_simple + #define make_simple cmark_make_simple + #define make_simple cmark_make_simple + #define doc_parser cmark_doc_parser + #define new_doc_parser cmark_new_doc_parser + #define free_doc_parser cmark_free_doc_parser + #define process_line cmark_process_line + #define finish cmark_finish #endif #ifdef __cplusplus @@ -4,6 +4,7 @@ #include <errno.h> #include "cmark.h" #include "buffer.h" +#include "debug.h" #include "bench.h" void print_usage() @@ -27,23 +28,17 @@ static void print_document(node_block *document, bool ast) } } -void parse_and_render(node_block *document, FILE *fp, bool ast) -{ - document = cmark_parse_file(fp); - start_timer(); - print_document(document, ast); - end_timer("print_document"); - start_timer(); - cmark_free_blocks(document); - end_timer("free_blocks"); -} - int main(int argc, char *argv[]) { int i, numfps = 0; bool ast = false; int files[argc]; - node_block *document = NULL; + unsigned char buffer[4096]; + cmark_doc_parser *parser; + size_t offset; + node_block *document; + + parser = cmark_new_doc_parser(); for (i = 1; i < argc; i++) { if (strcmp(argv[i], "--version") == 0) { @@ -64,22 +59,49 @@ int main(int argc, char *argv[]) } } - if (numfps == 0) { - parse_and_render(document, stdin, ast); - } else { - for (i = 0; i < numfps; i++) { - FILE *fp = fopen(argv[files[i]], "r"); + for (i = 0; i < numfps; i++) { + FILE *fp = fopen(argv[files[i]], "r"); + if (fp == NULL) { + fprintf(stderr, "Error opening file %s: %s\n", + argv[files[i]], strerror(errno)); + exit(1); + } - if (fp == NULL) { - fprintf(stderr, "Error opening file %s: %s\n", - argv[files[i]], strerror(errno)); - exit(1); - } + start_timer(); + while (fgets((char *)buffer, sizeof(buffer), fp)) { + offset = strlen((char *)buffer); + cmark_process_line(parser, buffer, offset); + } + end_timer("processing lines"); - parse_and_render(document, fp, ast); - fclose(fp); + fclose(fp); + } + + if (numfps == 0) { + /* + document = cmark_parse_file(stdin); + print_document(document, ast); + exit(0); + */ + + while (fgets((char *)buffer, sizeof(buffer), stdin)) { + offset = strlen((char *)buffer); + cmark_process_line(parser, buffer, offset); } } + start_timer(); + document = cmark_finish(parser); + end_timer("finishing document"); + cmark_free_doc_parser(parser); + + start_timer(); + print_document(document, ast); + end_timer("print_document"); + + start_timer(); + cmark_free_blocks(document); + end_timer("free_blocks"); + return 0; } |