Skip to content

Commit 0eaeaa4

Browse files
Added subscript extension
from: swiftlang#31
1 parent 3bc2f3e commit 0eaeaa4

File tree

5 files changed

+302
-0
lines changed

5 files changed

+302
-0
lines changed

extensions/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ set(LIBRARY_SOURCES
1010
ext_scanners.re
1111
ext_scanners.h
1212
tasklist.c
13+
superscript.c
1314
)
1415

1516
include_directories(

extensions/core-extensions.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "table.h"
77
#include "tagfilter.h"
88
#include "tasklist.h"
9+
#include "superscript.h"
910
#include "registry.h"
1011
#include "plugin.h"
1112

@@ -16,6 +17,7 @@ static int core_extensions_registration(cmark_plugin *plugin) {
1617
cmark_plugin_register_syntax_extension(plugin, create_autolink_extension());
1718
cmark_plugin_register_syntax_extension(plugin, create_tagfilter_extension());
1819
cmark_plugin_register_syntax_extension(plugin, create_tasklist_extension());
20+
cmark_plugin_register_syntax_extension(plugin, create_superscript_extension());
1921
return 1;
2022
}
2123

extensions/superscript.c

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
#include "superscript.h"
2+
#include <inlines.h>
3+
#include <parser.h>
4+
#include <render.h>
5+
6+
static cmark_chunk *S_get_node_literal_chunk(cmark_node *node) {
7+
if (node == NULL) {
8+
return NULL;
9+
}
10+
11+
switch (node->type) {
12+
case CMARK_NODE_HTML_BLOCK:
13+
case CMARK_NODE_TEXT:
14+
case CMARK_NODE_HTML_INLINE:
15+
case CMARK_NODE_CODE:
16+
case CMARK_NODE_FOOTNOTE_REFERENCE:
17+
return &node->as.literal;
18+
19+
case CMARK_NODE_CODE_BLOCK:
20+
return &node->as.code.literal;
21+
22+
default:
23+
break;
24+
}
25+
26+
return NULL;
27+
}
28+
29+
static bool S_node_contains_space(cmark_node *node) {
30+
cmark_chunk *chunk = S_get_node_literal_chunk(node);
31+
if (chunk)
32+
return (cmark_chunk_strchr(chunk, ' ', 0) != chunk->len);
33+
else
34+
return false;
35+
}
36+
37+
static bool S_children_contain_space(cmark_node *parent) {
38+
cmark_node *node = parent->first_child;
39+
while (node) {
40+
if (S_node_contains_space(node)) {
41+
return true;
42+
}
43+
node = node->next;
44+
}
45+
46+
return false;
47+
}
48+
49+
cmark_node_type CMARK_NODE_SUPERSCRIPT;
50+
51+
static cmark_node *match(cmark_syntax_extension *self, cmark_parser *parser,
52+
cmark_node *parent, unsigned char character,
53+
cmark_inline_parser *inline_parser) {
54+
cmark_node *res = NULL;
55+
int initpos = cmark_inline_parser_get_offset(inline_parser);
56+
57+
if (character == '^') {
58+
if (cmark_inline_parser_peek_at(inline_parser, initpos + 1) == '(') {
59+
res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
60+
cmark_node_set_literal(res, "^(");
61+
res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
62+
res->start_column = cmark_inline_parser_get_column(inline_parser);
63+
res->end_column = res->start_column + 2;
64+
65+
cmark_inline_parser_set_offset(inline_parser, initpos + 2);
66+
cmark_inline_parser_push_delimiter(inline_parser, '^', true, false, res);
67+
} else {
68+
int startpos = initpos + 1;
69+
int endpos = startpos;
70+
71+
cmark_chunk *chunk = cmark_inline_parser_get_chunk(inline_parser);
72+
bufsize_t len = chunk->len;
73+
74+
while (endpos < len) {
75+
unsigned char seekchar = cmark_inline_parser_peek_at(inline_parser, endpos);
76+
if (cmark_isspace(seekchar) || (cmark_ispunct(seekchar) && seekchar != '^'))
77+
break;
78+
endpos++;
79+
}
80+
81+
int nodelen = endpos - startpos;
82+
83+
// don't emit an empty node
84+
if (nodelen == 0)
85+
return NULL;
86+
87+
cmark_inline_parser_set_offset(inline_parser, startpos);
88+
89+
res = cmark_node_new_with_mem_and_ext(CMARK_NODE_SUPERSCRIPT, parser->mem, self);
90+
res->as.literal = cmark_chunk_dup(chunk, startpos, nodelen);
91+
res->start_line = cmark_inline_parser_get_line(inline_parser);
92+
res->start_column = cmark_inline_parser_get_column(inline_parser);
93+
94+
cmark_inline_parser_set_offset(inline_parser, endpos);
95+
96+
res->end_line = cmark_inline_parser_get_line(inline_parser);
97+
res->end_column = cmark_inline_parser_get_column(inline_parser);
98+
99+
const char *text = cmark_chunk_to_cstr(parser->mem, &res->as.literal);
100+
cmark_node_set_string_content(res, text);
101+
102+
cmark_parse_inlines(parser, res, parser->refmap, parser->options);
103+
}
104+
} else if (character == ')') {
105+
res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
106+
cmark_node_set_literal(res, ")");
107+
res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
108+
res->start_column = cmark_inline_parser_get_column(inline_parser);
109+
res->end_column = res->start_column + 1;
110+
111+
cmark_inline_parser_set_offset(inline_parser, initpos + 1);
112+
cmark_inline_parser_push_delimiter(inline_parser, '^', false, true, res);
113+
}
114+
115+
return res;
116+
}
117+
118+
static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser,
119+
cmark_inline_parser *inline_parser, delimiter *opener,
120+
delimiter *closer) {
121+
cmark_node *superscript;
122+
cmark_node *tmp, *next;
123+
delimiter *delim, *tmp_delim;
124+
delimiter *res = closer->next;
125+
126+
superscript = opener->inl_text;
127+
128+
if (!cmark_node_set_type(superscript, CMARK_NODE_SUPERSCRIPT))
129+
return res;
130+
131+
cmark_node_set_syntax_extension(superscript, self);
132+
133+
tmp = cmark_node_next(opener->inl_text);
134+
135+
while (tmp) {
136+
if (tmp == closer->inl_text)
137+
break;
138+
next = cmark_node_next(tmp);
139+
cmark_node_append_child(superscript, tmp);
140+
tmp = next;
141+
}
142+
143+
superscript->end_column = closer->inl_text->start_column + closer->inl_text->as.literal.len - 1;
144+
cmark_node_free(closer->inl_text);
145+
146+
delim = closer;
147+
while (delim != NULL && delim != opener) {
148+
tmp_delim = delim->previous;
149+
cmark_inline_parser_remove_delimiter(inline_parser, delim);
150+
delim = tmp_delim;
151+
}
152+
153+
cmark_inline_parser_remove_delimiter(inline_parser, opener);
154+
155+
return res;
156+
}
157+
158+
static const char *get_type_string(cmark_syntax_extension *extension,
159+
cmark_node *node) {
160+
return node->type == CMARK_NODE_SUPERSCRIPT ? "superscript" : "<unknown>";
161+
}
162+
163+
static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
164+
cmark_node_type child_type) {
165+
if (node->type != CMARK_NODE_SUPERSCRIPT)
166+
return false;
167+
168+
return CMARK_NODE_TYPE_INLINE_P(child_type);
169+
}
170+
171+
static void commonmark_render(cmark_syntax_extension *extension,
172+
cmark_renderer *renderer, cmark_node *node,
173+
cmark_event_type ev_type, int options) {
174+
bool should_wrap = S_children_contain_space(node);
175+
bool entering = (ev_type == CMARK_EVENT_ENTER);
176+
if (entering) {
177+
if (should_wrap)
178+
renderer->out(renderer, node, "^(", false, LITERAL);
179+
else
180+
renderer->out(renderer, node, "^", false, LITERAL);
181+
} else if (!entering && should_wrap) {
182+
renderer->out(renderer, node, ")", false, LITERAL);
183+
}
184+
}
185+
186+
static void latex_render(cmark_syntax_extension *extension,
187+
cmark_renderer *renderer, cmark_node *node,
188+
cmark_event_type ev_type, int options) {
189+
bool entering = (ev_type == CMARK_EVENT_ENTER);
190+
if (entering) {
191+
renderer->out(renderer, node, "^{", false, LITERAL);
192+
} else {
193+
renderer->out(renderer, node, "}", false, LITERAL);
194+
}
195+
}
196+
197+
static void man_render(cmark_syntax_extension *extension,
198+
cmark_renderer *renderer, cmark_node *node,
199+
cmark_event_type ev_type, int options) {
200+
// requires MOM
201+
bool entering = (ev_type == CMARK_EVENT_ENTER);
202+
if (entering) {
203+
renderer->cr(renderer);
204+
renderer->out(renderer, node, "\\*[SUP]", false, LITERAL);
205+
} else {
206+
renderer->out(renderer, node, "\\*[SUPX]", false, LITERAL);
207+
renderer->cr(renderer);
208+
}
209+
}
210+
211+
static void html_render(cmark_syntax_extension *extension,
212+
cmark_html_renderer *renderer, cmark_node *node,
213+
cmark_event_type ev_type, int options) {
214+
bool entering = (ev_type == CMARK_EVENT_ENTER);
215+
if (entering) {
216+
cmark_strbuf_puts(renderer->html, "<sup>");
217+
} else {
218+
cmark_strbuf_puts(renderer->html, "</sup>");
219+
}
220+
}
221+
222+
cmark_syntax_extension *create_superscript_extension(void) {
223+
cmark_syntax_extension *ext = cmark_syntax_extension_new("superscript");
224+
cmark_llist *special_chars = NULL;
225+
226+
cmark_syntax_extension_set_get_type_string_func(ext, get_type_string);
227+
cmark_syntax_extension_set_can_contain_func(ext, can_contain);
228+
cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render);
229+
cmark_syntax_extension_set_plaintext_render_func(ext, commonmark_render);
230+
cmark_syntax_extension_set_latex_render_func(ext, latex_render);
231+
cmark_syntax_extension_set_man_render_func(ext, man_render);
232+
cmark_syntax_extension_set_html_render_func(ext, html_render);
233+
CMARK_NODE_SUPERSCRIPT = cmark_syntax_extension_add_node(1);
234+
235+
cmark_syntax_extension_set_match_inline_func(ext, match);
236+
cmark_syntax_extension_set_inline_from_delim_func(ext, insert);
237+
238+
cmark_mem *mem = cmark_get_default_mem_allocator();
239+
special_chars = cmark_llist_append(mem, special_chars, (void *)'^');
240+
special_chars = cmark_llist_append(mem, special_chars, (void *)')');
241+
cmark_syntax_extension_set_special_inline_chars(ext, special_chars);
242+
243+
cmark_syntax_extension_set_emphasis(ext, 1);
244+
245+
return ext;
246+
}

extensions/superscript.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#ifndef CMARK_GFM_SUPERSCRIPT_H
2+
#define CMARK_GFM_SUPERSCRIPT_H
3+
4+
#include "cmark-gfm-core-extensions.h"
5+
6+
extern cmark_node_type CMARK_NODE_SUPERSCRIPT;
7+
cmark_syntax_extension *create_superscript_extension(void);
8+
9+
#endif /* CMARK_GFM_SUPERSCRIPT_H */

test/spec.txt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7731,6 +7731,50 @@ new paragraph~~.
77317731

77327732
</div>
77337733

7734+
<div class="extension">
7735+
7736+
## Superscript (extension)
7737+
7738+
GFM provides the `superscript` extension, which adds "superscript" spans.
7739+
7740+
There are two ways to write superscripts. For simple uses, you can use a
7741+
caret (`^`) to style the text between it and the next space (or the end of
7742+
the line):
7743+
7744+
```````````````````````````````` example superscript
7745+
y = x^2 + 2
7746+
7747+
Superscripting a whole ^word
7748+
.
7749+
<p>y = x<sup>2</sup> + 2</p>
7750+
<p>Superscripting a whole <sup>word</sup></p>
7751+
````````````````````````````````
7752+
7753+
In addition, if you would like to raise more than one word, you can add
7754+
parentheses around the text you would like to style in a superscript:
7755+
7756+
```````````````````````````````` example superscript
7757+
I would like to ^(raise this whole phrase), please.
7758+
.
7759+
<p>I would like to <sup>raise this whole phrase</sup>, please.</p>
7760+
````````````````````````````````
7761+
7762+
Superscripts can be nested, by adding additional carets:
7763+
7764+
```````````````````````````````` example superscript
7765+
z = t^x^2
7766+
.
7767+
<p>z = t<sup>x<sup>2</sup></sup></p>
7768+
````````````````````````````````
7769+
7770+
```````````````````````````````` example superscript
7771+
For my next trick, I will ^(raise my text ^(twice)), at the same time!
7772+
.
7773+
<p>For my next trick, I will <sup>raise my text <sup>twice</sup></sup>, at the same time!</p>
7774+
````````````````````````````````
7775+
7776+
</div>
7777+
77347778
## Links
77357779

77367780
A link contains [link text] (the visible text), a [link destination]

0 commit comments

Comments
 (0)