Coverage Report

Created: 2026-08-13 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cmark/src/blocks.c
Line
Count
Source
1
/**
2
 * Block parsing implementation.
3
 *
4
 * For a high-level overview of the block parsing process,
5
 * see http://spec.commonmark.org/0.24/#phase-1-block-structure
6
 */
7
8
#include <assert.h>
9
#include <limits.h>
10
#include <stdbool.h>
11
#include <stdio.h>
12
#include <stdlib.h>
13
14
#include "cmark_ctype.h"
15
#include "parser.h"
16
#include "cmark.h"
17
#include "node.h"
18
#include "references.h"
19
#include "utf8.h"
20
#include "scanners.h"
21
#include "inlines.h"
22
#include "houdini.h"
23
#include "buffer.h"
24
#include "chunk.h"
25
26
8.36M
#define CODE_INDENT 4
27
36.3M
#define TAB_STOP 4
28
29
#ifndef MIN
30
177k
#define MIN(x, y) ((x < y) ? x : y)
31
#endif
32
33
73.7M
#define peek_at(i, n) (i)->data[n]
34
35
2.46M
static bool S_last_line_blank(const cmark_node *node) {
36
2.46M
  return (node->flags & CMARK_NODE__LAST_LINE_BLANK) != 0;
37
2.46M
}
38
39
65.0k
static bool S_last_line_checked(const cmark_node *node) {
40
65.0k
  return (node->flags & CMARK_NODE__LAST_LINE_CHECKED) != 0;
41
65.0k
}
42
43
78.4M
static inline cmark_node_type S_type(const cmark_node *node) {
44
78.4M
  return (cmark_node_type)node->type;
45
78.4M
}
46
47
11.8M
static void S_set_last_line_blank(cmark_node *node, bool is_blank) {
48
11.8M
  if (is_blank)
49
540k
    node->flags |= CMARK_NODE__LAST_LINE_BLANK;
50
11.3M
  else
51
11.3M
    node->flags &= ~CMARK_NODE__LAST_LINE_BLANK;
52
11.8M
}
53
54
64.9k
static void S_set_last_line_checked(cmark_node *node) {
55
64.9k
  node->flags |= CMARK_NODE__LAST_LINE_CHECKED;
56
64.9k
}
57
58
209M
static inline bool S_is_line_end_char(char c) {
59
209M
  return (c == '\n' || c == '\r');
60
209M
}
61
62
4.96M
static inline bool S_is_space_or_tab(char c) {
63
4.96M
  return (c == ' ' || c == '\t');
64
4.96M
}
65
66
static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
67
                          size_t len, bool eof);
68
69
static void S_process_line(cmark_parser *parser, const unsigned char *buffer,
70
                           bufsize_t bytes);
71
72
static cmark_node *make_block(cmark_mem *mem, cmark_node_type tag,
73
7.51M
                              int start_line, int start_column) {
74
7.51M
  cmark_node *e;
75
76
7.51M
  e = (cmark_node *)mem->calloc(1, sizeof(*e));
77
7.51M
  e->mem = mem;
78
7.51M
  e->type = (uint16_t)tag;
79
7.51M
  e->flags = CMARK_NODE__OPEN;
80
7.51M
  e->start_line = start_line;
81
7.51M
  e->start_column = start_column;
82
7.51M
  e->end_line = start_line;
83
84
7.51M
  return e;
85
7.51M
}
86
87
// Create a root document node.
88
328
static cmark_node *make_document(cmark_mem *mem) {
89
328
  cmark_node *e = make_block(mem, CMARK_NODE_DOCUMENT, 1, 1);
90
328
  return e;
91
328
}
92
93
328
cmark_parser *cmark_parser_new_with_mem_into_root(int options, cmark_mem *mem, cmark_node *root) {
94
328
  cmark_parser *parser = (cmark_parser *)mem->calloc(1, sizeof(cmark_parser));
95
328
  parser->mem = mem;
96
97
328
  cmark_strbuf_init(mem, &parser->curline, 256);
98
328
  cmark_strbuf_init(mem, &parser->linebuf, 0);
99
328
  cmark_strbuf_init(mem, &parser->content, 0);
100
101
328
  root->flags = CMARK_NODE__OPEN;
102
103
328
  parser->refmap = cmark_reference_map_new(mem);
104
328
  parser->root = root;
105
328
  parser->current = root;
106
328
  parser->line_number = 0;
107
328
  parser->offset = 0;
108
328
  parser->column = 0;
109
328
  parser->first_nonspace = 0;
110
328
  parser->first_nonspace_column = 0;
111
328
  parser->thematic_break_kill_pos = 0;
112
328
  parser->indent = 0;
113
328
  parser->blank = false;
114
328
  parser->partially_consumed_tab = false;
115
328
  parser->last_line_length = 0;
116
328
  parser->options = options;
117
328
  parser->last_buffer_ended_with_cr = false;
118
119
328
  return parser;
120
328
}
121
122
328
cmark_parser *cmark_parser_new_with_mem(int options, cmark_mem *mem) {
123
328
  cmark_node *document = make_document(mem);
124
328
  return cmark_parser_new_with_mem_into_root(options, mem, document);
125
328
}
126
127
328
cmark_parser *cmark_parser_new(int options) {
128
328
  extern cmark_mem DEFAULT_MEM_ALLOCATOR;
129
328
  return cmark_parser_new_with_mem(options, &DEFAULT_MEM_ALLOCATOR);
130
328
}
131
132
328
void cmark_parser_free(cmark_parser *parser) {
133
328
  cmark_mem *mem = parser->mem;
134
328
  cmark_strbuf_free(&parser->curline);
135
328
  cmark_strbuf_free(&parser->linebuf);
136
328
  cmark_reference_map_free(parser->refmap);
137
328
  mem->free(parser);
138
328
}
139
140
static cmark_node *finalize(cmark_parser *parser, cmark_node *b);
141
142
// Returns true if line has only space characters, else false.
143
237k
static bool is_blank(cmark_strbuf *s, bufsize_t offset) {
144
244k
  while (offset < s->size) {
145
243k
    switch (s->ptr[offset]) {
146
0
    case '\r':
147
0
    case '\n':
148
0
      return true;
149
4.67k
    case ' ':
150
4.67k
      offset++;
151
4.67k
      break;
152
1.66k
    case '\t':
153
1.66k
      offset++;
154
1.66k
      break;
155
236k
    default:
156
236k
      return false;
157
243k
    }
158
243k
  }
159
160
1.13k
  return true;
161
237k
}
162
163
static inline bool can_contain(cmark_node_type parent_type,
164
8.78M
                               cmark_node_type child_type) {
165
8.78M
  return (parent_type == CMARK_NODE_DOCUMENT ||
166
6.14M
          parent_type == CMARK_NODE_BLOCK_QUOTE ||
167
3.92M
          parent_type == CMARK_NODE_ITEM ||
168
3.72M
          (parent_type == CMARK_NODE_LIST && child_type == CMARK_NODE_ITEM));
169
8.78M
}
170
171
5.43M
static inline bool accepts_lines(cmark_node_type block_type) {
172
5.43M
  return (block_type == CMARK_NODE_PARAGRAPH ||
173
5.13M
          block_type == CMARK_NODE_HEADING ||
174
5.05M
          block_type == CMARK_NODE_CODE_BLOCK);
175
5.43M
}
176
177
7.51M
static inline bool contains_inlines(cmark_node_type block_type) {
178
7.51M
  return (block_type == CMARK_NODE_PARAGRAPH ||
179
7.35M
          block_type == CMARK_NODE_HEADING);
180
7.51M
}
181
182
837k
static void add_line(cmark_chunk *ch, cmark_parser *parser) {
183
837k
  int chars_to_tab;
184
837k
  int i;
185
837k
  if (parser->partially_consumed_tab) {
186
2.39k
    parser->offset += 1; // skip over tab
187
    // add space characters:
188
2.39k
    chars_to_tab = TAB_STOP - (parser->column % TAB_STOP);
189
7.14k
    for (i = 0; i < chars_to_tab; i++) {
190
4.75k
      cmark_strbuf_putc(&parser->content, ' ');
191
4.75k
    }
192
2.39k
  }
193
837k
  cmark_strbuf_put(&parser->content, ch->data + parser->offset,
194
837k
                   ch->len - parser->offset);
195
837k
}
196
197
130k
static void remove_trailing_blank_lines(cmark_strbuf *ln) {
198
130k
  bufsize_t i;
199
130k
  unsigned char c;
200
201
1.82M
  for (i = ln->size - 1; i >= 0; --i) {
202
1.82M
    c = ln->ptr[i];
203
204
1.82M
    if (c != ' ' && c != '\t' && !S_is_line_end_char(c))
205
130k
      break;
206
1.82M
  }
207
208
130k
  if (i < 0) {
209
0
    cmark_strbuf_clear(ln);
210
0
    return;
211
0
  }
212
213
1.28M
  for (; i < ln->size; ++i) {
214
1.28M
    c = ln->ptr[i];
215
216
1.28M
    if (!S_is_line_end_char(c))
217
1.15M
      continue;
218
219
130k
    cmark_strbuf_truncate(ln, i);
220
130k
    break;
221
1.28M
  }
222
130k
}
223
224
// Check to see if a node ends with a blank line, descending
225
// if needed into lists and sublists.
226
2.82k
static bool S_ends_with_blank_line(cmark_node *node) {
227
65.0k
  while (!S_last_line_checked(node)) {
228
64.9k
    S_set_last_line_checked(node);
229
64.9k
    if (S_type(node) != CMARK_NODE_LIST && S_type(node) != CMARK_NODE_ITEM)
230
2.31k
      break;
231
62.6k
    if (!node->last_child)
232
470
      break;
233
62.2k
    node = node->last_child;
234
62.2k
  }
235
2.82k
  return S_last_line_blank(node);
236
2.82k
}
237
238
239
// returns true if content remains after link defs are resolved.
240
237k
static bool resolve_reference_link_definitions(cmark_parser *parser) {
241
237k
  bufsize_t pos;
242
237k
  cmark_strbuf *node_content = &parser->content;
243
237k
  cmark_chunk chunk = {node_content->ptr, node_content->size};
244
246k
  while (chunk.len && chunk.data[0] == '[' &&
245
17.8k
         (pos = cmark_parse_reference_inline(parser->mem, &chunk,
246
17.8k
                                             parser->refmap))) {
247
248
8.28k
    chunk.data += pos;
249
8.28k
    chunk.len -= pos;
250
8.28k
  }
251
237k
  cmark_strbuf_drop(node_content, (node_content->size - chunk.len));
252
237k
  return !is_blank(node_content, 0);
253
237k
}
254
255
7.51M
static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
256
7.51M
  bufsize_t pos;
257
7.51M
  cmark_node *item;
258
7.51M
  cmark_node *subitem;
259
7.51M
  cmark_node *parent;
260
7.51M
  bool has_content;
261
262
7.51M
  parent = b->parent;
263
7.51M
  assert(b->flags &
264
7.51M
         CMARK_NODE__OPEN); // shouldn't call finalize on closed blocks
265
7.51M
  b->flags &= ~CMARK_NODE__OPEN;
266
267
7.51M
  if (parser->curline.size == 0) {
268
    // end of input - line number has not been incremented
269
101k
    b->end_line = parser->line_number;
270
101k
    b->end_column = parser->last_line_length;
271
7.41M
  } else if (S_type(b) == CMARK_NODE_DOCUMENT ||
272
7.41M
             (S_type(b) == CMARK_NODE_CODE_BLOCK && b->as.code.fenced) ||
273
7.41M
             (S_type(b) == CMARK_NODE_HEADING && b->as.heading.setext)) {
274
75.0k
    b->end_line = parser->line_number;
275
75.0k
    b->end_column = parser->curline.size;
276
75.0k
    if (b->end_column && parser->curline.ptr[b->end_column - 1] == '\n')
277
75.0k
      b->end_column -= 1;
278
75.0k
    if (b->end_column && parser->curline.ptr[b->end_column - 1] == '\r')
279
0
      b->end_column -= 1;
280
7.33M
  } else {
281
7.33M
    b->end_line = parser->line_number - 1;
282
7.33M
    b->end_column = parser->last_line_length;
283
7.33M
  }
284
285
7.51M
  cmark_strbuf *node_content = &parser->content;
286
287
7.51M
  switch (S_type(b)) {
288
163k
  case CMARK_NODE_PARAGRAPH:
289
163k
  {
290
163k
    has_content = resolve_reference_link_definitions(parser);
291
163k
    if (!has_content) {
292
      // remove blank node (former reference def)
293
1.09k
      cmark_node_free(b);
294
162k
    } else {
295
162k
      b->len = node_content->size;
296
162k
      b->data = cmark_strbuf_detach(node_content);
297
162k
    }
298
163k
    break;
299
0
  }
300
301
131k
  case CMARK_NODE_CODE_BLOCK:
302
131k
    if (!b->as.code.fenced) { // indented code
303
130k
      remove_trailing_blank_lines(node_content);
304
130k
      cmark_strbuf_putc(node_content, '\n');
305
130k
    } else {
306
      // first line of contents becomes info
307
30.3M
      for (pos = 0; pos < node_content->size; ++pos) {
308
30.3M
        if (S_is_line_end_char(node_content->ptr[pos]))
309
465
          break;
310
30.3M
      }
311
465
      assert(pos < node_content->size);
312
313
465
      if (pos == 0) {
314
271
        b->as.code.info = NULL;
315
271
      } else {
316
194
        cmark_strbuf tmp = CMARK_BUF_INIT(parser->mem);
317
194
        houdini_unescape_html_f(&tmp, node_content->ptr, pos);
318
194
        cmark_strbuf_trim(&tmp);
319
194
        cmark_strbuf_unescape(&tmp);
320
194
        b->as.code.info = cmark_strbuf_detach(&tmp);
321
194
      }
322
323
465
      if (node_content->ptr[pos] == '\r')
324
0
        pos += 1;
325
465
      if (node_content->ptr[pos] == '\n')
326
465
        pos += 1;
327
465
      cmark_strbuf_drop(node_content, pos);
328
465
    }
329
131k
    b->len = node_content->size;
330
131k
    b->data = cmark_strbuf_detach(node_content);
331
131k
    break;
332
333
78.7k
  case CMARK_NODE_HEADING:
334
93.4k
  case CMARK_NODE_HTML_BLOCK:
335
93.4k
    b->len = node_content->size;
336
93.4k
    b->data = cmark_strbuf_detach(node_content);
337
93.4k
    break;
338
339
2.45M
  case CMARK_NODE_LIST:      // determine tight/loose status
340
2.45M
    b->as.list.tight = true; // tight by default
341
2.45M
    item = b->first_child;
342
343
4.90M
    while (item) {
344
      // check for non-final non-empty list item ending with blank line:
345
2.45M
      if (S_last_line_blank(item) && item->next) {
346
961
        b->as.list.tight = false;
347
961
        break;
348
961
      }
349
      // recurse into children of list item, to see if there are
350
      // spaces between them:
351
2.45M
      subitem = item->first_child;
352
2.65M
      while (subitem) {
353
197k
        if ((item->next || subitem->next) &&
354
2.82k
            S_ends_with_blank_line(subitem)) {
355
519
          b->as.list.tight = false;
356
519
          break;
357
519
        }
358
196k
        subitem = subitem->next;
359
196k
      }
360
2.45M
      if (!(b->as.list.tight)) {
361
519
        break;
362
519
      }
363
2.45M
      item = item->next;
364
2.45M
    }
365
366
2.45M
    break;
367
368
4.67M
  default:
369
4.67M
    break;
370
7.51M
  }
371
372
7.51M
  return parent;
373
7.51M
}
374
375
// Add a node as child of another.  Return pointer to child.
376
static cmark_node *add_child(cmark_parser *parser, cmark_node *parent,
377
7.51M
                             cmark_node_type block_type, int start_column) {
378
7.51M
  assert(parent);
379
380
  // if 'parent' isn't the kind of node that can accept this child,
381
  // then back up til we hit a node that can.
382
8.78M
  while (!can_contain(S_type(parent), block_type)) {
383
1.26M
    parent = finalize(parser, parent);
384
1.26M
  }
385
386
7.51M
  cmark_node *child =
387
7.51M
      make_block(parser->mem, block_type, parser->line_number, start_column);
388
7.51M
  child->parent = parent;
389
390
7.51M
  if (parent->last_child) {
391
2.64M
    parent->last_child->next = child;
392
2.64M
    child->prev = parent->last_child;
393
4.86M
  } else {
394
4.86M
    parent->first_child = child;
395
4.86M
    child->prev = NULL;
396
4.86M
  }
397
7.51M
  parent->last_child = child;
398
7.51M
  return child;
399
7.51M
}
400
401
// Walk through node and all children, recursively, parsing
402
// string content into inline content where appropriate.
403
static void process_inlines(cmark_mem *mem, cmark_node *root,
404
328
                            cmark_reference_map *refmap, int options) {
405
328
  cmark_iter *iter = cmark_iter_new(root);
406
328
  cmark_node *cur;
407
328
  cmark_event_type ev_type;
408
409
14.8M
  while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
410
14.8M
    cur = cmark_iter_get_node(iter);
411
14.8M
    if (ev_type == CMARK_EVENT_ENTER) {
412
7.51M
      if (contains_inlines(S_type(cur))) {
413
240k
        cmark_parse_inlines(mem, cur, refmap, options);
414
240k
        mem->free(cur->data);
415
240k
        cur->data = NULL;
416
240k
        cur->len = 0;
417
240k
      }
418
7.51M
    }
419
14.8M
  }
420
421
328
  cmark_iter_free(iter);
422
328
}
423
424
// Attempts to parse a list item marker (bullet or enumerated).
425
// On success, returns length of the marker, and populates
426
// data with the details.  On failure, returns 0.
427
static bufsize_t parse_list_marker(cmark_mem *mem, cmark_chunk *input,
428
                                   bufsize_t pos, bool interrupts_paragraph,
429
5.57M
                                   cmark_list **dataptr) {
430
5.57M
  unsigned char c;
431
5.57M
  bufsize_t startpos;
432
5.57M
  cmark_list *data;
433
5.57M
  bufsize_t i;
434
435
5.57M
  startpos = pos;
436
5.57M
  c = peek_at(input, pos);
437
438
5.57M
  if (c == '*' || c == '-' || c == '+') {
439
2.47M
    pos++;
440
2.47M
    if (!cmark_isspace(peek_at(input, pos))) {
441
13.1k
      return 0;
442
13.1k
    }
443
444
2.45M
    if (interrupts_paragraph) {
445
6.25k
      i = pos;
446
      // require non-blank content after list marker:
447
13.1k
      while (S_is_space_or_tab(peek_at(input, i))) {
448
6.91k
        i++;
449
6.91k
      }
450
6.25k
      if (peek_at(input, i) == '\n') {
451
153
        return 0;
452
153
      }
453
6.25k
    }
454
455
2.45M
    data = (cmark_list *)mem->calloc(1, sizeof(*data));
456
2.45M
    data->marker_offset = 0; // will be adjusted later
457
2.45M
    data->list_type = CMARK_BULLET_LIST;
458
2.45M
    data->bullet_char = c;
459
2.45M
    data->start = 0;
460
2.45M
    data->delimiter = CMARK_NO_DELIM;
461
2.45M
    data->tight = false;
462
3.10M
  } else if (cmark_isdigit(c)) {
463
5.01k
    int start = 0;
464
5.01k
    int digits = 0;
465
466
12.0k
    do {
467
12.0k
      start = (10 * start) + (peek_at(input, pos) - '0');
468
12.0k
      pos++;
469
12.0k
      digits++;
470
      // We limit to 9 digits to avoid overflow,
471
      // assuming max int is 2^31 - 1
472
      // This also seems to be the limit for 'start' in some browsers.
473
12.0k
    } while (digits < 9 && cmark_isdigit(peek_at(input, pos)));
474
475
5.01k
    if (interrupts_paragraph && start != 1) {
476
3.11k
      return 0;
477
3.11k
    }
478
1.89k
    c = peek_at(input, pos);
479
1.89k
    if (c == '.' || c == ')') {
480
77
      pos++;
481
77
      if (!cmark_isspace(peek_at(input, pos))) {
482
6
        return 0;
483
6
      }
484
71
      if (interrupts_paragraph) {
485
        // require non-blank content after list marker:
486
40
        i = pos;
487
12.2k
        while (S_is_space_or_tab(peek_at(input, i))) {
488
12.1k
          i++;
489
12.1k
        }
490
40
        if (S_is_line_end_char(peek_at(input, i))) {
491
4
          return 0;
492
4
        }
493
40
      }
494
495
67
      data = (cmark_list *)mem->calloc(1, sizeof(*data));
496
67
      data->marker_offset = 0; // will be adjusted later
497
67
      data->list_type = CMARK_ORDERED_LIST;
498
67
      data->bullet_char = 0;
499
67
      data->start = start;
500
67
      data->delimiter = (c == '.' ? CMARK_PERIOD_DELIM : CMARK_PAREN_DELIM);
501
67
      data->tight = false;
502
1.82k
    } else {
503
1.82k
      return 0;
504
1.82k
    }
505
3.10M
  } else {
506
3.10M
    return 0;
507
3.10M
  }
508
509
2.45M
  *dataptr = data;
510
2.45M
  return (pos - startpos);
511
5.57M
}
512
513
// Return 1 if list item belongs in list, else 0.
514
5.55k
static int lists_match(cmark_list *list_data, cmark_list *item_data) {
515
5.55k
  return (list_data->list_type == item_data->list_type &&
516
5.55k
          list_data->delimiter == item_data->delimiter &&
517
          // list_data->marker_offset == item_data.marker_offset &&
518
5.54k
          list_data->bullet_char == item_data->bullet_char);
519
5.55k
}
520
521
328
static cmark_node *finalize_document(cmark_parser *parser) {
522
101k
  while (parser->current != parser->root) {
523
101k
    parser->current = finalize(parser, parser->current);
524
101k
  }
525
526
328
  finalize(parser, parser->root);
527
528
  // Limit total size of extra content created from reference links to
529
  // document size to avoid superlinear growth. Always allow 100KB.
530
328
  if (parser->total_size > 100000)
531
328
    parser->refmap->max_ref_size = parser->total_size;
532
0
  else
533
0
    parser->refmap->max_ref_size = 100000;
534
535
328
  process_inlines(parser->mem, parser->root, parser->refmap, parser->options);
536
537
328
  cmark_strbuf_free(&parser->content);
538
539
328
  return parser->root;
540
328
}
541
542
91
cmark_node *cmark_parse_file(FILE *f, int options) {
543
91
  unsigned char buffer[4096];
544
91
  cmark_parser *parser = cmark_parser_new(options);
545
91
  size_t bytes;
546
91
  cmark_node *document;
547
548
9.44k
  while ((bytes = fread(buffer, 1, sizeof(buffer), f)) > 0) {
549
9.44k
    bool eof = bytes < sizeof(buffer);
550
9.44k
    S_parser_feed(parser, buffer, bytes, eof);
551
9.44k
    if (eof) {
552
90
      break;
553
90
    }
554
9.44k
  }
555
556
91
  document = cmark_parser_finish(parser);
557
91
  cmark_parser_free(parser);
558
91
  return document;
559
91
}
560
561
188
cmark_node *cmark_parse_document(const char *buffer, size_t len, int options) {
562
188
  cmark_parser *parser = cmark_parser_new(options);
563
188
  cmark_node *document;
564
565
188
  S_parser_feed(parser, (const unsigned char *)buffer, len, true);
566
567
188
  document = cmark_parser_finish(parser);
568
188
  cmark_parser_free(parser);
569
188
  return document;
570
188
}
571
572
1.14M
void cmark_parser_feed(cmark_parser *parser, const char *buffer, size_t len) {
573
1.14M
  S_parser_feed(parser, (const unsigned char *)buffer, len, false);
574
1.14M
}
575
576
static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
577
1.15M
                          size_t len, bool eof) {
578
1.15M
  const unsigned char *end = buffer + len;
579
1.15M
  static const uint8_t repl[] = {239, 191, 189};
580
581
1.15M
  if (len > UINT_MAX - parser->total_size)
582
0
    parser->total_size = UINT_MAX;
583
1.15M
  else
584
1.15M
    parser->total_size += (int)len;
585
586
  // Skip UTF-8 BOM if present; see #334
587
1.15M
  if (parser->line_number == 0 && parser->column == 0 && len >= 3 &&
588
70.0k
      *buffer == 0xEF && *(buffer + 1) == 0xBB &&
589
0
      *(buffer + 2) == 0xBF) {
590
0
    buffer += 3;
591
1.15M
  } else if (parser->last_buffer_ended_with_cr && *buffer == '\n') {
592
    // skip NL if last buffer ended with CR ; see #117
593
55
    buffer++;
594
55
  }
595
596
1.15M
  parser->last_buffer_ended_with_cr = false;
597
91.4M
  while (buffer < end) {
598
90.3M
    const unsigned char *eol;
599
90.3M
    bufsize_t chunk_len;
600
90.3M
    bool process = false;
601
158M
    for (eol = buffer; eol < end; ++eol) {
602
157M
      if (S_is_line_end_char(*eol)) {
603
3.43M
        process = true;
604
3.43M
        break;
605
3.43M
      }
606
154M
      if (*eol == '\0' && eol < end) {
607
86.2M
        break;
608
86.2M
      }
609
154M
    }
610
90.3M
    if (eol >= end && eof) {
611
206
      process = true;
612
206
    }
613
614
90.3M
    chunk_len = (eol - buffer);
615
90.3M
    if (process) {
616
3.43M
      if (parser->linebuf.size > 0) {
617
257k
        cmark_strbuf_put(&parser->linebuf, buffer, chunk_len);
618
257k
        S_process_line(parser, parser->linebuf.ptr, parser->linebuf.size);
619
257k
        cmark_strbuf_clear(&parser->linebuf);
620
3.18M
      } else {
621
3.18M
        S_process_line(parser, buffer, chunk_len);
622
3.18M
      }
623
86.8M
    } else {
624
86.8M
      if (eol < end && *eol == '\0') {
625
        // omit NULL byte
626
86.2M
        cmark_strbuf_put(&parser->linebuf, buffer, chunk_len);
627
        // add replacement character
628
86.2M
        cmark_strbuf_put(&parser->linebuf, repl, 3);
629
86.2M
      } else {
630
682k
        cmark_strbuf_put(&parser->linebuf, buffer, chunk_len);
631
682k
      }
632
86.8M
    }
633
634
90.3M
    buffer += chunk_len;
635
90.3M
    if (buffer < end) {
636
89.6M
      if (*buffer == '\0') {
637
        // skip over NULL
638
86.2M
        buffer++;
639
86.2M
      } else {
640
        // skip over line ending characters
641
3.43M
        if (*buffer == '\r') {
642
1.76M
          buffer++;
643
1.76M
          if (buffer == end)
644
23.2k
            parser->last_buffer_ended_with_cr = true;
645
1.76M
        }
646
3.43M
        if (buffer < end && *buffer == '\n')
647
1.67M
          buffer++;
648
3.43M
      }
649
89.6M
    }
650
90.3M
  }
651
1.15M
}
652
653
3.76k
static void chop_trailing_hashtags(cmark_chunk *ch) {
654
3.76k
  bufsize_t n, orig_n;
655
656
3.76k
  cmark_chunk_rtrim(ch);
657
3.76k
  orig_n = n = ch->len - 1;
658
659
  // if string ends in space followed by #s, remove these:
660
13.7k
  while (n >= 0 && peek_at(ch, n) == '#')
661
9.94k
    n--;
662
663
  // Check for a space before the final #s:
664
3.76k
  if (n != orig_n && n >= 0 && S_is_space_or_tab(peek_at(ch, n))) {
665
565
    ch->len = n;
666
565
    cmark_chunk_rtrim(ch);
667
565
  }
668
3.76k
}
669
670
// Check for thematic break.  On failure, return 0 and update
671
// thematic_break_kill_pos with the index at which the
672
// parse fails.  On success, return length of match.
673
// "...three or more hyphens, asterisks,
674
// or underscores on a line by themselves. If you wish, you may use
675
// spaces between the hyphens or asterisks."
676
static int S_scan_thematic_break(cmark_parser *parser, cmark_chunk *input,
677
5.38M
                                 bufsize_t offset) {
678
5.38M
  bufsize_t i;
679
5.38M
  char c;
680
5.38M
  char nextc = '\0';
681
5.38M
  int count;
682
5.38M
  i = offset;
683
5.38M
  c = peek_at(input, i);
684
5.38M
  if (!(c == '*' || c == '_' || c == '-')) {
685
3.10M
    parser->thematic_break_kill_pos = i;
686
3.10M
    return 0;
687
3.10M
  }
688
2.28M
  count = 1;
689
4.26M
  while ((nextc = peek_at(input, ++i))) {
690
4.26M
    if (nextc == c) {
691
1.01M
      count++;
692
3.25M
    } else if (nextc != ' ' && nextc != '\t') {
693
2.28M
      break;
694
2.28M
    }
695
4.26M
  }
696
2.28M
  if (count >= 3 && (nextc == '\r' || nextc == '\n')) {
697
40
    return (i - offset) + 1;
698
2.28M
  } else {
699
2.28M
    parser->thematic_break_kill_pos = i;
700
2.28M
    return 0;
701
2.28M
  }
702
2.28M
}
703
704
// Find first nonspace character from current offset, setting
705
// parser->first_nonspace, parser->first_nonspace_column,
706
// parser->indent, and parser->blank. Does not advance parser->offset.
707
16.3M
static void S_find_first_nonspace(cmark_parser *parser, cmark_chunk *input) {
708
16.3M
  char c;
709
16.3M
  int chars_to_tab = TAB_STOP - (parser->column % TAB_STOP);
710
711
16.3M
  if (parser->first_nonspace <= parser->offset) {
712
16.1M
    parser->first_nonspace = parser->offset;
713
16.1M
    parser->first_nonspace_column = parser->column;
714
19.3M
    while ((c = peek_at(input, parser->first_nonspace))) {
715
19.3M
      if (c == ' ') {
716
19.0k
        parser->first_nonspace += 1;
717
19.0k
        parser->first_nonspace_column += 1;
718
19.0k
        chars_to_tab = chars_to_tab - 1;
719
19.0k
        if (chars_to_tab == 0) {
720
189
          chars_to_tab = TAB_STOP;
721
189
        }
722
19.3M
      } else if (c == '\t') {
723
3.22M
        parser->first_nonspace += 1;
724
3.22M
        parser->first_nonspace_column += chars_to_tab;
725
3.22M
        chars_to_tab = TAB_STOP;
726
16.1M
      } else {
727
16.1M
        break;
728
16.1M
      }
729
19.3M
    }
730
16.1M
  }
731
732
16.3M
  parser->indent = parser->first_nonspace_column - parser->column;
733
16.3M
  parser->blank = S_is_line_end_char(peek_at(input, parser->first_nonspace));
734
16.3M
}
735
736
// Advance parser->offset and parser->column.  parser->offset is the
737
// byte position in input; parser->column is a virtual column number
738
// that takes into account tabs. (Multibyte characters are not taken
739
// into account, because the Markdown line prefixes we are interested in
740
// analyzing are entirely ASCII.)  The count parameter indicates
741
// how far to advance the offset.  If columns is true, then count
742
// indicates a number of columns; otherwise, a number of bytes.
743
// If advancing a certain number of columns partially consumes
744
// a tab character, parser->partially_consumed_tab is set to true.
745
static void S_advance_offset(cmark_parser *parser, cmark_chunk *input,
746
5.86M
                             bufsize_t count, bool columns) {
747
5.86M
  char c;
748
5.86M
  int chars_to_tab;
749
5.86M
  int chars_to_advance;
750
11.8M
  while (count > 0 && (c = peek_at(input, parser->offset))) {
751
5.97M
    if (c == '\t') {
752
195k
      chars_to_tab = TAB_STOP - (parser->column % TAB_STOP);
753
195k
      if (columns) {
754
177k
        parser->partially_consumed_tab = chars_to_tab > count;
755
177k
        chars_to_advance = MIN(count, chars_to_tab);
756
177k
        parser->column += chars_to_advance;
757
177k
        parser->offset += (parser->partially_consumed_tab ? 0 : 1);
758
177k
        count -= chars_to_advance;
759
177k
      } else {
760
17.6k
        parser->partially_consumed_tab = false;
761
17.6k
        parser->column += chars_to_tab;
762
17.6k
        parser->offset += 1;
763
17.6k
        count -= 1;
764
17.6k
      }
765
5.78M
    } else {
766
5.78M
      parser->partially_consumed_tab = false;
767
5.78M
      parser->offset += 1;
768
5.78M
      parser->column += 1; // assume ascii; block starts are ascii
769
5.78M
      count -= 1;
770
5.78M
    }
771
5.97M
  }
772
5.86M
}
773
774
5.58M
static bool S_last_child_is_open(cmark_node *container) {
775
5.58M
  return container->last_child &&
776
5.13M
         (container->last_child->flags & CMARK_NODE__OPEN);
777
5.58M
}
778
779
1.26M
static bool parse_block_quote_prefix(cmark_parser *parser, cmark_chunk *input) {
780
1.26M
  bool res = false;
781
1.26M
  bufsize_t matched = 0;
782
783
1.26M
  matched =
784
1.26M
      parser->indent <= 3 && peek_at(input, parser->first_nonspace) == '>';
785
1.26M
  if (matched) {
786
787
55.0k
    S_advance_offset(parser, input, parser->indent + 1, true);
788
789
55.0k
    if (S_is_space_or_tab(peek_at(input, parser->offset))) {
790
6.44k
      S_advance_offset(parser, input, 1, true);
791
6.44k
    }
792
793
55.0k
    res = true;
794
55.0k
  }
795
1.26M
  return res;
796
1.26M
}
797
798
static bool parse_node_item_prefix(cmark_parser *parser, cmark_chunk *input,
799
1.39M
                                   cmark_node *container) {
800
1.39M
  bool res = false;
801
802
1.39M
  if (parser->indent >=
803
1.39M
      container->as.list.marker_offset + container->as.list.padding) {
804
10.9k
    S_advance_offset(parser, input, container->as.list.marker_offset +
805
10.9k
                                        container->as.list.padding,
806
10.9k
                     true);
807
10.9k
    res = true;
808
1.38M
  } else if (parser->blank && container->first_child != NULL) {
809
    // if container->first_child is NULL, then the opening line
810
    // of the list item was blank after the list marker; in this
811
    // case, we are done with the list item.
812
112k
    S_advance_offset(parser, input, parser->first_nonspace - parser->offset,
813
112k
                     false);
814
112k
    res = true;
815
112k
  }
816
1.39M
  return res;
817
1.39M
}
818
819
static bool parse_code_block_prefix(cmark_parser *parser, cmark_chunk *input,
820
                                    cmark_node *container,
821
174k
                                    bool *should_continue) {
822
174k
  bool res = false;
823
824
174k
  if (!container->as.code.fenced) { // indented
825
171k
    if (parser->indent >= CODE_INDENT) {
826
16.4k
      S_advance_offset(parser, input, CODE_INDENT, true);
827
16.4k
      res = true;
828
154k
    } else if (parser->blank) {
829
25.4k
      S_advance_offset(parser, input, parser->first_nonspace - parser->offset,
830
25.4k
                       false);
831
25.4k
      res = true;
832
25.4k
    }
833
171k
  } else { // fenced
834
3.21k
    bufsize_t matched = 0;
835
836
3.21k
    if (parser->indent <= 3 && (peek_at(input, parser->first_nonspace) ==
837
3.16k
                                container->as.code.fence_char)) {
838
253
      matched = scan_close_code_fence(input, parser->first_nonspace);
839
253
    }
840
841
3.21k
    if (matched >= container->as.code.fence_length) {
842
      // closing fence - and since we're at
843
      // the end of a line, we can stop processing it:
844
94
      *should_continue = false;
845
94
      S_advance_offset(parser, input, matched, false);
846
94
      parser->current = finalize(parser, container);
847
3.11k
    } else {
848
      // skip opt. spaces of fence parser->offset
849
3.11k
      int i = container->as.code.fence_offset;
850
851
3.11k
      while (i > 0 && S_is_space_or_tab(peek_at(input, parser->offset))) {
852
0
        S_advance_offset(parser, input, 1, true);
853
0
        i--;
854
0
      }
855
3.11k
      res = true;
856
3.11k
    }
857
3.21k
  }
858
859
174k
  return res;
860
174k
}
861
862
static bool parse_html_block_prefix(cmark_parser *parser,
863
18.6k
                                    cmark_node *container) {
864
18.6k
  bool res = false;
865
18.6k
  int html_block_type = container->as.html_block_type;
866
867
18.6k
  assert(html_block_type >= 1 && html_block_type <= 7);
868
18.6k
  switch (html_block_type) {
869
4.38k
  case 1:
870
4.38k
  case 2:
871
5.35k
  case 3:
872
6.61k
  case 4:
873
10.0k
  case 5:
874
    // these types of blocks can accept blanks
875
10.0k
    res = true;
876
10.0k
    break;
877
8.17k
  case 6:
878
8.58k
  case 7:
879
8.58k
    res = !parser->blank;
880
8.58k
    break;
881
18.6k
  }
882
883
18.6k
  return res;
884
18.6k
}
885
886
/**
887
 * For each containing node, try to parse the associated line start.
888
 *
889
 * Will not close unmatched blocks, as we may have a lazy continuation
890
 * line -> http://spec.commonmark.org/0.24/#lazy-continuation-line
891
 *
892
 * Returns: The last matching node, or NULL
893
 */
894
static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input,
895
3.43M
                                     bool *all_matched) {
896
3.43M
  bool should_continue = true;
897
3.43M
  *all_matched = false;
898
3.43M
  cmark_node *container = parser->root;
899
3.43M
  cmark_node_type cont_type;
900
901
5.58M
  while (S_last_child_is_open(container)) {
902
4.87M
    container = container->last_child;
903
4.87M
    cont_type = S_type(container);
904
905
4.87M
    S_find_first_nonspace(parser, input);
906
907
4.87M
    switch (cont_type) {
908
1.26M
    case CMARK_NODE_BLOCK_QUOTE:
909
1.26M
      if (!parse_block_quote_prefix(parser, input))
910
1.20M
        goto done;
911
55.0k
      break;
912
1.52M
    case CMARK_NODE_LIST:
913
      // Avoid quadratic behavior caused by iterating deeply nested lists
914
      // for each blank line.
915
1.52M
      if (parser->blank) {
916
256k
        if (container->flags & CMARK_NODE__LIST_LAST_LINE_BLANK &&
917
19.1k
            parser->indent == 0) {
918
          // Abort early if we encounter multiple blank lines. Returning
919
          // NULL will cause S_process_line to skip the calls to
920
          // open_new_blocks and add_text_to_container. open_new_blocks
921
          // is a no-op for blank lines. add_text_to_container closes
922
          // remaining open nodes, but since we have a second blank
923
          // line, all open nodes have already been closed when the
924
          // first blank line was processed. Certain block types accept
925
          // empty lines as content, so add them here.
926
9.24k
          if (parser->current->type == CMARK_NODE_CODE_BLOCK ||
927
7.54k
              parser->current->type == CMARK_NODE_HTML_BLOCK) {
928
1.69k
            add_line(input, parser);
929
1.69k
          }
930
9.24k
          return NULL;
931
9.24k
        }
932
247k
        container->flags |= CMARK_NODE__LIST_LAST_LINE_BLANK;
933
1.27M
      } else {
934
1.27M
        container->flags &= ~CMARK_NODE__LIST_LAST_LINE_BLANK;
935
1.27M
      }
936
1.52M
      break;
937
1.52M
    case CMARK_NODE_ITEM:
938
1.39M
      if (!parse_node_item_prefix(parser, input, container))
939
1.27M
        goto done;
940
123k
      break;
941
174k
    case CMARK_NODE_CODE_BLOCK:
942
174k
      if (!parse_code_block_prefix(parser, input, container, &should_continue))
943
129k
        goto done;
944
44.9k
      break;
945
55.9k
    case CMARK_NODE_HEADING:
946
      // a heading can never contain more than one line
947
55.9k
      goto done;
948
11
    case CMARK_NODE_THEMATIC_BREAK:
949
      // a thematic break can never contain more than one line
950
11
      goto done;
951
18.6k
    case CMARK_NODE_HTML_BLOCK:
952
18.6k
      if (!parse_html_block_prefix(parser, container))
953
5.62k
        goto done;
954
13.0k
      break;
955
443k
    case CMARK_NODE_PARAGRAPH:
956
443k
      if (parser->blank)
957
53.2k
        goto done;
958
390k
      break;
959
390k
    default:
960
0
      break;
961
4.87M
    }
962
4.87M
  }
963
964
709k
  *all_matched = true;
965
966
3.42M
done:
967
3.42M
  if (!*all_matched) {
968
2.71M
    container = container->parent; // back up to last matching node
969
2.71M
  }
970
971
3.42M
  if (!should_continue) {
972
94
    container = NULL;
973
94
  }
974
975
3.42M
  return container;
976
709k
}
977
978
static void open_new_blocks(cmark_parser *parser, cmark_node **container,
979
3.42M
                            cmark_chunk *input, bool all_matched) {
980
3.42M
  bool indented;
981
3.42M
  cmark_list *data = NULL;
982
3.42M
  bool maybe_lazy = S_type(parser->current) == CMARK_NODE_PARAGRAPH;
983
3.42M
  cmark_node_type cont_type = S_type(*container);
984
3.42M
  bufsize_t matched = 0;
985
3.42M
  int lev = 0;
986
3.42M
  bool save_partially_consumed_tab;
987
3.42M
  bool has_content;
988
3.42M
  int save_offset;
989
3.42M
  int save_column;
990
991
8.11M
  while (cont_type != CMARK_NODE_CODE_BLOCK &&
992
8.07M
         cont_type != CMARK_NODE_HTML_BLOCK) {
993
994
8.04M
    S_find_first_nonspace(parser, input);
995
8.04M
    indented = parser->indent >= CODE_INDENT;
996
997
8.04M
    if (!indented && peek_at(input, parser->first_nonspace) == '>') {
998
999
2.21M
      bufsize_t blockquote_startpos = parser->first_nonspace;
1000
1001
2.21M
      S_advance_offset(parser, input,
1002
2.21M
                       parser->first_nonspace + 1 - parser->offset, false);
1003
      // optional following character
1004
2.21M
      if (S_is_space_or_tab(peek_at(input, parser->offset))) {
1005
2.75k
        S_advance_offset(parser, input, 1, true);
1006
2.75k
      }
1007
2.21M
      *container = add_child(parser, *container, CMARK_NODE_BLOCK_QUOTE,
1008
2.21M
                             blockquote_startpos + 1);
1009
1010
5.83M
    } else if (!indented && (matched = scan_atx_heading_start(
1011
5.67M
                                 input, parser->first_nonspace))) {
1012
4.13k
      bufsize_t hashpos;
1013
4.13k
      int level = 0;
1014
4.13k
      bufsize_t heading_startpos = parser->first_nonspace;
1015
1016
4.13k
      S_advance_offset(parser, input,
1017
4.13k
                       parser->first_nonspace + matched - parser->offset,
1018
4.13k
                       false);
1019
4.13k
      *container = add_child(parser, *container, CMARK_NODE_HEADING,
1020
4.13k
                             heading_startpos + 1);
1021
1022
4.13k
      hashpos = cmark_chunk_strchr(input, '#', parser->first_nonspace);
1023
1024
16.0k
      while (peek_at(input, hashpos) == '#') {
1025
11.9k
        level++;
1026
11.9k
        hashpos++;
1027
11.9k
      }
1028
1029
4.13k
      (*container)->as.heading.level = level;
1030
4.13k
      (*container)->as.heading.setext = false;
1031
4.13k
      (*container)->as.heading.internal_offset = matched;
1032
1033
5.82M
    } else if (!indented && (matched = scan_open_code_fence(
1034
5.66M
                                 input, parser->first_nonspace))) {
1035
465
      *container = add_child(parser, *container, CMARK_NODE_CODE_BLOCK,
1036
465
                             parser->first_nonspace + 1);
1037
465
      (*container)->as.code.fenced = true;
1038
465
      (*container)->as.code.fence_char = peek_at(input, parser->first_nonspace);
1039
465
      (*container)->as.code.fence_length = (matched > 255) ? 255 : matched;
1040
465
      (*container)->as.code.fence_offset =
1041
465
          (int8_t)(parser->first_nonspace - parser->offset);
1042
465
      (*container)->as.code.info = NULL;
1043
465
      S_advance_offset(parser, input,
1044
465
                       parser->first_nonspace + matched - parser->offset,
1045
465
                       false);
1046
1047
5.82M
    } else if (!indented && ((matched = scan_html_block_start(
1048
5.66M
                                  input, parser->first_nonspace)) ||
1049
5.65M
                             (cont_type != CMARK_NODE_PARAGRAPH &&
1050
5.27M
                              !maybe_lazy &&
1051
5.04M
                              (matched = scan_html_block_start_7(
1052
5.04M
                                   input, parser->first_nonspace))))) {
1053
14.7k
      *container = add_child(parser, *container, CMARK_NODE_HTML_BLOCK,
1054
14.7k
                             parser->first_nonspace + 1);
1055
14.7k
      (*container)->as.html_block_type = matched;
1056
      // note, we don't adjust parser->offset because the tag is part of the
1057
      // text
1058
5.81M
    } else if (!indented && cont_type == CMARK_NODE_PARAGRAPH &&
1059
374k
               (lev =
1060
374k
                    scan_setext_heading_line(input, parser->first_nonspace))) {
1061
      // finalize paragraph, resolving reference links
1062
74.6k
      has_content = resolve_reference_link_definitions(parser);
1063
1064
74.6k
      if (has_content) {
1065
1066
74.6k
        (*container)->type = (uint16_t)CMARK_NODE_HEADING;
1067
74.6k
        (*container)->as.heading.level = lev;
1068
74.6k
        (*container)->as.heading.setext = true;
1069
74.6k
        S_advance_offset(parser, input, input->len - 1 - parser->offset, false);
1070
74.6k
      }
1071
5.73M
    } else if (!indented &&
1072
5.57M
               !(cont_type == CMARK_NODE_PARAGRAPH && !all_matched) &&
1073
5.57M
               (parser->thematic_break_kill_pos <= parser->first_nonspace) &&
1074
5.38M
               S_scan_thematic_break(parser, input, parser->first_nonspace)) {
1075
      // it's only now that we know the line is not part of a setext heading:
1076
40
      *container = add_child(parser, *container, CMARK_NODE_THEMATIC_BREAK,
1077
40
                             parser->first_nonspace + 1);
1078
40
      S_advance_offset(parser, input, input->len - 1 - parser->offset, false);
1079
5.73M
    } else if ((!indented || cont_type == CMARK_NODE_LIST) &&
1080
5.70M
               parser->indent < 4 &&
1081
5.57M
               (matched = parse_list_marker(
1082
5.57M
                    parser->mem, input, parser->first_nonspace,
1083
5.57M
                    (*container)->type == CMARK_NODE_PARAGRAPH, &data))) {
1084
1085
      // Note that we can have new list items starting with >= 4
1086
      // spaces indent, as long as the list container is still open.
1087
2.45M
      int i = 0;
1088
1089
      // compute padding:
1090
2.45M
      S_advance_offset(parser, input,
1091
2.45M
                       parser->first_nonspace + matched - parser->offset,
1092
2.45M
                       false);
1093
1094
2.45M
      save_partially_consumed_tab = parser->partially_consumed_tab;
1095
2.45M
      save_offset = parser->offset;
1096
2.45M
      save_column = parser->column;
1097
1098
2.66M
      while (parser->column - save_column <= 5 &&
1099
2.66M
             S_is_space_or_tab(peek_at(input, parser->offset))) {
1100
209k
        S_advance_offset(parser, input, 1, true);
1101
209k
      }
1102
1103
2.45M
      i = parser->column - save_column;
1104
2.45M
      if (i >= 5 || i < 1 ||
1105
          // only spaces after list marker:
1106
2.26M
          S_is_line_end_char(peek_at(input, parser->offset))) {
1107
2.26M
        data->padding = matched + 1;
1108
2.26M
        parser->offset = save_offset;
1109
2.26M
        parser->column = save_column;
1110
2.26M
        parser->partially_consumed_tab = save_partially_consumed_tab;
1111
2.26M
        if (i > 0) {
1112
2.24k
          S_advance_offset(parser, input, 1, true);
1113
2.24k
        }
1114
2.26M
      } else {
1115
194k
        data->padding = matched + i;
1116
194k
      }
1117
1118
      // check container; if it's a list, see if this list item
1119
      // can continue the list; otherwise, create a list container.
1120
1121
2.45M
      data->marker_offset = parser->indent;
1122
1123
2.45M
      if (cont_type != CMARK_NODE_LIST ||
1124
2.45M
          !lists_match(&((*container)->as.list), data)) {
1125
2.45M
        *container = add_child(parser, *container, CMARK_NODE_LIST,
1126
2.45M
                               parser->first_nonspace + 1);
1127
1128
2.45M
        memcpy(&((*container)->as.list), data, sizeof(*data));
1129
2.45M
      }
1130
1131
      // add the list item
1132
2.45M
      *container = add_child(parser, *container, CMARK_NODE_ITEM,
1133
2.45M
                             parser->first_nonspace + 1);
1134
      /* TODO: static */
1135
2.45M
      memcpy(&((*container)->as.list), data, sizeof(*data));
1136
2.45M
      parser->mem->free(data);
1137
3.27M
    } else if (indented && !maybe_lazy && !parser->blank) {
1138
130k
      S_advance_offset(parser, input, CODE_INDENT, true);
1139
130k
      *container = add_child(parser, *container, CMARK_NODE_CODE_BLOCK,
1140
130k
                             parser->offset + 1);
1141
130k
      (*container)->as.code.fenced = false;
1142
130k
      (*container)->as.code.fence_char = 0;
1143
130k
      (*container)->as.code.fence_length = 0;
1144
130k
      (*container)->as.code.fence_offset = 0;
1145
130k
      (*container)->as.code.info = NULL;
1146
1147
3.14M
    } else {
1148
3.14M
      break;
1149
3.14M
    }
1150
1151
4.89M
    if (accepts_lines(S_type(*container))) {
1152
      // if it's a line container, it can't contain other containers
1153
209k
      break;
1154
209k
    }
1155
1156
4.68M
    cont_type = S_type(*container);
1157
4.68M
    maybe_lazy = false;
1158
4.68M
  }
1159
3.42M
}
1160
1161
static void add_text_to_container(cmark_parser *parser, cmark_node *container,
1162
                                  cmark_node *last_matched_container,
1163
3.42M
                                  cmark_chunk *input) {
1164
3.42M
  cmark_node *tmp;
1165
  // what remains at parser->offset is a text line.  add the text to the
1166
  // appropriate container.
1167
1168
3.42M
  S_find_first_nonspace(parser, input);
1169
1170
3.42M
  if (parser->blank && container->last_child)
1171
254k
    S_set_last_line_blank(container->last_child, true);
1172
1173
  // block quote lines are never blank as they start with >
1174
  // and we don't count blanks in fenced code for purposes of tight/loose
1175
  // lists or breaking out of lists.  we also don't set last_line_blank
1176
  // on an empty list item.
1177
3.42M
  const cmark_node_type ctype = S_type(container);
1178
3.42M
  const bool last_line_blank =
1179
3.42M
      (parser->blank && ctype != CMARK_NODE_BLOCK_QUOTE &&
1180
2.62M
       ctype != CMARK_NODE_HEADING && ctype != CMARK_NODE_THEMATIC_BREAK &&
1181
2.54M
       !(ctype == CMARK_NODE_CODE_BLOCK && container->as.code.fenced) &&
1182
2.54M
       !(ctype == CMARK_NODE_ITEM && container->first_child == NULL &&
1183
2.26M
         container->start_line == parser->line_number));
1184
1185
3.42M
  S_set_last_line_blank(container, last_line_blank);
1186
1187
3.42M
  tmp = container;
1188
11.6M
  while (tmp->parent) {
1189
8.21M
    S_set_last_line_blank(tmp->parent, false);
1190
8.21M
    tmp = tmp->parent;
1191
8.21M
  }
1192
1193
  // If the last line processed belonged to a paragraph node,
1194
  // and we didn't match all of the line prefixes for the open containers,
1195
  // and we didn't start any new containers,
1196
  // and the line isn't blank,
1197
  // then treat this as a "lazy continuation line" and add it to
1198
  // the open paragraph.
1199
3.42M
  if (parser->current != last_matched_container &&
1200
2.71M
      container == last_matched_container && !parser->blank &&
1201
154k
      S_type(parser->current) == CMARK_NODE_PARAGRAPH) {
1202
89.1k
    add_line(input, parser);
1203
3.34M
  } else { // not a lazy continuation
1204
    // Finalize any blocks that were not matched and set cur to container:
1205
9.47M
    while (parser->current != last_matched_container) {
1206
6.13M
      parser->current = finalize(parser, parser->current);
1207
6.13M
      assert(parser->current != NULL);
1208
6.13M
    }
1209
1210
3.34M
    if (S_type(container) == CMARK_NODE_CODE_BLOCK) {
1211
176k
      add_line(input, parser);
1212
3.16M
    } else if (S_type(container) == CMARK_NODE_HTML_BLOCK) {
1213
27.8k
      add_line(input, parser);
1214
1215
27.8k
      int matches_end_condition;
1216
27.8k
      switch (container->as.html_block_type) {
1217
4.38k
      case 1:
1218
        // </script>, </style>, </textarea>, </pre>
1219
4.38k
        matches_end_condition =
1220
4.38k
            scan_html_block_end_1(input, parser->first_nonspace);
1221
4.38k
        break;
1222
0
      case 2:
1223
        // -->
1224
0
        matches_end_condition =
1225
0
            scan_html_block_end_2(input, parser->first_nonspace);
1226
0
        break;
1227
1.45k
      case 3:
1228
        // ?>
1229
1.45k
        matches_end_condition =
1230
1.45k
            scan_html_block_end_3(input, parser->first_nonspace);
1231
1.45k
        break;
1232
1.48k
      case 4:
1233
        // >
1234
1.48k
        matches_end_condition =
1235
1.48k
            scan_html_block_end_4(input, parser->first_nonspace);
1236
1.48k
        break;
1237
11.8k
      case 5:
1238
        // ]]>
1239
11.8k
        matches_end_condition =
1240
11.8k
            scan_html_block_end_5(input, parser->first_nonspace);
1241
11.8k
        break;
1242
8.68k
      default:
1243
8.68k
        matches_end_condition = 0;
1244
8.68k
        break;
1245
27.8k
      }
1246
1247
27.8k
      if (matches_end_condition) {
1248
9.00k
        container = finalize(parser, container);
1249
9.00k
        assert(parser->current != NULL);
1250
9.00k
      }
1251
3.13M
    } else if (parser->blank) {
1252
      // ??? do nothing
1253
2.59M
    } else if (accepts_lines(S_type(container))) {
1254
304k
      if (S_type(container) == CMARK_NODE_HEADING &&
1255
3.76k
          container->as.heading.setext == false) {
1256
3.76k
        chop_trailing_hashtags(input);
1257
3.76k
      }
1258
304k
      S_advance_offset(parser, input, parser->first_nonspace - parser->offset,
1259
304k
                       false);
1260
304k
      add_line(input, parser);
1261
304k
    } else {
1262
      // create paragraph container for line
1263
237k
      container = add_child(parser, container, CMARK_NODE_PARAGRAPH,
1264
237k
                            parser->first_nonspace + 1);
1265
237k
      S_advance_offset(parser, input, parser->first_nonspace - parser->offset,
1266
237k
                       false);
1267
237k
      add_line(input, parser);
1268
237k
    }
1269
1270
3.34M
    parser->current = container;
1271
3.34M
  }
1272
3.42M
}
1273
1274
/* See http://spec.commonmark.org/0.24/#phase-1-block-structure */
1275
static void S_process_line(cmark_parser *parser, const unsigned char *buffer,
1276
3.43M
                           bufsize_t bytes) {
1277
3.43M
  cmark_node *last_matched_container;
1278
3.43M
  bool all_matched = true;
1279
3.43M
  cmark_node *container;
1280
3.43M
  cmark_chunk input;
1281
1282
3.43M
  if (parser->options & CMARK_OPT_VALIDATE_UTF8)
1283
1.29M
    cmark_utf8proc_check(&parser->curline, buffer, bytes);
1284
2.14M
  else
1285
2.14M
    cmark_strbuf_put(&parser->curline, buffer, bytes);
1286
1287
3.43M
  bytes = parser->curline.size;
1288
1289
  // ensure line ends with a newline:
1290
3.43M
  if (bytes == 0 || !S_is_line_end_char(parser->curline.ptr[bytes - 1]))
1291
3.43M
    cmark_strbuf_putc(&parser->curline, '\n');
1292
1293
3.43M
  parser->offset = 0;
1294
3.43M
  parser->column = 0;
1295
3.43M
  parser->first_nonspace = 0;
1296
3.43M
  parser->first_nonspace_column = 0;
1297
3.43M
  parser->thematic_break_kill_pos = 0;
1298
3.43M
  parser->indent = 0;
1299
3.43M
  parser->blank = false;
1300
3.43M
  parser->partially_consumed_tab = false;
1301
1302
3.43M
  input.data = parser->curline.ptr;
1303
3.43M
  input.len = parser->curline.size;
1304
1305
3.43M
  parser->line_number++;
1306
1307
3.43M
  last_matched_container = check_open_blocks(parser, &input, &all_matched);
1308
1309
3.43M
  if (!last_matched_container)
1310
9.34k
    goto finished;
1311
1312
3.42M
  container = last_matched_container;
1313
1314
3.42M
  open_new_blocks(parser, &container, &input, all_matched);
1315
1316
3.42M
  add_text_to_container(parser, container, last_matched_container, &input);
1317
1318
3.43M
finished:
1319
3.43M
  parser->last_line_length = input.len;
1320
3.43M
  if (parser->last_line_length &&
1321
3.43M
      input.data[parser->last_line_length - 1] == '\n')
1322
3.43M
    parser->last_line_length -= 1;
1323
3.43M
  if (parser->last_line_length &&
1324
3.17M
      input.data[parser->last_line_length - 1] == '\r')
1325
0
    parser->last_line_length -= 1;
1326
1327
3.43M
  cmark_strbuf_clear(&parser->curline);
1328
3.43M
}
1329
1330
328
cmark_node *cmark_parser_finish(cmark_parser *parser) {
1331
328
  if (parser->linebuf.size) {
1332
107
    S_process_line(parser, parser->linebuf.ptr, parser->linebuf.size);
1333
107
    cmark_strbuf_clear(&parser->linebuf);
1334
107
  }
1335
1336
328
  finalize_document(parser);
1337
1338
328
  cmark_consolidate_text_nodes(parser->root);
1339
1340
328
  cmark_strbuf_free(&parser->curline);
1341
1342
#if CMARK_DEBUG_NODES
1343
  if (cmark_node_check(parser->root, stderr)) {
1344
    abort();
1345
  }
1346
#endif
1347
328
  return parser->root;
1348
328
}