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