// situationally print newlines to make the generated html
// easier to read
#define PRETTY_PRINT 0
#include
#include
#include
#include
#include
#include
typedef struct StateMachine {
size_t top;
int cs;
int act;
const char * p;
const char * pb;
const char * pe;
const char * eof;
const char * ts;
const char * te;
const char * a1;
const char * a2;
const char * b1;
const char * b2;
bool f_inline;
bool f_strip;
bool list_mode;
bool header_mode;
GString * output;
GArray * stack;
GQueue * dstack;
int list_nest;
int d;
int b;
} StateMachine;
static const size_t MAX_STACK_DEPTH = 512;
static const int BLOCK_P = 1;
static const int INLINE_SPOILER = 2;
static const int BLOCK_SPOILER = 3;
static const int BLOCK_QUOTE = 4;
static const int BLOCK_EXPAND = 5;
static const int BLOCK_NODTEXT = 6;
static const int BLOCK_CODE = 7;
static const int BLOCK_TD = 8;
static const int INLINE_NODTEXT = 9;
static const int INLINE_B = 10;
static const int INLINE_I = 11;
static const int INLINE_U = 12;
static const int INLINE_S = 13;
static const int INLINE_TN = 14;
static const int BLOCK_TN = 15;
static const int BLOCK_TABLE = 16;
static const int BLOCK_THEAD = 17;
static const int BLOCK_TBODY = 18;
static const int BLOCK_TR = 19;
static const int BLOCK_UL = 20;
static const int BLOCK_LI = 21;
static const int BLOCK_TH = 22;
static const int BLOCK_H1 = 23;
static const int BLOCK_H2 = 24;
static const int BLOCK_H3 = 25;
static const int BLOCK_H4 = 26;
static const int BLOCK_H5 = 27;
static const int BLOCK_H6 = 28;
%%{
machine dtext;
access sm->;
variable p sm->p;
variable pe sm->pe;
variable eof sm->eof;
variable top sm->top;
variable ts sm->ts;
variable te sm->te;
variable act sm->act;
variable stack ((int *)sm->stack->data);
prepush {
size_t len = sm->stack->len;
if (len > MAX_STACK_DEPTH) {
free_machine(sm);
rb_raise(rb_eSyntaxError, "too many nested elements");
}
if (sm->top >= len) {
sm->stack = g_array_set_size(sm->stack, len + 16);
}
}
action mark_a1 {
sm->a1 = sm->p;
}
action mark_a2 {
sm->a2 = sm->p;
}
action mark_b1 {
sm->b1 = sm->p;
}
action mark_b2 {
sm->b2 = sm->p;
}
newline = '\r\n' | '\n';
nonnewline = any - (newline | '\0' | '\r');
nonquote = ^'"';
nonbracket = ^']';
nonpipe = ^'|';
nonpipebracket = nonpipe & nonbracket;
noncurly = ^'}';
mention = '@' graph+ >mark_a1 %mark_a2;
url = 'http' 's'? '://' graph+;
internal_url = '/' graph+;
basic_textile_link = '"' nonquote+ >mark_a1 '"' >mark_a2 ':' (url | internal_url) >mark_b1 %mark_b2;
bracketed_textile_link = '"' nonquote+ >mark_a1 '"' >mark_a2 ':[' (url | internal_url) >mark_b1 %mark_b2 :>> ']';
basic_wiki_link = '[[' nonpipebracket+ >mark_a1 %mark_a2 ']]';
aliased_wiki_link = '[[' nonpipebracket+ >mark_a1 %mark_a2 '|' nonbracket+ >mark_b1 %mark_b2 ']]';
post_link = '{{' noncurly+ >mark_a1 %mark_a2 '}}';
post_id = 'post #'i digit+ >mark_a1 %mark_a2;
forum_post_id = 'forum #'i digit+ >mark_a1 %mark_a2;
forum_topic_id = 'topic #'i digit+ >mark_a1 %mark_a2;
forum_topic_paged_id = 'topic #'i digit+ >mark_a1 %mark_a2 '/p' digit+ >mark_b1 %mark_b2;
comment_id = 'comment #'i digit+ >mark_a1 %mark_a2;
pool_id = 'pool #'i digit+ >mark_a1 %mark_a2;
user_id = 'user #'i digit+ >mark_a1 %mark_a2;
artist_id = 'artist #'i digit+ >mark_a1 %mark_a2;
github_issue_id = 'issue #'i digit+ >mark_a1 %mark_a2;
pixiv_id = 'pixiv #'i digit+ >mark_a1 %mark_a2;
pixiv_paged_id = 'pixiv #'i digit+ >mark_a1 %mark_a2 '/p' digit+ >mark_b1 %mark_b2;
ws = ' ' | '\t';
header = 'h' [123456] >mark_a1 %mark_a2 '.' ws*;
aliased_expand = '[expand=' (nonbracket+ >mark_a1 %mark_a2) ']';
list_item = '*'+ >mark_a1 %mark_a2 ws+ nonnewline+ >mark_b1 %mark_b2;
textile_link_desc := |*
'":' (internal_url | url);
*|;
textile_link := |*
'"';
any;
*|;
inline := |*
post_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "\">");
append(sm, false, "post #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
forum_post_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "\">");
append(sm, false, "forum #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
forum_topic_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "\">");
append(sm, false, "topic #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
forum_topic_paged_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "?page=");
append_segment(sm, true, sm->b1, sm->b2 - 1);
append(sm, true, "\">");
append(sm, false, "topic #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, false, "/p");
append_segment(sm, false, sm->b1, sm->b2 - 1);
append(sm, true, "");
};
comment_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "\">");
append(sm, false, "comment #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
pool_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "\">");
append(sm, false, "pool #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
user_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "\">");
append(sm, false, "user #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
artist_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "\">");
append(sm, false, "artist #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
github_issue_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "\">");
append(sm, false, "issue #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
pixiv_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "\">");
append(sm, false, "pixiv #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
pixiv_paged_id => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "&page=");
append_segment(sm, true, sm->b1, sm->b2 - 1);
append(sm, true, "\">");
append(sm, false, "pixiv #");
append_segment(sm, false, sm->a1, sm->a2 - 1);
append(sm, false, "/p");
append_segment(sm, false, sm->b1, sm->b2 - 1);
append(sm, true, "");
};
post_link => {
append(sm, true, "a1, sm->a2 - 1);
append(sm, true, "\">");
append_segment_html_escaped(sm, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
basic_wiki_link => {
GString * segment = g_string_new_len(sm->a1, sm->a2 - sm->a1);
GString * lowercase_segment = NULL;
underscore_string(segment->str, segment->len);
if (g_utf8_validate(segment->str, -1, NULL)) {
lowercase_segment = g_string_new(g_utf8_strdown(segment->str, -1));
} else {
lowercase_segment = g_string_new(g_ascii_strdown(segment->str, -1));
}
append(sm, true, "str, lowercase_segment->str + lowercase_segment->len - 1);
append(sm, true, "\">");
append_segment_html_escaped(sm, sm->a1, sm->a2 - 1);
append(sm, true, "");
g_string_free(lowercase_segment, TRUE);
g_string_free(segment, TRUE);
};
aliased_wiki_link => {
GString * segment = g_string_new_len(sm->a1, sm->a2 - sm->a1);
GString * lowercase_segment = NULL;
underscore_string(segment->str, segment->len);
if (g_utf8_validate(segment->str, -1, NULL)) {
lowercase_segment = g_string_new(g_utf8_strdown(segment->str, -1));
} else {
lowercase_segment = g_string_new(g_ascii_strdown(segment->str, -1));
}
append(sm, true, "str, lowercase_segment->str + lowercase_segment->len - 1);
append(sm, true, "\">");
append_segment_html_escaped(sm, sm->b1, sm->b2 - 1);
append(sm, true, "");
g_string_free(lowercase_segment, TRUE);
g_string_free(segment, TRUE);
};
basic_textile_link => {
if (is_boundary_c(fc)) {
sm->d = 2;
sm->b = true;
} else {
sm->d = 1;
sm->b = false;
}
append(sm, true, "b1, sm->b2 - sm->d);
append(sm, true, "\">");
append_segment_html_escaped(sm, sm->a1, sm->a2 - 1);
append(sm, true, "");
if (sm->b) {
append_c_html_escaped(sm, fc);
}
};
bracketed_textile_link => {
append(sm, true, "b1, sm->b2 - 1);
append(sm, true, "\">");
append_segment_html_escaped(sm, sm->a1, sm->a2 - 1);
append(sm, true, "");
};
url => {
if (is_boundary_c(fc)) {
sm->b = true;
sm->d = 2;
} else {
sm->b = false;
sm->d = 1;
}
append(sm, true, "ts, sm->te - sm->d);
append(sm, true, "\">");
append_segment_html_escaped(sm, sm->ts, sm->te - sm->d);
append(sm, true, "");
if (sm->b) {
append_c_html_escaped(sm, fc);
}
};
# probably a tag. examples include @.@ and @_@
'@' graph '@' => {
append_segment_html_escaped(sm, sm->ts, sm->te - 1);
};
mention => {
if (is_boundary_c(fc)) {
sm->b = true;
sm->d = 2;
} else {
sm->b = false;
sm->d = 1;
}
append(sm, true, "a1, sm->a2 - sm->d);
append(sm, true, "\">");
append_c(sm, '@');
append_segment_html_escaped(sm, sm->a1, sm->a2 - sm->d);
append(sm, true, "");
if (sm->b) {
append_c_html_escaped(sm, fc);
}
};
newline list_item => {
g_debug("inline list");
if (dstack_check(sm, BLOCK_LI)) {
g_debug(" rewind li");
dstack_rewind(sm);
} else if (dstack_check(sm, BLOCK_P)) {
g_debug(" rewind p");
dstack_rewind(sm);
} else if (sm->header_mode) {
g_debug(" rewind header");
dstack_rewind(sm);
}
g_debug(" next list");
fexec sm->ts + 1;
fnext list;
};
'[b]' => {
dstack_push(sm, &INLINE_B);
append(sm, true, "");
};
'[/b]' => {
if (dstack_check(sm, INLINE_B)) {
dstack_pop(sm);
append(sm, true, "");
} else {
append(sm, true, "[/b]");
}
};
'[i]' => {
dstack_push(sm, &INLINE_I);
append(sm, true, "");
};
'[/i]' => {
if (dstack_check(sm, INLINE_I)) {
dstack_pop(sm);
append(sm, true, "");
} else {
append(sm, true, "[/i]");
}
};
'[s]' => {
dstack_push(sm, &INLINE_S);
append(sm, true, "");
};
'[/s]' => {
if (dstack_check(sm, INLINE_S)) {
dstack_pop(sm);
append(sm, true, "");
} else {
append(sm, true, "[/s]");
}
};
'[u]' => {
dstack_push(sm, &INLINE_U);
append(sm, true, "");
};
'[/u]' => {
if (dstack_check(sm, INLINE_U)) {
dstack_pop(sm);
append(sm, true, "");
} else {
append(sm, true, "[/u]");
}
};
'[tn]' => {
dstack_push(sm, &INLINE_TN);
append(sm, true, "");
};
'[/tn]' => {
dstack_close_before_block(sm);
if (dstack_check(sm, BLOCK_TN)) {
dstack_pop(sm);
fret;
} else if (dstack_check(sm, INLINE_TN)) {
dstack_pop(sm);
append(sm, true, "");
} else {
append_block(sm, "[/tn]");
}
};
# these are block level elements that should kick us out of the inline
# scanner
header => {
dstack_rewind(sm);
fexec sm->a1 - 1;
fret;
};
'[quote]' => {
g_debug("inline [quote]");
dstack_close_before_block(sm);
fexec sm->ts;
fret;
};
'[/quote]' space* => {
g_debug("inline [/quote]");
dstack_close_before_block(sm);
if (dstack_check(sm, BLOCK_LI)) {
dstack_close_list(sm);
}
if (dstack_check(sm, BLOCK_QUOTE)) {
dstack_rewind(sm);
fret;
} else {
append_block(sm, "[/quote]");
}
};
'[spoiler]' => {
g_debug("inline [spoiler]");
g_debug(" push ");
dstack_push(sm, &INLINE_SPOILER);
append(sm, true, "");
};
'[/spoiler]' => {
g_debug("inline [/spoiler]");
dstack_close_before_block(sm);
if (dstack_check(sm, INLINE_SPOILER)) {
g_debug(" pop dstack");
g_debug(" print ");
dstack_pop(sm);
append(sm, true, "");
} else if (dstack_check(sm, BLOCK_SPOILER)) {
g_debug(" pop dstack");
g_debug(" print ");
g_debug(" return");
dstack_pop(sm);
append_block(sm, "");
fret;
} else {
append_block(sm, "[/spoiler]");
}
};
'[expand]' => {
g_debug("inline [expand]");
dstack_rewind(sm);
fexec(sm->p - 7);
fret;
};
'[/expand]' => {
dstack_close_before_block(sm);
if (dstack_check(sm, BLOCK_EXPAND)) {
append_block(sm, "");
dstack_pop(sm);
fret;
} else {
append_block(sm, "[/expand]");
}
};
'[nodtext]' => {
dstack_push(sm, &INLINE_NODTEXT);
fcall nodtext;
};
'[/th]' => {
if (dstack_check(sm, BLOCK_TH)) {
dstack_pop(sm);
append_block(sm, "");
fret;
} else {
append_block(sm, "[/th]");
}
};
'[/td]' => {
if (dstack_check(sm, BLOCK_TD)) {
dstack_pop(sm);
append_block(sm, "");
fret;
} else {
append_block(sm, "[/td]");
}
};
'\0' => {
g_debug("inline 0");
g_debug(" return");
fhold;
fret;
};
newline{2,} => {
g_debug("inline newline2");
g_debug(" return");
if (sm->list_mode) {
dstack_close_list(sm);
}
fexec sm->ts;
fret;
};
newline => {
g_debug("inline newline");
if (sm->header_mode) {
sm->header_mode = false;
dstack_rewind(sm);
fret;
} else {
append(sm, true, "
");
}
};
'\r' => {
append_c(sm, ' ');
};
any => {
g_debug("inline char: %c", fc);
append_c_html_escaped(sm, fc);
};
*|;
code := |*
'[/code]' => {
if (dstack_check(sm, BLOCK_CODE)) {
dstack_rewind(sm);
} else {
append(sm, true, "[/code]");
}
fret;
};
'\0' => {
fhold;
fret;
};
any => {
append_c_html_escaped(sm, fc);
};
*|;
nodtext := |*
'[/nodtext]' => {
if (dstack_check(sm, BLOCK_NODTEXT)) {
dstack_pop(sm);
append_block(sm, "
");
fret;
} else if (dstack_check(sm, INLINE_NODTEXT)) {
dstack_pop(sm);
fret;
} else {
append(sm, true, "[/nodtext]");
}
};
'\0' => {
fhold;
fret;
};
any => {
append_c_html_escaped(sm, fc);
};
*|;
table := |*
'[thead]' => {
dstack_push(sm, &BLOCK_THEAD);
append_block(sm, "");
};
'[/thead]' => {
if (dstack_check(sm, BLOCK_THEAD)) {
dstack_pop(sm);
append_block(sm, "");
} else {
append(sm, true, "[/thead]");
}
};
'[tbody]' => {
dstack_push(sm, &BLOCK_TBODY);
append_block(sm, "");
};
'[/tbody]' => {
if (dstack_check(sm, BLOCK_TBODY)) {
dstack_pop(sm);
append_block(sm, "");
} else {
append(sm, true, "[/tbody]");
}
};
'[th]' => {
dstack_push(sm, &BLOCK_TH);
append_block(sm, "");
fcall inline;
};
'[tr]' => {
dstack_push(sm, &BLOCK_TR);
append_block(sm, " | ");
};
'[/tr]' => {
if (dstack_check(sm, BLOCK_TR)) {
dstack_pop(sm);
append_block(sm, "
");
} else {
append(sm, true, "[/tr]");
}
};
'[td]' => {
dstack_push(sm, &BLOCK_TD);
append_block(sm, "");
fcall inline;
};
'[/table]' => {
if (dstack_check(sm, BLOCK_TABLE)) {
dstack_pop(sm);
append_block(sm, "");
fret;
} else {
append(sm, true, "[/table]");
}
};
'\0' => {
fhold;
fret;
};
any;
*|;
list := |*
list_item => {
int prev_nest = sm->list_nest;
append_closing_p_if(sm);
g_debug("list start");
sm->list_mode = true;
sm->list_nest = sm->a2 - sm->a1;
fexec sm->b1;
if (sm->list_nest > prev_nest) {
int i=0;
for (i=prev_nest; ilist_nest; ++i) {
g_debug(" dstack push ul");
g_debug(" print ");
append_block(sm, "");
dstack_push(sm, &BLOCK_UL);
}
} else if (sm->list_nest < prev_nest) {
int i=0;
for (i=sm->list_nest; i");
dstack_pop(sm);
append_block(sm, " ");
}
}
}
append_block(sm, "- ");
dstack_push(sm, &BLOCK_LI);
g_debug(" print
- ");
g_debug(" push li");
g_debug(" call inline");
fcall inline;
};
# exit list
(newline{2,} | '\0') => {
dstack_close_list(sm);
fexec sm->ts;
fret;
};
newline;
any => {
dstack_rewind(sm);
fhold;
fret;
};
*|;
main := |*
header => {
char header = *sm->a1;
if (sm->f_inline) {
header = '6';
}
if (!sm->f_strip) {
switch (header) {
case '1':
dstack_push(sm, &BLOCK_H1);
append_block(sm, "
");
break;
case '2':
dstack_push(sm, &BLOCK_H2);
append_block(sm, "");
break;
case '3':
dstack_push(sm, &BLOCK_H3);
append_block(sm, "");
break;
case '4':
dstack_push(sm, &BLOCK_H4);
append_block(sm, "");
break;
case '5':
dstack_push(sm, &BLOCK_H5);
append_block(sm, "");
break;
case '6':
dstack_push(sm, &BLOCK_H6);
append_block(sm, "");
break;
}
}
sm->header_mode = true;
fcall inline;
};
'[quote]' space* => {
g_debug("block [quote]");
g_debug(" push quote");
g_debug(" print ");
dstack_close_before_block(sm);
dstack_push(sm, &BLOCK_QUOTE);
append_block(sm, "");
};
'[spoiler]' space* => {
g_debug("block [spoiler]");
g_debug(" push spoiler");
g_debug(" print ");
dstack_close_before_block(sm);
dstack_push(sm, &BLOCK_SPOILER);
append_block(sm, " ");
};
'[/spoiler]' => {
g_debug("block [/spoiler]");
dstack_close_before_block(sm);
if (dstack_check(sm, BLOCK_SPOILER)) {
g_debug(" rewind");
dstack_rewind(sm);
}
};
'[code]' space* => {
g_debug("block [code]");
dstack_close_before_block(sm);
dstack_push(sm, &BLOCK_CODE);
append_block(sm, " ");
fcall code;
};
'[expand]' space* => {
g_debug("block [expand]");
dstack_close_before_block(sm);
dstack_push(sm, &BLOCK_EXPAND);
append_block(sm, "");
append_block(sm, " ");
append_block(sm, " ");
};
aliased_expand space* => {
g_debug("block [expand=]");
dstack_close_before_block(sm);
dstack_push(sm, &BLOCK_EXPAND);
append_block(sm, " ");
append(sm, true, "");
append_segment_html_escaped(sm, sm->a1, sm->a2 - 1);
append(sm, true, "");
append_block(sm, " ");
append_block(sm, " ");
};
'[nodtext]' space* => {
g_debug("block [nodtext]");
dstack_close_before_block(sm);
dstack_push(sm, &BLOCK_NODTEXT);
dstack_push(sm, &BLOCK_P);
append_block(sm, " ");
fcall nodtext;
};
'[table]' => {
dstack_close_before_block(sm);
dstack_push(sm, &BLOCK_TABLE);
append_block(sm, " ");
fcall table;
};
'[tn]' => {
dstack_push(sm, &BLOCK_TN);
append_block(sm, "");
fcall inline;
};
list_item => {
g_debug("block list");
g_debug(" call list");
sm->list_nest = 0;
sm->list_mode = true;
append_closing_p_if(sm);
fexec sm->ts;
fcall list;
};
'\0' => {
g_debug("block 0");
g_debug(" close dstack");
dstack_close(sm);
};
newline{2,} => {
g_debug("block newline2");
if (sm->header_mode) {
sm->header_mode = false;
dstack_rewind(sm);
} else if (sm->list_mode) {
dstack_close_list(sm);
} else {
dstack_close_before_block(sm);
}
};
newline => {
g_debug("block newline");
};
any => {
g_debug("block char: %c", fc);
fhold;
if (g_queue_is_empty(sm->dstack) || dstack_check(sm, BLOCK_QUOTE) || dstack_check(sm, BLOCK_SPOILER) || dstack_check(sm, BLOCK_EXPAND)) {
g_debug(" push p");
g_debug(" print ");
dstack_push(sm, &BLOCK_P);
append_block(sm, " ");
}
fcall inline;
};
*|;
}%%
%% write data;
static inline void underscore_string(char * str, size_t len) {
for (size_t i=0; idstack, (gpointer)element);
}
static inline int * dstack_pop(StateMachine * sm) {
return g_queue_pop_tail(sm->dstack);
}
static inline int * dstack_peek(StateMachine * sm) {
return g_queue_peek_tail(sm->dstack);
}
static inline bool dstack_search(StateMachine * sm, const int * element) {
return g_queue_find(sm->dstack, (gconstpointer)element);
}
static inline bool dstack_check(StateMachine * sm, int expected_element) {
int * top = dstack_peek(sm);
return top && *top == expected_element;
}
static inline bool dstack_check2(StateMachine * sm, int expected_element) {
int * top2 = NULL;
if (sm->dstack->length < 2) {
return false;
}
top2 = g_queue_peek_nth(sm->dstack, sm->dstack->length - 2);
return top2 && *top2 == expected_element;
}
static inline void append(StateMachine * sm, bool is_markup, const char * s) {
if (!(is_markup && sm->f_strip)) {
sm->output = g_string_append(sm->output, s);
}
}
static inline void append_c(StateMachine * sm, char s) {
sm->output = g_string_append_c(sm->output, s);
}
static inline void append_c_html_escaped(StateMachine * sm, char s) {
switch (s) {
case '<':
sm->output = g_string_append(sm->output, "<");
break;
case '>':
sm->output = g_string_append(sm->output, ">");
break;
case '&':
sm->output = g_string_append(sm->output, "&");
break;
case '"':
sm->output = g_string_append(sm->output, """);
break;
default:
sm->output = g_string_append_c(sm->output, s);
break;
}
}
static inline void append_segment(StateMachine * sm, bool is_markup, const char * a, const char * b) {
if (!(is_markup && sm->f_strip)) {
sm->output = g_string_append_len(sm->output, a, b - a + 1);
}
}
static inline void append_segment_uri_escaped(StateMachine * sm, const char * a, const char * b) {
if (sm->f_strip) {
return;
}
char * segment1 = NULL;
char * segment2 = NULL;
GString * segment_string = g_string_new_len(a, b - a + 1);
segment1 = g_uri_escape_string(segment_string->str, NULL, TRUE);
segment2 = g_markup_escape_text(segment1, -1);
sm->output = g_string_append(sm->output, segment2);
g_string_free(segment_string, TRUE);
g_free(segment1);
g_free(segment2);
}
static inline void append_segment_html_escaped(StateMachine * sm, const char * a, const char * b) {
gchar * segment = g_markup_escape_text(a, b - a + 1);
sm->output = g_string_append(sm->output, segment);
g_free(segment);
}
static inline void append_block(StateMachine * sm, const char * s) {
if (sm->f_inline) {
sm->output = g_string_append_c(sm->output, ' ');
} else if (sm->f_strip) {
// do nothing
} else {
sm->output = g_string_append(sm->output, s);
}
}
static void append_closing_p(StateMachine * sm) {
size_t i = sm->output->len;
if (i > 4 && !strncmp(sm->output->str + i - 4, " ", 4)) {
sm->output = g_string_truncate(sm->output, sm->output->len - 4);
}
if (i > 3 && !strncmp(sm->output->str + i - 3, "", 3)) {
sm->output = g_string_truncate(sm->output, sm->output->len - 3);
return;
}
append_block(sm, " ");
}
static void append_closing_p_if(StateMachine * sm) {
if (!dstack_check(sm, BLOCK_P)) {
return;
}
dstack_pop(sm);
append_closing_p(sm);
}
static void dstack_rewind(StateMachine * sm) {
int * element = dstack_pop(sm);
if (element == NULL) {
return;
}
if (*element == BLOCK_P) {
append_closing_p(sm);
} else if (*element == INLINE_SPOILER) {
append(sm, true, "");
} else if (*element == BLOCK_SPOILER) {
append_block(sm, "");
} else if (*element == BLOCK_QUOTE) {
append_block(sm, "");
} else if (*element == BLOCK_EXPAND) {
append_block(sm, "");
} else if (*element == BLOCK_NODTEXT) {
append_closing_p(sm);
} else if (*element == BLOCK_CODE) {
append_block(sm, "");
} else if (*element == BLOCK_TD) {
append_block(sm, "");
} else if (*element == INLINE_NODTEXT) {
} else if (*element == INLINE_B) {
append(sm, true, "");
} else if (*element == INLINE_I) {
append(sm, true, "");
} else if (*element == INLINE_U) {
append(sm, true, "");
} else if (*element == INLINE_S) {
append(sm, true, "");
} else if (*element == INLINE_TN) {
append(sm, true, "");
} else if (*element == BLOCK_TN) {
append_closing_p(sm);
} else if (*element == BLOCK_TABLE) {
append_block(sm, " ");
} else if (*element == BLOCK_THEAD) {
append_block(sm, "");
} else if (*element == BLOCK_TBODY) {
append_block(sm, "");
} else if (*element == BLOCK_TR) {
append_block(sm, "");
} else if (*element == BLOCK_UL) {
append_block(sm, "");
} else if (*element == BLOCK_LI) {
append_block(sm, "");
} else if (*element == BLOCK_H6) {
append_block(sm, "");
} else if (*element == BLOCK_H5) {
append_block(sm, "");
} else if (*element == BLOCK_H4) {
append_block(sm, "");
} else if (*element == BLOCK_H3) {
append_block(sm, "");
} else if (*element == BLOCK_H2) {
append_block(sm, "");
} else if (*element == BLOCK_H1) {
append_block(sm, "");
}
}
static void dstack_close_before_block(StateMachine * sm) {
while (1) {
if (dstack_check(sm, BLOCK_P)) {
dstack_pop(sm);
append_closing_p(sm);
} else if (dstack_check(sm, BLOCK_LI) || dstack_check(sm, BLOCK_UL)) {
dstack_rewind(sm);
} else {
return;
}
}
}
static void dstack_close(StateMachine * sm) {
while (dstack_peek(sm) != NULL) {
dstack_rewind(sm);
}
}
static void dstack_close_list(StateMachine * sm) {
while (dstack_check(sm, BLOCK_LI) || dstack_check(sm, BLOCK_UL)) {
dstack_rewind(sm);
}
sm->list_mode = false;
sm->list_nest = 0;
}
static inline bool is_boundary_c(char c) {
switch (c) {
case ':':
case ';':
case '.':
case ',':
case '!':
case '?':
case ')':
case ']':
case '<':
case '>':
return true;
}
return false;
}
static bool print_machine(StateMachine * sm) {
printf("p=%c\n", *sm->p);
return true;
}
static void init_machine(StateMachine * sm, VALUE input) {
size_t output_length = 0;
sm->p = RSTRING_PTR(input);
sm->pb = sm->p;
sm->pe = sm->p + RSTRING_LEN(input);
sm->eof = sm->pe;
sm->ts = NULL;
sm->te = NULL;
sm->cs = 0;
sm->act = 0;
sm->top = 0;
output_length = RSTRING_LEN(input);
if (output_length < (INT16_MAX / 2)) {
output_length *= 2;
}
sm->output = g_string_sized_new(output_length);
sm->a1 = NULL;
sm->a2 = NULL;
sm->b1 = NULL;
sm->b2 = NULL;
sm->f_inline = false;
sm->f_strip = false;
sm->stack = g_array_sized_new(FALSE, TRUE, sizeof(int), 16);
sm->dstack = g_queue_new();
sm->list_nest = 0;
sm->list_mode = false;
sm->header_mode = false;
sm->d = 0;
sm->b = 0;
}
static void free_machine(StateMachine * sm) {
g_string_free(sm->output, TRUE);
g_array_free(sm->stack, FALSE);
g_queue_free(sm->dstack);
g_free(sm);
}
static VALUE parse(int argc, VALUE * argv, VALUE self) {
VALUE input;
VALUE options;
VALUE opt_inline;
VALUE opt_strip;
VALUE ret;
rb_encoding * encoding = NULL;
StateMachine * sm = NULL;
g_debug("start\n");
if (argc == 0) {
rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)");
}
input = argv[0];
sm = (StateMachine *)g_malloc0(sizeof(StateMachine));
input = rb_str_cat(input, "\0", 1);
init_machine(sm, input);
if (argc > 1) {
options = argv[1];
if (!NIL_P(options)) {
opt_strip = rb_hash_aref(options, ID2SYM(rb_intern("strip")));
if (RTEST(opt_strip)) {
sm->f_strip = true;
}
opt_inline = rb_hash_aref(options, ID2SYM(rb_intern("inline")));
if (RTEST(opt_inline)) {
sm->f_inline = true;
}
}
}
%% write init;
%% write exec;
dstack_close(sm);
encoding = rb_enc_find("utf-8");
ret = rb_enc_str_new(sm->output->str, sm->output->len, encoding);
free_machine(sm);
return ret;
}
void Init_dtext() {
VALUE mDTextRagel = rb_define_module("DTextRagel");
rb_define_singleton_method(mDTextRagel, "parse", parse, -1);
}
|