]> git.wincent.com - wikitext.git/blob - ext/wikitext_ragel.rl
Extract repeated pattern into wiki_append_pre_start()
[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>'i
108         {
109             EMIT(PRE_END);
110             fbreak;
111         };
112
113         '<blockquote>'i
114         {
115             EMIT(BLOCKQUOTE_START);
116             fbreak;
117         };
118
119         '</blockquote>'i
120         {
121             EMIT(BLOCKQUOTE_END);
122             fbreak;
123         };
124
125         "'"{1,5}
126         {
127             if (DISTANCE() == 5)
128                 EMIT(STRONG_EM);
129             else if (DISTANCE() == 4)
130             {
131                 p--;
132                 EMIT(STRONG_EM);
133             }
134             else if (DISTANCE() == 3)
135                 EMIT(STRONG);
136             else if (DISTANCE() == 2)
137                 EMIT(EM);
138             else
139                 EMIT(PRINTABLE);
140             fbreak;
141         };
142
143         '<strong>'i
144         {
145             EMIT(STRONG_START);
146             fbreak;
147         };
148
149         '</strong>'i
150         {
151             EMIT(STRONG_END);
152             fbreak;
153         };
154
155         '<em>'i
156         {
157             EMIT(EM_START);
158             fbreak;
159         };
160
161         '</em>'i
162         {
163             EMIT(EM_END);
164             fbreak;
165         };
166
167         '`'
168         {
169             EMIT(TT);
170             fbreak;
171         };
172
173         '<tt>'i
174         {
175             EMIT(TT_START);
176             fbreak;
177         };
178
179         '</tt>'i
180         {
181             EMIT(TT_END);
182             fbreak;
183         };
184
185         # shorthand for <blockquote> and </blockquote>
186         '>' @mark ' '?
187         {
188             if (out->column_start == 1 || last_token_type == BLOCKQUOTE)
189                 EMIT(BLOCKQUOTE);
190             else
191             {
192                 REWIND();
193                 EMIT(GREATER);
194             }
195             fbreak;
196         };
197
198         # shorthand for <pre> and </pre>
199         ' ' @mark ' '*
200         {
201             if (out->column_start == 1 || last_token_type == BLOCKQUOTE)
202             {
203                 REWIND();
204                 EMIT(PRE);
205             }
206             else
207                 EMIT(SPACE);
208             fbreak;
209         };
210
211         '#'
212         {
213             if (out->column_start == 1              ||
214                 last_token_type == OL               ||
215                 last_token_type == UL               ||
216                 last_token_type == BLOCKQUOTE       ||
217                 last_token_type == BLOCKQUOTE_START)
218                 EMIT(OL);
219             else
220                 EMIT(PRINTABLE);
221             fbreak;
222         };
223
224         '*'
225         {
226             if (out->column_start == 1              ||
227                 last_token_type == OL               ||
228                 last_token_type == UL               ||
229                 last_token_type == BLOCKQUOTE       ||
230                 last_token_type == BLOCKQUOTE_START)
231                 EMIT(UL);
232             else
233                 EMIT(PRINTABLE);
234             fbreak;
235         };
236
237         '='+ @mark ' '*
238         {
239             if (out->column_start == 1 || last_token_type == BLOCKQUOTE || last_token_type == BLOCKQUOTE_START)
240             {
241                 REWIND();
242                 if (DISTANCE() == 1)
243                     EMIT(H1_START);
244                 else if (DISTANCE() == 2)
245                     EMIT(H2_START);
246                 else if (DISTANCE() == 3)
247                     EMIT(H3_START);
248                 else if (DISTANCE() == 4)
249                     EMIT(H4_START);
250                 else if (DISTANCE() == 5)
251                     EMIT(H5_START);
252                 else if (DISTANCE() == 6)
253                     EMIT(H6_START);
254                 else if (DISTANCE() > 6)
255                 {
256                     p = ts + 6;
257                     EMIT(H6_START);
258                 }
259             }
260             else if (AT_END() || NEXT_CHAR() == '\n' || NEXT_CHAR() == '\r')
261             {
262                 REWIND();
263                 if (DISTANCE() == 1)
264                     EMIT(H1_END);
265                 else if (DISTANCE() == 2)
266                     EMIT(H2_END);
267                 else if (DISTANCE() == 3)
268                     EMIT(H3_END);
269                 else if (DISTANCE() == 4)
270                     EMIT(H4_END);
271                 else if (DISTANCE() == 5)
272                     EMIT(H5_END);
273                 else if (DISTANCE() == 6)
274                     EMIT(H6_END);
275                 else if (DISTANCE() > 6)
276                 {
277                     p -= 6; // will scan the H6 on the next scan
278                     EMIT(PRINTABLE);
279                 }
280             }
281             else
282             {
283                 // note that a H*_END token will never match before a BLOCKQUOTE_END
284                 REWIND();
285                 EMIT(PRINTABLE);
286             }
287             fbreak;
288         };
289
290         uri
291         {
292             EMIT(URI);
293             fbreak;
294         };
295
296         mail
297         {
298             EMIT(MAIL);
299             fbreak;
300         };
301
302         path
303         {
304             EMIT(PATH);
305             fbreak;
306         };
307
308         '[['
309         {
310             EMIT(LINK_START);
311             fbreak;
312         };
313
314         ']]'
315         {
316             EMIT(LINK_END);
317             fbreak;
318         };
319
320         '|'
321         {
322             EMIT(SEPARATOR);
323             fbreak;
324         };
325
326         '['
327         {
328             EMIT(EXT_LINK_START);
329             fbreak;
330         };
331
332         ']'
333         {
334             EMIT(EXT_LINK_END);
335             fbreak;
336         };
337
338         '&quot;'
339         {
340             EMIT(QUOT_ENTITY);
341             fbreak;
342         };
343
344         '&amp;'
345         {
346             EMIT(AMP_ENTITY);
347             fbreak;
348         };
349
350         '&' alpha+ digit* ';'
351         {
352             EMIT(NAMED_ENTITY);
353             fbreak;
354         };
355
356         '&#' [xX] xdigit+ ';'
357         {
358             EMIT(HEX_ENTITY);
359             fbreak;
360         };
361
362         '&#' digit+ ';'
363         {
364             EMIT(DECIMAL_ENTITY);
365             fbreak;
366         };
367
368         '"'
369         {
370             EMIT(QUOT);
371             fbreak;
372         };
373
374         '&'
375         {
376             EMIT(AMP);
377             fbreak;
378         };
379
380         '<'
381         {
382             EMIT(LESS);
383             fbreak;
384         };
385
386         '>'
387         {
388             EMIT(GREATER);
389             fbreak;
390         };
391
392         '{{'
393         {
394             EMIT(IMG_START);
395             fbreak;
396         };
397
398         '}}'
399         {
400             EMIT(IMG_END);
401             fbreak;
402         };
403
404         '{'
405         {
406             EMIT(LEFT_CURLY);
407             fbreak;
408         };
409
410         '}'
411         {
412             EMIT(RIGHT_CURLY);
413             fbreak;
414         };
415
416         ("\r"? "\n") | "\r"
417         {
418             EMIT(CRLF);
419             out->column_stop = 1;
420             out->line_stop++;
421             fbreak;
422         };
423
424         # must tokenize these separately from the other PRINTABLE characters otherwise a string like:
425         #   See http://example.com/.
426         # will get greedily tokenized as PRINABLE, SPACE, PRINTABLE rather than PRINTABLE, SPACE, URI, SPECIAL_URI_CHARS
427         # this also applies to MAIL tokenization and input strings like:
428         #   Email me (user@example.com) for more info.
429         special_uri_chars
430         {
431             EMIT(SPECIAL_URI_CHARS);
432             fbreak;
433         };
434
435         alnum+
436         {
437             EMIT(ALNUM);
438             fbreak;
439         };
440
441         # all the printable ASCII characters (0x20 to 0x7e) excluding those explicitly covered elsewhere:
442         # we skip space (0x20), exclamation mark (0x21), quote (0x22), hash (0x23), ampersand (0x26), apostrophe (0x27),
443         # left parenthesis (0x28), right parenthesis (0x29), numbers (0x30..0x39), asterisk (0x2a), comma (0x2c), period (0x2e),
444         # colon (0x3a), semi-colon (0x3b), less than (0x3c), equals (0x3d), greater than (0x3e), question mark (0x3f), uppercase
445         # letters (0x41..0x5a), left bracket (0x5b), right bracket (0x5d), backtick (0x60), lowercase letters (0x61..0x7a), left
446         # curly brace (0x7b), vertical bar (0x7c) and right curly brace (0x7d).
447         (0x24..0x25 | 0x2b | 0x2d | 0x2f | 0x40 | 0x5c | 0x5e..0x5f | 0x7e)+
448         {
449             EMIT(PRINTABLE);
450             fbreak;
451         };
452
453         # here is where we handle the UTF-8 and everything else
454         #
455         #     one_byte_sequence   = byte begins with zero;
456         #     two_byte_sequence   = first byte begins with 110 (0xc0..0xdf), next with 10 (0x80..9xbf);
457         #     three_byte_sequence = first byte begins with 1110 (0xe0..0xef), next two with 10 (0x80..9xbf);
458         #     four_byte_sequence  = first byte begins with 11110 (0xf0..0xf7), next three with 10 (0x80..9xbf);
459         #
460         #     within the ranges specified, we also exclude these illegal sequences:
461         #       1100000x (c0 c1)    overlong encoding, lead byte of 2 byte seq but code point <= 127
462         #       11110101 (f5)       restricted by RFC 3629 lead byte of 4-byte sequence for codepoint above 10ffff
463         #       1111011x (f6, f7)   restricted by RFC 3629 lead byte of 4-byte sequence for codepoint above 10ffff
464         (0x01..0x1f | 0x7f)                             @non_printable_ascii        |
465         (0xc2..0xdf 0x80..0xbf)                         @two_byte_utf8_sequence     |
466         (0xe0..0xef 0x80..0xbf 0x80..0xbf)              @three_byte_utf8_sequence   |
467         (0xf0..0xf4 0x80..0xbf 0x80..0xbf 0x80..0xbf)   @four_byte_utf8_sequence
468         {
469             EMIT(DEFAULT);
470             out->column_stop = out->column_start + 1;
471             fbreak;
472         };
473
474     *|;
475
476     write data;
477 }%%
478
479 // for now we use the scanner as a tokenizer that returns one token at a time, just like ANTLR
480 // ultimately we could look at embedding all of the transformation inside the scanner itself (combined scanner/parser)
481 // pass in the last token because that's useful for the scanner to know
482 // p data pointer (required by Ragel machine); overriden with contents of last_token if supplied
483 // pe data end pointer (required by Ragel machine)
484 void next_token(token_t *out, token_t *last_token, char *p, char *pe)
485 {
486     int last_token_type = NO_TOKEN;
487     if (last_token)
488     {
489         last_token_type     = last_token->type;
490         p = last_token->stop;
491         out->line_start     = out->line_stop    = last_token->line_stop;
492         out->column_start   = out->column_stop  = last_token->column_stop;
493     }
494     else
495     {
496         out->line_start     = 1;
497         out->column_start   = 1;
498         out->line_stop      = 1;
499         out->column_stop    = 1;
500     }
501     out->type       = NO_TOKEN;
502     out->code_point = 0;
503     out->start      = p;
504     if (p == pe)
505     {
506         // all done, have reached end of input
507         out->stop  = p;
508         out->type  = END_OF_FILE;
509         return;
510     }
511
512     char    *mark;      // for manual backtracking
513     char    *eof = pe;  // required for backtracking (longest match determination)
514     int     cs;         // current state (standard Ragel)
515     char    *ts;        // token start (scanner)
516     char    *te;        // token end (scanner)
517     int     act;        // identity of last patterned matched (scanner)
518     %% write init;
519     %% write exec;
520     if (cs == wikitext_error)
521         rb_raise(eWikitextParserError, "failed before finding a token");
522     else if (out->type == NO_TOKEN)
523         rb_raise(eWikitextParserError, "failed to produce a token");
524 }