new parser

new parser

// vim:syntax=ragel
#include <stdio.h>
#ifdef __PIKE__
# include "global.h"
# include "interpret.h"
# include "stralloc.h"
# include "mapping.h"
# include "svalue.h"
# include "array.h"
# include "module.h"
#else
# include <string.h>
# include <stdlib.h>
#endif

#define push_n_text(T, n) do {						\
    const char *_ = (T);						\
    struct svalue *_sp_ = Pike_sp++;                                 	\
    _sp_->subtype=0;                                                    \
    _sp_->u.string=make_shared_binary_string(_,n);              	\
    debug_malloc_touch(_sp_->u.string);                                 \
    _sp_->type=PIKE_T_STRING;                                           \
  }while(0)

%%{
    machine psyc;
    write data;

    action parse_key {
	push_n_text(mark, (ptrdiff_t)(fpc - mark));
	c++;
    }

    action push_val {
	if (fpc - mark > 0) {
            string_builder_binary_strcat(s, mark, (ptrdiff_t)(fpc - mark));
        }
    }

    action list {
	list++;
    }

    action add_newline {
	string_builder_putchar(s, '\n');
    }

    action add_list {
	if (list >= 1) {
	    f_aggregate(list+1);
	    c -= list;
	    list = 0;
	}
    }

    action add_last {
	if (lastmod != 0) {
	    push_string(finish_string_builder(s));
	    inti_string_builder(s, 1);
	    c++;
	}
    }

    action curmod { lastmod = mod; mod = fc; }
    action mark { mark = fpc; }
    action markprev { mark = fpc-1; }
    action checkmod { if (lasmod == 0) Pike_error("Invalid variable/list continuation."); }
    action checkmod_equal {
	if (mod =! lastmod) {
	    Pike_error("Continuation of variable (modifier: '%c') with different modifier '%c'.", lastmod, mod);
	}
    }

    method = '_' . (alnum | '_')+;
    modifier = [:=+-?];
    value = (any - '\n');
    var = method >markprev %parse_key. '\t' . value* >mark %push_val . '\n';
    continuation = '\t' >checkmod . value* >mark %push_val . '\n';
    header_line = ((modifier >curmod . (var >add_list | continuation >checkmod_equal %list) >add_last) | continuation >add_newline);
    main := header_line* . method >add_last >mark . '\n' %*{ fbreak; };
}%%

/*! @module Public
 */

/*! @module Parser
 */

/*! @module PSYC
 */

/*! @decl object parse(string s)
 *!
 *! Parses a JSON-formatted string and returns the corresponding mapping.
 */
PIKEFUN object parse(string data, object o) {
    char *p, *pe, *mark;
    int c = 0;
    int list = 0;
    int done = 0;
    char lastmod = 0, mod = 0;
    // we wont be building more than one string at once.
    struct string_builder s;

    // length n can be alot but is certainly enough.
    init_string_builder(&s, 1);

    p = (char*)STR0(data);
    pe = p + data->len;

    %%
    %% write init;
    %% write exec;

    if (cs < PSYC_first_final) {
	Pike_error("Error parsing PSYC packet.\n");
    }

    f_aggregate_mapping(c);

    c = find_identifier("mc", prog);
    object_low_set_index(o, c, );

    if (pe - p > 0) {
	reset_string_builder(&s);
	string_builder_binary_strcat(&s, p, (ptrdiff_t)(pe - p));
	cur->var->u.string = finish_string_builder(&s);
    } else {
	cur->var->u.string = empty_pike_string;
    }

    c = find_identifier("data", prog);
    object_low_set_index(o, c, cur->var);

    // extract the mc.
    //free(cur);
    //free_string_builder(&s);

    RETURN (INT_TYPE)0;
}

// vim:syntax=ragel
#include <stdio.h>
#ifdef __PIKE__
# include "global.h"
# include "interpret.h"
# include "stralloc.h"
# include "mapping.h"
# include "svalue.h"
# include "array.h"
# include "module.h"
#else
# include <string.h>
# include <stdlib.h>
#endif

struct state {
    int cs, top;
};

struct depth {
    struct depth *up;
    struct depth *key;
#ifdef __PIKE__
    struct svalue *var;
#endif
    int level;
    int state;
};

%%{
    machine psyc;
    write data noerror nofinal;

    action descend {
	last = cur;
	cur->state = ftargs;
#ifndef __PIKE__
	printf("descending before state %d at '%c' (pos: %d).\n", ftargs, fc, (int)fpc);
#endif
	// TODO: use pikes alloc
	cur = (struct depth*)malloc(sizeof(struct depth));

	if (cur == NULL) {
#ifdef __PIKE__
	    Pike_error("malloc failed.");
#else
	    printf("malloc returned NULLLLLL!!!1!\n");
	    exit(-1);
#endif
	}

	memset(cur, 0, sizeof(struct depth));
	cur->level = last->level+1;
	cur->up = last;
    }

    action free {
	//free(last);
    }

    action ascend {
	last = cur;
#ifdef __PIKE__
	if (last->var->type == PIKE_T_MAPPING) {
	    //free(last->key);
	}
#endif
	cur = cur->up;

	if (cur == NULL) {
#ifdef __PIKE__
	    Pike_error("pointer gone to heaven at! length: %d\t cur: %d\t last: %d\t at '%.*s'\n", data->len, cur->state, last->state, MINIMUM(10, (int)(pe - p)), p);
#else
	    printf("pointer gone to heaven!\n");
	    exit(-1);
#endif
	}

#ifndef __PIKE__
	printf("ascending to %d at '%c' (pos: %d).\n", cur->state, fc, (int)fpc);
#endif
    }

    action jump {
	fgoto *cur->state;
    }

    action begin_string {
#ifdef __PIKE__
	reset_string_builder(&s);
#else
	printf("beginning string.\n");
#endif
    }

    action mark { i = fpc; }
    action marknext { i = fpc + 1; }

    action string_append {
#ifdef __PIKE__
	if (fpc - i > 0) {
	    string_builder_binary_strcat(&s, i, (ptrdiff_t)(fpc - i));
	}
#endif
    }

    action string_unquote {
#ifdef __PIKE__
	switch (fc) {
	case 'n':	string_builder_putchar(&s, '\n'); break;
	case 't':	string_builder_putchar(&s, '\t'); break;
	case 'r':	string_builder_putchar(&s, '\r'); break;
	case 'f':	string_builder_putchar(&s, '\f'); break;
	case 'b':	string_builder_putchar(&s, '\b'); break;
	case '"':
	case '\\':	string_builder_putchar(&s, fc); break;
	}
#endif
    }

    action end_string {
#ifdef __PIKE__
	cur->var = (struct svalue*)malloc(sizeof(struct svalue));
	cur->var->type = PIKE_T_STRING;
	cur->var->u.string = finish_string_builder(&s);
#else
	printf("string: '%.*s' length: %d\n", (int)(fpc - i), i, fpc - i);
#endif
    }

#	PIKE API:
#
#	string_builder_binary_strcat(string_builder *s, char *str, int lenth)
#
#	struct pike_string *finish_string_builder(struct string_builder *s)
#
#	void string_builder_append(struct string_builder *s,
#	                           PCHARP from,
#	                           ptrdiff_t len)
#
#	void string_builder_putchar(struct string_builder *s, int ch)
    string := (
	       start: (
		    '"' >string_append -> final |
		    '\\' >string_append -> unquote |
		    (any - ["\\]) -> start
		),
		unquote: (
		    [nt"\\/bfnrt] >string_unquote @marknext -> start
		)
    ) >mark >begin_string @end_string @ascend @jump;

    action begin_mapping {
#ifdef __PIKE__
	cur->var = (struct svalue*)malloc(sizeof(struct svalue));
	cur->var->type = PIKE_T_MAPPING;
	cur->var->u.mapping = debug_allocate_mapping(8);
#else
	printf("beginning mapping.\n");
#endif
    }

    action end_mapping {
#ifndef __PIKE__
	printf("end mapping.\n");
#endif
    }

    action add_mapping {
#ifdef __PIKE__
	mapping_insert(cur->var->u.mapping, cur->key->var, last->var);
#endif
    }

    alphtype char;
    access fsm.;

    myspace = ' ';

#	PIKE API
#
#	struct mapping *debug_allocate_mapping(int size)
#
#	mapping_insert(struct mapping *m,
#                        struct svalue *key,
#                       struct svalue *val)
    mapping := (
		  start: myspace* . (
		    '"' >descend >{ fgoto string; } . myspace* . ':' >{ cur->key = last; } >descend >{ fgoto value; } -> more |
		    '}' -> final
		  ),
		  more: myspace* . (
		    ',' -> start|
		    '}' -> final
		  ) >add_mapping
    ) >begin_mapping @ascend @jump;

    action begin_array {
#ifdef __PIKE__
	cur->var = (struct svalue*)malloc(sizeof(struct svalue));
	cur->var->type = PIKE_T_ARRAY;
	cur->var->u.array = low_allocate_array(0, 8);
#endif
    }

    action add_array {
#ifdef __PIKE__

	cur->var->u.array = append_array(cur->var->u.array, last->var);
#endif
//	free(last);
    }

    array := ( myspace* . (any - myspace) >descend >{ fhold; fgoto value;} . myspace* . (','  |  ']' @ascend @jump ) >add_array )*;

    value = (myspace* . (
		  '"' >{ fgoto string; } |
		  '{' >{ fgoto mapping; } |
		  '[' >begin_array >{ fgoto array; }
		 ) . myspace* );

    method = '_' . (alnum | '_')+;
    variable_name = [:=+-?] . method;
    var = variable_name >descend >mark >begin_string
	    . '\t' >string_append >end_string @ascend @{ cur->key = last; }
	    . value >descend . '\n' >add_mapping;

    action store_vars {
	// a check to find out if any vars were found.
	if (cur->var->type == PIKE_T_MAPPING) {
	    c = find_identifier("vars", prog);
	    object_low_set_index(o, c, cur->var);
	}
    }

    main := var* . method >store_vars >mark >begin_string . '\n' >string_append >end_string @{ done = 1; };
}%%

#ifdef __PIKE__
/*! @module Public
 */

/*! @module Parser
 */

/*! @module PSYC
 */

/*! @decl mapping parse(string s)
 *!
 *! Parses a JSON-formatted string and returns the corresponding mapping.
 */
PIKEFUN int parse(string data, object o) {
    char *p, *pe, *i;
    int c;
    char done = 0;
    struct depth *cur, *last;
    // we wont be building more than one string at once.
    struct string_builder s;
    struct state fsm;
    struct program *prog;

    if (!o || !(prog=o->prog)) {
	Pike_error("Lookup in destructed object.\n");
    }

    if (data->size_shift != 0) {
	Pike_error("Size shift != 0.");
	// no need to return. does a longjmp
    }

    // length n can be alot but is certainly enough.
    init_string_builder(&s, 1);

    p = (char*)STR0(data);
    pe = p + data->len;

    cur = (struct depth*)malloc(sizeof(struct depth));
    memset(cur, 0, sizeof(struct depth));
    cur->level = 0;

    cur->var = (struct svalue*)malloc(sizeof(struct svalue));
    cur->var->type = PIKE_T_MAPPING;
    cur->var->u.mapping = debug_allocate_mapping(8);

    %%
    %% write init;
    %% write exec;

    if ( done != 1 ) {
	//if (cur) free(cur);
	Pike_error("length: %d\t cur: %d\t last: %d\t at '%.*s'\n", data->len, cur->state, last->state, MINIMUM(10, (int)(pe - p)), p);
	//free_string_builder(&s);
    }

    c = find_identifier("mc", prog);
    object_low_set_index(o, c, cur->var);

    if (pe - p > 0) {
	reset_string_builder(&s);
	string_builder_binary_strcat(&s, p, (ptrdiff_t)(pe - p));
	cur->var->u.string = finish_string_builder(&s);
    } else {
	cur->var->u.string = empty_pike_string;
    }

    c = find_identifier("data", prog);
    object_low_set_index(o, c, cur->var);

    // extract the mc.
    //free(cur);
    //free_string_builder(&s);

    RETURN (INT_TYPE)0;
}

#else

int parse_psyc(char *d, struct state *f, int n) {
    char *p = d;
    char *pe = d + n;
    char *i = p;
    char done = 0;
    struct state fsm = *f;

    struct depth *cur, *last, *key;
    cur = (struct depth*)malloc(sizeof(struct depth));
    cur->level = 0;

    %% write init;
    %% write exec;

    printf("the machine stopped at char %c at position %d in state %d. left: %d, length: %d\n", *p, (int)p, fsm->cs, (int)(pe - p), strlen(p));
    free(cur);
    if ( done == 1 ) {
	printf("The machine parsed the packet successfully. data is: '%.*s'\n", pe - p, p);
    } else {
	printf("Error while parsing.");
    }

    return 0;
}

int main() {
    struct state fsm;

    char *one = "_sdlkf	\"hihihi\\\"\"\n_hallo	{ \"sdf\"  : \"sdf\", \"hihihahahiahia\\n\\f\" : \"\"}\n_hehe\t[ \"juchuu\" , \"wulle wulle wu\"   ]\n_message_public\nlksadsalkf ashdf kjhsafkj hsdakhf s\n\n\n\n";
    printf("%s\n", one);
    parse_psyc(one, &fsm, strlen(one));

    return 0;
}
#endif


GCDateParser: log on french rev calendar

/**

Ragel state machine for GEDCOM dates based on the 5.5 documentation.

	 DATE: = {Size=4:35}
	 [ <DATE_CALENDAR_ESCAPE> | <NULL>]
	 <DATE_CALENDAR>

	 DATE_APPROXIMATED: = {Size=4:35}
	 [
	 ABT <DATE> |
	 CAL <DATE> |
	 EST <DATE>
	 ]

	 DATE_CALENDAR: = {Size=4:35}
	 [ <DATE_GREG> | <DATE_JULN> | <DATE_HEBR> | <DATE_FREN> |
	 <DATE_FUTURE> ]

	 DATE_CALENDAR_ESCAPE: = {Size=4:15}
	 [ @#DHEBREW@ | @#DROMAN@ | @#DFRENCH R@ | @#DGREGORIAN@ |
	 @#DJULIAN@ | @#DUNKNOWN@ ]

	 DATE_EXACT: = {Size=10:11}
	 <DAY> <MONTH> <YEAR_GREG>

	 DATE_FREN: = {Size=4:35}
	 [ <YEAR> | <MONTH_FREN> <YEAR> | <DAY> <MONTH_FREN> <YEAR> ]
	 See <MONTH_FREN>

	 DATE_GREG: = {Size=4:35}
	 [ <YEAR_GREG> | <MONTH> <YEAR_GREG> | <DAY> <MONTH> <YEAR_GREG> ]
	 See <YEAR_GREG>.

	 DATE_HEBR: = {Size=4:35}
	 [ <YEAR> | <MONTH_HEBR> <YEAR> | <DAY> <MONTH_HEBR> <YEAR> ]
	 See <MONTH_HEBR>.

	 DATE_JULN: = {Size=4:35}
	 [ <YEAR> | <MONTH> <YEAR> | <DAY> <MONTH> <YEAR> ]

	 DATE_LDS_ORD: = {Size=4:35}
	 <DATE_VALUE>

	 DATE_PERIOD: = {Size=7:35}
	 [
	 FROM <DATE> |
	 TO <DATE> |
	 FROM <DATE> TO <DATE>
	 ]

	 DATE_PHRASE: = {Size=1:35}
	 (<TEXT>)

	 DATE_RANGE: = {Size=8:35}
	 [
	 BEF <DATE> |
	 AFT <DATE> |
	 BET <DATE> AND <DATE>
	 ]

	 DATE_VALUE: = {Size=1:35}
	 [
	 <DATE> |
	 <DATE_PERIOD> |
	 <DATE_RANGE>
	 <DATE_APPROXIMATED> |
	 INT <DATE> (<DATE_PHRASE>) |
	 (<DATE_PHRASE>)
	 ]

	 DAY: = {Size=1:2}
	 dd

	 MONTH: = {Size=3}
	 [ JAN | FEB | MAR | APR | MAY | JUN |
	 JUL | AUG | SEP | OCT | NOV | DEC ]

	 MONTH_FREN: = {Size=4}
	 [ VEND | BRUM | FRIM | NIVO | PLUV | VENT | GERM |
	 FLOR | PRAI | MESS | THER | FRUC | COMP ]

	 MONTH_HEBR: = {Size=3}
	 [ TSH | CSH | KSL | TVT | SHV | ADR | ADS |
	 NSN | IYR | SVN | TMZ | AAV | ELL ]

	 YEAR_GREG: = {Size=3:7}
	 [ <NUMBER> | <NUMBER>/<DIGIT><DIGIT> ]

*/

#import "GCDate_internal.h"

%%{
	machine date;

	action log {
		NSLog(@"%p log: %c", fpc, fc);
	}

	action tag {
		tag = fpc - data;
		//NSLog(@"%p     TAG: %d", fpc, tag);
	}

	action number {
		long len = (fpc - data) - tag;
		currentNumber = [[[NSString alloc] initWithBytes:fpc-len length:len encoding:NSUTF8StringEncoding] integerValue];
		//NSLog(@"%p num: %d", fpc, number);
	}

	action string {
		long len = (fpc - data) - tag;
		currentString = [[NSString alloc] initWithBytes:fpc-len length:len encoding:NSUTF8StringEncoding];
		//NSLog(@"%p string: %@", fpc, string);
	}

	action saveDay {
        [currentDateComponents setDay:currentNumber];
		//NSLog(@"%p saveDays: %d", fpc, currentNumber);
	}

	action saveMonthNumeral {
        [currentDateComponents setMonth:currentNumber];
		//NSLog(@"%p saveMonthNumeral: %d", fpc, currentNumber);
	}

	action saveMonthWord {
        [currentDateComponents setMonth:[_gregorianMonthNames indexOfObject:currentString]+1];
		//NSLog(@"%p saveMonthWord: %d", fpc, currentNumber);
	}

	action saveMonthFren {
        [currentDateComponents setMonth:[_frenchRevolutionaryMonthNames indexOfObject:currentString]+1];
		//NSLog(@"%p saveMonthFren: %d", fpc, currentNumber);
	}

	action saveMonthHebr {
        [currentDateComponents setMonth:[_hebrewMonthNames indexOfObject:currentString]+1];
		//NSLog(@"%p saveMonthHebr: %d", fpc, currentNumber);
	}

	action saveYear {
        [currentDateComponents setYear:currentNumber];
		//NSLog(@"%p saveYears: %d", fpc, currentNumber);
	}

	action saveYearGreg {
        yearGreg = currentString;
		//NSLog(@"%p saveYears: %d", fpc, currentNumber);
	}

    action setCalendarGregorian {
        calendar = _gregorianCalendar;
    }

    action setCalendarJulian {
        // NSGregorianCalendar has Julian October 4, 1582 >>> Gregorian October 15, 1582
        calendar = _gregorianCalendar;
    }

    action setCalendarHebrew {
        calendar = _hebrewCalendar;
    }

    action setCalendarFrenchRevolutionary {
        NSLog(@"WARNING: French revolutionary calendar is not currently supported!");
        calendar = _frenchRevolutionaryCalendar;
    }

    action saveApproximationQualifier {
        approximationQualifier = currentString;
    }

    action resetDate {
		//NSLog(@"%p saveDatePart.", fpc);

        previousDate = currentDate;

        currentDateComponents = [[NSDateComponents alloc] init];
        [currentDateComponents setYear:0];
        [currentDateComponents setMonth:0];
        [currentDateComponents setDay:0];
        [currentDateComponents setHour:0];
        [currentDateComponents setMinute:0];
        [currentDateComponents setSecond:0];
        [currentDateComponents setTimeZone:_utc];

        currentDate = nil;
    }

    action saveDateSimple {
		//NSLog(@"%p saveDateSimple.", fpc);
        currentDate = [GCDate dateWithSimpleDate:currentDateComponents calendar:calendar];
        if (yearGreg) {
            ((GCSimpleDate *)currentDate).yearGregSuffix = yearGreg;
        }
        yearGreg = nil;
    }

    action saveDateApproximate {
		//NSLog(@"%p saveDateApproximate.", fpc);
        currentDate = [GCDate dateWithApproximateDate:currentDate type:approximationQualifier];
    }

    action saveDatePhrase {
		//NSLog(@"%p saveDatePhrase.", fpc);
        currentDate = [GCDate dateWithPhrase:currentString];
    }

    action saveDateInterpreted {
		//NSLog(@"%p saveDateInterpreted.", fpc);
        currentDate = [GCDate dateWithInterpretedDate:previousDate phrase:currentDate];
    }

    action saveDateRange {
		//NSLog(@"%p saveDateRange.", fpc);
        currentDate = [GCDate dateWithRangeFrom:previousDate to:currentDate];
    }

    action saveDatePeriod {
		//NSLog(@"%p saveDatePeriod.", fpc);
        currentDate = [GCDate dateWithPeriodFrom:previousDate to:currentDate];
    }

	action finish {
		//NSLog(@"%p finish.", fpc);
        date = currentDate;
		finished = YES;
	}

	day					= ( digit digit? ) >tag %number %saveDay;

	monthWord			= (	'JAN' | 'FEB' | 'MAR' | 'APR' | 'MAY' | 'JUN' |
							'JUL' | 'AUG' | 'SEP' | 'OCT' | 'NOV' | 'DEC') >tag %string %saveMonthWord;
	monthNumeral		= ( digit digit? ) >tag %number %saveMonthNumeral;
	month				= ( monthWord | monthNumeral );

	monthFren			= (	'VEND' | 'BRUM' | 'FRIM' | 'NIVO' | 'PLUV' |
							'VENT' | 'GERM' | 'FLOR' | 'PRAI' | 'MESS' |
							'THER' | 'FRUC' | 'COMP' ) >tag %string %saveMonthFren;
	monthHebr			= (	'TSH' | 'CSH' | 'KSL' | 'TVT' | 'SHV' | 'ADR' |
							'ADS' | 'NSN' | 'IYR' | 'SVN' | 'TMZ' | 'AAV' |
							'ELL' ) >tag %string %saveMonthHebr;
	year 				= ( digit digit digit digit? ) >tag %number %saveYear;
    yearGregSuffix      = ( '/' digit digit ) >tag %string %saveYearGreg;
	yearGreg			= ( year | ( year yearGregSuffix ) );

    sep                 = ( ' ' | '/' | '-' );

	dateGreg			= ( yearGreg | (monthWord sep yearGreg) | (day sep month sep yearGreg) ) %setCalendarGregorian;
	dateJuln			= ( year | (monthWord sep year) | (day sep month sep year) ) %setCalendarJulian;
	dateHebr			= ( year | (monthHebr sep year) | (day sep monthHebr sep year) ) %setCalendarHebrew;
	dateFren			= ( year | (monthFren sep year) | (day sep monthFren sep year) ) %setCalendarFrenchRevolutionary;

	dateSimple			= ( '@#DGREGORIAN@ ' dateGreg |
							'@#DJULIAN@ ' dateJuln |
							'@#DHEBREW@ ' dateHebr |
							'@#DFRENCH R@ ' dateFren |
							dateGreg ) %saveDateSimple;

	datePhrase			= ( '(' any* >tag %string %saveDatePhrase ')' );

	dateInterpreted		= ( 'INT ' dateSimple %resetDate ' ' datePhrase ) %saveDateInterpreted;

	dateApproximated	= ( ('ABT' | 'CAL' | 'EST') >tag %string %saveApproximationQualifier ' ' dateSimple ) %saveDateApproximate;

	dateRange			= ( ('BET ' dateSimple %resetDate ' AND ' dateSimple)
						  | ('BEF ' dateSimple)
						  | ('AFT ' dateSimple %resetDate)
                          ) %saveDateRange ;

	datePeriod			= ( ('FROM ' dateSimple %resetDate ' TO ' dateSimple)
						  | ('FROM ' dateSimple %resetDate)
						  | ('TO ' dateSimple)
                          ) %saveDatePeriod ;

	main               := ( dateSimple
						  | datePeriod
						  | dateRange
						  | dateApproximated
						  | dateInterpreted
						  | datePhrase
                          )  %/finish;

}%%

%% write data;

@implementation GCDateParser

__strong static id _sharedDateParser = nil;
__strong static NSTimeZone *_utc;
__strong static NSCalendar *_gregorianCalendar;
__strong static NSCalendar *_hebrewCalendar;
__strong static NSCalendar *_frenchRevolutionaryCalendar;
__strong static NSArray *_gregorianMonthNames;
__strong static NSArray *_hebrewMonthNames;
__strong static NSArray *_frenchRevolutionaryMonthNames;

+ (void)initialize
{
    _sharedDateParser = [[self alloc] init];

    _utc = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];

    _gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    [_gregorianCalendar setTimeZone:_utc];

    _hebrewCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSHebrewCalendar];
    [_hebrewCalendar setTimeZone:_utc];

    //TODO, french revolutionariy calendar doesn't exist in ICU...
    _frenchRevolutionaryCalendar = nil;

    _gregorianMonthNames = @[ @"JAN", @"FEB", @"MAR", @"APR", @"MAY", @"JUN", @"JUL", @"AUG", @"SEP", @"OCT", @"NOV", @"DEC" ];

    _hebrewMonthNames = @[ @"TSH", @"CSH", @"KSL", @"TVT", @"SHV", @"ADR", @"ADS", @"NSN", @"IYR", @"SVN", @"TMZ", @"AAV", @"ELL" ];

    _frenchRevolutionaryMonthNames = @[ @"VEND", @"BRUM", @"FRIM", @"NIVO", @"PLUV", @"VENT", @"GERM", @"FLOR", @"PRAI", @"MESS", @"THER", @"FRUC", @"COMP" ];

}

+ (id)sharedDateParser
{
    return _sharedDateParser;
}

- (GCDate *)parseGedcom:(NSString *)str
{
    GCDate *date = nil;

    NSDateComponents *currentDateComponents = [[NSDateComponents alloc] init];
    [currentDateComponents setYear:0];
    [currentDateComponents setMonth:0];
    [currentDateComponents setDay:0];
    [currentDateComponents setHour:0];
    [currentDateComponents setMinute:0];
    [currentDateComponents setSecond:0];
    [currentDateComponents setTimeZone:_utc];
    NSCalendar *calendar = _gregorianCalendar;

    long tag = 0;
    NSInteger currentNumber = 0;
    NSString *currentString = nil;

    NSString *yearGreg = nil;

    NSString *approximationQualifier = nil;
    id previousDate = nil;
    id currentDate = nil;

    BOOL finished = NO;

    int cs = 0;
    const char *data = [str UTF8String];
    const char *p = data;
    const char *pe = p + [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
    const char *eof = pe;

    %% write init;
    %% write exec;

    if (!finished) {
        date = nil;
    }

    if (date == nil) {
        date = [GCDate dateWithInvalidDateString:str];
    }

    return date;
}

@end

/**

Ragel state machine for GEDCOM dates based on the 5.5 documentation.

	 DATE: = {Size=4:35}
	 [ <DATE_CALENDAR_ESCAPE> | <NULL>]
	 <DATE_CALENDAR>

	 DATE_APPROXIMATED: = {Size=4:35}
	 [
	 ABT <DATE> |
	 CAL <DATE> |
	 EST <DATE>
	 ]

	 DATE_CALENDAR: = {Size=4:35}
	 [ <DATE_GREG> | <DATE_JULN> | <DATE_HEBR> | <DATE_FREN> |
	 <DATE_FUTURE> ]

	 DATE_CALENDAR_ESCAPE: = {Size=4:15}
	 [ @#DHEBREW@ | @#DROMAN@ | @#DFRENCH R@ | @#DGREGORIAN@ |
	 @#DJULIAN@ | @#DUNKNOWN@ ]

	 DATE_EXACT: = {Size=10:11}
	 <DAY> <MONTH> <YEAR_GREG>

	 DATE_FREN: = {Size=4:35}
	 [ <YEAR> | <MONTH_FREN> <YEAR> | <DAY> <MONTH_FREN> <YEAR> ]
	 See <MONTH_FREN>

	 DATE_GREG: = {Size=4:35}
	 [ <YEAR_GREG> | <MONTH> <YEAR_GREG> | <DAY> <MONTH> <YEAR_GREG> ]
	 See <YEAR_GREG>.

	 DATE_HEBR: = {Size=4:35}
	 [ <YEAR> | <MONTH_HEBR> <YEAR> | <DAY> <MONTH_HEBR> <YEAR> ]
	 See <MONTH_HEBR>.

	 DATE_JULN: = {Size=4:35}
	 [ <YEAR> | <MONTH> <YEAR> | <DAY> <MONTH> <YEAR> ]

	 DATE_LDS_ORD: = {Size=4:35}
	 <DATE_VALUE>

	 DATE_PERIOD: = {Size=7:35}
	 [
	 FROM <DATE> |
	 TO <DATE> |
	 FROM <DATE> TO <DATE>
	 ]

	 DATE_PHRASE: = {Size=1:35}
	 (<TEXT>)

	 DATE_RANGE: = {Size=8:35}
	 [
	 BEF <DATE> |
	 AFT <DATE> |
	 BET <DATE> AND <DATE>
	 ]

	 DATE_VALUE: = {Size=1:35}
	 [
	 <DATE> |
	 <DATE_PERIOD> |
	 <DATE_RANGE>
	 <DATE_APPROXIMATED> |
	 INT <DATE> (<DATE_PHRASE>) |
	 (<DATE_PHRASE>)
	 ]

	 DAY: = {Size=1:2}
	 dd

	 MONTH: = {Size=3}
	 [ JAN | FEB | MAR | APR | MAY | JUN |
	 JUL | AUG | SEP | OCT | NOV | DEC ]

	 MONTH_FREN: = {Size=4}
	 [ VEND | BRUM | FRIM | NIVO | PLUV | VENT | GERM |
	 FLOR | PRAI | MESS | THER | FRUC | COMP ]

	 MONTH_HEBR: = {Size=3}
	 [ TSH | CSH | KSL | TVT | SHV | ADR | ADS |
	 NSN | IYR | SVN | TMZ | AAV | ELL ]

	 YEAR_GREG: = {Size=3:7}
	 [ <NUMBER> | <NUMBER>/<DIGIT><DIGIT> ]

*/

#import "GCDate_internal.h"

%%{
	machine date;

	action log {
		NSLog(@"%p log: %c", fpc, fc);
	}

	action tag {
		tag = fpc - data;
		//NSLog(@"%p     TAG: %d", fpc, tag);
	}

	action number {
		long len = (fpc - data) - tag;
		currentNumber = [[[NSString alloc] initWithBytes:fpc-len length:len encoding:NSUTF8StringEncoding] integerValue];
		//NSLog(@"%p num: %d", fpc, number);
	}

	action string {
		long len = (fpc - data) - tag;
		currentString = [[NSString alloc] initWithBytes:fpc-len length:len encoding:NSUTF8StringEncoding];
		//NSLog(@"%p string: %@", fpc, string);
	}

	action saveDay {
        [currentDateComponents setDay:currentNumber];
		//NSLog(@"%p saveDays: %d", fpc, currentNumber);
	}

	action saveMonthNumeral {
        [currentDateComponents setMonth:currentNumber];
		//NSLog(@"%p saveMonthNumeral: %d", fpc, currentNumber);
	}

	action saveMonthWord {
        [currentDateComponents setMonth:[_gregorianMonthNames indexOfObject:currentString]+1];
		//NSLog(@"%p saveMonthWord: %d", fpc, currentNumber);
	}

	action saveMonthFren {
        [currentDateComponents setMonth:[_frenchRevolutionaryMonthNames indexOfObject:currentString]+1];
		//NSLog(@"%p saveMonthFren: %d", fpc, currentNumber);
	}

	action saveMonthHebr {
        [currentDateComponents setMonth:[_hebrewMonthNames indexOfObject:currentString]+1];
		//NSLog(@"%p saveMonthHebr: %d", fpc, currentNumber);
	}

	action saveYear {
        [currentDateComponents setYear:currentNumber];
		//NSLog(@"%p saveYears: %d", fpc, currentNumber);
	}

	action saveYearGreg {
        yearGreg = currentString;
		//NSLog(@"%p saveYears: %d", fpc, currentNumber);
	}

    action setCalendarGregorian {
        calendar = _gregorianCalendar;
    }

    action setCalendarJulian {
        // NSGregorianCalendar has Julian October 4, 1582 >>> Gregorian October 15, 1582
        calendar = _gregorianCalendar;
    }

    action setCalendarHebrew {
        calendar = _hebrewCalendar;
    }

    action setCalendarFrenchRevolutionary {
        calendar = _frenchRevolutionaryCalendar;
    }

    action saveApproximationQualifier {
        approximationQualifier = currentString;
    }

    action resetDate {
		//NSLog(@"%p saveDatePart.", fpc);

        previousDate = currentDate;

        currentDateComponents = [[NSDateComponents alloc] init];
        [currentDateComponents setYear:0];
        [currentDateComponents setMonth:0];
        [currentDateComponents setDay:0];
        [currentDateComponents setHour:0];
        [currentDateComponents setMinute:0];
        [currentDateComponents setSecond:0];
        [currentDateComponents setTimeZone:_utc];

        currentDate = nil;
    }

    action saveDateSimple {
		//NSLog(@"%p saveDateSimple.", fpc);
        currentDate = [GCDate dateWithSimpleDate:currentDateComponents calendar:calendar];
        if (yearGreg) {
            ((GCSimpleDate *)currentDate).yearGregSuffix = yearGreg;
        }
        yearGreg = nil;
    }

    action saveDateApproximate {
		//NSLog(@"%p saveDateApproximate.", fpc);
        currentDate = [GCDate dateWithApproximateDate:currentDate type:approximationQualifier];
    }

    action saveDatePhrase {
		//NSLog(@"%p saveDatePhrase.", fpc);
        currentDate = [GCDate dateWithPhrase:currentString];
    }

    action saveDateInterpreted {
		//NSLog(@"%p saveDateInterpreted.", fpc);
        currentDate = [GCDate dateWithInterpretedDate:previousDate phrase:currentDate];
    }

    action saveDateRange {
		//NSLog(@"%p saveDateRange.", fpc);
        currentDate = [GCDate dateWithRangeFrom:previousDate to:currentDate];
    }

    action saveDatePeriod {
		//NSLog(@"%p saveDatePeriod.", fpc);
        currentDate = [GCDate dateWithPeriodFrom:previousDate to:currentDate];
    }

	action finish {
		//NSLog(@"%p finish.", fpc);
        date = currentDate;
		finished = YES;
	}

	day					= ( digit digit? ) >tag %number %saveDay;

	monthWord			= (	'JAN' | 'FEB' | 'MAR' | 'APR' | 'MAY' | 'JUN' |
							'JUL' | 'AUG' | 'SEP' | 'OCT' | 'NOV' | 'DEC') >tag %string %saveMonthWord;
	monthNumeral		= ( digit digit? ) >tag %number %saveMonthNumeral;
	month				= ( monthWord | monthNumeral );

	monthFren			= (	'VEND' | 'BRUM' | 'FRIM' | 'NIVO' | 'PLUV' |
							'VENT' | 'GERM' | 'FLOR' | 'PRAI' | 'MESS' |
							'THER' | 'FRUC' | 'COMP' ) >tag %string %saveMonthFren;
	monthHebr			= (	'TSH' | 'CSH' | 'KSL' | 'TVT' | 'SHV' | 'ADR' |
							'ADS' | 'NSN' | 'IYR' | 'SVN' | 'TMZ' | 'AAV' |
							'ELL' ) >tag %string %saveMonthHebr;
	year 				= ( digit digit digit digit? ) >tag %number %saveYear;
    yearGregSuffix      = ( '/' digit digit ) >tag %string %saveYearGreg;
	yearGreg			= ( year | ( year yearGregSuffix ) );

    sep                 = ( ' ' | '/' | '-' );

	dateGreg			= ( yearGreg | (monthWord sep yearGreg) | (day sep month sep yearGreg) ) %setCalendarGregorian;
	dateJuln			= ( year | (monthWord sep year) | (day sep month sep year) ) %setCalendarJulian;
	dateHebr			= ( year | (monthHebr sep year) | (day sep monthHebr sep year) ) %setCalendarHebrew;
	dateFren			= ( year | (monthFren sep year) | (day sep monthFren sep year) ) %setCalendarFrenchRevolutionary;

	dateSimple			= ( '@#DGREGORIAN@ ' dateGreg |
							'@#DJULIAN@ ' dateJuln |
							'@#DHEBREW@ ' dateHebr |
							'@#DFRENCH R@ ' dateFren |
							dateGreg ) %saveDateSimple;

	datePhrase			= ( '(' any* >tag %string %saveDatePhrase ')' );

	dateInterpreted		= ( 'INT ' dateSimple %resetDate ' ' datePhrase ) %saveDateInterpreted;

	dateApproximated	= ( ('ABT' | 'CAL' | 'EST') >tag %string %saveApproximationQualifier ' ' dateSimple ) %saveDateApproximate;

	dateRange			= ( ('BET ' dateSimple %resetDate ' AND ' dateSimple)
						  | ('BEF ' dateSimple)
						  | ('AFT ' dateSimple %resetDate)
                          ) %saveDateRange ;

	datePeriod			= ( ('FROM ' dateSimple %resetDate ' TO ' dateSimple)
						  | ('FROM ' dateSimple %resetDate)
						  | ('TO ' dateSimple)
                          ) %saveDatePeriod ;

	main               := ( dateSimple
						  | datePeriod
						  | dateRange
						  | dateApproximated
						  | dateInterpreted
						  | datePhrase
                          )  %/finish;

}%%

%% write data;

@implementation GCDateParser

__strong static id _sharedDateParser = nil;
__strong static NSTimeZone *_utc;
__strong static NSCalendar *_gregorianCalendar;
__strong static NSCalendar *_hebrewCalendar;
__strong static NSCalendar *_frenchRevolutionaryCalendar;
__strong static NSArray *_gregorianMonthNames;
__strong static NSArray *_hebrewMonthNames;
__strong static NSArray *_frenchRevolutionaryMonthNames;

+ (void)initialize
{
    _sharedDateParser = [[self alloc] init];

    _utc = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];

    _gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    [_gregorianCalendar setTimeZone:_utc];

    _hebrewCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSHebrewCalendar];
    [_hebrewCalendar setTimeZone:_utc];

    //TODO, french revolutionariy calendar doesn't exist in ICU...
    _frenchRevolutionaryCalendar = nil;

    _gregorianMonthNames = @[ @"JAN", @"FEB", @"MAR", @"APR", @"MAY", @"JUN", @"JUL", @"AUG", @"SEP", @"OCT", @"NOV", @"DEC" ];

    _hebrewMonthNames = @[ @"TSH", @"CSH", @"KSL", @"TVT", @"SHV", @"ADR", @"ADS", @"NSN", @"IYR", @"SVN", @"TMZ", @"AAV", @"ELL" ];

    _frenchRevolutionaryMonthNames = @[ @"VEND", @"BRUM", @"FRIM", @"NIVO", @"PLUV", @"VENT", @"GERM", @"FLOR", @"PRAI", @"MESS", @"THER", @"FRUC", @"COMP" ];

}

+ (id)sharedDateParser
{
    return _sharedDateParser;
}

- (GCDate *)parseGedcom:(NSString *)str
{
    GCDate *date = nil;

    NSDateComponents *currentDateComponents = [[NSDateComponents alloc] init];
    [currentDateComponents setYear:0];
    [currentDateComponents setMonth:0];
    [currentDateComponents setDay:0];
    [currentDateComponents setHour:0];
    [currentDateComponents setMinute:0];
    [currentDateComponents setSecond:0];
    [currentDateComponents setTimeZone:_utc];
    NSCalendar *calendar = _gregorianCalendar;

    long tag = 0;
    NSInteger currentNumber = 0;
    NSString *currentString = nil;

    NSString *yearGreg = nil;

    NSString *approximationQualifier = nil;
    id previousDate = nil;
    id currentDate = nil;

    BOOL finished = NO;

    int cs = 0;
    const char *data = [str UTF8String];
    const char *p = data;
    const char *pe = p + [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
    const char *eof = pe;

    %% write init;
    %% write exec;

    if (!finished) {
        date = nil;
    }

    if (date == nil) {
        date = [GCDate dateWithInvalidDateString:str];
    }

    return date;
}

@end


Corrected default quantifier mode name, :greedy instead of :basic

%%{
  machine re_scanner;

  wild                  = '.';
  backslash             = '\\';
  alternation           = '|';
  beginning_of_line     = '^';
  end_of_line           = '$';

  range_open            = '{';
  range_close           = '}';
  curlies               = range_open | range_close;

  group_open            = '(';
  group_close           = ')';
  parantheses           = group_open | group_close;

  set_open              = '[';
  set_close             = ']';
  brackets              = set_open | set_close;

  posix_class_name      = 'alnum' | 'alpha' | 'blank' |
                          'cntrl' | 'digit' | 'graph' |
                          'lower' | 'print' | 'punct' |
                          'space' | 'upper' | 'xdigit' |
                          'word'  | 'ascii';

  property_name         = 'Alnum' | 'Alpha' | 'Any'   | 'Ascii' | 'Blank' |
                          'Cntrl' | 'Digit' | 'Graph' | 'Lower' | 'Print' |
                          'Punct' | 'Space' | 'Upper' | 'Word' | 'Xdigit';

  category_letter       = 'L' . [ultmo]?;
  category_mark         = 'M' . [nce]?;
  category_number       = 'N' . [dlo]?;
  category_punctuation  = 'P' . [cdseifo]?;
  category_symbol       = 'S' . [mcko]?;
  category_separator    = 'Z' . [slp]?;
  category_codepoint    = 'C' . [cfson]?;

  posix_class           = '[:' . posix_class_name . ':]';

  char_type             = [dDhHsSwW];

  line_anchor           = beginning_of_line | end_of_line;
  anchor_char           = [AbBzZG];
  property_char         = [pP];

  escaped_char          = [aefnrtv];
  octal_sequence        = '0' . [0-7]{2};

  hex_sequence          = 'x' . xdigit{2};
  wide_hex_sequence     = 'x' . '{7' . xdigit{1,7} . '}';
  control_sequence      = ('c' | 'C-') . xdigit{1,2};
  meta_sequence         = 'M-' . xdigit{1,2};
  meta_control_sequence = 'M-\\C-' . xdigit{1,2};

  zero_or_one           = '?' | '??' | '?+';
  zero_or_more          = '*' | '*?' | '*+';
  one_or_more           = '+' | '+?' | '++';

  quantifier_greedy     = '?'  | '*'  | '+';
  quantifier_reluctant  = '??' | '*?' | '+?';
  quantifier_possessive = '?+' | '*+' | '++';
  quantifier_mode       = '?'  | '+';

  quantifier_range      = range_open . (digit+)? . ','? . (digit+)? .
                          range_close . quantifier_mode?;

  quantifiers           = quantifier_greedy | quantifier_reluctant |
                          quantifier_possessive | quantifier_range;

  group_comment         = '?#' . [^)]+ . group_close;

  group_atomic          = '?>';
  group_passive         = '?:';
  group_lookahead       = '?=';
  group_nlookahead      = '?!';
  group_lookbehind      = '?<=';
  group_nlookbehind     = '?<!';

  group_options         = '?' . ([mix]{1,3})? . '-' . ([mix]{1,3})? . ':';

  group_name            = alpha . alnum+;
  group_named           = '?<' . group_name . '>';

  group_type            = group_atomic | group_passive |
                          group_lookahead | group_nlookahead |
                          group_lookbehind | group_nlookbehind |
                          group_named;

  # characters the 'break' a literal
  meta_char             = wild | backslash | alternation |
                          curlies | parantheses | brackets |
                          line_anchor | quantifier_greedy;

  # character set scanner, continues consuming characters until it meets the
  # closing bracket of the set.
  character_set := |*
    ']' {
      self.emit(:character_set, :close, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    '^' {
      self.emit(:character_set, :negate, data[ts..te-1].pack('c*'), ts, te)
    };

    alnum . '-' . alnum { # TODO: add properties
      self.emit(:character_set, :range, data[ts..te-1].pack('c*'), ts, te)
    };

    '&&' {
      self.emit(:character_set, :intersection, data[ts..te-1].pack('c*'), ts, te)
    };

    '\\' . [\]\-\,b] {
      self.emit(:character_set, :member, data[ts..te-1].pack('c*'), ts, te)
    };

    posix_class {
      case text = data[ts..te-1].pack('c*')
      when '[:alnum:]';  self.emit(:character_set, :set_class_alnum,  text, ts, te)
      when '[:alpha:]';  self.emit(:character_set, :set_class_alpha,  text, ts, te)
      when '[:blank:]';  self.emit(:character_set, :set_class_blank,  text, ts, te)
      when '[:cntrl:]';  self.emit(:character_set, :set_class_cntrl,  text, ts, te)
      when '[:digit:]';  self.emit(:character_set, :set_class_digit,  text, ts, te)
      when '[:graph:]';  self.emit(:character_set, :set_class_graph,  text, ts, te)
      when '[:lower:]';  self.emit(:character_set, :set_class_lower,  text, ts, te)
      when '[:print:]';  self.emit(:character_set, :set_class_print,  text, ts, te)
      when '[:punct:]';  self.emit(:character_set, :set_class_punct,  text, ts, te)
      when '[:space:]';  self.emit(:character_set, :set_class_space,  text, ts, te)
      when '[:upper:]';  self.emit(:character_set, :set_class_upper,  text, ts, te)
      when '[:xdigit:]'; self.emit(:character_set, :set_class_xdigit, text, ts, te)
      when '[:word:]';   self.emit(:character_set, :set_class_word,   text, ts, te)
      when '[:ascii:]';  self.emit(:character_set, :set_class_ascii,  text, ts, te)
      else raise "Unsupported character posixe class at #{text} (char #{ts})"
      end
    };

    any {
      self.emit(:character_set, :member, data[ts..te-1].pack('c*'), ts, te)
    };
  *|;

  # escape sequence scanner
  escape_sequence := |*
    hex_sequence {
      self.emit(:escape_sequence, :hex, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    wide_hex_sequence {
      self.emit(:escape_sequence, :hex_wide, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    control_sequence {
      self.emit(:escape_sequence, :control, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    meta_sequence {
      self.emit(:escape_sequence, :meta, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    meta_control_sequence {
      self.emit(:escape_sequence, :meta_control, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    property_char . '{' . property_name . '}' > (escaped_alpha, 2) {
      text = data[ts..te-1].pack('c*')
      type = text[0,1] == 'p' ? :property: :inverted_property

      case name = data[ts+2..te-2].pack('c*')
      when 'Alnum';   self.emit(type, :alnum,  text, ts, te)
      when 'Alpha';   self.emit(type, :alpha,  text, ts, te)
      when 'Any';     self.emit(type, :any,    text, ts, te)
      when 'Ascii';   self.emit(type, :ascii,  text, ts, te)
      when 'Blank';   self.emit(type, :blank,  text, ts, te)
      when 'Cntrl';   self.emit(type, :cntrl,  text, ts, te)
      when 'Digit';   self.emit(type, :digit,  text, ts, te)
      when 'Graph';   self.emit(type, :graph,  text, ts, te)
      when 'Lower';   self.emit(type, :lower,  text, ts, te)
      when 'Print';   self.emit(type, :print,  text, ts, te)
      when 'Punct';   self.emit(type, :punct,  text, ts, te)
      when 'Space';   self.emit(type, :space,  text, ts, te)
      when 'Upper';   self.emit(type, :upper,  text, ts, te)
      when 'Word';    self.emit(type, :word,   text, ts, te)
      when 'Xdigit';  self.emit(type, :xdigit, text, ts, te)
      end
    };

    any > (escaped_alpha, 1)  {
      self.emit(:escape_sequence, :literal, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };
  *|;

  # Main scanner
  main := |*

    # Anchors
    alternation {
      self.emit(:meta, :alternation, data[ts..te-1].pack('c*'), ts, te)
    };

    # Character types
    #   .         any (except new line)
    #   \d, \D    digit, non-digit
    #   \h, \H    hex, non-hex
    #   \s, \S    whitespace, non-whitespace
    #   \w, \W    word, non-word
    wild {
      self.emit(:character_type, :any, data[ts..te-1].pack('c*'), ts, te)
    };

    backslash . char_type > (backslashed, 2) {
      case text = data[ts..te-1].pack('c*')
      when '\\d'; self.emit(:character_type, :digit,      text, ts, te)
      when '\\D'; self.emit(:character_type, :non_digit,  text, ts, te)
      when '\\h'; self.emit(:character_type, :hex,        text, ts, te)
      when '\\H'; self.emit(:character_type, :non_hex,    text, ts, te)
      when '\\s'; self.emit(:character_type, :space,      text, ts, te)
      when '\\S'; self.emit(:character_type, :non_space,  text, ts, te)
      when '\\w'; self.emit(:character_type, :word,       text, ts, te)
      when '\\W'; self.emit(:character_type, :non_word,   text, ts, te)
      else raise "Unsupported character type at #{text} (char #{ts})"
      end
    };

    # Anchors
    beginning_of_line {
      self.emit(:anchor, :beginning_of_line, data[ts..te-1].pack('c*'), ts, te)
    };

    end_of_line {
      self.emit(:anchor, :end_of_line, data[ts..te-1].pack('c*'), ts, te)
    };

    backslash . anchor_char > (backslashed, 3) {
      case text = data[ts..te-1].pack('c*')
      when '\\A'; self.emit(:anchor, :bos,                text, ts, te)
      when '\\z'; self.emit(:anchor, :eos,                text, ts, te)
      when '\\Z'; self.emit(:anchor, :eos_or_before_eol,  text, ts, te)
      when '\\b'; self.emit(:anchor, :word_boundary,      text, ts, te)
      when '\\B'; self.emit(:anchor, :non_word_boundary,  text, ts, te)
      else raise "Unsupported anchor at #{text} (char #{ts})"
      end
    };

    # Escaped sequences
    backslash > (backslashed, 1) {
      fcall escape_sequence;
    };

    # Character sets
    set_open {
      self.emit(:character_set, :open, data[ts..te-1].pack('c*'), ts, te)
      fcall character_set;
    };

    # (?#...) comments: parsed as a single expression, without introducing a
    # new nesting level. Comments may not include parentheses, escaped or not.
    group_open . group_comment {
      self.emit(:group, :comment, data[ts..te-1].pack('c*'), ts, te)
    };

    # (?mix-mix...) expression options:
    #   (?imx-imx)          option on/off
    #                         i: ignore case
    #                         m: multi-line (dot(.) match newline)
    #                         x: extended form
    #
    #   (?imx-imx:subexp)   option on/off for subexp
    group_open . group_options > (grouped, 2) {
      self.emit(:group, :options, data[ts..te-1].pack('c*'), ts, te)
    };

    # Extended Groups
    #   (subexp)            captured group
    #   (?:subexp)          passive (non-captured) group
    #   (?>subexp)          atomic group, don't backtrack in subexp.
    #   (?=subexp)          look-ahead
    #   (?!subexp)          negative look-ahead
    #   (?<=subexp)         look-behind
    #   (?<!subexp)         negative look-behind
    #   (?<name>subexp)     named group (single quotes are no supported, yet)
    # ------------------------------------------------------------------------
    group_open . group_type? > (grouped, 1) {
      case text =  data[ts..te-1].pack('c*')
      when '(?:';  self.emit(:group, :passive,      text, ts, te)
      when '(?>';  self.emit(:group, :atomic,       text, ts, te)
      when '(?=';  self.emit(:group, :lookahead,    text, ts, te)
      when '(?!';  self.emit(:group, :nlookahead,   text, ts, te)
      when '(?<='; self.emit(:group, :lookbehind,   text, ts, te)
      when '(?<!'; self.emit(:group, :nlookbehind,  text, ts, te)
      when '(?<[a-zA-Z]\w+>'
        self.emit(:group, :named, text, ts, te)
      else
        self.emit(:group, :capture, text, ts, te)
      end
    };

    group_close {
      self.emit(:group, :close, data[ts..te-1].pack('c*'), ts, te)
    };

    # Quantifiers
    # ------------------------------------------------------------------------
    zero_or_one {
      self.emit(:quantifier, :zero_or_one, data[ts..te-1].pack('c*'), ts, te)
    };

    zero_or_more {
      self.emit(:quantifier, :zero_or_more, data[ts..te-1].pack('c*'), ts, te)
    };

    one_or_more {
      self.emit(:quantifier, :one_or_more, data[ts..te-1].pack('c*'), ts, te)
    };

    # Intervals: min, max, and exact notations
    # ------------------------------------------------------------------------
    range_open . (digit+)? . ','? . (digit+)? . range_close . quantifier_mode? {
      self.emit(:quantifier, :interval, data[ts..te-1].pack('c*'), ts, te)
    };

    # Literal: anything, except meta characters. This includes 2, 3, and 4
    # unicode byte sequences.
    # ------------------------------------------------------------------------
    (any - meta_char)+ {
      self.emit(:literal, :literal, data[ts..te-1].pack('c*'), ts, te)
    };

  *|;
}%%

%%{
  machine re_scanner;

  wild                  = '.';
  backslash             = '\\';
  alternation           = '|';
  beginning_of_line     = '^';
  end_of_line           = '$';

  range_open            = '{';
  range_close           = '}';
  curlies               = range_open | range_close;

  group_open            = '(';
  group_close           = ')';
  parantheses           = group_open | group_close;

  set_open              = '[';
  set_close             = ']';
  brackets              = set_open | set_close;

  posix_class_name      = 'alnum' | 'alpha' | 'blank' |
                          'cntrl' | 'digit' | 'graph' |
                          'lower' | 'print' | 'punct' |
                          'space' | 'upper' | 'xdigit' |
                          'word'  | 'ascii';

  property_name         = 'Alnum' | 'Alpha' | 'Any'   | 'Ascii' | 'Blank' |
                          'Cntrl' | 'Digit' | 'Graph' | 'Lower' | 'Print' |
                          'Punct' | 'Space' | 'Upper' | 'Word' | 'Xdigit';

  category_letter       = 'L' . [ultmo]?;
  category_mark         = 'M' . [nce]?;
  category_number       = 'N' . [dlo]?;
  category_punctuation  = 'P' . [cdseifo]?;
  category_symbol       = 'S' . [mcko]?;
  category_separator    = 'Z' . [slp]?;
  category_codepoint    = 'C' . [cfson]?;

  posix_class           = '[:' . posix_class_name . ':]';

  char_type             = [dDhHsSwW];

  line_anchor           = beginning_of_line | end_of_line;
  anchor_char           = [AbBzZG];
  property_char         = [pP];

  escaped_char          = [aefnrtv];
  octal_sequence        = '0' . [0-7]{2};

  hex_sequence          = 'x' . xdigit{2};
  wide_hex_sequence     = 'x' . '{7' . xdigit{1,7} . '}';
  control_sequence      = ('c' | 'C-') . xdigit{1,2};
  meta_sequence         = 'M-' . xdigit{1,2};
  meta_control_sequence = 'M-\\C-' . xdigit{1,2};

  zero_or_one           = '?' | '??' | '?+';
  zero_or_more          = '*' | '*?' | '*+';
  one_or_more           = '+' | '+?' | '++';

  quantifier_basic      = '?'  | '*'  | '+';
  quantifier_reluctant  = '??' | '*?' | '+?';
  quantifier_possessive = '?+' | '*+' | '++';
  quantifier_mode       = '?'  | '+';

  quantifier_range      = range_open . (digit+)? . ','? . (digit+)? .
                          range_close . quantifier_mode?;

  quantifiers           = quantifier_basic | quantifier_reluctant |
                          quantifier_possessive | quantifier_range;

  group_comment         = '?#' . [^)]+ . group_close;

  group_atomic          = '?>';
  group_passive         = '?:';
  group_lookahead       = '?=';
  group_nlookahead      = '?!';
  group_lookbehind      = '?<=';
  group_nlookbehind     = '?<!';

  group_options         = '?' . ([mix]{1,3})? . '-' . ([mix]{1,3})? . ':';

  group_name            = alpha . alnum+;
  group_named           = '?<' . group_name . '>';

  group_type            = group_atomic | group_passive |
                          group_lookahead | group_nlookahead |
                          group_lookbehind | group_nlookbehind |
                          group_named;

  # characters the 'break' a literal
  meta_char             = wild | backslash | alternation |
                          curlies | parantheses | brackets |
                          line_anchor | quantifier_basic;

  # character set scanner, continues consuming characters until it meets the
  # closing bracket of the set.
  character_set := |*
    ']' {
      self.emit(:character_set, :close, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    '^' {
      self.emit(:character_set, :negate, data[ts..te-1].pack('c*'), ts, te)
    };

    alnum . '-' . alnum {
      self.emit(:character_set, :range, data[ts..te-1].pack('c*'), ts, te)
    };

    '&&' {
      self.emit(:character_set, :intersection, data[ts..te-1].pack('c*'), ts, te)
    };

    '\\' . [\]\-\,b] {
      self.emit(:character_set, :member, data[ts..te-1].pack('c*'), ts, te)
    };

    posix_class {
      case text = data[ts..te-1].pack('c*')
      when '[:alnum:]';  self.emit(:character_set, :set_class_alnum,  text, ts, te)
      when '[:alpha:]';  self.emit(:character_set, :set_class_alpha,  text, ts, te)
      when '[:blank:]';  self.emit(:character_set, :set_class_blank,  text, ts, te)
      when '[:cntrl:]';  self.emit(:character_set, :set_class_cntrl,  text, ts, te)
      when '[:digit:]';  self.emit(:character_set, :set_class_digit,  text, ts, te)
      when '[:graph:]';  self.emit(:character_set, :set_class_graph,  text, ts, te)
      when '[:lower:]';  self.emit(:character_set, :set_class_lower,  text, ts, te)
      when '[:print:]';  self.emit(:character_set, :set_class_print,  text, ts, te)
      when '[:punct:]';  self.emit(:character_set, :set_class_punct,  text, ts, te)
      when '[:space:]';  self.emit(:character_set, :set_class_space,  text, ts, te)
      when '[:upper:]';  self.emit(:character_set, :set_class_upper,  text, ts, te)
      when '[:xdigit:]'; self.emit(:character_set, :set_class_xdigit, text, ts, te)
      when '[:word:]';   self.emit(:character_set, :set_class_word,   text, ts, te)
      when '[:ascii:]';  self.emit(:character_set, :set_class_ascii,  text, ts, te)
      else raise "Unsupported character posixe class at #{text} (char #{ts})"
      end
    };

    any {
      self.emit(:character_set, :member, data[ts..te-1].pack('c*'), ts, te)
    };
  *|;

  # escape sequence scanner
  escape_sequence := |*
    hex_sequence {
      self.emit(:escape_sequence, :hex, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    wide_hex_sequence {
      self.emit(:escape_sequence, :hex_wide, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    control_sequence {
      self.emit(:escape_sequence, :control, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    meta_sequence {
      self.emit(:escape_sequence, :meta, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    meta_control_sequence {
      self.emit(:escape_sequence, :meta_control, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };

    property_char . '{' . property_name . '}' > (escaped_alpha, 2) {
      text = data[ts..te-1].pack('c*')
      type = text[0,1] == 'p' ? :property: :inverted_property

      case name = data[ts+2..te-2].pack('c*')
      when 'Alnum';   self.emit(type, :alnum,  text, ts, te)
      when 'Alpha';   self.emit(type, :alpha,  text, ts, te)
      when 'Any';     self.emit(type, :any,    text, ts, te)
      when 'Ascii';   self.emit(type, :ascii,  text, ts, te)
      when 'Blank';   self.emit(type, :blank,  text, ts, te)
      when 'Cntrl';   self.emit(type, :cntrl,  text, ts, te)
      when 'Digit';   self.emit(type, :digit,  text, ts, te)
      when 'Graph';   self.emit(type, :graph,  text, ts, te)
      when 'Lower';   self.emit(type, :lower,  text, ts, te)
      when 'Print';   self.emit(type, :print,  text, ts, te)
      when 'Punct';   self.emit(type, :punct,  text, ts, te)
      when 'Space';   self.emit(type, :space,  text, ts, te)
      when 'Upper';   self.emit(type, :upper,  text, ts, te)
      when 'Word';    self.emit(type, :word,   text, ts, te)
      when 'Xdigit';  self.emit(type, :xdigit, text, ts, te)
      end
    };

    any > (escaped_alpha, 1)  {
      self.emit(:escape_sequence, :literal, data[ts..te-1].pack('c*'), ts, te)
      fret;
    };
  *|;

  # Main scanner
  main := |*

    # Anchors
    alternation {
      self.emit(:meta, :alternation, data[ts..te-1].pack('c*'), ts, te)
    };

    # Character types
    #   .         any (except new line)
    #   \d, \D    digit, non-digit
    #   \h, \H    hex, non-hex
    #   \s, \S    whitespace, non-whitespace
    #   \w, \W    word, non-word
    wild {
      self.emit(:character_type, :any, data[ts..te-1].pack('c*'), ts, te)
    };

    backslash . char_type > (backslashed, 2) {
      case text = data[ts..te-1].pack('c*')
      when '\\d'; self.emit(:character_type, :digit,      text, ts, te)
      when '\\D'; self.emit(:character_type, :non_digit,  text, ts, te)
      when '\\h'; self.emit(:character_type, :hex,        text, ts, te)
      when '\\H'; self.emit(:character_type, :non_hex,    text, ts, te)
      when '\\s'; self.emit(:character_type, :space,      text, ts, te)
      when '\\S'; self.emit(:character_type, :non_space,  text, ts, te)
      when '\\w'; self.emit(:character_type, :word,       text, ts, te)
      when '\\W'; self.emit(:character_type, :non_word,   text, ts, te)
      else raise "Unsupported character type at #{text} (char #{ts})"
      end
    };

    # Anchors
    beginning_of_line {
      self.emit(:anchor, :beginning_of_line, data[ts..te-1].pack('c*'), ts, te)
    };

    end_of_line {
      self.emit(:anchor, :end_of_line, data[ts..te-1].pack('c*'), ts, te)
    };

    backslash . anchor_char > (backslashed, 3) {
      case text = data[ts..te-1].pack('c*')
      when '\\A'; self.emit(:anchor, :bos,                text, ts, te)
      when '\\z'; self.emit(:anchor, :eos,                text, ts, te)
      when '\\Z'; self.emit(:anchor, :eos_or_before_eol,  text, ts, te)
      when '\\b'; self.emit(:anchor, :word_boundary,      text, ts, te)
      when '\\B'; self.emit(:anchor, :non_word_boundary,  text, ts, te)
      else raise "Unsupported anchor at #{text} (char #{ts})"
      end
    };

    # Escaped sequences
    backslash > (backslashed, 1) {
      fcall escape_sequence;
    };

    # Character sets
    set_open {
      self.emit(:character_set, :open, data[ts..te-1].pack('c*'), ts, te)
      fcall character_set;
    };

    # (?#...) comments: parsed as a single expression, without introducing a
    # new nesting level. Comments may not include parentheses, escaped or not.
    group_open . group_comment {
      self.emit(:group, :comment, data[ts..te-1].pack('c*'), ts, te)
    };

    # (?mix-mix...) expression options:
    #   (?imx-imx)          option on/off
    #                         i: ignore case
    #                         m: multi-line (dot(.) match newline)
    #                         x: extended form
    #
    #   (?imx-imx:subexp)   option on/off for subexp
    group_open . group_options > (grouped, 2) {
      self.emit(:group, :options, data[ts..te-1].pack('c*'), ts, te)
    };

    # Extended Groups
    #   (subexp)            captured group
    #   (?:subexp)          passive (non-captured) group
    #   (?>subexp)          atomic group, don't backtrack in subexp.
    #   (?=subexp)          look-ahead
    #   (?!subexp)          negative look-ahead
    #   (?<=subexp)         look-behind
    #   (?<!subexp)         negative look-behind
    #   (?<name>subexp)     named group (single quotes are no supported, yet)
    # ------------------------------------------------------------------------
    group_open . group_type? > (grouped, 1) {
      case text =  data[ts..te-1].pack('c*')
      when '(?:';  self.emit(:group, :passive,      text, ts, te)
      when '(?>';  self.emit(:group, :atomic,       text, ts, te)
      when '(?=';  self.emit(:group, :lookahead,    text, ts, te)
      when '(?!';  self.emit(:group, :nlookahead,   text, ts, te)
      when '(?<='; self.emit(:group, :lookbehind,   text, ts, te)
      when '(?<!'; self.emit(:group, :nlookbehind,  text, ts, te)
      when '(?<[a-zA-Z]\w+>'
        self.emit(:group, :named, text, ts, te)
      else
        self.emit(:group, :capture, text, ts, te)
      end
    };

    group_close {
      self.emit(:group, :close, data[ts..te-1].pack('c*'), ts, te)
    };

    # Quantifiers
    # ------------------------------------------------------------------------
    zero_or_one {
      self.emit(:quantifier, :zero_or_one, data[ts..te-1].pack('c*'), ts, te)
    };

    zero_or_more {
      self.emit(:quantifier, :zero_or_more, data[ts..te-1].pack('c*'), ts, te)
    };

    one_or_more {
      self.emit(:quantifier, :one_or_more, data[ts..te-1].pack('c*'), ts, te)
    };

    # Intervals: min, max, and exact notations
    # ------------------------------------------------------------------------
    range_open . (digit+)? . ','? . (digit+)? . range_close . quantifier_mode? {
      self.emit(:quantifier, :interval, data[ts..te-1].pack('c*'), ts, te)
    };

    # Literal: anything, except meta characters. This includes 2, 3, and 4
    # unicode byte sequences.
    # ------------------------------------------------------------------------
    (any - meta_char)+ {
      self.emit(:literal, :literal, data[ts..te-1].pack('c*'), ts, te)
    };

  *|;
}%%


Refactor class, id, style, and language patterns to make the machine much smaller and faster.

%%{

  machine redcloth_common;

  action A { MARK(); }
  action B { MARK_B(); }
  action ATTR { MARK_ATTR(); }
  action SET_ATTR { SET_ATTRIBUTES(); }
  action T { STORE("text"); }
  action X { CLEAR_REGS(); RESET_REG(); }
  action cat { CAT(block); }

  # simple
  LF = ( '\n' ) ;
  default = ^0 ;
  EOF = 0 ;

  # textile element attributes
  class = [a-z0-9\- _]+ ;
  id = [a-z0-9\-_]+ ;
  style_chars = [a-z0-9: ;\-_#&];
  style_url = "url(" [a-z0-9'".\\/#?=+@_\-]+ ")";
  style = style_chars (style_chars+ | style_url)* ; # maybe put in uri when in url()
  language = [a-z\-_]+ ;

  A_LEFT = "<" %{ ATTR_SET("align", "left"); } ;
  A_RIGHT = ">" %{ ATTR_SET("align", "right"); } ;
  A_JUSTIFIED = "<>" %{ ATTR_SET("align", "justify"); } ;
  A_CENTER = "=" %{ ATTR_SET("align", "center"); } ;
  A_PADLEFT = "(" when following_hash_is_ol_not_id %{ ATTR_INC("padding-left"); } ;
  A_PADRIGHT = ")" %{ ATTR_INC("padding-right"); } ;
  A_HLGN = ( A_LEFT | A_RIGHT | A_JUSTIFIED | A_CENTER | A_PADLEFT | A_PADRIGHT ) ;
  A_LIMIT = ( A_LEFT | A_CENTER | A_RIGHT ) ;
  A_VLGN = ( "-" %{ ATTR_SET("vertical-align", "middle"); } | "^" %{ ATTR_SET("vertical-align", "top"); } | "~" %{ ATTR_SET("vertical-align", "bottom"); } ) ;
  C_CLASS = class >ATTR %{ STORE_ATTR("class"); } ;
  C_ID = "#" id >ATTR %{ STORE_ATTR("id"); } ;
  C_CLASS_ID = "(" ( C_CLASS | C_ID | C_CLASS C_ID ) ")" ;
  C_LNGE = ( "[" language >ATTR %{ STORE_ATTR("lang"); } "]" ) ;
  C_STYL = ( "{" style >ATTR %{ STORE_ATTR("style"); } "}" ) ;
  S_CSPN = ( "\\" [0-9]+ >ATTR %{ STORE_ATTR("colspan"); } ) ;
  S_RSPN = ( "/" [0-9]+ >ATTR %{ STORE_ATTR("rowspan"); } ) ;
  D_HEADER = "_" %{ ATTR_SET("th", "true"); } ;
  A = ( ( A_HLGN | A_VLGN )* ) ;
  A2 = ( A_LIMIT? ) ;
  S = ( S_CSPN | S_RSPN )* ;
  C = ( C_CLASS_ID | C_STYL | C_LNGE )* ;
  D = ( D_HEADER ) ;
  N_CONT = "_" %{ ATTR_SET("list_continue", "true"); };
  N_NUM = digit+ >ATTR %{ STORE_ATTR("start"); };
  N = ( N_CONT | N_NUM )? ;
  PUNCT = ( "!" | '"' | "#" | "$" | "%" | "&" | "'" | "," | "-" | "." | "/" | ":" | ";" | "=" | "?" | "\\" | "^" | "`" | "|" | "~" | "[" | "]" | "(" | ")" | "<" ) ;
  dotspace = ("." " "*) ;
  indent =  [ \t]* ;

  # very un-DRY; Adrian says an action-stripping macro will come in a future Ragel version
  A_LEFT_noactions = "<" ;
  A_RIGHT_noactions = ">" ;
  A_JUSTIFIED_noactions = "<>" ;
  A_CENTER_noactions = "=" ;
  A_PADLEFT_noactions = "(" ;
  A_PADRIGHT_noactions = ")"  ;
  A_HLGN_noactions = ( A_LEFT_noactions | A_RIGHT_noactions | A_JUSTIFIED_noactions | A_CENTER_noactions | A_PADLEFT_noactions | A_PADRIGHT_noactions ) ;
  A_VLGN_noactions = ( "-" | "^" | "~" ) ;
  C_CLASS_noactions = ( "(" ( class )? ("#" id )? ")" ) ;
  C_LNGE_noactions = ( "[" language "]" ) ;
  C_STYL_noactions = ( "{" style "}" ) ;
  A_noactions = ( ( A_HLGN_noactions | A_VLGN_noactions )* ) ;
  C_noactions = ( C_CLASS_noactions | C_STYL_noactions | C_LNGE_noactions )* ;
  C_noquotes_noactions = C_noactions -- '"' ;
  N_CONT_noactions = "_" ;
  N_NUM_noactions = digit+ ;
  N_noactions = ( N_CONT_noactions | N_NUM_noactions )? ;

  # text blocks
  trailing = PUNCT - ("'" | '"') ;
  chars = (default - space)+ ;
  phrase = chars -- trailing ;

  # html tags (from Hpricot)
  NameChar = [\-A-Za-z0-9._:?] ;
  Name = [A-Za-z_:] NameChar* ;
  NameAttr = NameChar+ ;
  Q1Attr = [^']* ;
  Q2Attr = [^"]* ;
  UnqAttr = ( space | [^ \t\r\n<>"'] [^ \t\r\n<>]* ) ;
  Nmtoken = NameChar+ ;
  Attr =  NameAttr "="  ('"' Q2Attr '"' | "'" Q1Attr "'" ) space* ;
  AttrEnd = ( NameAttr space* "=" space* UnqAttr? | Nmtoken ) ;
  AttrSet = ( Attr | Nmtoken space+ ) ;

  script_tag_start = ( "<script" [^>]* ">" ) >X >A %T ;
  script_tag_end = ( "</script>" >A %T LF? ) >X ;

  code_tag_start = "<code" [^>]* ">" ;
  code_tag_end = "</code>" ;

  notextile = "<notextile>" >X (default+ -- "</notextile>") >A %T "</notextile>";

  # URI tokens (lifted from Mongrel)
  CTL = (cntrl | 127);
  safe = ("$" | "-" | "_" | ".");
  extra = ("!" | "*" | "'" | "(" | ")" | "," | "#");
  reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
  unsafe = (CTL | " " | "\"" | "%" | "<" | ">");
  national = any -- (alpha | digit | reserved | extra | safe | unsafe);
  unreserved = (alpha | digit | safe | extra | national);
  escape = ("%" xdigit xdigit);
  uchar = (unreserved | escape);
  pchar = (uchar | ":" | "@" | "&" | "=" | "+");
  scheme = ( alpha | digit | "+" | "-" | "." )+ ;
  absolute_uri = (scheme ":" (uchar | reserved )*);
  safepath = (pchar* (alpha | digit | safe) pchar*) ;
  path = (safepath ( "/" pchar* )*) ;
  query = ( uchar | reserved )* ;
  param = ( pchar | "/" )* ;
  params = (param ( ";" param )*) ;
  rel_path = (path (";" params)?) ("?" query)?;
  absolute_path = ("/"+ rel_path?);
  target = ("#" pchar*) ;
  uri = (target | absolute_uri | absolute_path | rel_path) ;

  # common
  title = ( '(' default+ >ATTR %{ STORE_ATTR("title"); } :> ')' ) ;
  word = ( alnum | safe | " " ) ;
  mspace = ( ( " " | "\t" | LF )+ ) -- LF{2} ;
  mtext = ( chars (mspace chars)* ) ;

}%%;

%%{

  machine redcloth_common;

  action A { MARK(); }
  action B { MARK_B(); }
  action ATTR { MARK_ATTR(); }
  action SET_ATTR { SET_ATTRIBUTES(); }
  action T { STORE("text"); }
  action X { CLEAR_REGS(); RESET_REG(); }
  action cat { CAT(block); }

  # simple
  LF = ( '\n' ) ;
  default = ^0 ;
  EOF = 0 ;

  # textile element attributes
  A_LEFT = "<" %{ ATTR_SET("align", "left"); } ;
  A_RIGHT = ">" %{ ATTR_SET("align", "right"); } ;
  A_JUSTIFIED = "<>" %{ ATTR_SET("align", "justify"); } ;
  A_CENTER = "=" %{ ATTR_SET("align", "center"); } ;
  A_PADLEFT = "(" when following_hash_is_ol_not_id %{ ATTR_INC("padding-left"); } ;
  A_PADRIGHT = ")" %{ ATTR_INC("padding-right"); } ;
  A_HLGN = ( A_LEFT | A_RIGHT | A_JUSTIFIED | A_CENTER | A_PADLEFT | A_PADRIGHT ) ;
  A_LIMIT = ( A_LEFT | A_CENTER | A_RIGHT ) ;
  A_VLGN = ( "-" %{ ATTR_SET("vertical-align", "middle"); } | "^" %{ ATTR_SET("vertical-align", "top"); } | "~" %{ ATTR_SET("vertical-align", "bottom"); } ) ;
  C_CLASS = [^()#]+ >ATTR %{ STORE_ATTR("class"); } ;
  C_ID = "#" [^) ]+ >ATTR %{ STORE_ATTR("id"); } ;
  C_CLASS_ID = "(" ( C_CLASS | C_ID | C_CLASS C_ID ) ")" ;
  C_LNGE = ( "[" [^\]]+ >ATTR %{ STORE_ATTR("lang"); } "]" ) ;
  C_STYL = ( "{" [^}]+ >ATTR %{ STORE_ATTR("style"); } "}" ) ;
  S_CSPN = ( "\\" [0-9]+ >ATTR %{ STORE_ATTR("colspan"); } ) ;
  S_RSPN = ( "/" [0-9]+ >ATTR %{ STORE_ATTR("rowspan"); } ) ;
  D_HEADER = "_" %{ ATTR_SET("th", "true"); } ;
  A = ( ( A_HLGN | A_VLGN )* ) ;
  A2 = ( A_LIMIT? ) ;
  S = ( S_CSPN | S_RSPN )* ;
  C = ( C_CLASS_ID | C_STYL | C_LNGE )* ;
  D = ( D_HEADER ) ;
  N_CONT = "_" %{ ATTR_SET("list_continue", "true"); };
  N_NUM = digit+ >ATTR %{ STORE_ATTR("start"); };
  N = ( N_CONT | N_NUM )? ;
  PUNCT = ( "!" | '"' | "#" | "$" | "%" | "&" | "'" | "," | "-" | "." | "/" | ":" | ";" | "=" | "?" | "\\" | "^" | "`" | "|" | "~" | "[" | "]" | "(" | ")" | "<" ) ;
  dotspace = ("." " "*) ;
  indent =  [ \t]* ;

  # very un-DRY; Adrian says an action-stripping macro will come in a future Ragel version
  A_LEFT_noactions = "<" ;
  A_RIGHT_noactions = ">" ;
  A_JUSTIFIED_noactions = "<>" ;
  A_CENTER_noactions = "=" ;
  A_PADLEFT_noactions = "(" ;
  A_PADRIGHT_noactions = ")"  ;
  A_HLGN_noactions = ( A_LEFT_noactions | A_RIGHT_noactions | A_JUSTIFIED_noactions | A_CENTER_noactions | A_PADLEFT_noactions | A_PADRIGHT_noactions ) ;
  A_VLGN_noactions = ( "-" | "^" | "~" ) ;
  C_CLASS_noactions = ( "(" ( [^)#]+ )? ("#" [^)]+ )? ")" ) ;
  C_LNGE_noactions = ( "[" [^\]]+ "]" ) ;
  C_STYL_noactions = ( "{" [^}]+ "}" ) ;
  A_noactions = ( ( A_HLGN_noactions | A_VLGN_noactions )* ) ;
  C_noactions = ( C_CLASS_noactions | C_STYL_noactions | C_LNGE_noactions )* ;
  C_noquotes_noactions = C_noactions -- '"' ;
  N_CONT_noactions = "_" ;
  N_NUM_noactions = digit+ ;
  N_noactions = ( N_CONT_noactions | N_NUM_noactions )? ;

  # text blocks
  trailing = PUNCT - ("'" | '"') ;
  chars = (default - space)+ ;
  phrase = chars -- trailing ;

  # html tags (from Hpricot)
  NameChar = [\-A-Za-z0-9._:?] ;
  Name = [A-Za-z_:] NameChar* ;
  NameAttr = NameChar+ ;
  Q1Attr = [^']* ;
  Q2Attr = [^"]* ;
  UnqAttr = ( space | [^ \t\r\n<>"'] [^ \t\r\n<>]* ) ;
  Nmtoken = NameChar+ ;
  Attr =  NameAttr "="  ('"' Q2Attr '"' | "'" Q1Attr "'" ) space* ;
  AttrEnd = ( NameAttr space* "=" space* UnqAttr? | Nmtoken ) ;
  AttrSet = ( Attr | Nmtoken space+ ) ;

  script_tag_start = ( "<script" [^>]* ">" ) >X >A %T ;
  script_tag_end = ( "</script>" >A %T LF? ) >X ;

  code_tag_start = "<code" [^>]* ">" ;
  code_tag_end = "</code>" ;

  notextile = "<notextile>" >X (default+ -- "</notextile>") >A %T "</notextile>";

  # URI tokens (lifted from Mongrel)
  CTL = (cntrl | 127);
  safe = ("$" | "-" | "_" | ".");
  extra = ("!" | "*" | "'" | "(" | ")" | "," | "#");
  reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
  unsafe = (CTL | " " | "\"" | "%" | "<" | ">");
  national = any -- (alpha | digit | reserved | extra | safe | unsafe);
  unreserved = (alpha | digit | safe | extra | national);
  escape = ("%" xdigit xdigit);
  uchar = (unreserved | escape);
  pchar = (uchar | ":" | "@" | "&" | "=" | "+");
  scheme = ( alpha | digit | "+" | "-" | "." )+ ;
  absolute_uri = (scheme ":" (uchar | reserved )*);
  safepath = (pchar* (alpha | digit | safe) pchar*) ;
  path = (safepath ( "/" pchar* )*) ;
  query = ( uchar | reserved )* ;
  param = ( pchar | "/" )* ;
  params = (param ( ";" param )*) ;
  rel_path = (path (";" params)?) ("?" query)?;
  absolute_path = ("/"+ rel_path?);
  target = ("#" pchar*) ;
  uri = (target | absolute_uri | absolute_path | rel_path) ;

  # common
  title = ( '(' default+ >ATTR %{ STORE_ATTR("title"); } :> ')' ) ;
  word = ( alnum | safe | " " ) ;
  mspace = ( ( " " | "\t" | LF )+ ) -- LF{2} ;
  mtext = ( chars (mspace chars)* ) ;

}%%;