diff --git a/ext/liquid/block.c b/ext/liquid/block.c index 2ffeddd..47f589d 100644 --- a/ext/liquid/block.c +++ b/ext/liquid/block.c @@ -1,7 +1,7 @@ #include "liquid_ext.h" VALUE cLiquidBlock; -ID intern_assert_missing_delimitation, intern_block_delimiter, intern_is_blank, intern_new, +ID intern_assert_missing_delimitation, intern_block_delimiter, intern_is_blank, intern_new_with_options, intern_tags, intern_unknown_tag, intern_unterminated_tag, intern_unterminated_variable; @@ -133,7 +133,7 @@ static VALUE rb_parse_body(VALUE self, VALUE tokenizerObj) break; } case TOKEN_STRING: - rb_ary_push(nodelist, rb_str_new(token.str, token.length)); + rb_ary_push(nodelist, liquid_string_slice_new(token.str, token.length)); if (blank) { int i; for (i = 0; i < token.length; i++) { @@ -156,7 +156,6 @@ void init_liquid_block() intern_assert_missing_delimitation = rb_intern("assert_missing_delimitation!"); intern_block_delimiter = rb_intern("block_delimiter"); intern_is_blank = rb_intern("blank?"); - intern_new = rb_intern("new"); intern_new_with_options = rb_intern("new_with_options"); intern_tags = rb_intern("tags"); intern_unknown_tag = rb_intern("unknown_tag"); diff --git a/ext/liquid/liquid_ext.c b/ext/liquid/liquid_ext.c index d611231..d315218 100644 --- a/ext/liquid/liquid_ext.c +++ b/ext/liquid/liquid_ext.c @@ -2,9 +2,11 @@ VALUE mLiquid; VALUE cLiquidTemplate, cLiquidTag, cLiquidVariable; +ID intern_new; void Init_liquid(void) { + intern_new = rb_intern("new"); mLiquid = rb_define_module("Liquid"); cLiquidTemplate = rb_define_class_under(mLiquid, "Template", rb_cObject); cLiquidTag = rb_define_class_under(mLiquid, "Tag", rb_cObject); @@ -12,4 +14,5 @@ void Init_liquid(void) init_liquid_tokenizer(); init_liquid_block(); + init_liquid_string_slice(); } diff --git a/ext/liquid/liquid_ext.h b/ext/liquid/liquid_ext.h index 3eba3ae..36b9a10 100644 --- a/ext/liquid/liquid_ext.h +++ b/ext/liquid/liquid_ext.h @@ -7,8 +7,10 @@ #include "tokenizer.h" #include "block.h" +#include "slice.h" #include "utils.h" +extern ID intern_new; extern VALUE mLiquid; extern VALUE cLiquidTemplate, cLiquidTag, cLiquidVariable; diff --git a/ext/liquid/slice.c b/ext/liquid/slice.c new file mode 100644 index 0000000..2fcc564 --- /dev/null +++ b/ext/liquid/slice.c @@ -0,0 +1,125 @@ +#include "liquid_ext.h" + +VALUE cLiquidStringSlice; + +static void mark_slice(void *ptr) +{ + if (!ptr) + return; + struct string_slice *slice = ptr; + rb_gc_mark(slice->source); +} + +static void free_slice(void *ptr) +{ + struct string_slice *slice = ptr; + xfree(slice); +} + +VALUE liquid_string_slice_new(char *str, long length) +{ + return rb_funcall(cLiquidStringSlice, intern_new, 3, rb_str_new(str, length), INT2FIX(0), INT2FIX(length)); +} + +static VALUE rb_allocate(VALUE klass) +{ + struct string_slice *slice; + VALUE obj = Data_Make_Struct(klass, struct string_slice, mark_slice, free_slice, slice); + return obj; +} + +static VALUE rb_initialize(VALUE self, VALUE source, VALUE offset_value, VALUE length_value) +{ + long offset = rb_fix2int(offset_value); + long length = rb_fix2int(length_value); + if (length < 0) + rb_raise(rb_eArgError, "negative string length"); + if (offset < 0) + rb_raise(rb_eArgError, "negative string offset"); + + if (TYPE(source) == T_DATA && RBASIC_CLASS(source) == cLiquidStringSlice) { + struct string_slice *source_slice = DATA_PTR(source); + source = source_slice->source; + offset += source_slice->str - RSTRING_PTR(source); + } else { + source = rb_string_value(&source); + source = rb_str_dup_frozen(source); + } + + struct string_slice *slice; + Data_Get_Struct(self, struct string_slice, slice); + slice->source = source; + slice->str = RSTRING_PTR(source) + offset; + slice->length = length; + if (length > RSTRING_LEN(source) - offset) + rb_raise(rb_eArgError, "slice bounds outside source string bounds"); + + return Qnil; +} + +static VALUE rb_slice_to_str(VALUE self) +{ + struct string_slice *slice; + Data_Get_Struct(self, struct string_slice, slice); + + VALUE source = slice->source; + if (slice->str == RSTRING_PTR(source) && slice->length == RSTRING_LEN(source)) + return source; + + source = rb_str_new(slice->str, slice->length); + slice->source = source; + slice->str = RSTRING_PTR(source); + return source; +} + +static VALUE rb_slice_slice(VALUE self, VALUE offset, VALUE length) +{ + return rb_funcall(cLiquidStringSlice, intern_new, 3, self, offset, length); +} + +static VALUE rb_slice_length(VALUE self) +{ + struct string_slice *slice; + Data_Get_Struct(self, struct string_slice, slice); + return INT2FIX(slice->length); +} + +static VALUE rb_slice_equal(VALUE self, VALUE other) +{ + struct string_slice *this_slice; + Data_Get_Struct(self, struct string_slice, this_slice); + + char *other_str; + long other_length; + if (TYPE(other) == T_DATA && RBASIC_CLASS(other) == cLiquidStringSlice) { + struct string_slice *other_slice = DATA_PTR(other); + other_str = other_slice->str; + other_length = other_slice->length; + } else { + other = rb_string_value(&other); + other_length = RSTRING_LEN(other); + other_str = RSTRING_PTR(other); + } + bool equal = this_slice->length == other_length && !memcmp(this_slice->str, other_str, other_length); + return equal ? Qtrue : Qfalse; +} + +static VALUE rb_slice_inspect(VALUE self) +{ + VALUE quoted = rb_str_inspect(rb_slice_to_str(self)); + return rb_sprintf("#", (int)RSTRING_LEN(quoted), RSTRING_PTR(quoted)); +} + +void init_liquid_string_slice() +{ + cLiquidStringSlice = rb_define_class_under(mLiquid, "StringSlice", rb_cObject); + rb_define_alloc_func(cLiquidStringSlice, rb_allocate); + rb_define_method(cLiquidStringSlice, "initialize", rb_initialize, 3); + rb_define_method(cLiquidStringSlice, "==", rb_slice_equal, 1); + rb_define_method(cLiquidStringSlice, "length", rb_slice_length, 0); + rb_define_alias(cLiquidStringSlice, "size", "length"); + rb_define_method(cLiquidStringSlice, "slice", rb_slice_slice, 2); + rb_define_method(cLiquidStringSlice, "to_str", rb_slice_to_str, 0); + rb_define_alias(cLiquidStringSlice, "to_s", "to_str"); + rb_define_method(cLiquidStringSlice, "inspect", rb_slice_inspect, 0); +} diff --git a/ext/liquid/slice.h b/ext/liquid/slice.h new file mode 100644 index 0000000..050b465 --- /dev/null +++ b/ext/liquid/slice.h @@ -0,0 +1,18 @@ +#ifndef LIQUID_SLICE_H +#define LIQUID_SLICE_H + +extern VALUE cLiquidStringSlice; + +struct string_slice { + VALUE source; + char *str; + long length; +}; + +VALUE liquid_string_slice_new(char *str, long length); + +void init_liquid_string_slice(); + +#define STRING_SLICE_GET_STRUCT(obj) ((struct string_slice *)obj_get_data_ptr(obj, cLiquidStringSlice)) + +#endif diff --git a/ext/liquid/tokenizer.c b/ext/liquid/tokenizer.c index a70efa3..d00a6ba 100644 --- a/ext/liquid/tokenizer.c +++ b/ext/liquid/tokenizer.c @@ -21,7 +21,7 @@ static VALUE rb_initialize(VALUE self, VALUE source) { struct liquid_tokenizer *tokenizer; - Check_Type(source, T_STRING); + source = rb_string_value(&source); Data_Get_Struct(self, struct liquid_tokenizer, tokenizer); tokenizer->cursor = RSTRING_PTR(source); tokenizer->length = RSTRING_LEN(source); diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 6d06854..795db80 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -29,7 +29,7 @@ module Liquid end def increment_used_resources(key, obj) - @resource_limits[key] += if obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash) + @resource_limits[key] += if obj.kind_of?(StringSlice) || obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash) obj.length else 1 diff --git a/test/liquid/block_test.rb b/test/liquid/block_test.rb index f4b4494..483b682 100644 --- a/test/liquid/block_test.rb +++ b/test/liquid/block_test.rb @@ -12,34 +12,34 @@ class BlockTest < Test::Unit::TestCase template = Liquid::Template.parse("{{funk}} ") assert_equal 2, template.root.nodelist.size assert_equal Variable, template.root.nodelist[0].class - assert_equal String, template.root.nodelist[1].class + assert_equal StringSlice, template.root.nodelist[1].class end def test_variable_end template = Liquid::Template.parse(" {{funk}}") assert_equal 2, template.root.nodelist.size - assert_equal String, template.root.nodelist[0].class + assert_equal StringSlice, template.root.nodelist[0].class assert_equal Variable, template.root.nodelist[1].class end def test_variable_middle template = Liquid::Template.parse(" {{funk}} ") assert_equal 3, template.root.nodelist.size - assert_equal String, template.root.nodelist[0].class + assert_equal StringSlice, template.root.nodelist[0].class assert_equal Variable, template.root.nodelist[1].class - assert_equal String, template.root.nodelist[2].class + assert_equal StringSlice, template.root.nodelist[2].class end def test_variable_many_embedded_fragments template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ") assert_equal 7, template.root.nodelist.size - assert_equal [String, Variable, String, Variable, String, Variable, String], + assert_equal [StringSlice, Variable, StringSlice, Variable, StringSlice, Variable, StringSlice], block_types(template.root.nodelist) end def test_with_block template = Liquid::Template.parse(" {% comment %} {% endcomment %} ") - assert_equal [String, Comment, String], block_types(template.root.nodelist) + assert_equal [StringSlice, Comment, StringSlice], block_types(template.root.nodelist) assert_equal 3, template.root.nodelist.size end diff --git a/test/liquid/parsing_quirks_test.rb b/test/liquid/parsing_quirks_test.rb index ef21117..5df524f 100644 --- a/test/liquid/parsing_quirks_test.rb +++ b/test/liquid/parsing_quirks_test.rb @@ -8,7 +8,7 @@ class ParsingQuirksTest < Test::Unit::TestCase template = Template.parse(text) assert_equal text, template.render - assert_equal [String], template.root.nodelist.collect {|i| i.class} + assert_equal [StringSlice], template.root.nodelist.collect {|i| i.class} end def test_raise_on_single_close_bracet diff --git a/test/liquid/string_slice_test.rb b/test/liquid/string_slice_test.rb new file mode 100644 index 0000000..0565a32 --- /dev/null +++ b/test/liquid/string_slice_test.rb @@ -0,0 +1,34 @@ +require 'test_helper' + +class StringSliceTest < Test::Unit::TestCase + def test_new_from_string + assert_equal 'slice', Liquid::StringSlice.new("slice and dice", 0, 5).to_str + assert_equal 'and', Liquid::StringSlice.new("slice and dice", 6, 3).to_str + assert_equal 'dice', Liquid::StringSlice.new("slice and dice", 10, 4).to_str + assert_equal 'slice and dice', Liquid::StringSlice.new("slice and dice", 0, 14).to_str + end + + def test_new_from_slice + slice1 = Liquid::StringSlice.new("slice and dice", 0, 14) + slice2 = Liquid::StringSlice.new(slice1, 6, 8) + slice3 = Liquid::StringSlice.new(slice2, 0, 3) + assert_equal "slice and dice", slice1.to_str + assert_equal "and dice", slice2.to_str + assert_equal "and", slice3.to_str + end + + def test_slice + slice = Liquid::StringSlice.new("slice and dice", 2, 10) + assert_equal "and", slice.slice(4, 3).to_str + end + + def test_length + slice = Liquid::StringSlice.new("slice and dice", 6, 3) + assert_equal 3, slice.length + assert_equal 3, slice.size + end + + def test_equal + assert_equal 'and', Liquid::StringSlice.new("slice and dice", 6, 3) + end +end