]> git.wincent.com - wikitext.git/blob - ext/wikitext_ragel.rl
_Wikitext_append_sanitized_link_target consistency changes
[wikitext.git] / ext / wikitext_ragel.rl
1 // Copyright 2008-2009 Wincent Colaiuta
2 // This program is free software: you can redistribute it and/or modify
3 // it under the terms of the GNU General Public License as published by
4 // the Free Software Foundation, either version 3 of the License, or
5 // (at your option) any later version.
6 //
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 // GNU General Public License for more details.
11 //
12 // You should have received a copy of the GNU General Public License
13 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
14
15 //----------------------------------------------------------------------//
16 // NOTE: wikitext_ragel.c is generated from wikitext_ragel.rl, so       //
17 //       if you make changes to the former they will be overwritten.    //
18 //       You should perform all your edits in wikitext_ragel.rl.        //
19 //----------------------------------------------------------------------//
20
21 #include "wikitext_ragel.h"
22 #include "wikitext.h"
23 #include <stdio.h>
24
25 #define EMIT(t)     do { out->type = t; out->stop = p + 1; out->column_stop += (out->stop - out->start); } while (0)
26 #define MARK()      do { mark = p; } while (0)
27 #define REWIND()    do { p = mark; } while (0)
28 #define AT_END()    (p + 1 == pe)
29 #define DISTANCE()  (p + 1 - ts)
30 #define NEXT_CHAR() (*(p + 1))
31
32 %%{
33     machine wikitext;
34
35     action mark
36     {
37         MARK();
38     }
39
40     action non_printable_ascii
41     {
42         out->code_point = *p & 0x7f;
43     }
44
45     action two_byte_utf8_sequence
46     {
47         out->code_point = ((uint32_t)(*(p - 1)) & 0x1f) << 6 |
48             (*p & 0x3f);
49     }
50
51     action three_byte_utf8_sequence
52     {
53         out->code_point = ((uint32_t)(*(p - 2)) & 0x0f) << 12 |
54             ((uint32_t)(*(p - 1)) & 0x3f) << 6 |
55             (*p & 0x3f);
56     }
57
58     action four_byte_utf8_sequence
59     {
60         out->code_point = ((uint32_t)(*(p - 3)) & 0x07) << 18 |
61             ((uint32_t)(*(p - 2)) & 0x3f) << 12 |
62             ((uint32_t)(*(p - 1)) & 0x3f) << 6 |
63             (*p & 0x3f);
64     }
65
66     # simple approximation for matching email addresses; not quite RFC 2822!
67     user                = (alnum | [_\.] | '-')+ ;
68     tld                 = alpha{2,5} ;
69     domain              = (alnum+ '.')+ tld ;
70     mail                = user '@' domain ;
71
72     uri_chars           = (alnum | [@$&'(\*\+=%_~/#] | '-')+ ;
73     special_uri_chars   = ([:!\(\),;\.\?])+ ;
74     uri                 = ('mailto:'i mail) |
75                           (('http'i [sS]? '://' | 'ftp://'i | 'svn://'i) uri_chars (special_uri_chars uri_chars)*) ;
76     path                = '/' ([a-zA-Z0-9_\-.]+ '/'?)* ;
77
78     main := |*
79
80         '<nowiki>'i
81         {
82             EMIT(NO_WIKI_START);
83             fbreak;
84         };
85
86         '</nowiki>'i
87         {
88             EMIT(NO_WIKI_END);
89             fbreak;
90         };
91
92         '<pre>'i
93         {
94             EMIT(PRE_START);
95             fbreak;
96         };
97
98         '</pre>'i
99         {
100             EMIT(PRE_END);
101             fbreak;
102         };
103
104         '<blockquote>'i
105         {
106             EMIT(BLOCKQUOTE_START);
107             fbreak;
108         };
109
110         '</blockquote>'i
111         {
112             EMIT(BLOCKQUOTE_END);
113             fbreak;
114         };
115
116         "'"{1,5}
117         {
118             if (DISTANCE() == 5)
119                 EMIT(STRONG_EM);
120             else if (DISTANCE() == 4)
121             {
122                 p--;
123                 EMIT(STRONG_EM);
124             }
125             else if (DISTANCE() == 3)
126                 EMIT(STRONG);
127             else if (DISTANCE() == 2)
128                 EMIT(EM);
129             else
130                 EMIT(PRINTABLE);
131             fbreak;
132         };
133
134         '<strong>'i
135         {
136             EMIT(STRONG_START);
137             fbreak;
138         };
139
140         '</strong>'i
141         {
142             EMIT(STRONG_END);
143             fbreak;
144         };
145
146         '<em>'i
147         {
148             EMIT(EM_START);
149             fbreak;
150         };
151
152         '</em>'i
153         {
154             EMIT(EM_END);
155             fbreak;
156         };
157
158         '`'
159         {
160             EMIT(TT);
161             fbreak;
162         };
163
164         '<tt>'i
165         {
166             EMIT(TT_START);
167             fbreak;
168         };
169
170         '</tt>'i
171         {
172             EMIT(TT_END);
173             fbreak;
174         };
175
176         # shorthand for <blockquote> and </blockquote>
177         '>' @mark ' '?
178         {
179             if (out->column_start == 1 || last_token_type == BLOCKQUOTE)
180                 EMIT(BLOCKQUOTE);
181             else
182             {
183                 REWIND();
184                 EMIT(GREATER);
185             }
186             fbreak;
187         };
188
189         # shorthand for <pre> and </pre>
190         ' ' @mark ' '*
191         {
192             if (out->column_start == 1 || last_token_type == BLOCKQUOTE)
193             {
194                 REWIND();
195                 EMIT(PRE);
196             }
197             else
198                 EMIT(SPACE);
199             fbreak;
200         };
201
202         '#'
203         {
204             if (out->column_start == 1              ||
205                 last_token_type == OL               ||
206                 last_token_type == UL               ||
207                 last_token_type == BLOCKQUOTE       ||
208                 last_token_type == BLOCKQUOTE_START)
209                 EMIT(OL);
210             else
211                 EMIT(PRINTABLE);
212             fbreak;
213         };
214
215         '*'
216         {
217             if (out->column_start == 1              ||
218                 last_token_type == OL               ||
219                 last_token_type == UL               ||
220                 last_token_type == BLOCKQUOTE       ||
221                 last_token_type == BLOCKQUOTE_START)
222                 EMIT(UL);
223             else
224                 EMIT(PRINTABLE);
225             fbreak;
226         };
227
228         '='+ @mark ' '*
229         {
230             if (out->column_start == 1 || last_token_type == BLOCKQUOTE || last_token_type == BLOCKQUOTE_START)
231             {
232                 REWIND();
233                 if (DISTANCE() == 1)
234                     EMIT(H1_START);
235                 else if (DISTANCE() == 2)
236                     EMIT(H2_START);
237                 else if (DISTANCE() == 3)
238                     EMIT(H3_START);
239                 else if (DISTANCE() == 4)
240                     EMIT(H4_START);
241                 else if (DISTANCE() == 5)
242                     EMIT(H5_START);
243                 else if (DISTANCE() == 6)
244                     EMIT(H6_START);
245                 else if (DISTANCE() > 6)
246                 {
247                     p = ts + 6;
248                     EMIT(H6_START);
249                 }
250             }
251             else if (AT_END() || NEXT_CHAR() == '\n' || NEXT_CHAR() == '\r')
252             {
253                 REWIND();
254                 if (DISTANCE() == 1)
255                     EMIT(H1_END);
256                 else if (DISTANCE() == 2)
257                     EMIT(H2_END);
258                 else if (DISTANCE() == 3)
259                     EMIT(H3_END);
260                 else if (DISTANCE() == 4)
261                     EMIT(H4_END);
262                 else if (DISTANCE() == 5)
263                     EMIT(H5_END);
264                 else if (DISTANCE() == 6)
265                     EMIT(H6_END);
266                 else if (DISTANCE() > 6)
267                 {
268                     p -= 6; // will scan the H6 on the next scan
269                     EMIT(PRINTABLE);
270                 }
271             }
272             else
273             {
274                 // note that a H*_END token will never match before a BLOCKQUOTE_END
275                 REWIND();
276                 EMIT(PRINTABLE);
277             }
278             fbreak;
279         };
280
281         uri
282         {
283             EMIT(URI);
284             fbreak;
285         };
286
287         mail
288         {
289             EMIT(MAIL);
290             fbreak;
291         };
292
293         path
294         {
295             EMIT(PATH);
296             fbreak;
297         };
298
299         '[['
300         {
301             EMIT(LINK_START);
302             fbreak;
303         };
304
305         ']]'
306         {
307             EMIT(LINK_END);
308             fbreak;
309         };
310
311         '|'
312         {
313             EMIT(SEPARATOR);
314             fbreak;
315         };
316
317         '['
318         {
319             EMIT(EXT_LINK_START);
320             fbreak;
321         };
322
323         ']'
324         {
325             EMIT(EXT_LINK_END);
326             fbreak;
327         };
328
329         '&quot;'
330         {
331             EMIT(QUOT_ENTITY);
332             fbreak;
333         };
334
335         '&amp;'
336         {
337             EMIT(AMP_ENTITY);
338             fbreak;
339         };
340
341         '&' alpha+ digit* ';'
342         {
343             EMIT(NAMED_ENTITY);
344             fbreak;
345         };
346
347         '&#' [xX] xdigit+ ';'
348         {
349             EMIT(HEX_ENTITY);
350             fbreak;
351         };
352
353         '&#' digit+ ';'
354         {
355             EMIT(DECIMAL_ENTITY);
356             fbreak;
357         };
358
359         '"'
360         {
361             EMIT(QUOT);
362             fbreak;
363         };
364
365         '&'
366         {
367             EMIT(AMP);
368             fbreak;
369         };
370
371         '<'
372         {
373             EMIT(LESS);
374             fbreak;
375         };
376
377         '>'
378         {
379             EMIT(GREATER);
380             fbreak;
381         };
382
383         '{{'
384         {
385             EMIT(IMG_START);
386             fbreak;
387         };
388
389         '}}'
390         {
391             EMIT(IMG_END);
392             fbreak;
393         };
394
395         '{'
396         {
397             EMIT(LEFT_CURLY);
398             fbreak;
399         };
400
401         '}'
402         {
403             EMIT(RIGHT_CURLY);
404             fbreak;
405         };
406
407         ("\r"? "\n") | "\r"
408         {
409             EMIT(CRLF);
410             out->column_stop = 1;
411             out->line_stop++;
412             fbreak;
413         };
414
415         # must tokenize these separately from the other PRINTABLE characters otherwise a string like:
416         #   See http://example.com/.
417         # will get greedily tokenized as PRINABLE, SPACE, PRINTABLE rather than PRINTABLE, SPACE, URI, SPECIAL_URI_CHARS
418         # this also applies to MAIL tokenization and input strings like:
419         #   Email me (user@example.com) for more info.
420         special_uri_chars
421         {
422             EMIT(SPECIAL_URI_CHARS);
423             fbreak;
424         };
425
426         alnum+
427         {
428             EMIT(ALNUM);
429             fbreak;
430         };
431
432         # all the printable ASCII characters (0x20 to 0x7e) excluding those explicitly covered elsewhere:
433         # we skip space (0x20), exclamation mark (0x21), quote (0x22), hash (0x23), ampersand (0x26), apostrophe (0x27),
434         # left parenthesis (0x28), right parenthesis (0x29), numbers (0x30..0x39), asterisk (0x2a), comma (0x2c), period (0x2e),
435         # colon (0x3a), semi-colon (0x3b), less than (0x3c), equals (0x3d), greater than (0x3e), question mark (0x3f), uppercase
436         # letters (0x41..0x5a), left bracket (0x5b), right bracket (0x5d), backtick (0x60), lowercase letters (0x61..0x7a), left
437         # curly brace (0x7b), vertical bar (0x7c) and right curly brace (0x7d).
438         (0x24..0x25 | 0x2b | 0x2d | 0x2f | 0x40 | 0x5c | 0x5e..0x5f | 0x7e)+
439         {
440             EMIT(PRINTABLE);
441             fbreak;
442         };
443
444         # here is where we handle the UTF-8 and everything else
445         #
446         #     one_byte_sequence   = byte begins with zero;
447         #     two_byte_sequence   = first byte begins with 110 (0xc0..0xdf), next with 10 (0x80..9xbf);
448         #     three_byte_sequence = first byte begins with 1110 (0xe0..0xef), next two with 10 (0x80..9xbf);
449         #     four_byte_sequence  = first byte begins with 11110 (0xf0..0xf7), next three with 10 (0x80..9xbf);
450         #
451         #     within the ranges specified, we also exclude these illegal sequences:
452         #       1100000x (c0 c1)    overlong encoding, lead byte of 2 byte seq but code point <= 127
453         #       11110101 (f5)       restricted by RFC 3629 lead byte of 4-byte sequence for codepoint above 10ffff
454         #       1111011x (f6, f7)   restricted by RFC 3629 lead byte of 4-byte sequence for codepoint above 10ffff
455         (0x01..0x1f | 0x7f)                             @non_printable_ascii        |
456         (0xc2..0xdf 0x80..0xbf)                         @two_byte_utf8_sequence     |
457         (0xe0..0xef 0x80..0xbf 0x80..0xbf)              @three_byte_utf8_sequence   |
458         (0xf0..0xf4 0x80..0xbf 0x80..0xbf 0x80..0xbf)   @four_byte_utf8_sequence
459         {
460             EMIT(DEFAULT);
461             out->column_stop = out->column_start + 1;
462             fbreak;
463         };
464
465     *|;
466
467     write data;
468 }%%
469
470 // for now we use the scanner as a tokenizer that returns one token at a time, just like ANTLR
471 // ultimately we could look at embedding all of the transformation inside the scanner itself (combined scanner/parser)
472 // pass in the last token because that's useful for the scanner to know
473 // p data pointer (required by Ragel machine); overriden with contents of last_token if supplied
474 // pe data end pointer (required by Ragel machine)
475 void next_token(token_t *out, token_t *last_token, char *p, char *pe)
476 {
477     int last_token_type = NO_TOKEN;
478     if (last_token)
479     {
480         last_token_type     = last_token->type;
481         p = last_token->stop;
482         out->line_start     = out->line_stop    = last_token->line_stop;
483         out->column_start   = out->column_stop  = last_token->column_stop;
484     }
485     else
486     {
487         out->line_start     = 1;
488         out->column_start   = 1;
489         out->line_stop      = 1;
490         out->column_stop    = 1;
491     }
492     out->type       = NO_TOKEN;
493     out->code_point = 0;
494     out->start      = p;
495     if (p == pe)
496     {
497         // all done, have reached end of input
498         out->stop  = p;
499         out->type  = END_OF_FILE;
500         return;
501     }
502
503     char    *mark;      // for manual backtracking
504     char    *eof = pe;  // required for backtracking (longest match determination)
505     int     cs;         // current state (standard Ragel)
506     char    *ts;        // token start (scanner)
507     char    *te;        // token end (scanner)
508     int     act;        // identity of last patterned matched (scanner)
509     %% write init;
510     %% write exec;
511     if (cs == wikitext_error)
512         rb_raise(eWikitextParserError, "failed before finding a token");
513     else if (out->type == NO_TOKEN)
514         rb_raise(eWikitextParserError, "failed to produce a token");
515 }