199 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Ragel
		
	
	
	
			
		
		
	
	
			199 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Ragel
		
	
	
	
#include "lexer.h"
 | 
						|
 | 
						|
/*
 | 
						|
The following two macros allow the Ragel grammar to use generic function calls
 | 
						|
without relying on the setup of the C or Java lexer. Using these macros we can
 | 
						|
also pass along `self` to the callback functions without having to hard-code
 | 
						|
this in to the Ragel grammar.
 | 
						|
 | 
						|
In the C lexer we don't need the `data` variable (since this is pulled in based
 | 
						|
on `ts` and `te`) so the macro ignores this argument.
 | 
						|
*/
 | 
						|
 | 
						|
#define callback(name, data, encoding, start, stop) \
 | 
						|
    liboga_xml_lexer_callback(self, name, encoding, start, stop);
 | 
						|
 | 
						|
#define callback_simple(name) \
 | 
						|
    liboga_xml_lexer_callback_simple(self, name);
 | 
						|
 | 
						|
#define advance_line(amount) \
 | 
						|
    rb_funcall(self, id_advance_line, 1, INT2NUM(amount));
 | 
						|
 | 
						|
#define html_script_p() \
 | 
						|
    rb_funcall(self, id_html_script_p, 0) == Qtrue
 | 
						|
 | 
						|
#define html_style_p() \
 | 
						|
    rb_funcall(self, id_html_style_p, 0) == Qtrue
 | 
						|
 | 
						|
ID id_advance_line;
 | 
						|
ID id_html_script_p;
 | 
						|
ID id_html_style_p;
 | 
						|
ID id_html_p;
 | 
						|
 | 
						|
%%machine c_lexer;
 | 
						|
 | 
						|
/**
 | 
						|
 * Calls a method defined in the Ruby side of the lexer. The String value is
 | 
						|
 * created based on the values of `ts` and `te` and uses the encoding specified
 | 
						|
 * in `encoding`.
 | 
						|
 *
 | 
						|
 * @example
 | 
						|
 *  rb_encoding *encoding = rb_enc_get(...);
 | 
						|
 *  liboga_xml_lexer_callback(self, "on_string", encoding, ts, te);
 | 
						|
 */
 | 
						|
void liboga_xml_lexer_callback(
 | 
						|
    VALUE self,
 | 
						|
    ID name,
 | 
						|
    rb_encoding *encoding,
 | 
						|
    const char *ts,
 | 
						|
    const char *te
 | 
						|
)
 | 
						|
{
 | 
						|
    VALUE value = rb_enc_str_new(ts, te - ts, encoding);
 | 
						|
 | 
						|
    rb_funcall(self, name, 1, value);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Calls a method defined in the Ruby side of the lexer without passing it any
 | 
						|
 * arguments.
 | 
						|
 *
 | 
						|
 * @example
 | 
						|
 *  liboga_xml_lexer_callback_simple(self, "on_cdata_start");
 | 
						|
 */
 | 
						|
void liboga_xml_lexer_callback_simple(VALUE self, VALUE name)
 | 
						|
{
 | 
						|
    rb_funcall(self, name, 0);
 | 
						|
}
 | 
						|
 | 
						|
%% write data;
 | 
						|
 | 
						|
/**
 | 
						|
 * Lexes the String specifies as the method argument. Token values have the
 | 
						|
 * same encoding as the input value.
 | 
						|
 *
 | 
						|
 * This method keeps track of an internal state using the instance variables
 | 
						|
 * `@act` and `@cs`.
 | 
						|
 */
 | 
						|
VALUE oga_xml_lexer_advance(VALUE self, VALUE data_block)
 | 
						|
{
 | 
						|
    OgaLexerState *state;
 | 
						|
    int lines;
 | 
						|
 | 
						|
    /* Whether or not HTML mode is enabled */
 | 
						|
    int html_p = rb_funcall(self, id_html_p, 0) == Qtrue;
 | 
						|
 | 
						|
    /* Make sure that all data passed back to Ruby has the proper encoding. */
 | 
						|
    rb_encoding *encoding = rb_enc_get(data_block);
 | 
						|
 | 
						|
    char *data_str_val = StringValueCStr(data_block);
 | 
						|
 | 
						|
    const char *p    = data_str_val;
 | 
						|
    const char *pe   = data_str_val + strlen(data_str_val);
 | 
						|
    const char *eof  = pe;
 | 
						|
    const char *ts   = 0;
 | 
						|
    const char *te   = 0;
 | 
						|
    const char *mark = 0;
 | 
						|
 | 
						|
    ID id_advance_line        = rb_intern("advance_line");
 | 
						|
    ID id_on_attribute        = rb_intern("on_attribute");
 | 
						|
    ID id_on_attribute_ns     = rb_intern("on_attribute_ns");
 | 
						|
    ID id_on_cdata_start      = rb_intern("on_cdata_start");
 | 
						|
    ID id_on_cdata_body       = rb_intern("on_cdata_body");
 | 
						|
    ID id_on_cdata_end        = rb_intern("on_cdata_end");
 | 
						|
    ID id_on_comment_start    = rb_intern("on_comment_start");
 | 
						|
    ID id_on_comment_body     = rb_intern("on_comment_body");
 | 
						|
    ID id_on_comment_end      = rb_intern("on_comment_end");
 | 
						|
    ID id_on_doctype_end      = rb_intern("on_doctype_end");
 | 
						|
    ID id_on_doctype_inline   = rb_intern("on_doctype_inline");
 | 
						|
    ID id_on_doctype_name     = rb_intern("on_doctype_name");
 | 
						|
    ID id_on_doctype_start    = rb_intern("on_doctype_start");
 | 
						|
    ID id_on_doctype_type     = rb_intern("on_doctype_type");
 | 
						|
    ID id_on_element_end      = rb_intern("on_element_end");
 | 
						|
    ID id_on_element_name     = rb_intern("on_element_name");
 | 
						|
    ID id_on_element_ns       = rb_intern("on_element_ns");
 | 
						|
    ID id_on_element_open_end = rb_intern("on_element_open_end");
 | 
						|
    ID id_on_proc_ins_end     = rb_intern("on_proc_ins_end");
 | 
						|
    ID id_on_proc_ins_name    = rb_intern("on_proc_ins_name");
 | 
						|
    ID id_on_proc_ins_start   = rb_intern("on_proc_ins_start");
 | 
						|
    ID id_on_proc_ins_body    = rb_intern("on_proc_ins_body");
 | 
						|
    ID id_on_string_body      = rb_intern("on_string_body");
 | 
						|
    ID id_on_string_dquote    = rb_intern("on_string_dquote");
 | 
						|
    ID id_on_string_squote    = rb_intern("on_string_squote");
 | 
						|
    ID id_on_text             = rb_intern("on_text");
 | 
						|
    ID id_on_xml_decl_end     = rb_intern("on_xml_decl_end");
 | 
						|
    ID id_on_xml_decl_start   = rb_intern("on_xml_decl_start");
 | 
						|
 | 
						|
    Data_Get_Struct(self, OgaLexerState, state);
 | 
						|
 | 
						|
    lines = state->lines;
 | 
						|
 | 
						|
    %% write exec;
 | 
						|
 | 
						|
    state->lines = lines;
 | 
						|
 | 
						|
    return Qnil;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Resets the internal state of the lexer.
 | 
						|
 */
 | 
						|
VALUE oga_xml_lexer_reset(VALUE self)
 | 
						|
{
 | 
						|
    OgaLexerState *state;
 | 
						|
 | 
						|
    Data_Get_Struct(self, OgaLexerState, state);
 | 
						|
 | 
						|
    state->act   = 0;
 | 
						|
    state->cs    = c_lexer_start;
 | 
						|
    state->lines = 0;
 | 
						|
    state->top   = 0;
 | 
						|
 | 
						|
    return Qnil;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Frees the associated lexer state struct.
 | 
						|
 */
 | 
						|
void oga_xml_lexer_free(void *state)
 | 
						|
{
 | 
						|
    free((OgaLexerState *) state);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Allocates and wraps the C lexer state struct. This state is used to keep
 | 
						|
 * track of the current position, line numbers, etc.
 | 
						|
 */
 | 
						|
VALUE oga_xml_lexer_allocate(VALUE klass)
 | 
						|
{
 | 
						|
    OgaLexerState *state = malloc(sizeof(OgaLexerState));
 | 
						|
 | 
						|
    return Data_Wrap_Struct(klass, NULL, oga_xml_lexer_free, state);
 | 
						|
}
 | 
						|
 | 
						|
%%{
 | 
						|
    include base_lexer "base_lexer.rl";
 | 
						|
 | 
						|
    variable top state->top;
 | 
						|
    variable stack state->stack;
 | 
						|
    variable act state->act;
 | 
						|
    variable cs state->cs;
 | 
						|
}%%
 | 
						|
 | 
						|
void Init_liboga_xml_lexer()
 | 
						|
{
 | 
						|
    VALUE mOga   = rb_const_get(rb_cObject, rb_intern("Oga"));
 | 
						|
    VALUE mXML   = rb_const_get(mOga, rb_intern("XML"));
 | 
						|
    VALUE cLexer = rb_define_class_under(mXML, "Lexer", rb_cObject);
 | 
						|
 | 
						|
    id_advance_line  = rb_intern("advance_line");
 | 
						|
    id_html_script_p = rb_intern("html_script?");
 | 
						|
    id_html_style_p  = rb_intern("html_style?");
 | 
						|
    id_html_p        = rb_intern("html?");
 | 
						|
 | 
						|
    rb_define_method(cLexer, "advance_native", oga_xml_lexer_advance, 1);
 | 
						|
    rb_define_method(cLexer, "reset_native", oga_xml_lexer_reset, 0);
 | 
						|
 | 
						|
    rb_define_alloc_func(cLexer, oga_xml_lexer_allocate);
 | 
						|
}
 |