markly 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/bin/markly +94 -0
  3. data/ext/markly/arena.c +103 -0
  4. data/ext/markly/autolink.c +425 -0
  5. data/ext/markly/autolink.h +8 -0
  6. data/ext/markly/blocks.c +1585 -0
  7. data/ext/markly/buffer.c +278 -0
  8. data/ext/markly/buffer.h +116 -0
  9. data/ext/markly/case_fold_switch.inc +4327 -0
  10. data/ext/markly/chunk.h +135 -0
  11. data/ext/markly/cmark-gfm-core-extensions.h +54 -0
  12. data/ext/markly/cmark-gfm-extension_api.h +736 -0
  13. data/ext/markly/cmark-gfm-extensions_export.h +42 -0
  14. data/ext/markly/cmark-gfm.h +817 -0
  15. data/ext/markly/cmark-gfm_export.h +42 -0
  16. data/ext/markly/cmark-gfm_version.h +7 -0
  17. data/ext/markly/cmark.c +55 -0
  18. data/ext/markly/cmark_ctype.c +44 -0
  19. data/ext/markly/cmark_ctype.h +33 -0
  20. data/ext/markly/commonmark.c +519 -0
  21. data/ext/markly/config.h +76 -0
  22. data/ext/markly/core-extensions.c +27 -0
  23. data/ext/markly/entities.inc +2138 -0
  24. data/ext/markly/ext_scanners.c +1159 -0
  25. data/ext/markly/ext_scanners.h +24 -0
  26. data/ext/markly/extconf.rb +7 -0
  27. data/ext/markly/footnotes.c +40 -0
  28. data/ext/markly/footnotes.h +25 -0
  29. data/ext/markly/houdini.h +57 -0
  30. data/ext/markly/houdini_href_e.c +100 -0
  31. data/ext/markly/houdini_html_e.c +66 -0
  32. data/ext/markly/houdini_html_u.c +149 -0
  33. data/ext/markly/html.c +465 -0
  34. data/ext/markly/html.h +27 -0
  35. data/ext/markly/inlines.c +1633 -0
  36. data/ext/markly/inlines.h +29 -0
  37. data/ext/markly/iterator.c +159 -0
  38. data/ext/markly/iterator.h +26 -0
  39. data/ext/markly/latex.c +466 -0
  40. data/ext/markly/linked_list.c +37 -0
  41. data/ext/markly/man.c +278 -0
  42. data/ext/markly/map.c +122 -0
  43. data/ext/markly/map.h +41 -0
  44. data/ext/markly/markly.c +1226 -0
  45. data/ext/markly/markly.h +16 -0
  46. data/ext/markly/node.c +979 -0
  47. data/ext/markly/node.h +118 -0
  48. data/ext/markly/parser.h +58 -0
  49. data/ext/markly/plaintext.c +235 -0
  50. data/ext/markly/plugin.c +36 -0
  51. data/ext/markly/plugin.h +34 -0
  52. data/ext/markly/references.c +42 -0
  53. data/ext/markly/references.h +26 -0
  54. data/ext/markly/registry.c +63 -0
  55. data/ext/markly/registry.h +24 -0
  56. data/ext/markly/render.c +205 -0
  57. data/ext/markly/render.h +62 -0
  58. data/ext/markly/scanners.c +20382 -0
  59. data/ext/markly/scanners.h +62 -0
  60. data/ext/markly/scanners.re +326 -0
  61. data/ext/markly/strikethrough.c +167 -0
  62. data/ext/markly/strikethrough.h +9 -0
  63. data/ext/markly/syntax_extension.c +149 -0
  64. data/ext/markly/syntax_extension.h +34 -0
  65. data/ext/markly/table.c +803 -0
  66. data/ext/markly/table.h +12 -0
  67. data/ext/markly/tagfilter.c +60 -0
  68. data/ext/markly/tagfilter.h +8 -0
  69. data/ext/markly/tasklist.c +156 -0
  70. data/ext/markly/tasklist.h +8 -0
  71. data/ext/markly/utf8.c +317 -0
  72. data/ext/markly/utf8.h +35 -0
  73. data/ext/markly/xml.c +181 -0
  74. data/lib/markly.rb +43 -0
  75. data/lib/markly/flags.rb +37 -0
  76. data/lib/markly/markly.so +0 -0
  77. data/lib/markly/node.rb +70 -0
  78. data/lib/markly/node/inspect.rb +59 -0
  79. data/lib/markly/renderer.rb +133 -0
  80. data/lib/markly/renderer/html_renderer.rb +252 -0
  81. data/lib/markly/version.rb +5 -0
  82. metadata +211 -0
@@ -0,0 +1,27 @@
1
+ #ifndef CMARK_HTML_H
2
+ #define CMARK_HTML_H
3
+
4
+ #include "buffer.h"
5
+ #include "node.h"
6
+
7
+ CMARK_INLINE
8
+ static void cmark_html_render_cr(cmark_strbuf *html) {
9
+ if (html->size && html->ptr[html->size - 1] != '\n')
10
+ cmark_strbuf_putc(html, '\n');
11
+ }
12
+
13
+ #define BUFFER_SIZE 100
14
+
15
+ CMARK_INLINE
16
+ static void cmark_html_render_sourcepos(cmark_node *node, cmark_strbuf *html, int options) {
17
+ char buffer[BUFFER_SIZE];
18
+ if (CMARK_OPT_SOURCEPOS & options) {
19
+ snprintf(buffer, BUFFER_SIZE, " data-sourcepos=\"%d:%d-%d:%d\"",
20
+ cmark_node_get_start_line(node), cmark_node_get_start_column(node),
21
+ cmark_node_get_end_line(node), cmark_node_get_end_column(node));
22
+ cmark_strbuf_puts(html, buffer);
23
+ }
24
+ }
25
+
26
+
27
+ #endif
@@ -0,0 +1,1633 @@
1
+ #include <stdlib.h>
2
+ #include <string.h>
3
+ #include <stdio.h>
4
+
5
+ #include "cmark_ctype.h"
6
+ #include "config.h"
7
+ #include "node.h"
8
+ #include "parser.h"
9
+ #include "references.h"
10
+ #include "cmark-gfm.h"
11
+ #include "houdini.h"
12
+ #include "utf8.h"
13
+ #include "scanners.h"
14
+ #include "inlines.h"
15
+ #include "syntax_extension.h"
16
+
17
+ static const char *EMDASH = "\xE2\x80\x94";
18
+ static const char *ENDASH = "\xE2\x80\x93";
19
+ static const char *ELLIPSES = "\xE2\x80\xA6";
20
+ static const char *LEFTDOUBLEQUOTE = "\xE2\x80\x9C";
21
+ static const char *RIGHTDOUBLEQUOTE = "\xE2\x80\x9D";
22
+ static const char *LEFTSINGLEQUOTE = "\xE2\x80\x98";
23
+ static const char *RIGHTSINGLEQUOTE = "\xE2\x80\x99";
24
+
25
+ // Macros for creating various kinds of simple.
26
+ #define make_str(subj, sc, ec, s) make_literal(subj, CMARK_NODE_TEXT, sc, ec, s)
27
+ #define make_code(subj, sc, ec, s) make_literal(subj, CMARK_NODE_CODE, sc, ec, s)
28
+ #define make_raw_html(subj, sc, ec, s) make_literal(subj, CMARK_NODE_HTML_INLINE, sc, ec, s)
29
+ #define make_linebreak(mem) make_simple(mem, CMARK_NODE_LINEBREAK)
30
+ #define make_softbreak(mem) make_simple(mem, CMARK_NODE_SOFTBREAK)
31
+ #define make_emph(mem) make_simple(mem, CMARK_NODE_EMPH)
32
+ #define make_strong(mem) make_simple(mem, CMARK_NODE_STRONG)
33
+
34
+ #define MAXBACKTICKS 80
35
+
36
+ typedef struct bracket {
37
+ struct bracket *previous;
38
+ struct delimiter *previous_delimiter;
39
+ cmark_node *inl_text;
40
+ bufsize_t position;
41
+ bool image;
42
+ bool active;
43
+ bool bracket_after;
44
+ } bracket;
45
+
46
+ typedef struct subject{
47
+ cmark_mem *mem;
48
+ cmark_chunk input;
49
+ int line;
50
+ bufsize_t pos;
51
+ int block_offset;
52
+ int column_offset;
53
+ cmark_map *refmap;
54
+ delimiter *last_delim;
55
+ bracket *last_bracket;
56
+ bufsize_t backticks[MAXBACKTICKS + 1];
57
+ bool scanned_for_backticks;
58
+ } subject;
59
+
60
+ // Extensions may populate this.
61
+ static int8_t SKIP_CHARS[256];
62
+
63
+ static CMARK_INLINE bool S_is_line_end_char(char c) {
64
+ return (c == '\n' || c == '\r');
65
+ }
66
+
67
+ static delimiter *S_insert_emph(subject *subj, delimiter *opener,
68
+ delimiter *closer);
69
+
70
+ static int parse_inline(cmark_parser *parser, subject *subj, cmark_node *parent, int options);
71
+
72
+ static void subject_from_buf(cmark_mem *mem, int line_number, int block_offset, subject *e,
73
+ cmark_chunk *buffer, cmark_map *refmap);
74
+ static bufsize_t subject_find_special_char(subject *subj, int options);
75
+
76
+ // Create an inline with a literal string value.
77
+ static CMARK_INLINE cmark_node *make_literal(subject *subj, cmark_node_type t,
78
+ int start_column, int end_column,
79
+ cmark_chunk s) {
80
+ cmark_node *e = (cmark_node *)subj->mem->calloc(1, sizeof(*e));
81
+ cmark_strbuf_init(subj->mem, &e->content, 0);
82
+ e->type = (uint16_t)t;
83
+ e->as.literal = s;
84
+ e->start_line = e->end_line = subj->line;
85
+ // columns are 1 based.
86
+ e->start_column = start_column + 1 + subj->column_offset + subj->block_offset;
87
+ e->end_column = end_column + 1 + subj->column_offset + subj->block_offset;
88
+ return e;
89
+ }
90
+
91
+ // Create an inline with no value.
92
+ static CMARK_INLINE cmark_node *make_simple(cmark_mem *mem, cmark_node_type t) {
93
+ cmark_node *e = (cmark_node *)mem->calloc(1, sizeof(*e));
94
+ cmark_strbuf_init(mem, &e->content, 0);
95
+ e->type = (uint16_t)t;
96
+ return e;
97
+ }
98
+
99
+ // Like make_str, but parses entities.
100
+ static cmark_node *make_str_with_entities(subject *subj,
101
+ int start_column, int end_column,
102
+ cmark_chunk *content) {
103
+ cmark_strbuf unescaped = CMARK_BUF_INIT(subj->mem);
104
+
105
+ if (houdini_unescape_html(&unescaped, content->data, content->len)) {
106
+ return make_str(subj, start_column, end_column, cmark_chunk_buf_detach(&unescaped));
107
+ } else {
108
+ return make_str(subj, start_column, end_column, *content);
109
+ }
110
+ }
111
+
112
+ // Duplicate a chunk by creating a copy of the buffer not by reusing the
113
+ // buffer like cmark_chunk_dup does.
114
+ static cmark_chunk chunk_clone(cmark_mem *mem, cmark_chunk *src) {
115
+ cmark_chunk c;
116
+ bufsize_t len = src->len;
117
+
118
+ c.len = len;
119
+ c.data = (unsigned char *)mem->calloc(len + 1, 1);
120
+ c.alloc = 1;
121
+ if (len)
122
+ memcpy(c.data, src->data, len);
123
+ c.data[len] = '\0';
124
+
125
+ return c;
126
+ }
127
+
128
+ static cmark_chunk cmark_clean_autolink(cmark_mem *mem, cmark_chunk *url,
129
+ int is_email) {
130
+ cmark_strbuf buf = CMARK_BUF_INIT(mem);
131
+
132
+ cmark_chunk_trim(url);
133
+
134
+ if (url->len == 0) {
135
+ cmark_chunk result = CMARK_CHUNK_EMPTY;
136
+ return result;
137
+ }
138
+
139
+ if (is_email)
140
+ cmark_strbuf_puts(&buf, "mailto:");
141
+
142
+ houdini_unescape_html_f(&buf, url->data, url->len);
143
+ return cmark_chunk_buf_detach(&buf);
144
+ }
145
+
146
+ static CMARK_INLINE cmark_node *make_autolink(subject *subj,
147
+ int start_column, int end_column,
148
+ cmark_chunk url, int is_email) {
149
+ cmark_node *link = make_simple(subj->mem, CMARK_NODE_LINK);
150
+ link->as.link.url = cmark_clean_autolink(subj->mem, &url, is_email);
151
+ link->as.link.title = cmark_chunk_literal("");
152
+ link->start_line = link->end_line = subj->line;
153
+ link->start_column = start_column + 1;
154
+ link->end_column = end_column + 1;
155
+ cmark_node_append_child(link, make_str_with_entities(subj, start_column + 1, end_column - 1, &url));
156
+ return link;
157
+ }
158
+
159
+ static void subject_from_buf(cmark_mem *mem, int line_number, int block_offset, subject *e,
160
+ cmark_chunk *chunk, cmark_map *refmap) {
161
+ int i;
162
+ e->mem = mem;
163
+ e->input = *chunk;
164
+ e->line = line_number;
165
+ e->pos = 0;
166
+ e->block_offset = block_offset;
167
+ e->column_offset = 0;
168
+ e->refmap = refmap;
169
+ e->last_delim = NULL;
170
+ e->last_bracket = NULL;
171
+ for (i = 0; i <= MAXBACKTICKS; i++) {
172
+ e->backticks[i] = 0;
173
+ }
174
+ e->scanned_for_backticks = false;
175
+ }
176
+
177
+ static CMARK_INLINE int isbacktick(int c) { return (c == '`'); }
178
+
179
+ static CMARK_INLINE unsigned char peek_char_n(subject *subj, bufsize_t n) {
180
+ // NULL bytes should have been stripped out by now. If they're
181
+ // present, it's a programming error:
182
+ assert(!(subj->pos + n < subj->input.len && subj->input.data[subj->pos + n] == 0));
183
+ return (subj->pos + n < subj->input.len) ? subj->input.data[subj->pos + n] : 0;
184
+ }
185
+
186
+ static CMARK_INLINE unsigned char peek_char(subject *subj) {
187
+ return peek_char_n(subj, 0);
188
+ }
189
+
190
+ static CMARK_INLINE unsigned char peek_at(subject *subj, bufsize_t pos) {
191
+ return subj->input.data[pos];
192
+ }
193
+
194
+ // Return true if there are more characters in the subject.
195
+ static CMARK_INLINE int is_eof(subject *subj) {
196
+ return (subj->pos >= subj->input.len);
197
+ }
198
+
199
+ // Advance the subject. Doesn't check for eof.
200
+ #define advance(subj) (subj)->pos += 1
201
+
202
+ static CMARK_INLINE bool skip_spaces(subject *subj) {
203
+ bool skipped = false;
204
+ while (peek_char(subj) == ' ' || peek_char(subj) == '\t') {
205
+ advance(subj);
206
+ skipped = true;
207
+ }
208
+ return skipped;
209
+ }
210
+
211
+ static CMARK_INLINE bool skip_line_end(subject *subj) {
212
+ bool seen_line_end_char = false;
213
+ if (peek_char(subj) == '\r') {
214
+ advance(subj);
215
+ seen_line_end_char = true;
216
+ }
217
+ if (peek_char(subj) == '\n') {
218
+ advance(subj);
219
+ seen_line_end_char = true;
220
+ }
221
+ return seen_line_end_char || is_eof(subj);
222
+ }
223
+
224
+ // Take characters while a predicate holds, and return a string.
225
+ static CMARK_INLINE cmark_chunk take_while(subject *subj, int (*f)(int)) {
226
+ unsigned char c;
227
+ bufsize_t startpos = subj->pos;
228
+ bufsize_t len = 0;
229
+
230
+ while ((c = peek_char(subj)) && (*f)(c)) {
231
+ advance(subj);
232
+ len++;
233
+ }
234
+
235
+ return cmark_chunk_dup(&subj->input, startpos, len);
236
+ }
237
+
238
+ // Return the number of newlines in a given span of text in a subject. If
239
+ // the number is greater than zero, also return the number of characters
240
+ // between the last newline and the end of the span in `since_newline`.
241
+ static int count_newlines(subject *subj, bufsize_t from, bufsize_t len, int *since_newline) {
242
+ int nls = 0;
243
+ int since_nl = 0;
244
+
245
+ while (len--) {
246
+ if (subj->input.data[from++] == '\n') {
247
+ ++nls;
248
+ since_nl = 0;
249
+ } else {
250
+ ++since_nl;
251
+ }
252
+ }
253
+
254
+ if (!nls)
255
+ return 0;
256
+
257
+ *since_newline = since_nl;
258
+ return nls;
259
+ }
260
+
261
+ // Adjust `node`'s `end_line`, `end_column`, and `subj`'s `line` and
262
+ // `column_offset` according to the number of newlines in a just-matched span
263
+ // of text in `subj`.
264
+ static void adjust_subj_node_newlines(subject *subj, cmark_node *node, int matchlen, int extra, int options) {
265
+ if (!(options & CMARK_OPT_SOURCEPOS)) {
266
+ return;
267
+ }
268
+
269
+ int since_newline;
270
+ int newlines = count_newlines(subj, subj->pos - matchlen - extra, matchlen, &since_newline);
271
+ if (newlines) {
272
+ subj->line += newlines;
273
+ node->end_line += newlines;
274
+ node->end_column = since_newline;
275
+ subj->column_offset = -subj->pos + since_newline + extra;
276
+ }
277
+ }
278
+
279
+ // Try to process a backtick code span that began with a
280
+ // span of ticks of length openticklength length (already
281
+ // parsed). Return 0 if you don't find matching closing
282
+ // backticks, otherwise return the position in the subject
283
+ // after the closing backticks.
284
+ static bufsize_t scan_to_closing_backticks(subject *subj,
285
+ bufsize_t openticklength) {
286
+
287
+ bool found = false;
288
+ if (openticklength > MAXBACKTICKS) {
289
+ // we limit backtick string length because of the array subj->backticks:
290
+ return 0;
291
+ }
292
+ if (subj->scanned_for_backticks &&
293
+ subj->backticks[openticklength] <= subj->pos) {
294
+ // return if we already know there's no closer
295
+ return 0;
296
+ }
297
+ while (!found) {
298
+ // read non backticks
299
+ unsigned char c;
300
+ while ((c = peek_char(subj)) && c != '`') {
301
+ advance(subj);
302
+ }
303
+ if (is_eof(subj)) {
304
+ break;
305
+ }
306
+ bufsize_t numticks = 0;
307
+ while (peek_char(subj) == '`') {
308
+ advance(subj);
309
+ numticks++;
310
+ }
311
+ // store position of ender
312
+ if (numticks <= MAXBACKTICKS) {
313
+ subj->backticks[numticks] = subj->pos - numticks;
314
+ }
315
+ if (numticks == openticklength) {
316
+ return (subj->pos);
317
+ }
318
+ }
319
+ // got through whole input without finding closer
320
+ subj->scanned_for_backticks = true;
321
+ return 0;
322
+ }
323
+
324
+ // Destructively modify string, converting newlines to
325
+ // spaces, then removing a single leading + trailing space,
326
+ // unless the code span consists entirely of space characters.
327
+ static void S_normalize_code(cmark_strbuf *s) {
328
+ bufsize_t r, w;
329
+ bool contains_nonspace = false;
330
+
331
+ for (r = 0, w = 0; r < s->size; ++r) {
332
+ switch (s->ptr[r]) {
333
+ case '\r':
334
+ if (s->ptr[r + 1] != '\n') {
335
+ s->ptr[w++] = ' ';
336
+ }
337
+ break;
338
+ case '\n':
339
+ s->ptr[w++] = ' ';
340
+ break;
341
+ default:
342
+ s->ptr[w++] = s->ptr[r];
343
+ }
344
+ if (s->ptr[r] != ' ') {
345
+ contains_nonspace = true;
346
+ }
347
+ }
348
+
349
+ // begins and ends with space?
350
+ if (contains_nonspace &&
351
+ s->ptr[0] == ' ' && s->ptr[w - 1] == ' ') {
352
+ cmark_strbuf_drop(s, 1);
353
+ cmark_strbuf_truncate(s, w - 2);
354
+ } else {
355
+ cmark_strbuf_truncate(s, w);
356
+ }
357
+
358
+ }
359
+
360
+
361
+ // Parse backtick code section or raw backticks, return an inline.
362
+ // Assumes that the subject has a backtick at the current position.
363
+ static cmark_node *handle_backticks(subject *subj, int options) {
364
+ cmark_chunk openticks = take_while(subj, isbacktick);
365
+ bufsize_t startpos = subj->pos;
366
+ bufsize_t endpos = scan_to_closing_backticks(subj, openticks.len);
367
+
368
+ if (endpos == 0) { // not found
369
+ subj->pos = startpos; // rewind
370
+ return make_str(subj, subj->pos, subj->pos, openticks);
371
+ } else {
372
+ cmark_strbuf buf = CMARK_BUF_INIT(subj->mem);
373
+
374
+ cmark_strbuf_set(&buf, subj->input.data + startpos,
375
+ endpos - startpos - openticks.len);
376
+ S_normalize_code(&buf);
377
+
378
+ cmark_node *node = make_code(subj, startpos, endpos - openticks.len - 1, cmark_chunk_buf_detach(&buf));
379
+ adjust_subj_node_newlines(subj, node, endpos - startpos, openticks.len, options);
380
+ return node;
381
+ }
382
+ }
383
+
384
+
385
+ // Scan ***, **, or * and return number scanned, or 0.
386
+ // Advances position.
387
+ static int scan_delims(subject *subj, unsigned char c, bool *can_open,
388
+ bool *can_close) {
389
+ int numdelims = 0;
390
+ bufsize_t before_char_pos, after_char_pos;
391
+ int32_t after_char = 0;
392
+ int32_t before_char = 0;
393
+ int len;
394
+ bool left_flanking, right_flanking;
395
+
396
+ if (subj->pos == 0) {
397
+ before_char = 10;
398
+ } else {
399
+ before_char_pos = subj->pos - 1;
400
+ // walk back to the beginning of the UTF_8 sequence:
401
+ while ((peek_at(subj, before_char_pos) >> 6 == 2 || SKIP_CHARS[peek_at(subj, before_char_pos)]) && before_char_pos > 0) {
402
+ before_char_pos -= 1;
403
+ }
404
+ len = cmark_utf8proc_iterate(subj->input.data + before_char_pos,
405
+ subj->pos - before_char_pos, &before_char);
406
+ if (len == -1 || (before_char < 256 && SKIP_CHARS[(unsigned char) before_char])) {
407
+ before_char = 10;
408
+ }
409
+ }
410
+
411
+ if (c == '\'' || c == '"') {
412
+ numdelims++;
413
+ advance(subj); // limit to 1 delim for quotes
414
+ } else {
415
+ while (peek_char(subj) == c) {
416
+ numdelims++;
417
+ advance(subj);
418
+ }
419
+ }
420
+
421
+ if (subj->pos == subj->input.len) {
422
+ after_char = 10;
423
+ } else {
424
+ after_char_pos = subj->pos;
425
+ while (SKIP_CHARS[peek_at(subj, after_char_pos)] && after_char_pos < subj->input.len) {
426
+ after_char_pos += 1;
427
+ }
428
+ len = cmark_utf8proc_iterate(subj->input.data + after_char_pos,
429
+ subj->input.len - after_char_pos, &after_char);
430
+ if (len == -1 || (after_char < 256 && SKIP_CHARS[(unsigned char) after_char])) {
431
+ after_char = 10;
432
+ }
433
+ }
434
+
435
+ left_flanking = numdelims > 0 && !cmark_utf8proc_is_space(after_char) &&
436
+ (!cmark_utf8proc_is_punctuation(after_char) ||
437
+ cmark_utf8proc_is_space(before_char) ||
438
+ cmark_utf8proc_is_punctuation(before_char));
439
+ right_flanking = numdelims > 0 && !cmark_utf8proc_is_space(before_char) &&
440
+ (!cmark_utf8proc_is_punctuation(before_char) ||
441
+ cmark_utf8proc_is_space(after_char) ||
442
+ cmark_utf8proc_is_punctuation(after_char));
443
+ if (c == '_') {
444
+ *can_open = left_flanking &&
445
+ (!right_flanking || cmark_utf8proc_is_punctuation(before_char));
446
+ *can_close = right_flanking &&
447
+ (!left_flanking || cmark_utf8proc_is_punctuation(after_char));
448
+ } else if (c == '\'' || c == '"') {
449
+ *can_open = left_flanking && !right_flanking &&
450
+ before_char != ']' && before_char != ')';
451
+ *can_close = right_flanking;
452
+ } else {
453
+ *can_open = left_flanking;
454
+ *can_close = right_flanking;
455
+ }
456
+ return numdelims;
457
+ }
458
+
459
+ /*
460
+ static void print_delimiters(subject *subj)
461
+ {
462
+ delimiter *delim;
463
+ delim = subj->last_delim;
464
+ while (delim != NULL) {
465
+ printf("Item at stack pos %p: %d %d %d next(%p) prev(%p)\n",
466
+ (void*)delim, delim->delim_char,
467
+ delim->can_open, delim->can_close,
468
+ (void*)delim->next, (void*)delim->previous);
469
+ delim = delim->previous;
470
+ }
471
+ }
472
+ */
473
+
474
+ static void remove_delimiter(subject *subj, delimiter *delim) {
475
+ if (delim == NULL)
476
+ return;
477
+ if (delim->next == NULL) {
478
+ // end of list:
479
+ assert(delim == subj->last_delim);
480
+ subj->last_delim = delim->previous;
481
+ } else {
482
+ delim->next->previous = delim->previous;
483
+ }
484
+ if (delim->previous != NULL) {
485
+ delim->previous->next = delim->next;
486
+ }
487
+ subj->mem->free(delim);
488
+ }
489
+
490
+ static void pop_bracket(subject *subj) {
491
+ bracket *b;
492
+ if (subj->last_bracket == NULL)
493
+ return;
494
+ b = subj->last_bracket;
495
+ subj->last_bracket = subj->last_bracket->previous;
496
+ subj->mem->free(b);
497
+ }
498
+
499
+ static void push_delimiter(subject *subj, unsigned char c, bool can_open,
500
+ bool can_close, cmark_node *inl_text) {
501
+ delimiter *delim = (delimiter *)subj->mem->calloc(1, sizeof(delimiter));
502
+ delim->delim_char = c;
503
+ delim->can_open = can_open;
504
+ delim->can_close = can_close;
505
+ delim->inl_text = inl_text;
506
+ delim->length = inl_text->as.literal.len;
507
+ delim->previous = subj->last_delim;
508
+ delim->next = NULL;
509
+ if (delim->previous != NULL) {
510
+ delim->previous->next = delim;
511
+ }
512
+ subj->last_delim = delim;
513
+ }
514
+
515
+ static void push_bracket(subject *subj, bool image, cmark_node *inl_text) {
516
+ bracket *b = (bracket *)subj->mem->calloc(1, sizeof(bracket));
517
+ if (subj->last_bracket != NULL) {
518
+ subj->last_bracket->bracket_after = true;
519
+ }
520
+ b->image = image;
521
+ b->active = true;
522
+ b->inl_text = inl_text;
523
+ b->previous = subj->last_bracket;
524
+ b->previous_delimiter = subj->last_delim;
525
+ b->position = subj->pos;
526
+ b->bracket_after = false;
527
+ subj->last_bracket = b;
528
+ }
529
+
530
+ // Assumes the subject has a c at the current position.
531
+ static cmark_node *handle_delim(subject *subj, unsigned char c, bool smart) {
532
+ bufsize_t numdelims;
533
+ cmark_node *inl_text;
534
+ bool can_open, can_close;
535
+ cmark_chunk contents;
536
+
537
+ numdelims = scan_delims(subj, c, &can_open, &can_close);
538
+
539
+ if (c == '\'' && smart) {
540
+ contents = cmark_chunk_literal(RIGHTSINGLEQUOTE);
541
+ } else if (c == '"' && smart) {
542
+ contents =
543
+ cmark_chunk_literal(can_close ? RIGHTDOUBLEQUOTE : LEFTDOUBLEQUOTE);
544
+ } else {
545
+ contents = cmark_chunk_dup(&subj->input, subj->pos - numdelims, numdelims);
546
+ }
547
+
548
+ inl_text = make_str(subj, subj->pos - numdelims, subj->pos - 1, contents);
549
+
550
+ if ((can_open || can_close) && (!(c == '\'' || c == '"') || smart)) {
551
+ push_delimiter(subj, c, can_open, can_close, inl_text);
552
+ }
553
+
554
+ return inl_text;
555
+ }
556
+
557
+ // Assumes we have a hyphen at the current position.
558
+ static cmark_node *handle_hyphen(subject *subj, bool smart) {
559
+ int startpos = subj->pos;
560
+
561
+ advance(subj);
562
+
563
+ if (!smart || peek_char(subj) != '-') {
564
+ return make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("-"));
565
+ }
566
+
567
+ while (smart && peek_char(subj) == '-') {
568
+ advance(subj);
569
+ }
570
+
571
+ int numhyphens = subj->pos - startpos;
572
+ int en_count = 0;
573
+ int em_count = 0;
574
+ int i;
575
+ cmark_strbuf buf = CMARK_BUF_INIT(subj->mem);
576
+
577
+ if (numhyphens % 3 == 0) { // if divisible by 3, use all em dashes
578
+ em_count = numhyphens / 3;
579
+ } else if (numhyphens % 2 == 0) { // if divisible by 2, use all en dashes
580
+ en_count = numhyphens / 2;
581
+ } else if (numhyphens % 3 == 2) { // use one en dash at end
582
+ en_count = 1;
583
+ em_count = (numhyphens - 2) / 3;
584
+ } else { // use two en dashes at the end
585
+ en_count = 2;
586
+ em_count = (numhyphens - 4) / 3;
587
+ }
588
+
589
+ for (i = em_count; i > 0; i--) {
590
+ cmark_strbuf_puts(&buf, EMDASH);
591
+ }
592
+
593
+ for (i = en_count; i > 0; i--) {
594
+ cmark_strbuf_puts(&buf, ENDASH);
595
+ }
596
+
597
+ return make_str(subj, startpos, subj->pos - 1, cmark_chunk_buf_detach(&buf));
598
+ }
599
+
600
+ // Assumes we have a period at the current position.
601
+ static cmark_node *handle_period(subject *subj, bool smart) {
602
+ advance(subj);
603
+ if (smart && peek_char(subj) == '.') {
604
+ advance(subj);
605
+ if (peek_char(subj) == '.') {
606
+ advance(subj);
607
+ return make_str(subj, subj->pos - 3, subj->pos - 1, cmark_chunk_literal(ELLIPSES));
608
+ } else {
609
+ return make_str(subj, subj->pos - 2, subj->pos - 1, cmark_chunk_literal(".."));
610
+ }
611
+ } else {
612
+ return make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("."));
613
+ }
614
+ }
615
+
616
+ static cmark_syntax_extension *get_extension_for_special_char(cmark_parser *parser, unsigned char c) {
617
+ cmark_llist *tmp_ext;
618
+
619
+ for (tmp_ext = parser->inline_syntax_extensions; tmp_ext; tmp_ext=tmp_ext->next) {
620
+ cmark_syntax_extension *ext = (cmark_syntax_extension *) tmp_ext->data;
621
+ cmark_llist *tmp_char;
622
+ for (tmp_char = ext->special_inline_chars; tmp_char; tmp_char=tmp_char->next) {
623
+ unsigned char tmp_c = (unsigned char)(size_t)tmp_char->data;
624
+
625
+ if (tmp_c == c) {
626
+ return ext;
627
+ }
628
+ }
629
+ }
630
+
631
+ return NULL;
632
+ }
633
+
634
+ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *stack_bottom) {
635
+ delimiter *closer = subj->last_delim;
636
+ delimiter *opener;
637
+ delimiter *old_closer;
638
+ bool opener_found;
639
+ delimiter *openers_bottom[3][128];
640
+ int i;
641
+
642
+ // initialize openers_bottom:
643
+ memset(&openers_bottom, 0, sizeof(openers_bottom));
644
+ for (i=0; i < 3; i++) {
645
+ openers_bottom[i]['*'] = stack_bottom;
646
+ openers_bottom[i]['_'] = stack_bottom;
647
+ openers_bottom[i]['\''] = stack_bottom;
648
+ openers_bottom[i]['"'] = stack_bottom;
649
+ }
650
+
651
+ // move back to first relevant delim.
652
+ while (closer != NULL && closer->previous != stack_bottom) {
653
+ closer = closer->previous;
654
+ }
655
+
656
+ // now move forward, looking for closers, and handling each
657
+ while (closer != NULL) {
658
+ cmark_syntax_extension *extension = get_extension_for_special_char(parser, closer->delim_char);
659
+ if (closer->can_close) {
660
+ // Now look backwards for first matching opener:
661
+ opener = closer->previous;
662
+ opener_found = false;
663
+ while (opener != NULL && opener != stack_bottom &&
664
+ opener != openers_bottom[closer->length % 3][closer->delim_char]) {
665
+ if (opener->can_open && opener->delim_char == closer->delim_char) {
666
+ // interior closer of size 2 can't match opener of size 1
667
+ // or of size 1 can't match 2
668
+ if (!(closer->can_open || opener->can_close) ||
669
+ closer->length % 3 == 0 ||
670
+ (opener->length + closer->length) % 3 != 0) {
671
+ opener_found = true;
672
+ break;
673
+ }
674
+ }
675
+ opener = opener->previous;
676
+ }
677
+ old_closer = closer;
678
+
679
+ if (extension) {
680
+ if (opener_found)
681
+ closer = extension->insert_inline_from_delim(extension, parser, subj, opener, closer);
682
+ else
683
+ closer = closer->next;
684
+ } else if (closer->delim_char == '*' || closer->delim_char == '_') {
685
+ if (opener_found) {
686
+ closer = S_insert_emph(subj, opener, closer);
687
+ } else {
688
+ closer = closer->next;
689
+ }
690
+ } else if (closer->delim_char == '\'') {
691
+ cmark_chunk_free(subj->mem, &closer->inl_text->as.literal);
692
+ closer->inl_text->as.literal = cmark_chunk_literal(RIGHTSINGLEQUOTE);
693
+ if (opener_found) {
694
+ cmark_chunk_free(subj->mem, &opener->inl_text->as.literal);
695
+ opener->inl_text->as.literal = cmark_chunk_literal(LEFTSINGLEQUOTE);
696
+ }
697
+ closer = closer->next;
698
+ } else if (closer->delim_char == '"') {
699
+ cmark_chunk_free(subj->mem, &closer->inl_text->as.literal);
700
+ closer->inl_text->as.literal = cmark_chunk_literal(RIGHTDOUBLEQUOTE);
701
+ if (opener_found) {
702
+ cmark_chunk_free(subj->mem, &opener->inl_text->as.literal);
703
+ opener->inl_text->as.literal = cmark_chunk_literal(LEFTDOUBLEQUOTE);
704
+ }
705
+ closer = closer->next;
706
+ }
707
+ if (!opener_found) {
708
+ // set lower bound for future searches for openers
709
+ openers_bottom[old_closer->length % 3][old_closer->delim_char] =
710
+ old_closer->previous;
711
+ if (!old_closer->can_open) {
712
+ // we can remove a closer that can't be an
713
+ // opener, once we've seen there's no
714
+ // matching opener:
715
+ remove_delimiter(subj, old_closer);
716
+ }
717
+ }
718
+ } else {
719
+ closer = closer->next;
720
+ }
721
+ }
722
+ // free all delimiters in list until stack_bottom:
723
+ while (subj->last_delim != NULL && subj->last_delim != stack_bottom) {
724
+ remove_delimiter(subj, subj->last_delim);
725
+ }
726
+ }
727
+
728
+ static delimiter *S_insert_emph(subject *subj, delimiter *opener,
729
+ delimiter *closer) {
730
+ delimiter *delim, *tmp_delim;
731
+ bufsize_t use_delims;
732
+ cmark_node *opener_inl = opener->inl_text;
733
+ cmark_node *closer_inl = closer->inl_text;
734
+ bufsize_t opener_num_chars = opener_inl->as.literal.len;
735
+ bufsize_t closer_num_chars = closer_inl->as.literal.len;
736
+ cmark_node *tmp, *tmpnext, *emph;
737
+
738
+ // calculate the actual number of characters used from this closer
739
+ use_delims = (closer_num_chars >= 2 && opener_num_chars >= 2) ? 2 : 1;
740
+
741
+ // remove used characters from associated inlines.
742
+ opener_num_chars -= use_delims;
743
+ closer_num_chars -= use_delims;
744
+ opener_inl->as.literal.len = opener_num_chars;
745
+ closer_inl->as.literal.len = closer_num_chars;
746
+
747
+ // free delimiters between opener and closer
748
+ delim = closer->previous;
749
+ while (delim != NULL && delim != opener) {
750
+ tmp_delim = delim->previous;
751
+ remove_delimiter(subj, delim);
752
+ delim = tmp_delim;
753
+ }
754
+
755
+ // create new emph or strong, and splice it in to our inlines
756
+ // between the opener and closer
757
+ emph = use_delims == 1 ? make_emph(subj->mem) : make_strong(subj->mem);
758
+
759
+ tmp = opener_inl->next;
760
+ while (tmp && tmp != closer_inl) {
761
+ tmpnext = tmp->next;
762
+ cmark_node_append_child(emph, tmp);
763
+ tmp = tmpnext;
764
+ }
765
+ cmark_node_insert_after(opener_inl, emph);
766
+
767
+ emph->start_line = opener_inl->start_line;
768
+ emph->end_line = closer_inl->end_line;
769
+ emph->start_column = opener_inl->start_column;
770
+ emph->end_column = closer_inl->end_column;
771
+
772
+ // if opener has 0 characters, remove it and its associated inline
773
+ if (opener_num_chars == 0) {
774
+ cmark_node_free(opener_inl);
775
+ remove_delimiter(subj, opener);
776
+ }
777
+
778
+ // if closer has 0 characters, remove it and its associated inline
779
+ if (closer_num_chars == 0) {
780
+ // remove empty closer inline
781
+ cmark_node_free(closer_inl);
782
+ // remove closer from list
783
+ tmp_delim = closer->next;
784
+ remove_delimiter(subj, closer);
785
+ closer = tmp_delim;
786
+ }
787
+
788
+ return closer;
789
+ }
790
+
791
+ // Parse backslash-escape or just a backslash, returning an inline.
792
+ static cmark_node *handle_backslash(cmark_parser *parser, subject *subj) {
793
+ advance(subj);
794
+ unsigned char nextchar = peek_char(subj);
795
+ if ((parser->backslash_ispunct ? parser->backslash_ispunct : cmark_ispunct)(nextchar)) {
796
+ // only ascii symbols and newline can be escaped
797
+ advance(subj);
798
+ return make_str(subj, subj->pos - 2, subj->pos - 1, cmark_chunk_dup(&subj->input, subj->pos - 1, 1));
799
+ } else if (!is_eof(subj) && skip_line_end(subj)) {
800
+ return make_linebreak(subj->mem);
801
+ } else {
802
+ return make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("\\"));
803
+ }
804
+ }
805
+
806
+ // Parse an entity or a regular "&" string.
807
+ // Assumes the subject has an '&' character at the current position.
808
+ static cmark_node *handle_entity(subject *subj) {
809
+ cmark_strbuf ent = CMARK_BUF_INIT(subj->mem);
810
+ bufsize_t len;
811
+
812
+ advance(subj);
813
+
814
+ len = houdini_unescape_ent(&ent, subj->input.data + subj->pos,
815
+ subj->input.len - subj->pos);
816
+
817
+ if (len == 0)
818
+ return make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("&"));
819
+
820
+ subj->pos += len;
821
+ return make_str(subj, subj->pos - 1 - len, subj->pos - 1, cmark_chunk_buf_detach(&ent));
822
+ }
823
+
824
+ // Clean a URL: remove surrounding whitespace, and remove \ that escape
825
+ // punctuation.
826
+ cmark_chunk cmark_clean_url(cmark_mem *mem, cmark_chunk *url) {
827
+ cmark_strbuf buf = CMARK_BUF_INIT(mem);
828
+
829
+ cmark_chunk_trim(url);
830
+
831
+ if (url->len == 0) {
832
+ cmark_chunk result = CMARK_CHUNK_EMPTY;
833
+ return result;
834
+ }
835
+
836
+ houdini_unescape_html_f(&buf, url->data, url->len);
837
+
838
+ cmark_strbuf_unescape(&buf);
839
+ return cmark_chunk_buf_detach(&buf);
840
+ }
841
+
842
+ cmark_chunk cmark_clean_title(cmark_mem *mem, cmark_chunk *title) {
843
+ cmark_strbuf buf = CMARK_BUF_INIT(mem);
844
+ unsigned char first, last;
845
+
846
+ if (title->len == 0) {
847
+ cmark_chunk result = CMARK_CHUNK_EMPTY;
848
+ return result;
849
+ }
850
+
851
+ first = title->data[0];
852
+ last = title->data[title->len - 1];
853
+
854
+ // remove surrounding quotes if any:
855
+ if ((first == '\'' && last == '\'') || (first == '(' && last == ')') ||
856
+ (first == '"' && last == '"')) {
857
+ houdini_unescape_html_f(&buf, title->data + 1, title->len - 2);
858
+ } else {
859
+ houdini_unescape_html_f(&buf, title->data, title->len);
860
+ }
861
+
862
+ cmark_strbuf_unescape(&buf);
863
+ return cmark_chunk_buf_detach(&buf);
864
+ }
865
+
866
+ // Parse an autolink or HTML tag.
867
+ // Assumes the subject has a '<' character at the current position.
868
+ static cmark_node *handle_pointy_brace(subject *subj, int options) {
869
+ bufsize_t matchlen = 0;
870
+ cmark_chunk contents;
871
+
872
+ advance(subj); // advance past first <
873
+
874
+ // first try to match a URL autolink
875
+ matchlen = scan_autolink_uri(&subj->input, subj->pos);
876
+ if (matchlen > 0) {
877
+ contents = cmark_chunk_dup(&subj->input, subj->pos, matchlen - 1);
878
+ subj->pos += matchlen;
879
+
880
+ return make_autolink(subj, subj->pos - 1 - matchlen, subj->pos - 1, contents, 0);
881
+ }
882
+
883
+ // next try to match an email autolink
884
+ matchlen = scan_autolink_email(&subj->input, subj->pos);
885
+ if (matchlen > 0) {
886
+ contents = cmark_chunk_dup(&subj->input, subj->pos, matchlen - 1);
887
+ subj->pos += matchlen;
888
+
889
+ return make_autolink(subj, subj->pos - 1 - matchlen, subj->pos - 1, contents, 1);
890
+ }
891
+
892
+ // finally, try to match an html tag
893
+ matchlen = scan_html_tag(&subj->input, subj->pos);
894
+ if (matchlen > 0) {
895
+ contents = cmark_chunk_dup(&subj->input, subj->pos - 1, matchlen + 1);
896
+ subj->pos += matchlen;
897
+ cmark_node *node = make_raw_html(subj, subj->pos - matchlen - 1, subj->pos - 1, contents);
898
+ adjust_subj_node_newlines(subj, node, matchlen, 1, options);
899
+ return node;
900
+ }
901
+
902
+ if (options & CMARK_OPT_LIBERAL_HTML_TAG) {
903
+ matchlen = scan_liberal_html_tag(&subj->input, subj->pos);
904
+ if (matchlen > 0) {
905
+ contents = cmark_chunk_dup(&subj->input, subj->pos - 1, matchlen + 1);
906
+ subj->pos += matchlen;
907
+ cmark_node *node = make_raw_html(subj, subj->pos - matchlen - 1, subj->pos - 1, contents);
908
+ adjust_subj_node_newlines(subj, node, matchlen, 1, options);
909
+ return node;
910
+ }
911
+ }
912
+
913
+ // if nothing matches, just return the opening <:
914
+ return make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("<"));
915
+ }
916
+
917
+ // Parse a link label. Returns 1 if successful.
918
+ // Note: unescaped brackets are not allowed in labels.
919
+ // The label begins with `[` and ends with the first `]` character
920
+ // encountered. Backticks in labels do not start code spans.
921
+ static int link_label(subject *subj, cmark_chunk *raw_label) {
922
+ bufsize_t startpos = subj->pos;
923
+ int length = 0;
924
+ unsigned char c;
925
+
926
+ // advance past [
927
+ if (peek_char(subj) == '[') {
928
+ advance(subj);
929
+ } else {
930
+ return 0;
931
+ }
932
+
933
+ while ((c = peek_char(subj)) && c != '[' && c != ']') {
934
+ if (c == '\\') {
935
+ advance(subj);
936
+ length++;
937
+ if (cmark_ispunct(peek_char(subj))) {
938
+ advance(subj);
939
+ length++;
940
+ }
941
+ } else {
942
+ advance(subj);
943
+ length++;
944
+ }
945
+ if (length > MAX_LINK_LABEL_LENGTH) {
946
+ goto noMatch;
947
+ }
948
+ }
949
+
950
+ if (c == ']') { // match found
951
+ *raw_label =
952
+ cmark_chunk_dup(&subj->input, startpos + 1, subj->pos - (startpos + 1));
953
+ cmark_chunk_trim(raw_label);
954
+ advance(subj); // advance past ]
955
+ return 1;
956
+ }
957
+
958
+ noMatch:
959
+ subj->pos = startpos; // rewind
960
+ return 0;
961
+ }
962
+
963
+ static bufsize_t manual_scan_link_url_2(cmark_chunk *input, bufsize_t offset,
964
+ cmark_chunk *output) {
965
+ bufsize_t i = offset;
966
+ size_t nb_p = 0;
967
+
968
+ while (i < input->len) {
969
+ if (input->data[i] == '\\' &&
970
+ i + 1 < input-> len &&
971
+ cmark_ispunct(input->data[i+1]))
972
+ i += 2;
973
+ else if (input->data[i] == '(') {
974
+ ++nb_p;
975
+ ++i;
976
+ if (nb_p > 32)
977
+ return -1;
978
+ } else if (input->data[i] == ')') {
979
+ if (nb_p == 0)
980
+ break;
981
+ --nb_p;
982
+ ++i;
983
+ } else if (cmark_isspace(input->data[i])) {
984
+ if (i == offset) {
985
+ return -1;
986
+ }
987
+ break;
988
+ } else {
989
+ ++i;
990
+ }
991
+ }
992
+
993
+ if (i >= input->len)
994
+ return -1;
995
+
996
+ {
997
+ cmark_chunk result = {input->data + offset, i - offset, 0};
998
+ *output = result;
999
+ }
1000
+ return i - offset;
1001
+ }
1002
+
1003
+ static bufsize_t manual_scan_link_url(cmark_chunk *input, bufsize_t offset,
1004
+ cmark_chunk *output) {
1005
+ bufsize_t i = offset;
1006
+
1007
+ if (i < input->len && input->data[i] == '<') {
1008
+ ++i;
1009
+ while (i < input->len) {
1010
+ if (input->data[i] == '>') {
1011
+ ++i;
1012
+ break;
1013
+ } else if (input->data[i] == '\\')
1014
+ i += 2;
1015
+ else if (input->data[i] == '\n' || input->data[i] == '<')
1016
+ return -1;
1017
+ else
1018
+ ++i;
1019
+ }
1020
+ } else {
1021
+ return manual_scan_link_url_2(input, offset, output);
1022
+ }
1023
+
1024
+ if (i >= input->len)
1025
+ return -1;
1026
+
1027
+ {
1028
+ cmark_chunk result = {input->data + offset + 1, i - 2 - offset, 0};
1029
+ *output = result;
1030
+ }
1031
+ return i - offset;
1032
+ }
1033
+
1034
+ // Return a link, an image, or a literal close bracket.
1035
+ static cmark_node *handle_close_bracket(cmark_parser *parser, subject *subj) {
1036
+ bufsize_t initial_pos, after_link_text_pos;
1037
+ bufsize_t endurl, starttitle, endtitle, endall;
1038
+ bufsize_t sps, n;
1039
+ cmark_reference *ref = NULL;
1040
+ cmark_chunk url_chunk, title_chunk;
1041
+ cmark_chunk url, title;
1042
+ bracket *opener;
1043
+ cmark_node *inl;
1044
+ cmark_chunk raw_label;
1045
+ int found_label;
1046
+ cmark_node *tmp, *tmpnext;
1047
+ bool is_image;
1048
+
1049
+ advance(subj); // advance past ]
1050
+ initial_pos = subj->pos;
1051
+
1052
+ // get last [ or ![
1053
+ opener = subj->last_bracket;
1054
+
1055
+ if (opener == NULL) {
1056
+ return make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("]"));
1057
+ }
1058
+
1059
+ if (!opener->active) {
1060
+ // take delimiter off stack
1061
+ pop_bracket(subj);
1062
+ return make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("]"));
1063
+ }
1064
+
1065
+ // If we got here, we matched a potential link/image text.
1066
+ // Now we check to see if it's a link/image.
1067
+ is_image = opener->image;
1068
+
1069
+ after_link_text_pos = subj->pos;
1070
+
1071
+ // First, look for an inline link.
1072
+ if (peek_char(subj) == '(' &&
1073
+ ((sps = scan_spacechars(&subj->input, subj->pos + 1)) > -1) &&
1074
+ ((n = manual_scan_link_url(&subj->input, subj->pos + 1 + sps,
1075
+ &url_chunk)) > -1)) {
1076
+
1077
+ // try to parse an explicit link:
1078
+ endurl = subj->pos + 1 + sps + n;
1079
+ starttitle = endurl + scan_spacechars(&subj->input, endurl);
1080
+
1081
+ // ensure there are spaces btw url and title
1082
+ endtitle = (starttitle == endurl)
1083
+ ? starttitle
1084
+ : starttitle + scan_link_title(&subj->input, starttitle);
1085
+
1086
+ endall = endtitle + scan_spacechars(&subj->input, endtitle);
1087
+
1088
+ if (peek_at(subj, endall) == ')') {
1089
+ subj->pos = endall + 1;
1090
+
1091
+ title_chunk =
1092
+ cmark_chunk_dup(&subj->input, starttitle, endtitle - starttitle);
1093
+ url = cmark_clean_url(subj->mem, &url_chunk);
1094
+ title = cmark_clean_title(subj->mem, &title_chunk);
1095
+ cmark_chunk_free(subj->mem, &url_chunk);
1096
+ cmark_chunk_free(subj->mem, &title_chunk);
1097
+ goto match;
1098
+
1099
+ } else {
1100
+ // it could still be a shortcut reference link
1101
+ subj->pos = after_link_text_pos;
1102
+ }
1103
+ }
1104
+
1105
+ // Next, look for a following [link label] that matches in refmap.
1106
+ // skip spaces
1107
+ raw_label = cmark_chunk_literal("");
1108
+ found_label = link_label(subj, &raw_label);
1109
+ if (!found_label) {
1110
+ // If we have a shortcut reference link, back up
1111
+ // to before the spacse we skipped.
1112
+ subj->pos = initial_pos;
1113
+ }
1114
+
1115
+ if ((!found_label || raw_label.len == 0) && !opener->bracket_after) {
1116
+ cmark_chunk_free(subj->mem, &raw_label);
1117
+ raw_label = cmark_chunk_dup(&subj->input, opener->position,
1118
+ initial_pos - opener->position - 1);
1119
+ found_label = true;
1120
+ }
1121
+
1122
+ if (found_label) {
1123
+ ref = (cmark_reference *)cmark_map_lookup(subj->refmap, &raw_label);
1124
+ cmark_chunk_free(subj->mem, &raw_label);
1125
+ }
1126
+
1127
+ if (ref != NULL) { // found
1128
+ url = chunk_clone(subj->mem, &ref->url);
1129
+ title = chunk_clone(subj->mem, &ref->title);
1130
+ goto match;
1131
+ } else {
1132
+ goto noMatch;
1133
+ }
1134
+
1135
+ noMatch:
1136
+ // If we fall through to here, it means we didn't match a link.
1137
+ // What if we're a footnote link?
1138
+ if (parser->options & CMARK_OPT_FOOTNOTES &&
1139
+ opener->inl_text->next &&
1140
+ opener->inl_text->next->type == CMARK_NODE_TEXT &&
1141
+ !opener->inl_text->next->next) {
1142
+ cmark_chunk *literal = &opener->inl_text->next->as.literal;
1143
+ if (literal->len > 1 && literal->data[0] == '^') {
1144
+ inl = make_simple(subj->mem, CMARK_NODE_FOOTNOTE_REFERENCE);
1145
+ inl->as.literal = cmark_chunk_dup(literal, 1, literal->len - 1);
1146
+ inl->start_line = inl->end_line = subj->line;
1147
+ inl->start_column = opener->inl_text->start_column;
1148
+ inl->end_column = subj->pos + subj->column_offset + subj->block_offset;
1149
+ cmark_node_insert_before(opener->inl_text, inl);
1150
+ cmark_node_free(opener->inl_text->next);
1151
+ cmark_node_free(opener->inl_text);
1152
+ process_emphasis(parser, subj, opener->previous_delimiter);
1153
+ pop_bracket(subj);
1154
+ return NULL;
1155
+ }
1156
+ }
1157
+
1158
+ pop_bracket(subj); // remove this opener from delimiter list
1159
+ subj->pos = initial_pos;
1160
+ return make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("]"));
1161
+
1162
+ match:
1163
+ inl = make_simple(subj->mem, is_image ? CMARK_NODE_IMAGE : CMARK_NODE_LINK);
1164
+ inl->as.link.url = url;
1165
+ inl->as.link.title = title;
1166
+ inl->start_line = inl->end_line = subj->line;
1167
+ inl->start_column = opener->inl_text->start_column;
1168
+ inl->end_column = subj->pos + subj->column_offset + subj->block_offset;
1169
+ cmark_node_insert_before(opener->inl_text, inl);
1170
+ // Add link text:
1171
+ tmp = opener->inl_text->next;
1172
+ while (tmp) {
1173
+ tmpnext = tmp->next;
1174
+ cmark_node_append_child(inl, tmp);
1175
+ tmp = tmpnext;
1176
+ }
1177
+
1178
+ // Free the bracket [:
1179
+ cmark_node_free(opener->inl_text);
1180
+
1181
+ process_emphasis(parser, subj, opener->previous_delimiter);
1182
+ pop_bracket(subj);
1183
+
1184
+ // Now, if we have a link, we also want to deactivate earlier link
1185
+ // delimiters. (This code can be removed if we decide to allow links
1186
+ // inside links.)
1187
+ if (!is_image) {
1188
+ opener = subj->last_bracket;
1189
+ while (opener != NULL) {
1190
+ if (!opener->image) {
1191
+ if (!opener->active) {
1192
+ break;
1193
+ } else {
1194
+ opener->active = false;
1195
+ }
1196
+ }
1197
+ opener = opener->previous;
1198
+ }
1199
+ }
1200
+
1201
+ return NULL;
1202
+ }
1203
+
1204
+ // Parse a hard or soft linebreak, returning an inline.
1205
+ // Assumes the subject has a cr or newline at the current position.
1206
+ static cmark_node *handle_newline(subject *subj) {
1207
+ bufsize_t nlpos = subj->pos;
1208
+ // skip over cr, crlf, or lf:
1209
+ if (peek_at(subj, subj->pos) == '\r') {
1210
+ advance(subj);
1211
+ }
1212
+ if (peek_at(subj, subj->pos) == '\n') {
1213
+ advance(subj);
1214
+ }
1215
+ ++subj->line;
1216
+ subj->column_offset = -subj->pos;
1217
+ // skip spaces at beginning of line
1218
+ skip_spaces(subj);
1219
+ if (nlpos > 1 && peek_at(subj, nlpos - 1) == ' ' &&
1220
+ peek_at(subj, nlpos - 2) == ' ') {
1221
+ return make_linebreak(subj->mem);
1222
+ } else {
1223
+ return make_softbreak(subj->mem);
1224
+ }
1225
+ }
1226
+
1227
+ // "\r\n\\`&_*[]<!"
1228
+ static int8_t SPECIAL_CHARS[256] = {
1229
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1230
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
1231
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1232
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,
1233
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1234
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1235
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1236
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1237
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1238
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1239
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
1240
+
1241
+ // " ' . -
1242
+ static char SMART_PUNCT_CHARS[] = {
1243
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1244
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0,
1245
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1246
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1247
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1248
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1249
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1250
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1251
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1252
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1253
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1254
+ };
1255
+
1256
+ static bufsize_t subject_find_special_char(subject *subj, int options) {
1257
+ bufsize_t n = subj->pos + 1;
1258
+
1259
+ while (n < subj->input.len) {
1260
+ if (SPECIAL_CHARS[subj->input.data[n]])
1261
+ return n;
1262
+ if (options & CMARK_OPT_SMART && SMART_PUNCT_CHARS[subj->input.data[n]])
1263
+ return n;
1264
+ n++;
1265
+ }
1266
+
1267
+ return subj->input.len;
1268
+ }
1269
+
1270
+ void cmark_inlines_add_special_character(unsigned char c, bool emphasis) {
1271
+ SPECIAL_CHARS[c] = 1;
1272
+ if (emphasis)
1273
+ SKIP_CHARS[c] = 1;
1274
+ }
1275
+
1276
+ void cmark_inlines_remove_special_character(unsigned char c, bool emphasis) {
1277
+ SPECIAL_CHARS[c] = 0;
1278
+ if (emphasis)
1279
+ SKIP_CHARS[c] = 0;
1280
+ }
1281
+
1282
+ static cmark_node *try_extensions(cmark_parser *parser,
1283
+ cmark_node *parent,
1284
+ unsigned char c,
1285
+ subject *subj) {
1286
+ cmark_node *res = NULL;
1287
+ cmark_llist *tmp;
1288
+
1289
+ for (tmp = parser->inline_syntax_extensions; tmp; tmp = tmp->next) {
1290
+ cmark_syntax_extension *ext = (cmark_syntax_extension *) tmp->data;
1291
+ res = ext->match_inline(ext, parser, parent, c, subj);
1292
+
1293
+ if (res)
1294
+ break;
1295
+ }
1296
+
1297
+ return res;
1298
+ }
1299
+
1300
+ // Parse an inline, advancing subject, and add it as a child of parent.
1301
+ // Return 0 if no inline can be parsed, 1 otherwise.
1302
+ static int parse_inline(cmark_parser *parser, subject *subj, cmark_node *parent, int options) {
1303
+ cmark_node *new_inl = NULL;
1304
+ cmark_chunk contents;
1305
+ unsigned char c;
1306
+ bufsize_t startpos, endpos;
1307
+ c = peek_char(subj);
1308
+ if (c == 0) {
1309
+ return 0;
1310
+ }
1311
+ switch (c) {
1312
+ case '\r':
1313
+ case '\n':
1314
+ new_inl = handle_newline(subj);
1315
+ break;
1316
+ case '`':
1317
+ new_inl = handle_backticks(subj, options);
1318
+ break;
1319
+ case '\\':
1320
+ new_inl = handle_backslash(parser, subj);
1321
+ break;
1322
+ case '&':
1323
+ new_inl = handle_entity(subj);
1324
+ break;
1325
+ case '<':
1326
+ new_inl = handle_pointy_brace(subj, options);
1327
+ break;
1328
+ case '*':
1329
+ case '_':
1330
+ case '\'':
1331
+ case '"':
1332
+ new_inl = handle_delim(subj, c, (options & CMARK_OPT_SMART) != 0);
1333
+ break;
1334
+ case '-':
1335
+ new_inl = handle_hyphen(subj, (options & CMARK_OPT_SMART) != 0);
1336
+ break;
1337
+ case '.':
1338
+ new_inl = handle_period(subj, (options & CMARK_OPT_SMART) != 0);
1339
+ break;
1340
+ case '[':
1341
+ advance(subj);
1342
+ new_inl = make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("["));
1343
+ push_bracket(subj, false, new_inl);
1344
+ break;
1345
+ case ']':
1346
+ new_inl = handle_close_bracket(parser, subj);
1347
+ break;
1348
+ case '!':
1349
+ advance(subj);
1350
+ if (peek_char(subj) == '[' && peek_char_n(subj, 1) != '^') {
1351
+ advance(subj);
1352
+ new_inl = make_str(subj, subj->pos - 2, subj->pos - 1, cmark_chunk_literal("!["));
1353
+ push_bracket(subj, true, new_inl);
1354
+ } else {
1355
+ new_inl = make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("!"));
1356
+ }
1357
+ break;
1358
+ default:
1359
+ new_inl = try_extensions(parser, parent, c, subj);
1360
+ if (new_inl != NULL)
1361
+ break;
1362
+
1363
+ endpos = subject_find_special_char(subj, options);
1364
+ contents = cmark_chunk_dup(&subj->input, subj->pos, endpos - subj->pos);
1365
+ startpos = subj->pos;
1366
+ subj->pos = endpos;
1367
+
1368
+ // if we're at a newline, strip trailing spaces.
1369
+ if (S_is_line_end_char(peek_char(subj))) {
1370
+ cmark_chunk_rtrim(&contents);
1371
+ }
1372
+
1373
+ new_inl = make_str(subj, startpos, endpos - 1, contents);
1374
+ }
1375
+ if (new_inl != NULL) {
1376
+ cmark_node_append_child(parent, new_inl);
1377
+ }
1378
+
1379
+ return 1;
1380
+ }
1381
+
1382
+ // Parse inlines from parent's string_content, adding as children of parent.
1383
+ void cmark_parse_inlines(cmark_parser *parser,
1384
+ cmark_node *parent,
1385
+ cmark_map *refmap,
1386
+ int options) {
1387
+ subject subj;
1388
+ cmark_chunk content = {parent->content.ptr, parent->content.size, 0};
1389
+ subject_from_buf(parser->mem, parent->start_line, parent->start_column - 1 + parent->internal_offset, &subj, &content, refmap);
1390
+ cmark_chunk_rtrim(&subj.input);
1391
+
1392
+ while (!is_eof(&subj) && parse_inline(parser, &subj, parent, options))
1393
+ ;
1394
+
1395
+ process_emphasis(parser, &subj, NULL);
1396
+ // free bracket and delim stack
1397
+ while (subj.last_delim) {
1398
+ remove_delimiter(&subj, subj.last_delim);
1399
+ }
1400
+ while (subj.last_bracket) {
1401
+ pop_bracket(&subj);
1402
+ }
1403
+ }
1404
+
1405
+ // Parse zero or more space characters, including at most one newline.
1406
+ static void spnl(subject *subj) {
1407
+ skip_spaces(subj);
1408
+ if (skip_line_end(subj)) {
1409
+ skip_spaces(subj);
1410
+ }
1411
+ }
1412
+
1413
+ // Parse reference. Assumes string begins with '[' character.
1414
+ // Modify refmap if a reference is encountered.
1415
+ // Return 0 if no reference found, otherwise position of subject
1416
+ // after reference is parsed.
1417
+ bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_chunk *input,
1418
+ cmark_map *refmap) {
1419
+ subject subj;
1420
+
1421
+ cmark_chunk lab;
1422
+ cmark_chunk url;
1423
+ cmark_chunk title;
1424
+
1425
+ bufsize_t matchlen = 0;
1426
+ bufsize_t beforetitle;
1427
+
1428
+ subject_from_buf(mem, -1, 0, &subj, input, NULL);
1429
+
1430
+ // parse label:
1431
+ if (!link_label(&subj, &lab) || lab.len == 0)
1432
+ return 0;
1433
+
1434
+ // colon:
1435
+ if (peek_char(&subj) == ':') {
1436
+ advance(&subj);
1437
+ } else {
1438
+ return 0;
1439
+ }
1440
+
1441
+ // parse link url:
1442
+ spnl(&subj);
1443
+ if ((matchlen = manual_scan_link_url(&subj.input, subj.pos, &url)) > -1) {
1444
+ subj.pos += matchlen;
1445
+ } else {
1446
+ return 0;
1447
+ }
1448
+
1449
+ // parse optional link_title
1450
+ beforetitle = subj.pos;
1451
+ spnl(&subj);
1452
+ matchlen = subj.pos == beforetitle ? 0 : scan_link_title(&subj.input, subj.pos);
1453
+ if (matchlen) {
1454
+ title = cmark_chunk_dup(&subj.input, subj.pos, matchlen);
1455
+ subj.pos += matchlen;
1456
+ } else {
1457
+ subj.pos = beforetitle;
1458
+ title = cmark_chunk_literal("");
1459
+ }
1460
+
1461
+ // parse final spaces and newline:
1462
+ skip_spaces(&subj);
1463
+ if (!skip_line_end(&subj)) {
1464
+ if (matchlen) { // try rewinding before title
1465
+ subj.pos = beforetitle;
1466
+ skip_spaces(&subj);
1467
+ if (!skip_line_end(&subj)) {
1468
+ return 0;
1469
+ }
1470
+ } else {
1471
+ return 0;
1472
+ }
1473
+ }
1474
+ // insert reference into refmap
1475
+ cmark_reference_create(refmap, &lab, &url, &title);
1476
+ return subj.pos;
1477
+ }
1478
+
1479
+ unsigned char cmark_inline_parser_peek_char(cmark_inline_parser *parser) {
1480
+ return peek_char(parser);
1481
+ }
1482
+
1483
+ unsigned char cmark_inline_parser_peek_at(cmark_inline_parser *parser, bufsize_t pos) {
1484
+ return peek_at(parser, pos);
1485
+ }
1486
+
1487
+ int cmark_inline_parser_is_eof(cmark_inline_parser *parser) {
1488
+ return is_eof(parser);
1489
+ }
1490
+
1491
+ static char *
1492
+ my_strndup (const char *s, size_t n)
1493
+ {
1494
+ char *result;
1495
+ size_t len = strlen (s);
1496
+
1497
+ if (n < len)
1498
+ len = n;
1499
+
1500
+ result = (char *) malloc (len + 1);
1501
+ if (!result)
1502
+ return 0;
1503
+
1504
+ result[len] = '\0';
1505
+ return (char *) memcpy (result, s, len);
1506
+ }
1507
+
1508
+ char *cmark_inline_parser_take_while(cmark_inline_parser *parser, cmark_inline_predicate pred) {
1509
+ unsigned char c;
1510
+ bufsize_t startpos = parser->pos;
1511
+ bufsize_t len = 0;
1512
+
1513
+ while ((c = peek_char(parser)) && (*pred)(c)) {
1514
+ advance(parser);
1515
+ len++;
1516
+ }
1517
+
1518
+ return my_strndup((const char *) parser->input.data + startpos, len);
1519
+ }
1520
+
1521
+ void cmark_inline_parser_push_delimiter(cmark_inline_parser *parser,
1522
+ unsigned char c,
1523
+ int can_open,
1524
+ int can_close,
1525
+ cmark_node *inl_text) {
1526
+ push_delimiter(parser, c, can_open != 0, can_close != 0, inl_text);
1527
+ }
1528
+
1529
+ void cmark_inline_parser_remove_delimiter(cmark_inline_parser *parser, delimiter *delim) {
1530
+ remove_delimiter(parser, delim);
1531
+ }
1532
+
1533
+ int cmark_inline_parser_scan_delimiters(cmark_inline_parser *parser,
1534
+ int max_delims,
1535
+ unsigned char c,
1536
+ int *left_flanking,
1537
+ int *right_flanking,
1538
+ int *punct_before,
1539
+ int *punct_after) {
1540
+ int numdelims = 0;
1541
+ bufsize_t before_char_pos;
1542
+ int32_t after_char = 0;
1543
+ int32_t before_char = 0;
1544
+ int len;
1545
+ bool space_before, space_after;
1546
+
1547
+ if (parser->pos == 0) {
1548
+ before_char = 10;
1549
+ } else {
1550
+ before_char_pos = parser->pos - 1;
1551
+ // walk back to the beginning of the UTF_8 sequence:
1552
+ while (peek_at(parser, before_char_pos) >> 6 == 2 && before_char_pos > 0) {
1553
+ before_char_pos -= 1;
1554
+ }
1555
+ len = cmark_utf8proc_iterate(parser->input.data + before_char_pos,
1556
+ parser->pos - before_char_pos, &before_char);
1557
+ if (len == -1) {
1558
+ before_char = 10;
1559
+ }
1560
+ }
1561
+
1562
+ while (peek_char(parser) == c && numdelims < max_delims) {
1563
+ numdelims++;
1564
+ advance(parser);
1565
+ }
1566
+
1567
+ len = cmark_utf8proc_iterate(parser->input.data + parser->pos,
1568
+ parser->input.len - parser->pos, &after_char);
1569
+ if (len == -1) {
1570
+ after_char = 10;
1571
+ }
1572
+
1573
+ *punct_before = cmark_utf8proc_is_punctuation(before_char);
1574
+ *punct_after = cmark_utf8proc_is_punctuation(after_char);
1575
+ space_before = cmark_utf8proc_is_space(before_char) != 0;
1576
+ space_after = cmark_utf8proc_is_space(after_char) != 0;
1577
+
1578
+ *left_flanking = numdelims > 0 && !cmark_utf8proc_is_space(after_char) &&
1579
+ !(*punct_after && !space_before && !*punct_before);
1580
+ *right_flanking = numdelims > 0 && !cmark_utf8proc_is_space(before_char) &&
1581
+ !(*punct_before && !space_after && !*punct_after);
1582
+
1583
+ return numdelims;
1584
+ }
1585
+
1586
+ void cmark_inline_parser_advance_offset(cmark_inline_parser *parser) {
1587
+ advance(parser);
1588
+ }
1589
+
1590
+ int cmark_inline_parser_get_offset(cmark_inline_parser *parser) {
1591
+ return parser->pos;
1592
+ }
1593
+
1594
+ void cmark_inline_parser_set_offset(cmark_inline_parser *parser, int offset) {
1595
+ parser->pos = offset;
1596
+ }
1597
+
1598
+ int cmark_inline_parser_get_column(cmark_inline_parser *parser) {
1599
+ return parser->pos + 1 + parser->column_offset + parser->block_offset;
1600
+ }
1601
+
1602
+ cmark_chunk *cmark_inline_parser_get_chunk(cmark_inline_parser *parser) {
1603
+ return &parser->input;
1604
+ }
1605
+
1606
+ int cmark_inline_parser_in_bracket(cmark_inline_parser *parser, int image) {
1607
+ for (bracket *b = parser->last_bracket; b; b = b->previous)
1608
+ if (b->active && b->image == (image != 0))
1609
+ return 1;
1610
+ return 0;
1611
+ }
1612
+
1613
+ void cmark_node_unput(cmark_node *node, int n) {
1614
+ node = node->last_child;
1615
+ while (n > 0 && node && node->type == CMARK_NODE_TEXT) {
1616
+ if (node->as.literal.len < n) {
1617
+ n -= node->as.literal.len;
1618
+ node->as.literal.len = 0;
1619
+ } else {
1620
+ node->as.literal.len -= n;
1621
+ n = 0;
1622
+ }
1623
+ node = node->prev;
1624
+ }
1625
+ }
1626
+
1627
+ delimiter *cmark_inline_parser_get_last_delimiter(cmark_inline_parser *parser) {
1628
+ return parser->last_delim;
1629
+ }
1630
+
1631
+ int cmark_inline_parser_get_line(cmark_inline_parser *parser) {
1632
+ return parser->line;
1633
+ }