SSA: Add missing VSFilter 2.39 tags to the parser.

Fixes some wrong font sizes in [BSS]_Darker_Than_Black_-_Gemini_of_the_Meteor_-_04_[8767FC3C].mkv.
These were caused by the parser skipping command blocks with unknown tags.
Maybe it just shouldn't do that?

/*
 * SubParsing.m.rl
 * Created by Alexander Strange on 7/25/07.
 *
 * This file is part of Perian.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#import "SubParsing.h"
#import "SubUtilities.h"
#import "SubContext.h"
#import "Codecprintf.h"

%%machine SSAfile;
%%write data;

@implementation SubRenderSpan
+(SubRenderSpan*)startingSpanForDiv:(SubRenderDiv*)div delegate:(SubRenderer*)delegate
{
	SubRenderSpan *span = [[SubRenderSpan alloc] init];
	span->offset = 0;
	span->ex = [delegate spanExtraFromRenderDiv:div];
	span->delegate = delegate;
	return [span autorelease];
}

-(SubRenderSpan*)cloneWithDelegate:(SubRenderer*)delegate_
{
	SubRenderSpan *span = [[SubRenderSpan alloc] init];
	span->offset = offset;
	span->ex = [delegate_ cloneSpanExtra:self];
	span->delegate = delegate_;
	return [span autorelease];
}

-(void)dealloc
{
	[delegate releaseSpanExtra:ex];
	[super dealloc];
}

-(void)finalize
{
	[delegate releaseSpanExtra:ex];
	[super finalize];
}

-(NSString*)description
{
	return [NSString stringWithFormat:@"Span at %d: %@", offset, [delegate describeSpanEx:ex]];
}
@end

@implementation SubRenderDiv
-(NSString*)description
{
	int i, sc = [spans count];
	NSMutableString *tmp = [NSMutableString stringWithFormat:@"div \"%@\" with %d spans:", text, sc];
	for (i = 0; i < sc; i++) {[tmp appendFormat:@" %d",((SubRenderSpan*)[spans objectAtIndex:i])->offset];}
	[tmp appendFormat:@" %d", [text length]];
	return tmp;
}

-(SubRenderDiv*)init
{
	if (self = [super init]) {
		text = nil;
		styleLine = nil;
		marginL = marginR = marginV = layer = 0;
		spans = nil;

		posX = posY = 0;
		alignH = kSubAlignmentMiddle; alignV = kSubAlignmentBottom;

		positioned = NO;
		render_complexity = 0;
	}

	return self;
}

-(SubRenderDiv*)nextDivWithDelegate:(SubRenderer*)delegate
{
	SubRenderDiv *div = [[[SubRenderDiv alloc] init] autorelease];

	div->text    = [[NSMutableString string] retain];
  div->styleLine = [styleLine retain];
	div->marginL = marginL;
	div->marginR = marginR;
	div->marginV = marginV;
	div->layer   = layer;

	div->spans   = [[NSMutableArray arrayWithObject:[[spans objectAtIndex:[spans count]-1] cloneWithDelegate:delegate]] retain];

	div->posX    = posX;
	div->posY    = posY;
	div->alignH  = alignH;
	div->alignV  = alignV;
  div->wrapStyle = wrapStyle;

	div->positioned = positioned;
	div->render_complexity = render_complexity;

	return div;
}

-(void)dealloc
{
	[text release];
	[styleLine release];
	[spans release];
	[super dealloc];
}
@end

extern BOOL IsScriptASS(NSDictionary *headers);

static NSArray *SplitByFormat(NSString *format, NSArray *lines)
{
	NSArray *formarray = STSplitStringIgnoringWhitespace(format,@",");
	int i, numlines = [lines count], numfields = [formarray count];
	NSMutableArray *ar = [NSMutableArray arrayWithCapacity:numlines];

	for (i = 0; i < numlines; i++) {
		NSString *s = [lines objectAtIndex:i];
		NSArray *splitline = STSplitStringWithCount(s, @",", numfields);

		if ([splitline count] != numfields) continue;
		[ar addObject:[NSDictionary dictionaryWithObjects:splitline
												  forKeys:formarray]];
	}

	return ar;
}

void SubParseSSAFile(NSString *ssastr, NSDictionary **headers, NSArray **styles, NSArray **subs)
{
	unsigned len = [ssastr length];
	NSData *ssaData;
	const unichar *ssa = STUnicodeForString(ssastr, &ssaData);
	NSMutableDictionary *hd = [NSMutableDictionary dictionary];
	NSMutableArray *stylearr = [NSMutableArray array], *eventarr = [NSMutableArray array], *cur_array=NULL;
	NSCharacterSet *wcs = [NSCharacterSet whitespaceCharacterSet];
	NSString *str=NULL, *styleformat=NULL, *eventformat=NULL;

	const unichar *p = ssa, *pe = ssa + len, *strbegin = p;
	int cs=0;

#define send() [[[NSString alloc] initWithCharactersNoCopy:(unichar*)strbegin length:p-strbegin freeWhenDone:NO] autorelease]

	%%{
		alphtype unsigned short;

		action sstart {strbegin = p;}
		action setheaderval {[hd setObject:send() forKey:str];}
		action savestr {str = send();}
		action csvlineend {[cur_array addObject:[send() stringByTrimmingCharactersInSet:wcs]];}
		action setupevents {
			cur_array=eventarr;
			eventformat = IsScriptASS(hd) ?
				@"Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text":
				@"Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text";
		}

		nl = ("\n" | "\r" | "\r\n");
		str = any*;
		ws = space | 0xa0;
		bom = 0xfeff;

		hline = ((";" str) | (([^;] str) >sstart %savestr :> (":" ws* str >sstart %setheaderval)?))? :> nl;

		header = "[Script Info]" nl hline*;

		format = "Format:" ws* %sstart str %savestr :> nl;

		action assformat {styleformat = @"Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding";}
		action ssaformat {styleformat = @"Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding";}

		stylename = ("[" [Vv] "4 Styles]") %ssaformat | ("[" [Vv] "4+ Styles]") %assformat;

		sline = (("Style:" ws* %sstart str %csvlineend) | str) :> nl;

		styles = stylename % {cur_array=stylearr;} nl :> (format %{styleformat=str;})? <: (sline*);

		event_txt = (("Dialogue:" ws* %sstart str %csvlineend %/csvlineend) | str);
		event = event_txt :> nl;

		lines = "[Events]" %setupevents nl :> (format %{eventformat=str;})? <: (event*);

		main := bom? header styles lines?;
	}%%

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

	[ssaData release];

	*headers = hd;
	if (styles) *styles = SplitByFormat(styleformat, stylearr);
	if (subs) *subs = SplitByFormat(eventformat, eventarr);
}

%%machine SSAtag;
%%write data;

static int compare_layer(const void *a, const void *b)
{
	const SubRenderDiv *divA = *(id*)a, *divB = *(id*)b;

	if (divA->layer < divB->layer) return -1;
	else if (divA->layer > divB->layer) return 1;
	return 0;
}

NSArray *SubParsePacket(NSString *packet, SubContext *context, SubRenderer *delegate)
{
	packet = STStandardizeStringNewlines(packet);
	NSArray *lines = (context->scriptType == kSubTypeSRT) ? [NSArray arrayWithObject:[packet substringToIndex:[packet length]-1]] : [packet componentsSeparatedByString:@"\n"];
	size_t line_count = [lines count];
	NSMutableArray *divs = [NSMutableArray arrayWithCapacity:line_count];
	int i;

	for (i = 0; i < line_count; i++) {
		NSString *inputText = [lines objectAtIndex:(context->collisions == kSubCollisionsReverse) ? (line_count - i - 1) : i];
		SubRenderDiv *div = [[[SubRenderDiv alloc] init] autorelease];

		div->text = [[NSMutableString string] retain];
		div->spans = [[NSMutableArray array] retain];

		if (context->scriptType == kSubTypeSRT) {
			div->styleLine = [context->defaultStyle retain];
			div->marginL = div->styleLine->marginL;
			div->marginR = div->styleLine->marginR;
			div->marginV = div->styleLine->marginV;
			div->layer = 0;
			div->wrapStyle = kSubLineWrapTopWider;
		} else {
			NSArray *fields = STSplitStringWithCount(inputText, @",", 9);
			if ([fields count] < 9) continue;
			div->layer = [[fields objectAtIndex:1] intValue];
			div->styleLine = [[context styleForName:[fields objectAtIndex:2]] retain];
			div->marginL = [[fields objectAtIndex:4] intValue];
			div->marginR = [[fields objectAtIndex:5] intValue];
			div->marginV = [[fields objectAtIndex:6] intValue];
			inputText = [fields objectAtIndex:8];
			if ([inputText length] == 0) continue;

			if (div->marginL == 0) div->marginL = div->styleLine->marginL;
			if (div->marginR == 0) div->marginR = div->styleLine->marginR;
			if (div->marginV == 0) div->marginV = div->styleLine->marginV;

			div->wrapStyle = context->wrapStyle;
		}

		div->alignH = div->styleLine->alignH;
		div->alignV = div->styleLine->alignV;

#undef send
#define send()  [[[NSString alloc] initWithCharactersNoCopy:(unichar*)outputbegin length:p-outputbegin freeWhenDone:NO] autorelease]
#define psend() [[[NSString alloc] initWithCharactersNoCopy:(unichar*)parambegin length:p-parambegin freeWhenDone:NO] autorelease]
#define tag(tagt, p) [delegate spanChangedTag:tag_##tagt span:current_span div:div param:&(p)]

		{
			size_t linelen = [inputText length];
			NSData *linebufData;
			const unichar *linebuf = STUnicodeForString(inputText, &linebufData);
			const unichar *p = linebuf, *pe = linebuf + linelen, *outputbegin = p, *parambegin=p, *last_tag_start=p;
			const unichar *pb = p;
			int cs = 0;
			SubRenderSpan *current_span = [SubRenderSpan startingSpanForDiv:div delegate:delegate];
			unsigned chars_deleted = 0; float floatnum = 0;
			NSString *strval=NULL;
			float curX, curY;
			int intnum = 0;
			BOOL reachedEnd = NO, setWrapStyle = NO, setPosition = NO, setAlignForDiv = NO, dropThisSpan = NO;

			%%{
				action bold {tag(b, intnum);}
				action italic {tag(i, intnum);}
				action underline {tag(u, intnum);}
				action strikeout {tag(s, intnum);}
				action outlinesize {tag(bord, floatnum);}
				action shadowdist {tag(shad, floatnum);}
				action bluredge {tag(be, intnum);}
				action fontname {tag(fn, strval);}
				action fontsize {tag(fs, floatnum);}
				action scalex {tag(fscx, floatnum);}
				action scaley {tag(fscy, floatnum);}
				action tracking {tag(fsp, floatnum);}
				action frz {tag(frz, floatnum);}
				action frx {tag(frx, floatnum);}
				action fry {tag(fry, floatnum);}
				action primaryc {tag(1c, intnum);}
				action secondaryc {tag(2c, intnum);}
				action outlinec {tag(3c, intnum);}
				action shadowc {tag(4c, intnum);}
				action alpha {tag(alpha, intnum);}
				action primarya {tag(1a, intnum);}
				action secondarya {tag(2a, intnum);}
				action outlinea {tag(3a, intnum);}
				action shadowa {tag(4a, intnum);}
				action stylerevert {tag(r, strval);}
				action drawingmode {tag(p, floatnum); dropThisSpan = floatnum > 0;}

				action paramset {parambegin=p;}
				action setintnum {intnum = [psend() intValue];}
				action sethexnum {intnum = strtoul([psend() UTF8String], NULL, 16);}
				action setfloatnum {floatnum = [psend() floatValue];}
				action setstringval {strval = psend();}
				action nullstring {strval = @"";}
				action setpos {curX=curY=0; sscanf([psend() UTF8String], "(%f,%f", &curX, &curY);}

				action ssaalign {
					if (!setAlignForDiv) {
						setAlignForDiv = YES;

						ParseASSAlignment(SSA2ASSAlignment(intnum), &div->alignH, &div->alignV);
					}
				}

				action align {
					if (!setAlignForDiv) {
						setAlignForDiv = YES;

						ParseASSAlignment(intnum, &div->alignH, &div->alignV);
					}
				}

				action wrapstyle {
					if (!setWrapStyle) {
						setWrapStyle = YES;

						div->wrapStyle = intnum;

					}
				}

				action position {
					if (!setPosition) {
						setPosition = YES;

						div->posX = curX;
						div->posY = curY;
						div->positioned = YES;
					}
				}

				action origin {
					div->shouldResetPens = YES;
				}

				intnum = ("-"? [0-9]+) >paramset %setintnum;
				flag = [01] >paramset %setintnum;
				floatn = ("-"? ([0-9]+ ("." [0-9]*)?) | ([0-9]* "." [0-9]+));
				floatnum = floatn >paramset %setfloatnum;
				string = (([^\\}]+) >paramset %setstringval | "" %nullstring );
				color = ("H"|"&"){,2} (xdigit+) >paramset %sethexnum "&"?;
				parens = "(" [^)]* ")";
				pos = ("(" floatn "," floatn ")") >paramset %setpos;
				move = ("(" (floatn ","){3,5} floatn ")") >paramset %setpos;

				cmd = "\\" (
							"b" intnum %bold
							|"i" flag %italic
							|"u" flag %underline
							|"s" flag %strikeout
							|"bord" floatnum %outlinesize
							|"shad" floatnum %shadowdist
							|"be" floatnum %bluredge
							|"blur" floatnum
							|"fax" floatnum
							|"fay" floatnum
							|"fn" string %fontname
							|"fs" floatnum %fontsize
							|"fscx" floatnum %scalex
							|"fscy" floatnum %scaley
							|"fsp" floatnum %tracking
							|("fr" "z"? floatnum %frz)
							|"frx" floatnum %frx
							|"fry" floatnum %fry
							|"fe" intnum
							|("1"? "c" color %primaryc)
							|"2c" color %secondaryc
							|"3c" color %outlinec
							|"4c" color %shadowc
							|"alpha" color %alpha
							|"1a" color %primarya
							|"2a" color %secondarya
							|"3a" color %outlinea
							|"4a" color %shadowa
							|"a" intnum %ssaalign
							|"an" intnum %align
							|([kK] [fo]? intnum)
							|"q" intnum %wrapstyle
							|"r" string %stylerevert
							|"pos" pos %position
							|"move" move %position
							|"t" parens
							|"org" parens %origin
							|("fad" "e"? parens)
							|"i"? "clip" parens
							|"p" floatnum %drawingmode
							|"pbo" floatnum
							|"xbord" floatnum
							|"ybord" floatnum
							|"xshad" floatnum
							|"yshad" floatnum
					   );

				tag = "{" (cmd* | any*) :> "}";

				action backslash_handler {
					p--;
					[div->text appendString:send()];
					unichar c = *(p+1), o=c;

					if (c) {
						switch (c) {
							case 'N': case 'n':
								o = '\n';
								break;
							case 'h':
								o = 0xA0; //non-breaking space
								break;
						}

						[div->text appendFormat:@"%C",o];
					}

					chars_deleted++;

					p++;
					outputbegin = p+1;
				}

				action enter_tag {
					if (dropThisSpan) chars_deleted += p - outputbegin;
					else if (p > outputbegin) [div->text appendString:send()];
					if (p == pe) reachedEnd = YES;

					if (p != pb) {
						[div->spans addObject:current_span];

						if (!reachedEnd) current_span = [current_span cloneWithDelegate:delegate];
					}

					last_tag_start = p;
				}

				action exit_tag {
					p++;
					chars_deleted += (p - last_tag_start);

					current_span->offset = (p - pb) - chars_deleted;
					outputbegin = p;

					p--;
				}

				special = ("\\" :> any) @backslash_handler | tag >enter_tag @exit_tag;
				sub_text_char = [^\\{];
				sub_text = sub_text_char+;

				main := ((sub_text | special)* "\\"?) %/enter_tag;
			}%%

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

			if (!reachedEnd) Codecprintf(NULL, "parse error: %s\n", [inputText UTF8String]);
			if (linebuf[linelen-1] == '\\') [div->text appendString:@"\\"];
			[linebufData release];
			[divs addObject:div];
		}

	}

	STSortMutableArrayStably(divs, compare_layer);
	return divs;
}

/*
 * SubParsing.m.rl
 * Created by Alexander Strange on 7/25/07.
 *
 * This file is part of Perian.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#import "SubParsing.h"
#import "SubUtilities.h"
#import "SubContext.h"
#import "Codecprintf.h"

%%machine SSAfile;
%%write data;

@implementation SubRenderSpan
+(SubRenderSpan*)startingSpanForDiv:(SubRenderDiv*)div delegate:(SubRenderer*)delegate
{
	SubRenderSpan *span = [[SubRenderSpan alloc] init];
	span->offset = 0;
	span->ex = [delegate spanExtraFromRenderDiv:div];
	span->delegate = delegate;
	return [span autorelease];
}

-(SubRenderSpan*)cloneWithDelegate:(SubRenderer*)delegate_
{
	SubRenderSpan *span = [[SubRenderSpan alloc] init];
	span->offset = offset;
	span->ex = [delegate_ cloneSpanExtra:self];
	span->delegate = delegate_;
	return [span autorelease];
}

-(void)dealloc
{
	[delegate releaseSpanExtra:ex];
	[super dealloc];
}

-(void)finalize
{
	[delegate releaseSpanExtra:ex];
	[super finalize];
}

-(NSString*)description
{
	return [NSString stringWithFormat:@"Span at %d: %@", offset, [delegate describeSpanEx:ex]];
}
@end

@implementation SubRenderDiv
-(NSString*)description
{
	int i, sc = [spans count];
	NSMutableString *tmp = [NSMutableString stringWithFormat:@"div \"%@\" with %d spans:", text, sc];
	for (i = 0; i < sc; i++) {[tmp appendFormat:@" %d",((SubRenderSpan*)[spans objectAtIndex:i])->offset];}
	[tmp appendFormat:@" %d", [text length]];
	return tmp;
}

-(SubRenderDiv*)init
{
	if (self = [super init]) {
		text = nil;
		styleLine = nil;
		marginL = marginR = marginV = layer = 0;
		spans = nil;

		posX = posY = 0;
		alignH = kSubAlignmentMiddle; alignV = kSubAlignmentBottom;

		positioned = NO;
		render_complexity = 0;
	}

	return self;
}

-(SubRenderDiv*)nextDivWithDelegate:(SubRenderer*)delegate
{
	SubRenderDiv *div = [[[SubRenderDiv alloc] init] autorelease];

	div->text    = [[NSMutableString string] retain];
  div->styleLine = [styleLine retain];
	div->marginL = marginL;
	div->marginR = marginR;
	div->marginV = marginV;
	div->layer   = layer;

	div->spans   = [[NSMutableArray arrayWithObject:[[spans objectAtIndex:[spans count]-1] cloneWithDelegate:delegate]] retain];

	div->posX    = posX;
	div->posY    = posY;
	div->alignH  = alignH;
	div->alignV  = alignV;
  div->wrapStyle = wrapStyle;

	div->positioned = positioned;
	div->render_complexity = render_complexity;

	return div;
}

-(void)dealloc
{
	[text release];
	[styleLine release];
	[spans release];
	[super dealloc];
}
@end

extern BOOL IsScriptASS(NSDictionary *headers);

static NSArray *SplitByFormat(NSString *format, NSArray *lines)
{
	NSArray *formarray = STSplitStringIgnoringWhitespace(format,@",");
	int i, numlines = [lines count], numfields = [formarray count];
	NSMutableArray *ar = [NSMutableArray arrayWithCapacity:numlines];

	for (i = 0; i < numlines; i++) {
		NSString *s = [lines objectAtIndex:i];
		NSArray *splitline = STSplitStringWithCount(s, @",", numfields);

		if ([splitline count] != numfields) continue;
		[ar addObject:[NSDictionary dictionaryWithObjects:splitline
												  forKeys:formarray]];
	}

	return ar;
}

void SubParseSSAFile(NSString *ssastr, NSDictionary **headers, NSArray **styles, NSArray **subs)
{
	unsigned len = [ssastr length];
	NSData *ssaData;
	const unichar *ssa = STUnicodeForString(ssastr, &ssaData);
	NSMutableDictionary *hd = [NSMutableDictionary dictionary];
	NSMutableArray *stylearr = [NSMutableArray array], *eventarr = [NSMutableArray array], *cur_array=NULL;
	NSCharacterSet *wcs = [NSCharacterSet whitespaceCharacterSet];
	NSString *str=NULL, *styleformat=NULL, *eventformat=NULL;

	const unichar *p = ssa, *pe = ssa + len, *strbegin = p;
	int cs=0;

#define send() [[[NSString alloc] initWithCharactersNoCopy:(unichar*)strbegin length:p-strbegin freeWhenDone:NO] autorelease]

	%%{
		alphtype unsigned short;

		action sstart {strbegin = p;}
		action setheaderval {[hd setObject:send() forKey:str];}
		action savestr {str = send();}
		action csvlineend {[cur_array addObject:[send() stringByTrimmingCharactersInSet:wcs]];}
		action setupevents {
			cur_array=eventarr;
			eventformat = IsScriptASS(hd) ?
				@"Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text":
				@"Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text";
		}

		nl = ("\n" | "\r" | "\r\n");
		str = any*;
		ws = space | 0xa0;
		bom = 0xfeff;

		hline = ((";" str) | (([^;] str) >sstart %savestr :> (":" ws* str >sstart %setheaderval)?))? :> nl;

		header = "[Script Info]" nl hline*;

		format = "Format:" ws* %sstart str %savestr :> nl;

		action assformat {styleformat = @"Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding";}
		action ssaformat {styleformat = @"Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding";}

		stylename = ("[" [Vv] "4 Styles]") %ssaformat | ("[" [Vv] "4+ Styles]") %assformat;

		sline = (("Style:" ws* %sstart str %csvlineend) | str) :> nl;

		styles = stylename % {cur_array=stylearr;} nl :> (format %{styleformat=str;})? <: (sline*);

		event_txt = (("Dialogue:" ws* %sstart str %csvlineend %/csvlineend) | str);
		event = event_txt :> nl;

		lines = "[Events]" %setupevents nl :> (format %{eventformat=str;})? <: (event*);

		main := bom? header styles lines?;
	}%%

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

	[ssaData release];

	*headers = hd;
	if (styles) *styles = SplitByFormat(styleformat, stylearr);
	if (subs) *subs = SplitByFormat(eventformat, eventarr);
}

%%machine SSAtag;
%%write data;

static int compare_layer(const void *a, const void *b)
{
	const SubRenderDiv *divA = *(id*)a, *divB = *(id*)b;

	if (divA->layer < divB->layer) return -1;
	else if (divA->layer > divB->layer) return 1;
	return 0;
}

NSArray *SubParsePacket(NSString *packet, SubContext *context, SubRenderer *delegate)
{
	packet = STStandardizeStringNewlines(packet);
	NSArray *lines = (context->scriptType == kSubTypeSRT) ? [NSArray arrayWithObject:[packet substringToIndex:[packet length]-1]] : [packet componentsSeparatedByString:@"\n"];
	size_t line_count = [lines count];
	NSMutableArray *divs = [NSMutableArray arrayWithCapacity:line_count];
	int i;

	for (i = 0; i < line_count; i++) {
		NSString *inputText = [lines objectAtIndex:(context->collisions == kSubCollisionsReverse) ? (line_count - i - 1) : i];
		SubRenderDiv *div = [[[SubRenderDiv alloc] init] autorelease];

		div->text = [[NSMutableString string] retain];
		div->spans = [[NSMutableArray array] retain];

		if (context->scriptType == kSubTypeSRT) {
			div->styleLine = [context->defaultStyle retain];
			div->marginL = div->styleLine->marginL;
			div->marginR = div->styleLine->marginR;
			div->marginV = div->styleLine->marginV;
			div->layer = 0;
			div->wrapStyle = kSubLineWrapTopWider;
		} else {
			NSArray *fields = STSplitStringWithCount(inputText, @",", 9);
			if ([fields count] < 9) continue;
			div->layer = [[fields objectAtIndex:1] intValue];
			div->styleLine = [[context styleForName:[fields objectAtIndex:2]] retain];
			div->marginL = [[fields objectAtIndex:4] intValue];
			div->marginR = [[fields objectAtIndex:5] intValue];
			div->marginV = [[fields objectAtIndex:6] intValue];
			inputText = [fields objectAtIndex:8];
			if ([inputText length] == 0) continue;

			if (div->marginL == 0) div->marginL = div->styleLine->marginL;
			if (div->marginR == 0) div->marginR = div->styleLine->marginR;
			if (div->marginV == 0) div->marginV = div->styleLine->marginV;

			div->wrapStyle = context->wrapStyle;
		}

		div->alignH = div->styleLine->alignH;
		div->alignV = div->styleLine->alignV;

#undef send
#define send()  [[[NSString alloc] initWithCharactersNoCopy:(unichar*)outputbegin length:p-outputbegin freeWhenDone:NO] autorelease]
#define psend() [[[NSString alloc] initWithCharactersNoCopy:(unichar*)parambegin length:p-parambegin freeWhenDone:NO] autorelease]
#define tag(tagt, p) [delegate spanChangedTag:tag_##tagt span:current_span div:div param:&(p)]

		{
			size_t linelen = [inputText length];
			NSData *linebufData;
			const unichar *linebuf = STUnicodeForString(inputText, &linebufData);
			const unichar *p = linebuf, *pe = linebuf + linelen, *outputbegin = p, *parambegin=p, *last_tag_start=p;
			const unichar *pb = p;
			int cs = 0;
			SubRenderSpan *current_span = [SubRenderSpan startingSpanForDiv:div delegate:delegate];
			unsigned chars_deleted = 0; float floatnum = 0;
			NSString *strval=NULL;
			float curX, curY;
			int intnum = 0;
			BOOL reachedEnd = NO, setWrapStyle = NO, setPosition = NO, setAlignForDiv = NO, dropThisSpan = NO;

			%%{
				action bold {tag(b, intnum);}
				action italic {tag(i, intnum);}
				action underline {tag(u, intnum);}
				action strikeout {tag(s, intnum);}
				action outlinesize {tag(bord, floatnum);}
				action shadowdist {tag(shad, floatnum);}
				action bluredge {tag(be, intnum);}
				action fontname {tag(fn, strval);}
				action fontsize {tag(fs, floatnum);}
				action scalex {tag(fscx, floatnum);}
				action scaley {tag(fscy, floatnum);}
				action tracking {tag(fsp, floatnum);}
				action frz {tag(frz, floatnum);}
				action frx {tag(frx, floatnum);}
				action fry {tag(fry, floatnum);}
				action primaryc {tag(1c, intnum);}
				action secondaryc {tag(2c, intnum);}
				action outlinec {tag(3c, intnum);}
				action shadowc {tag(4c, intnum);}
				action alpha {tag(alpha, intnum);}
				action primarya {tag(1a, intnum);}
				action secondarya {tag(2a, intnum);}
				action outlinea {tag(3a, intnum);}
				action shadowa {tag(4a, intnum);}
				action stylerevert {tag(r, strval);}
				action drawingmode {tag(p, floatnum); dropThisSpan = floatnum > 0;}

				action paramset {parambegin=p;}
				action setintnum {intnum = [psend() intValue];}
				action sethexnum {intnum = strtoul([psend() UTF8String], NULL, 16);}
				action setfloatnum {floatnum = [psend() floatValue];}
				action setstringval {strval = psend();}
				action nullstring {strval = @"";}
				action setpos {curX=curY=0; sscanf([psend() UTF8String], "(%f,%f", &curX, &curY);}

				action ssaalign {
					if (!setAlignForDiv) {
						setAlignForDiv = YES;

						ParseASSAlignment(SSA2ASSAlignment(intnum), &div->alignH, &div->alignV);
					}
				}

				action align {
					if (!setAlignForDiv) {
						setAlignForDiv = YES;

						ParseASSAlignment(intnum, &div->alignH, &div->alignV);
					}
				}

				action wrapstyle {
					if (!setWrapStyle) {
						setWrapStyle = YES;

						div->wrapStyle = intnum;

					}
				}

				action position {
					if (!setPosition) {
						setPosition = YES;

						div->posX = curX;
						div->posY = curY;
						div->positioned = YES;
					}
				}

				action origin {
					div->shouldResetPens = YES;
				}

				intnum = ("-"? [0-9]+) >paramset %setintnum;
				flag = [01] >paramset %setintnum;
				floatn = ("-"? ([0-9]+ ("." [0-9]*)?) | ([0-9]* "." [0-9]+));
				floatnum = floatn >paramset %setfloatnum;
				string = (([^\\}]+) >paramset %setstringval | "" %nullstring );
				color = ("H"|"&"){,2} (xdigit+) >paramset %sethexnum "&"?;
				parens = "(" [^)]* ")";
				pos = ("(" floatn "," floatn ")") >paramset %setpos;
				move = ("(" (floatn ","){3,5} floatn ")") >paramset %setpos;

				cmd = "\\" (
							"b" intnum %bold
							|"i" flag %italic
							|"u" flag %underline
							|"s" flag %strikeout
							|"bord" floatnum %outlinesize
							|"shad" floatnum %shadowdist
							|"be" flag %bluredge
							|"blur" floatnum
							|"fn" string %fontname
							|"fs" floatnum %fontsize
							|"fscx" floatnum %scalex
							|"fscy" floatnum %scaley
							|"fsp" floatnum %tracking
							|("fr" "z"? floatnum %frz)
							|"frx" floatnum %frx
							|"fry" floatnum %fry
							|"fe" intnum
							|("1"? "c" color %primaryc)
							|"2c" color %secondaryc
							|"3c" color %outlinec
							|"4c" color %shadowc
							|"alpha" color %alpha
							|"1a" color %primarya
							|"2a" color %secondarya
							|"3a" color %outlinea
							|"4a" color %shadowa
							|"a" intnum %ssaalign
							|"an" intnum %align
							|([kK] [fo]? intnum)
							|"q" intnum %wrapstyle
							|"r" string %stylerevert
							|"pos" pos %position
							|"move" move %position
							|"t" parens
							|"org" parens %origin
							|("fad" "e"? parens)
							|"clip" parens
							|"p" floatnum %drawingmode
							|"pbo" floatnum
					   );

				tag = "{" (cmd* | any*) :> "}";

				action backslash_handler {
					p--;
					[div->text appendString:send()];
					unichar c = *(p+1), o=c;

					if (c) {
						switch (c) {
							case 'N': case 'n':
								o = '\n';
								break;
							case 'h':
								o = 0xA0; //non-breaking space
								break;
						}

						[div->text appendFormat:@"%C",o];
					}

					chars_deleted++;

					p++;
					outputbegin = p+1;
				}

				action enter_tag {
					if (dropThisSpan) chars_deleted += p - outputbegin;
					else if (p > outputbegin) [div->text appendString:send()];
					if (p == pe) reachedEnd = YES;

					if (p != pb) {
						[div->spans addObject:current_span];

						if (!reachedEnd) current_span = [current_span cloneWithDelegate:delegate];
					}

					last_tag_start = p;
				}

				action exit_tag {
					p++;
					chars_deleted += (p - last_tag_start);

					current_span->offset = (p - pb) - chars_deleted;
					outputbegin = p;

					p--;
				}

				special = ("\\" :> any) @backslash_handler | tag >enter_tag @exit_tag;
				sub_text_char = [^\\{];
				sub_text = sub_text_char+;

				main := ((sub_text | special)* "\\"?) %/enter_tag;
			}%%

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

			if (!reachedEnd) Codecprintf(NULL, "parse error: %s\n", [inputText UTF8String]);
			if (linebuf[linelen-1] == '\\') [div->text appendString:@"\\"];
			[linebufData release];
			[divs addObject:div];
		}

	}

	STSortMutableArrayStably(divs, compare_layer);
	return divs;
}


fix ifNoneMatch typo in http parser

fix ifNoneMatch typo in http parser

// Copyright (c) 2009 - Decho Corp.

#include "mordor/common/pch.h"

#include "mordor/common/http/parser.h"

#include <locale>
#include <sstream>
#include <string>

#include "mordor/common/version.h"

// From uri.rl
std::string unescape(const std::string& str, bool spaceAsPlus = false);

static
HTTP::Version
parseVersion(const char *str)
{
    HTTP::Version ver;
    ver.major = atoi(str + 5);
    ver.minor = atoi(strchr(str + 5, '.') + 1);
    return ver;
}

static
HTTP::Method
parseMethod(const char *str, const char *end)
{
    for(size_t i = 0; i < 8; ++i) {
        if (strnicmp(str, HTTP::methods[i], end - str) == 0) {
            return (HTTP::Method)i;
        }
    }
    throw std::invalid_argument("Unrecognized method");
}

static boost::posix_time::time_input_facet rfc1123Facet_in("%a, %d %b %Y %H:%M:%S GMT",
        1 /* starting refcount, so this never gets deleted */);
static boost::posix_time::time_input_facet rfc850Facet_in("%A, %d-%b-%y %H:%M:%S GMT",
        1 /* starting refcount, so this never gets deleted */);
static boost::posix_time::time_input_facet ansiFacet_in("%a %b %e %H:%M:%S %Y",
        1 /* starting refcount, so this never gets deleted */);

static
boost::posix_time::ptime
parseHttpDate(const char *str, size_t size)
{
    boost::posix_time::ptime result;
    std::string val(str, size);

    #define ATTEMPT_WITH_FACET(facet)                              \
    {                                                              \
        std::istringstream is(val);                                \
        is.imbue(std::locale(is.getloc(), facet));                 \
        is >> result;                                              \
        if (!result.is_not_a_date_time())                          \
            return result;                                         \
    }

    ATTEMPT_WITH_FACET(&rfc1123Facet_in);
    ATTEMPT_WITH_FACET(&rfc850Facet_in);
    ATTEMPT_WITH_FACET(&ansiFacet_in);
    NOTREACHED();
}

static
std::string
unfold(char *p, char *pe)
{
    char *start = p;
    char *pw = p;

    while (p < pe) {
        // Skip leading whitespace
        if (pw == start) {
            if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
                ++p; ++pw; ++start;
                continue;
            }
        }
        // Only copy if necessary
        if (pw != p) {
            *pw = *p;
        }
        ++p; ++pw;
    }
    // Remove trailing whitespace
    do {
        --pw;
    } while ((*pw == ' ' || *pw == '\t' || *pw == '\r' || *pw == '\n') && pw >= start);
    ++pw;
    return std::string(start, pw - start);
}

std::string
HTTP::unquote(const char *str, size_t size)
{
    if (size == 0 || (str[0] != '"' && str[0] != '('))
        return std::string(str, size);
    ASSERT((str[size - 1] == '"' && str[0] == '"') ||
           (str[size - 1] == ')' && str[0] == '('));
    std::string result(str + 1, size - 2);
    char *p = const_cast<char *>(result.c_str());
    char *pe = p + result.size();
    char *pw = p;

    bool escaping = false;
    while (p < pe) {
        if (escaping) {
            escaping = false;
        } else if (*p == '\\') {
            escaping = true;
            ++p;
            continue;
        }
        // Only copy if necessary
        if (pw != p)
            *pw = *p;
        ++p; ++pw;
    }
    result.resize(pw - result.c_str());
    return result;
}

std::string
HTTP::unquote(const std::string &str)
{
    if (str.empty() || (str[0] != '"' && str[0] != '('))
        return str;
    return unquote(str.c_str(), str.size());
}

%%{
    machine http_parser;

    # See RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616.html

    action mark { mark = fpc; }
    action done { fbreak; }

    # basic character types
    OCTET = any;
    CHAR = ascii;
    UPALPHA = "A".."Z";
    LOALPHA = "a".."z";
    ALPHA = alpha;
    DIGIT = digit;
    CTL = cntrl | 127;
    CR = "\r";
    LF = "\n";
    SP = " ";
    HT = "\t";

    # almost-basic character types
    # note that we allow a single LF for a CR LF
    CRLF = CR LF;
    LWS = CRLF? ( SP | HT )+;
    TEXT = LWS | (OCTET -- CTL);
    HEX = xdigit;

    # some basic tokens
    separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT;
    token = (CHAR -- (separators | CTL))+;
    quoted_pair = "\\" CHAR;
    ctext = TEXT -- ("(" | ")");
    # TODO: truly nested comments
    base_comment = "(" ( ctext | quoted_pair )* ")";
    comment = "(" ( ctext | quoted_pair | base_comment )* ")";
    qdtext = TEXT -- "\"";
    quoted_string = "\"" ( qdtext | quoted_pair )* "\"";

    base64char = ALPHA | DIGIT | '+' | '/';
    base64 = (base64char{4})+ ( (base64char{3} '=') | (base64char{2} '==') )?;

    action parse_HTTP_Version {
        ASSERT(m_ver);
        *m_ver = parseVersion(mark);
        mark = NULL;
    }

    HTTP_Version = ("HTTP/" DIGIT+ "." DIGIT+) >mark %parse_HTTP_Version;

    action save_date {
        ASSERT(m_date);
        *m_date = parseHttpDate(mark, fpc - mark);
        mark = NULL;
    }

    wkday = "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun";
    weekday = "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday";
    month = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec";
    date1 = DIGIT{2} SP month SP DIGIT{4};
    date2 = DIGIT{2} "-" month "-" DIGIT{2};
    date3 = month SP ( DIGIT{2} | (SP DIGIT));
    time = DIGIT{2} ":" DIGIT{2} ":" DIGIT{2};
    rfc1123_date = wkday "," SP date1 SP time SP "GMT";
    rfc850_date = weekday "," SP date2 SP time SP "GMT";
    asctime_date = wkday SP date3 SP time SP DIGIT{4};
    HTTP_date = (rfc1123_date | rfc850_date | asctime_date) >mark %save_date;

    delta_seconds = DIGIT+;

    action save_product_name {
        m_product.product = std::string(mark, fpc - mark);
        mark = NULL;
    }
    action save_product_version {
        m_product.version = std::string(mark, fpc - mark);
        mark = NULL;
    }
    action save_product {
        ASSERT(m_productAndCommentList);
        m_productAndCommentList->push_back(m_product);
        m_product = Product();
    }
    action save_comment {
		ASSERT(m_productAndCommentList);
        m_productAndCommentList->push_back(HTTP::unquote(mark, fpc - mark));
        mark = NULL;
    }

    product_version = token;
    product = token >mark %save_product_name ("/" product_version >mark %save_product_version)?;
    product_or_comment = (product %save_product) | (comment >mark %save_comment);
    product_and_comment_list = LWS* product_or_comment ( LWS+ product_or_comment)* LWS*;

    qvalue = ('0' ('.' DIGIT{0,3})?) | ('1' ('.' '0'{0,3})?);

    subtag = ALPHA{1,8};
    primary_tag = ALPHA{1,8};
    language_tag = primary_tag ("-" subtag);

    action start_etag {
        ASSERT(m_eTag);
        m_eTag->weak = false;
    }
    action save_weak {
        ASSERT(m_eTag);
        m_eTag->unspecified = false;
        m_eTag->weak = true;
    }
    action save_etag {
        ASSERT(m_eTag);
        m_eTag->unspecified = false;
        m_eTag->value = unquote(mark, fpc - mark);
    }
    action set_etag_list {
        m_eTag = &m_tempETag;
    }
    action save_unspecified {
        ASSERT(m_eTagSet);
        m_eTagSet->insert(ETag());
    }
    action save_etag_element {
        ASSERT(m_eTagSet);
        ASSERT(m_eTag == & m_tempETag);
        m_eTagSet->insert(m_tempETag);
    }

    weak = "W/" %save_weak;
    opaque_tag = quoted_string >mark %save_etag;
    entity_tag = ((weak)? opaque_tag) >start_etag;

    etag_list = LWS* ("*" % save_unspecified | (LWS* entity_tag %save_etag_element ( LWS* ',' LWS* entity_tag %save_etag_element)* LWS*) LWS* ) > set_etag_list;

    bytes_unit = "bytes";
    other_range_unit = token;
    range_unit = bytes_unit | other_range_unit;

    action save_field_name {
        m_temp1 = std::string(mark, fpc - mark);
        mark = NULL;
    }
    action save_field_value {
        std::string fieldValue = unfold((char*)mark, (char*)fpc);

        StringMap::iterator it = m_entity->extension.find(m_temp1);
        if (it == m_entity->extension.end()) {
            m_entity->extension[m_temp1] = fieldValue;
        } else {
            it->second.append(", ");
            it->second.append(fieldValue);
        }
        mark = NULL;
    }

    field_chars = OCTET -- (CTL | CR LF SP HT);
    field_name = token >mark %save_field_name;
    field_value = TEXT* >mark %save_field_value;
    message_header = field_name ":" field_value;

    action save_string {
        *m_string = std::string(mark, fpc - mark);
        mark = NULL;
    }
    action save_ulong {
        *m_ulong = strtoull(mark, NULL, 10);
        mark = NULL;
    }

    action save_element {
        ASSERT(m_list || m_set);
        if (m_list)
            m_list->push_back(std::string(mark, fpc - mark));
        else
            m_set->insert(std::string(mark, fpc - mark));
        mark = NULL;
    }
    element = token >mark %save_element;
    list = (LWS* element ( LWS* ',' LWS* element)* LWS*);

    action save_parameterized_list_element {
        ValueWithParameters vp;
        vp.value = std::string(mark, fpc - mark);
        m_parameterizedList->push_back(vp);
        m_parameters = &m_parameterizedList->back().parameters;
        mark = NULL;
    }

    action save_parameter_attribute {
        m_temp1 = std::string(mark, fpc - mark);
        // Don't NULL out here; could be base64 later
    }

    action save_parameter_value {
        (*m_parameters)[m_temp1] = unquote(mark, fpc - mark);
        mark = NULL;
    }

    attribute = (token - 'q'i) >mark %save_parameter_attribute; # q separates params from accept-params
    value = (token | quoted_string) >mark %save_parameter_value;
    parameter = attribute '=' value;
    parameterizedListElement = token >mark %save_parameterized_list_element (';' parameter)*;
    parameterizedList = LWS* parameterizedListElement ( LWS* ',' LWS* parameterizedListElement)* LWS*;

    action save_auth_scheme {
        if (m_challengeList && ((!m_challengeList->empty() && m_auth == &m_challengeList->back())
            || m_challengeList->empty())) {
            AuthParams ap;
            m_challengeList->push_back(ap);
            m_auth = &m_challengeList->back();
        }
        m_auth->scheme = std::string(mark, fpc - mark);
        m_parameters = &m_auth->parameters;
        mark = NULL;
    }

    action save_base64_param {
        m_auth->base64 = std::string(mark, fpc - mark);
        mark = NULL;
    }

    auth_param = attribute '=' value;
    auth_scheme = token;
    base64_auth_param = base64 %save_base64_param;
    challenge = auth_scheme >mark %save_auth_scheme (SP ((auth_param ( LWS* ',' LWS* auth_param )*) | base64_auth_param))? LWS*;
    credentials = auth_scheme >mark %save_auth_scheme (LWS+ ((auth_param (LWS* ',' LWS* auth_param )*) | base64_auth_param))? LWS*;
    challengeList = LWS* challenge ( LWS* ',' LWS* challenge)* LWS*;

    action set_connection {
        m_set = &m_general->connection;
        m_list = NULL;
    }

    action set_date {
        m_date = &m_general->date;
    }

    action set_proxy_connection {
        m_set = &m_general->proxyConnection;
        m_list = NULL;
    }

    action set_trailer {
        m_set = &m_general->trailer;
        m_list = NULL;
    }

    action set_transfer_encoding {
        m_parameterizedList = &m_general->transferEncoding;
    }

    action save_upgrade_product {
        m_general->upgrade.push_back(m_product);
        m_product = Product();
    }

    Connection = 'Connection:'i @set_connection list;
    Date = 'Date:'i @set_date LWS* HTTP_date LWS*;
    # NON-STANDARD!!!
    Proxy_Connection = 'Proxy-Connection:'i @set_proxy_connection list;
    Trailer = 'Trailer:'i @set_trailer list;
    Transfer_Encoding = 'Transfer-Encoding:'i @set_transfer_encoding parameterizedList;
    Upgrade = 'Upgrade:'i LWS* product %save_upgrade_product ( LWS* ',' LWS* product %save_upgrade_product)* LWS*;

    general_header = Connection | Date | Proxy_Connection | Trailer | Transfer_Encoding | Upgrade;
    general_header_names = 'Connection'i | 'Date'i | 'Proxy-Connection'i | 'Trailer'i | 'Transfer-Encoding'i | 'Upgrade'i;

    action set_content_encoding {
        m_list = &m_entity->contentEncoding;
        m_set = NULL;
    }

    action set_content_length {
        m_ulong = &m_entity->contentLength;
    }

    action save_cr_first_byte_pos {
        m_entity->contentRange.first = strtoull(mark, NULL, 10);
        mark = NULL;
    }

    action save_cr_last_byte_pos {
        m_entity->contentRange.last = strtoull(mark, NULL, 10);
        mark = NULL;
    }

    action save_blank_cr {
        m_entity->contentRange.last = 0;
    }

    action save_instance_length {
        m_entity->contentRange.instance = strtoull(mark, NULL, 10);
        mark = NULL;
    }

    action set_content_type
    {
        m_parameters = &m_entity->contentType.parameters;
    }
    action save_type
    {
        m_entity->contentType.type = std::string(mark, fpc - mark);
        mark = NULL;
    }
    action save_subtype
    {
        m_entity->contentType.subtype = std::string(mark, fpc - mark);
        mark = NULL;
    }

    action set_expires
    {
        m_date = &m_entity->expires;
    }
    action set_last_modified
    {
        m_date = &m_entity->lastModified;
    }

    Content_Encoding = 'Content-Encoding:'i @set_content_encoding list;
    Content_Length = 'Content-Length:'i @set_content_length LWS* DIGIT+ >mark %save_ulong LWS*;

    byte_range_resp_spec = (DIGIT+ >mark %save_cr_first_byte_pos '-' DIGIT+ >mark %save_cr_last_byte_pos) | '*' %save_blank_cr;
    content_range_spec = bytes_unit SP byte_range_resp_spec '/' ( DIGIT+ >mark %save_instance_length | '*');
    Content_Range = 'Content-Range:'i LWS* content_range_spec LWS*;

    type = token >mark %save_type;
    subtype = token >mark %save_subtype;
    media_type = type '/' subtype (';' LWS* parameter)*;
    Content_Type = 'Content-Type:'i @set_content_type LWS* media_type LWS*;

    Expires = 'Expires:'i @set_expires LWS* HTTP_date LWS*;
    Last_Modified = 'Last-Modified:'i @set_last_modified LWS* HTTP_date LWS*;

    entity_header = Content_Encoding | Content_Length | Content_Range | Content_Type | Expires | Last_Modified; # | extension_header;
    entity_header_names = 'Content-Encoding'i | 'Content-Length'i | 'Content-Range'i | 'Content-Type'i | 'Expires'i | 'Last-Modified'i;

}%%

%%{
    machine http_request_parser;
    include http_parser;
    include uri_parser "../uri.rl";

    action parse_Method {
        m_request->requestLine.method = parseMethod(mark, fpc);
        mark = NULL;
    }

    action set_request_uri {
        m_uri = &m_request->requestLine.uri;
    }

    action set_authorization {
        m_challengeList = NULL;
        m_auth = &m_request->request.authorization;
    }

    action save_expectation {
        KeyValueWithParameters kvp;
        kvp.key = std::string(mark, fpc - mark);
        m_request->request.expect.push_back(kvp);
        mark = NULL;
    }
    action save_expectation_value {
        m_request->request.expect.back().value = unquote(mark, fpc - mark);
        mark = NULL;
    }
    action save_expectation_param {
        m_temp1 = std::string(mark, fpc - mark);
        m_request->request.expect.back().parameters[m_temp1] = "";
        mark = NULL;
    }
    action save_expectation_param_value {
        m_request->request.expect.back().parameters[m_temp1] = unquote(mark, fpc - mark);
        mark = NULL;
    }

    action set_host {
        m_string = &m_request->request.host;
    }

    action set_if_match {
        m_eTagSet = &m_request->request.ifMatch;
    }

    action set_if_modified_since {
        m_date = &m_request->request.ifModifiedSince;
    }

    action set_if_none_match {
        m_eTagSet = &m_request->request.ifNoneMatch;
    }

    action set_if_range_entity_tag {
        m_request->request.ifRange = ETag();
        m_eTag = boost::get<ETag>(&m_request->request.ifRange);
    }

    action set_if_range_http_date {
        m_request->request.ifRange = boost::posix_time::ptime();
        m_date = boost::get<boost::posix_time::ptime>(&m_request->request.ifRange);
    }

    action set_if_unmodified_since {
        m_date = &m_request->request.ifModifiedSince;
    }

    action set_proxy_authorization {
        m_challengeList = NULL;
        m_auth = &m_request->request.proxyAuthorization;
    }

    action save_first_byte_pos {
        m_request->request.range.push_back(RangeSet::value_type(
            strtoull(mark, NULL, 10), ~0ull));
        mark = NULL;
    }
    action save_last_byte_pos {
        if (mark != NULL) {
            m_request->request.range.back().second = strtoull(mark, NULL, 10);
        }
        mark = NULL;
    }
    action save_suffix_byte_pos {
        m_request->request.range.push_back(RangeSet::value_type(
            ~0ull, strtoull(mark, NULL, 10)));
        mark = NULL;
    }

    action set_referer {
        m_uri = &m_request->request.referer;
    }

    action save_accept_list_element {
        AcceptValueWithParameters avp;
        avp.value = std::string(mark, fpc - mark);
        m_acceptList->push_back(avp);
        m_parameters = &m_acceptList->back().parameters;
        mark = NULL;
    }

    action save_qvalue {
        m_acceptList->back().qvalue = 0;
        size_t i = 0;
        unsigned int curPlace = 1000;
        for (; i < 5 && mark < fpc; ++i, ++mark) {
            if (i == 1)
                continue;
            unsigned int cur = *mark - '0';
            m_acceptList->back().qvalue += cur * curPlace;
            curPlace /= 10;
        }
        mark = NULL;
    }

    action save_accept_attribute {
        m_temp1 = std::string(mark, fpc - mark);
        m_acceptList->back().acceptParams[m_temp1] = "";
        mark = NULL;
    }

    action save_accept_value {
        m_acceptList->back().acceptParams[m_temp1] = unquote(mark, fpc - mark);
        mark = NULL;
    }

    action set_te {
        m_acceptList = &m_request->request.te;
    }

    action set_user_agent {
        m_productAndCommentList = &m_request->request.userAgent;
    }

    Authorization = 'Authorization:'i @set_authorization LWS* credentials;

    expect_params = ';' token >mark %save_expectation_param ( '=' (token | quoted_string) >mark %save_expectation_param_value )?;
    expectation = token >mark %save_expectation ( '=' (token | quoted_string) >mark %save_expectation_value expect_params* )?;
    Expect = 'Expect:'i LWS* expectation ( LWS* ',' LWS* expectation )* LWS*;

    Host = 'Host:'i @set_host LWS* (host (':' port)?) >mark %save_string LWS*;

    If_Match = 'If-Match:'i @set_if_match etag_list;
    If_Modified_Since = 'If-Modified-Since:'i @set_if_modified_since LWS* HTTP_date LWS*;
    If_None_Match = 'If-None-Match:'i @set_if_none_match etag_list;
    If_Range = 'If-Range:'i LWS* (entity_tag >set_if_range_entity_tag | HTTP_date >set_if_range_http_date)  LWS*;
    If_Unmodified_Since = 'If-Unmodified-Since:'i @set_if_unmodified_since LWS* HTTP_date LWS*;

    Proxy_Authorization = 'Proxy-Authorization:'i @set_proxy_authorization credentials;

    byte_range_spec = DIGIT+ >mark %save_first_byte_pos '-' (DIGIT+ >mark %save_last_byte_pos)?;
    suffix_byte_range_spec = '-' DIGIT+ > mark %save_suffix_byte_pos;
    byte_range_set = LWS* (byte_range_spec | suffix_byte_range_spec) ( LWS* ',' LWS* (byte_range_spec | suffix_byte_range_spec))* LWS*;
    ranges_specifier = bytes_unit '=' byte_range_set;
    Range = 'Range:'i LWS* ranges_specifier;

    Referer = 'Referer:'i @set_referer LWS* (absolute_URI | relative_URI);

    accept_extension = ';' token >mark %save_accept_attribute ('=' (token | quoted_string) >mark %save_accept_value)?;
    accept_params = ';q='i qvalue >mark %save_qvalue (accept_extension)*;
    acceptListElement = token >mark %save_accept_list_element (';' parameter)* (accept_params)?;
    acceptList = LWS* acceptListElement ( LWS* ',' LWS* acceptListElement)* LWS*;
    TE = 'TE:'i @set_te acceptList;

    User_Agent = 'User-Agent:'i @set_user_agent product_and_comment_list;

    request_header = Authorization | Expect | Host | If_Match | If_Modified_Since | If_None_Match | If_Range | If_Unmodified_Since | Proxy_Authorization | Range | Referer | TE | User_Agent;
    request_header_names = 'Authorization'i | 'Expect'i | 'Host'i | 'If-Match'i | 'If-Modified-Since'i | 'If-None-Match'i | 'If-Range'i | 'If-Unmodified-Since'i | 'Proxy-Authorization'i | 'Range'i | 'Referer'i | 'TE'i | 'User-Agent'i;

    extension_header = (token - (general_header_names | request_header_names | entity_header_names)) >mark %save_field_name
        ':' field_value;

    Method = token >mark %parse_Method;
    # we explicitly add query to path_absolute, because the URI spec changed from RFC 2396 to RFC 3986
    # with the query not being part of hier_part
    Request_URI = ( "*" | absolute_URI | (path_absolute ( "?" query )?) | authority);
    Request_Line = Method SP Request_URI >set_request_uri SP HTTP_Version CRLF;
    Request = Request_Line ((general_header | request_header | entity_header | extension_header) CRLF)* CRLF @done;

    main := Request;
    write data;
}%%

void
HTTP::HTTPParser::init()
{
    m_string = NULL;
    m_set = NULL;
    m_list = NULL;
    m_parameterizedList = NULL;
    m_parameters = NULL;
    m_auth = NULL;
    m_challengeList = NULL;
    m_ulong = NULL;
    m_eTag = NULL;
    m_eTagSet = NULL;
    m_productAndCommentList = NULL;
    RagelParser::init();
}

HTTP::RequestParser::RequestParser(Request& request)
: m_request(&request),
  m_ver(&request.requestLine.ver),
  m_path(&request.requestLine.uri.path),
  m_general(&request.general),
  m_entity(&request.entity)
{}

void
HTTP::RequestParser::init()
{
    HTTPParser::init();
    %% write init;
}

bool
HTTP::RequestParser::final() const
{
    return cs >= http_request_parser_first_final;
}

bool
HTTP::RequestParser::error() const
{
    return cs == http_request_parser_error;
}

void
HTTP::RequestParser::exec()
{
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
    %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
}

%%{
    machine http_response_parser;
    include http_parser;
    include uri_parser "../uri.rl";

    action parse_Status_Code {
        m_response->status.status = (HTTP::Status)atoi(mark);
        mark = NULL;
    }

    action parse_Reason_Phrase {
        m_response->status.reason = std::string(mark, fpc - mark);
        mark = NULL;
    }

    action set_accept_ranges
    {
        m_set = &m_response->response.acceptRanges;
        m_list = NULL;
    }
    action set_etag
    {
        m_eTag = &m_response->response.eTag;
    }
    action set_proxy_authenticate {
        m_challengeList = &m_response->response.proxyAuthenticate;
    }
    action set_retry_after_http_date {
        m_response->response.retryAfter = boost::posix_time::ptime();
        m_date = boost::get<boost::posix_time::ptime>(&m_response->response.retryAfter);
    }
    action set_retry_after_delta_seconds {
        m_response->response.retryAfter = ~0ull;
        m_ulong = boost::get<unsigned long long>(&m_response->response.retryAfter);
    }
    action set_server {
        m_productAndCommentList = &m_response->response.server;
    }
    action set_www_authenticate {
        m_challengeList = &m_response->response.wwwAuthenticate;
    }

    Accept_Ranges = 'Accept-Ranges:'i @set_accept_ranges list;
    ETag = 'ETag:'i @set_etag LWS* entity_tag;
    # This *should* be absolute_URI, but we're generous
    Location = 'Location:'i LWS* URI_reference LWS*;
    Proxy_Authenticate = 'Proxy-Authenticate:'i @set_proxy_authenticate challengeList;
    Retry_After = 'Retry-After:'i LWS* (HTTP_date %set_retry_after_http_date | delta_seconds >mark %set_retry_after_delta_seconds %save_ulong) LWS*;
    Server = 'Server:'i @set_server product_and_comment_list;
    WWW_Authenticate = 'WWW-Authenticate:'i @set_www_authenticate challengeList;

    response_header = Accept_Ranges | ETag | Location | Proxy_Authenticate | Retry_After | Server | WWW_Authenticate;
    response_header_names = 'Accept-Ranges'i | 'ETag'i | 'Location'i | 'Proxy-Authenticate'i | 'Retry-After'i | 'Server'i | 'WWW-Authenticate'i;

    extension_header = (token - (general_header_names | response_header_names | entity_header_names)) >mark %save_field_name
        ':' field_value;

    Status_Code = DIGIT{3} > mark %parse_Status_Code;
    Reason_Phrase = (TEXT -- (CR | LF))* >mark %parse_Reason_Phrase;
    Status_Line = HTTP_Version SP Status_Code SP Reason_Phrase CRLF;
    Response = Status_Line ((general_header | response_header | entity_header | extension_header) CRLF)* CRLF @done;

    main := Response;

    write data;
}%%

HTTP::ResponseParser::ResponseParser(Response& response)
: m_response(&response),
  m_ver(&response.status.ver),
  m_uri(&response.response.location),
  m_path(&response.response.location.path),
  m_general(&response.general),
  m_entity(&response.entity)
{}

void
HTTP::ResponseParser::init()
{
    HTTPParser::init();
    %% write init;
}

bool
HTTP::ResponseParser::final() const
{
    return cs >= http_response_parser_first_final;
}

bool
HTTP::ResponseParser::error() const
{
    return cs == http_response_parser_error;
}

void
HTTP::ResponseParser::exec()
{
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
    %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
}

%%{
    machine http_trailer_parser;
    include http_parser;

    extension_header = (token - (entity_header_names)) >mark %save_field_name ':' field_value;

    trailer = ((entity_header | extension_header) CRLF)*;

    main := trailer CRLF @done;

    write data;
}%%

HTTP::TrailerParser::TrailerParser(EntityHeaders& entity)
: m_entity(&entity)
{}

void
HTTP::TrailerParser::init()
{
    HTTPParser::init();
    %% write init;
}

bool
HTTP::TrailerParser::final() const
{
    return cs >= http_trailer_parser_first_final;
}

bool
HTTP::TrailerParser::error() const
{
    return cs == http_trailer_parser_error;
}

void
HTTP::TrailerParser::exec()
{
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
    %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
}

%%{
    machine http_list_parser;
    include http_parser;

    main := list;

    write data;
}%%

HTTP::ListParser::ListParser(StringSet& stringSet)
: m_set(&stringSet),
  m_list(NULL)
{}

void
HTTP::ListParser::init()
{
    RagelParser::init();
    %% write init;
}

bool
HTTP::ListParser::final() const
{
    return cs >= http_list_parser_first_final;
}

bool
HTTP::ListParser::error() const
{
    return cs == http_list_parser_error;
}

void
HTTP::ListParser::exec()
{
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
    %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
}

// Copyright (c) 2009 - Decho Corp.

#include "mordor/common/pch.h"

#include "mordor/common/http/parser.h"

#include <locale>
#include <sstream>
#include <string>

#include "mordor/common/version.h"

// From uri.rl
std::string unescape(const std::string& str, bool spaceAsPlus = false);

static
HTTP::Version
parseVersion(const char *str)
{
    HTTP::Version ver;
    ver.major = atoi(str + 5);
    ver.minor = atoi(strchr(str + 5, '.') + 1);
    return ver;
}

static
HTTP::Method
parseMethod(const char *str, const char *end)
{
    for(size_t i = 0; i < 8; ++i) {
        if (strnicmp(str, HTTP::methods[i], end - str) == 0) {
            return (HTTP::Method)i;
        }
    }
    throw std::invalid_argument("Unrecognized method");
}

static boost::posix_time::time_input_facet rfc1123Facet_in("%a, %d %b %Y %H:%M:%S GMT",
        1 /* starting refcount, so this never gets deleted */);
static boost::posix_time::time_input_facet rfc850Facet_in("%A, %d-%b-%y %H:%M:%S GMT",
        1 /* starting refcount, so this never gets deleted */);
static boost::posix_time::time_input_facet ansiFacet_in("%a %b %e %H:%M:%S %Y",
        1 /* starting refcount, so this never gets deleted */);

static
boost::posix_time::ptime
parseHttpDate(const char *str, size_t size)
{
    boost::posix_time::ptime result;
    std::string val(str, size);

    #define ATTEMPT_WITH_FACET(facet)                              \
    {                                                              \
        std::istringstream is(val);                                \
        is.imbue(std::locale(is.getloc(), facet));                 \
        is >> result;                                              \
        if (!result.is_not_a_date_time())                          \
            return result;                                         \
    }

    ATTEMPT_WITH_FACET(&rfc1123Facet_in);
    ATTEMPT_WITH_FACET(&rfc850Facet_in);
    ATTEMPT_WITH_FACET(&ansiFacet_in);
    NOTREACHED();
}

static
std::string
unfold(char *p, char *pe)
{
    char *start = p;
    char *pw = p;

    while (p < pe) {
        // Skip leading whitespace
        if (pw == start) {
            if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
                ++p; ++pw; ++start;
                continue;
            }
        }
        // Only copy if necessary
        if (pw != p) {
            *pw = *p;
        }
        ++p; ++pw;
    }
    // Remove trailing whitespace
    do {
        --pw;
    } while ((*pw == ' ' || *pw == '\t' || *pw == '\r' || *pw == '\n') && pw >= start);
    ++pw;
    return std::string(start, pw - start);
}

std::string
HTTP::unquote(const char *str, size_t size)
{
    if (size == 0 || (str[0] != '"' && str[0] != '('))
        return std::string(str, size);
    ASSERT((str[size - 1] == '"' && str[0] == '"') ||
           (str[size - 1] == ')' && str[0] == '('));
    std::string result(str + 1, size - 2);
    char *p = const_cast<char *>(result.c_str());
    char *pe = p + result.size();
    char *pw = p;

    bool escaping = false;
    while (p < pe) {
        if (escaping) {
            escaping = false;
        } else if (*p == '\\') {
            escaping = true;
            ++p;
            continue;
        }
        // Only copy if necessary
        if (pw != p)
            *pw = *p;
        ++p; ++pw;
    }
    result.resize(pw - result.c_str());
    return result;
}

std::string
HTTP::unquote(const std::string &str)
{
    if (str.empty() || (str[0] != '"' && str[0] != '('))
        return str;
    return unquote(str.c_str(), str.size());
}

%%{
    machine http_parser;

    # See RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616.html

    action mark { mark = fpc; }
    action done { fbreak; }

    # basic character types
    OCTET = any;
    CHAR = ascii;
    UPALPHA = "A".."Z";
    LOALPHA = "a".."z";
    ALPHA = alpha;
    DIGIT = digit;
    CTL = cntrl | 127;
    CR = "\r";
    LF = "\n";
    SP = " ";
    HT = "\t";

    # almost-basic character types
    # note that we allow a single LF for a CR LF
    CRLF = CR LF;
    LWS = CRLF? ( SP | HT )+;
    TEXT = LWS | (OCTET -- CTL);
    HEX = xdigit;

    # some basic tokens
    separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT;
    token = (CHAR -- (separators | CTL))+;
    quoted_pair = "\\" CHAR;
    ctext = TEXT -- ("(" | ")");
    # TODO: truly nested comments
    base_comment = "(" ( ctext | quoted_pair )* ")";
    comment = "(" ( ctext | quoted_pair | base_comment )* ")";
    qdtext = TEXT -- "\"";
    quoted_string = "\"" ( qdtext | quoted_pair )* "\"";

    base64char = ALPHA | DIGIT | '+' | '/';
    base64 = (base64char{4})+ ( (base64char{3} '=') | (base64char{2} '==') )?;

    action parse_HTTP_Version {
        ASSERT(m_ver);
        *m_ver = parseVersion(mark);
        mark = NULL;
    }

    HTTP_Version = ("HTTP/" DIGIT+ "." DIGIT+) >mark %parse_HTTP_Version;

    action save_date {
        ASSERT(m_date);
        *m_date = parseHttpDate(mark, fpc - mark);
        mark = NULL;
    }

    wkday = "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun";
    weekday = "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday";
    month = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec";
    date1 = DIGIT{2} SP month SP DIGIT{4};
    date2 = DIGIT{2} "-" month "-" DIGIT{2};
    date3 = month SP ( DIGIT{2} | (SP DIGIT));
    time = DIGIT{2} ":" DIGIT{2} ":" DIGIT{2};
    rfc1123_date = wkday "," SP date1 SP time SP "GMT";
    rfc850_date = weekday "," SP date2 SP time SP "GMT";
    asctime_date = wkday SP date3 SP time SP DIGIT{4};
    HTTP_date = (rfc1123_date | rfc850_date | asctime_date) >mark %save_date;

    delta_seconds = DIGIT+;

    action save_product_name {
        m_product.product = std::string(mark, fpc - mark);
        mark = NULL;
    }
    action save_product_version {
        m_product.version = std::string(mark, fpc - mark);
        mark = NULL;
    }
    action save_product {
        ASSERT(m_productAndCommentList);
        m_productAndCommentList->push_back(m_product);
        m_product = Product();
    }
    action save_comment {
		ASSERT(m_productAndCommentList);
        m_productAndCommentList->push_back(HTTP::unquote(mark, fpc - mark));
        mark = NULL;
    }

    product_version = token;
    product = token >mark %save_product_name ("/" product_version >mark %save_product_version)?;
    product_or_comment = (product %save_product) | (comment >mark %save_comment);
    product_and_comment_list = LWS* product_or_comment ( LWS+ product_or_comment)* LWS*;

    qvalue = ('0' ('.' DIGIT{0,3})?) | ('1' ('.' '0'{0,3})?);

    subtag = ALPHA{1,8};
    primary_tag = ALPHA{1,8};
    language_tag = primary_tag ("-" subtag);

    action start_etag {
        ASSERT(m_eTag);
        m_eTag->weak = false;
    }
    action save_weak {
        ASSERT(m_eTag);
        m_eTag->unspecified = false;
        m_eTag->weak = true;
    }
    action save_etag {
        ASSERT(m_eTag);
        m_eTag->unspecified = false;
        m_eTag->value = unquote(mark, fpc - mark);
    }
    action set_etag_list {
        m_eTag = &m_tempETag;
    }
    action save_unspecified {
        ASSERT(m_eTagSet);
        m_eTagSet->insert(ETag());
    }
    action save_etag_element {
        ASSERT(m_eTagSet);
        ASSERT(m_eTag == & m_tempETag);
        m_eTagSet->insert(m_tempETag);
    }

    weak = "W/" %save_weak;
    opaque_tag = quoted_string >mark %save_etag;
    entity_tag = ((weak)? opaque_tag) >start_etag;

    etag_list = LWS* ("*" % save_unspecified | (LWS* entity_tag %save_etag_element ( LWS* ',' LWS* entity_tag %save_etag_element)* LWS*) LWS* ) > set_etag_list;

    bytes_unit = "bytes";
    other_range_unit = token;
    range_unit = bytes_unit | other_range_unit;

    action save_field_name {
        m_temp1 = std::string(mark, fpc - mark);
        mark = NULL;
    }
    action save_field_value {
        std::string fieldValue = unfold((char*)mark, (char*)fpc);

        StringMap::iterator it = m_entity->extension.find(m_temp1);
        if (it == m_entity->extension.end()) {
            m_entity->extension[m_temp1] = fieldValue;
        } else {
            it->second.append(", ");
            it->second.append(fieldValue);
        }
        mark = NULL;
    }

    field_chars = OCTET -- (CTL | CR LF SP HT);
    field_name = token >mark %save_field_name;
    field_value = TEXT* >mark %save_field_value;
    message_header = field_name ":" field_value;

    action save_string {
        *m_string = std::string(mark, fpc - mark);
        mark = NULL;
    }
    action save_ulong {
        *m_ulong = strtoull(mark, NULL, 10);
        mark = NULL;
    }

    action save_element {
        ASSERT(m_list || m_set);
        if (m_list)
            m_list->push_back(std::string(mark, fpc - mark));
        else
            m_set->insert(std::string(mark, fpc - mark));
        mark = NULL;
    }
    element = token >mark %save_element;
    list = (LWS* element ( LWS* ',' LWS* element)* LWS*);

    action save_parameterized_list_element {
        ValueWithParameters vp;
        vp.value = std::string(mark, fpc - mark);
        m_parameterizedList->push_back(vp);
        m_parameters = &m_parameterizedList->back().parameters;
        mark = NULL;
    }

    action save_parameter_attribute {
        m_temp1 = std::string(mark, fpc - mark);
        // Don't NULL out here; could be base64 later
    }

    action save_parameter_value {
        (*m_parameters)[m_temp1] = unquote(mark, fpc - mark);
        mark = NULL;
    }

    attribute = (token - 'q'i) >mark %save_parameter_attribute; # q separates params from accept-params
    value = (token | quoted_string) >mark %save_parameter_value;
    parameter = attribute '=' value;
    parameterizedListElement = token >mark %save_parameterized_list_element (';' parameter)*;
    parameterizedList = LWS* parameterizedListElement ( LWS* ',' LWS* parameterizedListElement)* LWS*;

    action save_auth_scheme {
        if (m_challengeList && ((!m_challengeList->empty() && m_auth == &m_challengeList->back())
            || m_challengeList->empty())) {
            AuthParams ap;
            m_challengeList->push_back(ap);
            m_auth = &m_challengeList->back();
        }
        m_auth->scheme = std::string(mark, fpc - mark);
        m_parameters = &m_auth->parameters;
        mark = NULL;
    }

    action save_base64_param {
        m_auth->base64 = std::string(mark, fpc - mark);
        mark = NULL;
    }

    auth_param = attribute '=' value;
    auth_scheme = token;
    base64_auth_param = base64 %save_base64_param;
    challenge = auth_scheme >mark %save_auth_scheme (SP ((auth_param ( LWS* ',' LWS* auth_param )*) | base64_auth_param))? LWS*;
    credentials = auth_scheme >mark %save_auth_scheme (LWS+ ((auth_param (LWS* ',' LWS* auth_param )*) | base64_auth_param))? LWS*;
    challengeList = LWS* challenge ( LWS* ',' LWS* challenge)* LWS*;

    action set_connection {
        m_set = &m_general->connection;
        m_list = NULL;
    }

    action set_date {
        m_date = &m_general->date;
    }

    action set_proxy_connection {
        m_set = &m_general->proxyConnection;
        m_list = NULL;
    }

    action set_trailer {
        m_set = &m_general->trailer;
        m_list = NULL;
    }

    action set_transfer_encoding {
        m_parameterizedList = &m_general->transferEncoding;
    }

    action save_upgrade_product {
        m_general->upgrade.push_back(m_product);
        m_product = Product();
    }

    Connection = 'Connection:'i @set_connection list;
    Date = 'Date:'i @set_date LWS* HTTP_date LWS*;
    # NON-STANDARD!!!
    Proxy_Connection = 'Proxy-Connection:'i @set_proxy_connection list;
    Trailer = 'Trailer:'i @set_trailer list;
    Transfer_Encoding = 'Transfer-Encoding:'i @set_transfer_encoding parameterizedList;
    Upgrade = 'Upgrade:'i LWS* product %save_upgrade_product ( LWS* ',' LWS* product %save_upgrade_product)* LWS*;

    general_header = Connection | Date | Proxy_Connection | Trailer | Transfer_Encoding | Upgrade;
    general_header_names = 'Connection'i | 'Date'i | 'Proxy-Connection'i | 'Trailer'i | 'Transfer-Encoding'i | 'Upgrade'i;

    action set_content_encoding {
        m_list = &m_entity->contentEncoding;
        m_set = NULL;
    }

    action set_content_length {
        m_ulong = &m_entity->contentLength;
    }

    action save_cr_first_byte_pos {
        m_entity->contentRange.first = strtoull(mark, NULL, 10);
        mark = NULL;
    }

    action save_cr_last_byte_pos {
        m_entity->contentRange.last = strtoull(mark, NULL, 10);
        mark = NULL;
    }

    action save_blank_cr {
        m_entity->contentRange.last = 0;
    }

    action save_instance_length {
        m_entity->contentRange.instance = strtoull(mark, NULL, 10);
        mark = NULL;
    }

    action set_content_type
    {
        m_parameters = &m_entity->contentType.parameters;
    }
    action save_type
    {
        m_entity->contentType.type = std::string(mark, fpc - mark);
        mark = NULL;
    }
    action save_subtype
    {
        m_entity->contentType.subtype = std::string(mark, fpc - mark);
        mark = NULL;
    }

    action set_expires
    {
        m_date = &m_entity->expires;
    }
    action set_last_modified
    {
        m_date = &m_entity->lastModified;
    }

    Content_Encoding = 'Content-Encoding:'i @set_content_encoding list;
    Content_Length = 'Content-Length:'i @set_content_length LWS* DIGIT+ >mark %save_ulong LWS*;

    byte_range_resp_spec = (DIGIT+ >mark %save_cr_first_byte_pos '-' DIGIT+ >mark %save_cr_last_byte_pos) | '*' %save_blank_cr;
    content_range_spec = bytes_unit SP byte_range_resp_spec '/' ( DIGIT+ >mark %save_instance_length | '*');
    Content_Range = 'Content-Range:'i LWS* content_range_spec LWS*;

    type = token >mark %save_type;
    subtype = token >mark %save_subtype;
    media_type = type '/' subtype (';' LWS* parameter)*;
    Content_Type = 'Content-Type:'i @set_content_type LWS* media_type LWS*;

    Expires = 'Expires:'i @set_expires LWS* HTTP_date LWS*;
    Last_Modified = 'Last-Modified:'i @set_last_modified LWS* HTTP_date LWS*;

    entity_header = Content_Encoding | Content_Length | Content_Range | Content_Type | Expires | Last_Modified; # | extension_header;
    entity_header_names = 'Content-Encoding'i | 'Content-Length'i | 'Content-Range'i | 'Content-Type'i | 'Expires'i | 'Last-Modified'i;

}%%

%%{
    machine http_request_parser;
    include http_parser;
    include uri_parser "../uri.rl";

    action parse_Method {
        m_request->requestLine.method = parseMethod(mark, fpc);
        mark = NULL;
    }

    action set_request_uri {
        m_uri = &m_request->requestLine.uri;
    }

    action set_authorization {
        m_challengeList = NULL;
        m_auth = &m_request->request.authorization;
    }

    action save_expectation {
        KeyValueWithParameters kvp;
        kvp.key = std::string(mark, fpc - mark);
        m_request->request.expect.push_back(kvp);
        mark = NULL;
    }
    action save_expectation_value {
        m_request->request.expect.back().value = unquote(mark, fpc - mark);
        mark = NULL;
    }
    action save_expectation_param {
        m_temp1 = std::string(mark, fpc - mark);
        m_request->request.expect.back().parameters[m_temp1] = "";
        mark = NULL;
    }
    action save_expectation_param_value {
        m_request->request.expect.back().parameters[m_temp1] = unquote(mark, fpc - mark);
        mark = NULL;
    }

    action set_host {
        m_string = &m_request->request.host;
    }

    action set_if_match {
        m_eTagSet = &m_request->request.ifMatch;
    }

    action set_if_modified_since {
        m_date = &m_request->request.ifModifiedSince;
    }

    action set_if_none_match {
        m_eTagSet = &m_request->request.ifMatch;
    }

    action set_if_range_entity_tag {
        m_request->request.ifRange = ETag();
        m_eTag = boost::get<ETag>(&m_request->request.ifRange);
    }

    action set_if_range_http_date {
        m_request->request.ifRange = boost::posix_time::ptime();
        m_date = boost::get<boost::posix_time::ptime>(&m_request->request.ifRange);
    }

    action set_if_unmodified_since {
        m_date = &m_request->request.ifModifiedSince;
    }

    action set_proxy_authorization {
        m_challengeList = NULL;
        m_auth = &m_request->request.proxyAuthorization;
    }

    action save_first_byte_pos {
        m_request->request.range.push_back(RangeSet::value_type(
            strtoull(mark, NULL, 10), ~0ull));
        mark = NULL;
    }
    action save_last_byte_pos {
        if (mark != NULL) {
            m_request->request.range.back().second = strtoull(mark, NULL, 10);
        }
        mark = NULL;
    }
    action save_suffix_byte_pos {
        m_request->request.range.push_back(RangeSet::value_type(
            ~0ull, strtoull(mark, NULL, 10)));
        mark = NULL;
    }

    action set_referer {
        m_uri = &m_request->request.referer;
    }

    action save_accept_list_element {
        AcceptValueWithParameters avp;
        avp.value = std::string(mark, fpc - mark);
        m_acceptList->push_back(avp);
        m_parameters = &m_acceptList->back().parameters;
        mark = NULL;
    }

    action save_qvalue {
        m_acceptList->back().qvalue = 0;
        size_t i = 0;
        unsigned int curPlace = 1000;
        for (; i < 5 && mark < fpc; ++i, ++mark) {
            if (i == 1)
                continue;
            unsigned int cur = *mark - '0';
            m_acceptList->back().qvalue += cur * curPlace;
            curPlace /= 10;
        }
        mark = NULL;
    }

    action save_accept_attribute {
        m_temp1 = std::string(mark, fpc - mark);
        m_acceptList->back().acceptParams[m_temp1] = "";
        mark = NULL;
    }

    action save_accept_value {
        m_acceptList->back().acceptParams[m_temp1] = unquote(mark, fpc - mark);
        mark = NULL;
    }

    action set_te {
        m_acceptList = &m_request->request.te;
    }

    action set_user_agent {
        m_productAndCommentList = &m_request->request.userAgent;
    }

    Authorization = 'Authorization:'i @set_authorization LWS* credentials;

    expect_params = ';' token >mark %save_expectation_param ( '=' (token | quoted_string) >mark %save_expectation_param_value )?;
    expectation = token >mark %save_expectation ( '=' (token | quoted_string) >mark %save_expectation_value expect_params* )?;
    Expect = 'Expect:'i LWS* expectation ( LWS* ',' LWS* expectation )* LWS*;

    Host = 'Host:'i @set_host LWS* (host (':' port)?) >mark %save_string LWS*;

    If_Match = 'If-Match:'i @set_if_match etag_list;
    If_Modified_Since = 'If-Modified-Since:'i @set_if_modified_since LWS* HTTP_date LWS*;
    If_None_Match = 'If-None-Match:'i @set_if_none_match etag_list;
    If_Range = 'If-Range:'i LWS* (entity_tag >set_if_range_entity_tag | HTTP_date >set_if_range_http_date)  LWS*;
    If_Unmodified_Since = 'If-Unmodified-Since:'i @set_if_unmodified_since LWS* HTTP_date LWS*;

    Proxy_Authorization = 'Proxy-Authorization:'i @set_proxy_authorization credentials;

    byte_range_spec = DIGIT+ >mark %save_first_byte_pos '-' (DIGIT+ >mark %save_last_byte_pos)?;
    suffix_byte_range_spec = '-' DIGIT+ > mark %save_suffix_byte_pos;
    byte_range_set = LWS* (byte_range_spec | suffix_byte_range_spec) ( LWS* ',' LWS* (byte_range_spec | suffix_byte_range_spec))* LWS*;
    ranges_specifier = bytes_unit '=' byte_range_set;
    Range = 'Range:'i LWS* ranges_specifier;

    Referer = 'Referer:'i @set_referer LWS* (absolute_URI | relative_URI);

    accept_extension = ';' token >mark %save_accept_attribute ('=' (token | quoted_string) >mark %save_accept_value)?;
    accept_params = ';q='i qvalue >mark %save_qvalue (accept_extension)*;
    acceptListElement = token >mark %save_accept_list_element (';' parameter)* (accept_params)?;
    acceptList = LWS* acceptListElement ( LWS* ',' LWS* acceptListElement)* LWS*;
    TE = 'TE:'i @set_te acceptList;

    User_Agent = 'User-Agent:'i @set_user_agent product_and_comment_list;

    request_header = Authorization | Expect | Host | If_Match | If_Modified_Since | If_None_Match | If_Range | If_Unmodified_Since | Proxy_Authorization | Range | Referer | TE | User_Agent;
    request_header_names = 'Authorization'i | 'Expect'i | 'Host'i | 'If-Match'i | 'If-Modified-Since'i | 'If-None-Match'i | 'If-Range'i | 'If-Unmodified-Since'i | 'Proxy-Authorization'i | 'Range'i | 'Referer'i | 'TE'i | 'User-Agent'i;

    extension_header = (token - (general_header_names | request_header_names | entity_header_names)) >mark %save_field_name
        ':' field_value;

    Method = token >mark %parse_Method;
    # we explicitly add query to path_absolute, because the URI spec changed from RFC 2396 to RFC 3986
    # with the query not being part of hier_part
    Request_URI = ( "*" | absolute_URI | (path_absolute ( "?" query )?) | authority);
    Request_Line = Method SP Request_URI >set_request_uri SP HTTP_Version CRLF;
    Request = Request_Line ((general_header | request_header | entity_header | extension_header) CRLF)* CRLF @done;

    main := Request;
    write data;
}%%

void
HTTP::HTTPParser::init()
{
    m_string = NULL;
    m_set = NULL;
    m_list = NULL;
    m_parameterizedList = NULL;
    m_parameters = NULL;
    m_auth = NULL;
    m_challengeList = NULL;
    m_ulong = NULL;
    m_eTag = NULL;
    m_eTagSet = NULL;
    m_productAndCommentList = NULL;
    RagelParser::init();
}

HTTP::RequestParser::RequestParser(Request& request)
: m_request(&request),
  m_ver(&request.requestLine.ver),
  m_path(&request.requestLine.uri.path),
  m_general(&request.general),
  m_entity(&request.entity)
{}

void
HTTP::RequestParser::init()
{
    HTTPParser::init();
    %% write init;
}

bool
HTTP::RequestParser::final() const
{
    return cs >= http_request_parser_first_final;
}

bool
HTTP::RequestParser::error() const
{
    return cs == http_request_parser_error;
}

void
HTTP::RequestParser::exec()
{
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
    %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
}

%%{
    machine http_response_parser;
    include http_parser;
    include uri_parser "../uri.rl";

    action parse_Status_Code {
        m_response->status.status = (HTTP::Status)atoi(mark);
        mark = NULL;
    }

    action parse_Reason_Phrase {
        m_response->status.reason = std::string(mark, fpc - mark);
        mark = NULL;
    }

    action set_accept_ranges
    {
        m_set = &m_response->response.acceptRanges;
        m_list = NULL;
    }
    action set_etag
    {
        m_eTag = &m_response->response.eTag;
    }
    action set_proxy_authenticate {
        m_challengeList = &m_response->response.proxyAuthenticate;
    }
    action set_retry_after_http_date {
        m_response->response.retryAfter = boost::posix_time::ptime();
        m_date = boost::get<boost::posix_time::ptime>(&m_response->response.retryAfter);
    }
    action set_retry_after_delta_seconds {
        m_response->response.retryAfter = ~0ull;
        m_ulong = boost::get<unsigned long long>(&m_response->response.retryAfter);
    }
    action set_server {
        m_productAndCommentList = &m_response->response.server;
    }
    action set_www_authenticate {
        m_challengeList = &m_response->response.wwwAuthenticate;
    }

    Accept_Ranges = 'Accept-Ranges:'i @set_accept_ranges list;
    ETag = 'ETag:'i @set_etag LWS* entity_tag;
    # This *should* be absolute_URI, but we're generous
    Location = 'Location:'i LWS* URI_reference LWS*;
    Proxy_Authenticate = 'Proxy-Authenticate:'i @set_proxy_authenticate challengeList;
    Retry_After = 'Retry-After:'i LWS* (HTTP_date %set_retry_after_http_date | delta_seconds >mark %set_retry_after_delta_seconds %save_ulong) LWS*;
    Server = 'Server:'i @set_server product_and_comment_list;
    WWW_Authenticate = 'WWW-Authenticate:'i @set_www_authenticate challengeList;

    response_header = Accept_Ranges | ETag | Location | Proxy_Authenticate | Retry_After | Server | WWW_Authenticate;
    response_header_names = 'Accept-Ranges'i | 'ETag'i | 'Location'i | 'Proxy-Authenticate'i | 'Retry-After'i | 'Server'i | 'WWW-Authenticate'i;

    extension_header = (token - (general_header_names | response_header_names | entity_header_names)) >mark %save_field_name
        ':' field_value;

    Status_Code = DIGIT{3} > mark %parse_Status_Code;
    Reason_Phrase = (TEXT -- (CR | LF))* >mark %parse_Reason_Phrase;
    Status_Line = HTTP_Version SP Status_Code SP Reason_Phrase CRLF;
    Response = Status_Line ((general_header | response_header | entity_header | extension_header) CRLF)* CRLF @done;

    main := Response;

    write data;
}%%

HTTP::ResponseParser::ResponseParser(Response& response)
: m_response(&response),
  m_ver(&response.status.ver),
  m_uri(&response.response.location),
  m_path(&response.response.location.path),
  m_general(&response.general),
  m_entity(&response.entity)
{}

void
HTTP::ResponseParser::init()
{
    HTTPParser::init();
    %% write init;
}

bool
HTTP::ResponseParser::final() const
{
    return cs >= http_response_parser_first_final;
}

bool
HTTP::ResponseParser::error() const
{
    return cs == http_response_parser_error;
}

void
HTTP::ResponseParser::exec()
{
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
    %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
}

%%{
    machine http_trailer_parser;
    include http_parser;

    extension_header = (token - (entity_header_names)) >mark %save_field_name ':' field_value;

    trailer = ((entity_header | extension_header) CRLF)*;

    main := trailer CRLF @done;

    write data;
}%%

HTTP::TrailerParser::TrailerParser(EntityHeaders& entity)
: m_entity(&entity)
{}

void
HTTP::TrailerParser::init()
{
    HTTPParser::init();
    %% write init;
}

bool
HTTP::TrailerParser::final() const
{
    return cs >= http_trailer_parser_first_final;
}

bool
HTTP::TrailerParser::error() const
{
    return cs == http_trailer_parser_error;
}

void
HTTP::TrailerParser::exec()
{
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
    %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
}

%%{
    machine http_list_parser;
    include http_parser;

    main := list;

    write data;
}%%

HTTP::ListParser::ListParser(StringSet& stringSet)
: m_set(&stringSet),
  m_list(NULL)
{}

void
HTTP::ListParser::init()
{
    RagelParser::init();
    %% write init;
}

bool
HTTP::ListParser::final() const
{
    return cs >= http_list_parser_first_final;
}

bool
HTTP::ListParser::error() const
{
    return cs == http_list_parser_error;
}

void
HTTP::ListParser::exec()
{
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
    %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
}


Add parsing of binary body to JavaParser

package stompede.stomp;

import org.jruby.Ruby;
import org.jruby.RubyModule;
import org.jruby.RubyClass;
import org.jruby.RubyObject;
import org.jruby.RubyFixnum;
import org.jruby.RubyString;
import org.jruby.RubyNumeric;
import org.jruby.exceptions.RaiseException;

import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.Block;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;

%%{
  machine message;

  action mark {
    mark = p;
  }

  action mark_message {
    mark_message = context.runtime.getClassFromPath("Stompede::Stomp::Message").callMethod("new", context.nil, context.nil);
    /*mark_message_size = 0;*/
  }

  action write_command {
    mark_message.callMethod(context, "write_command", RubyString.newString(context.runtime, data, mark, p - mark));
    mark = -1;
  }

  action mark_key {
    mark_key = RubyString.newString(context.runtime, data, mark, p - mark);
    mark = -1;
  }

  action write_header {
    IRubyObject args[] = { mark_key, RubyString.newString(context.runtime, data, mark, p - mark) };
    mark_message.callMethod(context, "write_header", args);
    mark_key = null;
    mark = -1;
  }

  action finish_headers {
    IRubyObject content_length = mark_message.callMethod(context, "content_length");

    if ( ! content_length.isNil()) {
      mark_content_length = RubyNumeric.num2int(content_length);
    } else {
      mark_content_length = -1;
    }
  }

  action write_body {
    mark_message.callMethod(context, "write_body", RubyString.newString(context.runtime, data, mark, p - mark));
    mark = -1;
  }

  action consume_null {
    ((mark_content_length != -1) && ((p - mark) < mark_content_length))
  }

  action consume_octet {
    ((mark_content_length == -1) || ((p - mark) < mark_content_length))
  }

  action check_message_size {
    /*mark_message_size += 1;*/
    /*if (mark_message_size > max_message_size) {*/
    /*  rb_raise(eMessageSizeExceeded, "");*/
    /*}*/
  }

  action finish_message {
    block.yield(context, mark_message);
    mark_message = null;
  }

  include message_common "parser_common.rl";
}%%

@JRubyClass(name="JavaParser", parent="Object")
public class JavaParser extends RubyObject {
  %% write data noprefix;

  private class State {
    public int cs = JavaParser.start;
    public byte[] chunk;
    public int mark = -1;
    public RubyString mark_key;
    public IRubyObject mark_message;
    public int mark_message_size = -1;
    public int mark_content_length = -1;
  }

  private RaiseException exception;
  private long maxMessageSize;
  private State state;

  public JavaParser(Ruby runtime, RubyClass klass) {
    super(runtime, klass);
    state = new State();
  }

  @JRubyMethod
  public IRubyObject initialize(ThreadContext context) {
    RubyModule mStomp = context.runtime.getClassFromPath("Stompede::Stomp");
    return initialize(context, mStomp.callMethod("max_message_size"));
  }

  @JRubyMethod(argTypes = {RubyFixnum.class})
  public IRubyObject initialize(ThreadContext context, IRubyObject maxMessageSize) {
    this.maxMessageSize = ((RubyFixnum) maxMessageSize).getLongValue();
    return context.nil;
  }

  @JRubyMethod(argTypes = {RubyString.class})
  public IRubyObject parse(ThreadContext context, IRubyObject chunk, Block block) {
    byte data[] = ((RubyString) chunk).getBytes();

    int p = 0;
    int pe = data.length;

    int cs = state.cs;
    int mark = state.mark;
    RubyString mark_key = state.mark_key;
    IRubyObject mark_message = state.mark_message;
    int mark_message_size = state.mark_message_size;
    int mark_content_length = state.mark_content_length;

    %% write exec;

    state.cs = cs;
    state.mark = mark;
    state.mark_key = mark_key;
    state.mark_message = mark_message;
    state.mark_message_size = mark_message_size;
    state.mark_content_length = mark_content_length;

    if (cs == error) {
      // RaiseException error = context.runtime.getClassFromPath("Stompede::Stomp").callMethod("build_parse_error");
      // throw error;
    }

    return context.nil;
  }
}

package stompede.stomp;

import org.jruby.Ruby;
import org.jruby.RubyModule;
import org.jruby.RubyClass;
import org.jruby.RubyObject;
import org.jruby.RubyFixnum;
import org.jruby.RubyString;
import org.jruby.exceptions.RaiseException;

import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.Block;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;

%%{
  machine message;

  action mark {
    mark = p;
  }

  action mark_message {
    mark_message = context.runtime.getClassFromPath("Stompede::Stomp::Message").callMethod("new", context.nil, context.nil);
    /*mark_message_size = 0;*/
  }

  action write_command {
    mark_message.callMethod(context, "write_command", RubyString.newString(context.runtime, data, mark, p - mark));
    mark = -1;
  }

  action mark_key {
    mark_key = RubyString.newString(context.runtime, data, mark, p - mark);
    mark = -1;
  }

  action write_header {
    IRubyObject args[] = { mark_key, RubyString.newString(context.runtime, data, mark, p - mark) };
    mark_message.callMethod(context, "write_header", args);
    mark_key = null;
    mark = -1;
  }

  action finish_headers {
    /*VALUE length = rb_funcall(mark_message, g_content_length, 0);*/
    /*if ( ! NIL_P(length)) {*/
    /*  mark_content_length = NUM2LONG(length);*/
    /*} else {*/
    /*  mark_content_length = -1;*/
    /*}*/
  }

  action write_body {
    mark_message.callMethod(context, "write_body", RubyString.newString(context.runtime, data, mark, p - mark));
    mark = -1;
  }

  action consume_null {
    false
    /*((mark_content_length != -1) && (MARK_LEN < mark_content_length))*/
  }

  action consume_octet {
    true
    /*((mark_content_length == -1) || (MARK_LEN < mark_content_length))*/
  }

  action check_message_size {
    /*mark_message_size += 1;*/
    /*if (mark_message_size > max_message_size) {*/
    /*  rb_raise(eMessageSizeExceeded, "");*/
    /*}*/
  }

  action finish_message {
    block.yield(context, mark_message);
    mark_message = null;
  }

  include message_common "parser_common.rl";
}%%

@JRubyClass(name="JavaParser", parent="Object")
public class JavaParser extends RubyObject {
  %% write data noprefix;

  private class State {
    public int cs = JavaParser.start;
    public byte[] chunk;
    public int mark = -1;
    public RubyString mark_key;
    public IRubyObject mark_message;
    public int mark_message_size = -1;
    public int mark_content_length = -1;
  }

  private RaiseException exception;
  private long maxMessageSize;
  private State state;

  public JavaParser(Ruby runtime, RubyClass klass) {
    super(runtime, klass);
    state = new State();
  }

  @JRubyMethod
  public IRubyObject initialize(ThreadContext context) {
    RubyModule mStomp = context.runtime.getClassFromPath("Stompede::Stomp");
    return initialize(context, mStomp.callMethod("max_message_size"));
  }

  @JRubyMethod(argTypes = {RubyFixnum.class})
  public IRubyObject initialize(ThreadContext context, IRubyObject maxMessageSize) {
    this.maxMessageSize = ((RubyFixnum) maxMessageSize).getLongValue();
    return context.nil;
  }

  @JRubyMethod(argTypes = {RubyString.class})
  public IRubyObject parse(ThreadContext context, IRubyObject chunk, Block block) {
    byte data[] = ((RubyString) chunk).getBytes();

    int p = 0;
    int pe = data.length;

    int cs = state.cs;
    int mark = state.mark;
    RubyString mark_key = state.mark_key;
    IRubyObject mark_message = state.mark_message;
    int mark_message_size = state.mark_message_size;
    int mark_content_length = state.mark_content_length;

    %% write exec;

    state.cs = cs;
    state.mark = mark;
    state.mark_key = mark_key;
    state.mark_message = mark_message;
    state.mark_message_size = mark_message_size;
    state.mark_content_length = mark_content_length;

    if (cs == error) {
      // RaiseException error = context.runtime.getClassFromPath("Stompede::Stomp").callMethod("build_parse_error");
      // throw error;
    }

    return context.nil;
  }
}