Skip to content

Commit

Permalink
Implement Liquid::Raw#parse (#71)
Browse files Browse the repository at this point in the history
* Implement Liquid::Raw#parse

* Address comments
  • Loading branch information
peterzhu2118 authored Oct 7, 2020
1 parent e5c45cb commit 1eccb2c
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 0 deletions.
2 changes: 2 additions & 0 deletions ext/liquid_c/liquid.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "variable.h"
#include "lexer.h"
#include "parser.h"
#include "raw.h"
#include "resource_limits.h"
#include "block.h"
#include "context.h"
Expand Down Expand Up @@ -46,6 +47,7 @@ void Init_liquid_c(void)

init_liquid_tokenizer();
init_liquid_parser();
init_liquid_raw();
init_liquid_resource_limits();
init_liquid_variable();
init_liquid_block();
Expand Down
105 changes: 105 additions & 0 deletions ext/liquid_c/raw.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "liquid.h"
#include "raw.h"
#include "stringutil.h"
#include "tokenizer.h"

static VALUE id_block_name, id_raise_tag_never_closed, id_block_delimiter, id_ivar_body;

struct full_token_possibly_invalid_t {
long body_len;
const char *delimiter_start;
long delimiter_len;
};

static bool match_full_token_possibly_invalid(token_t *token, struct full_token_possibly_invalid_t *match)
{
const char *str = token->str_full;
long len = token->len_full;

match->body_len = 0;
match->delimiter_start = NULL;
match->delimiter_len = 0;

if (len < 5) return false; // Must be at least 5 characters: \{%\w%\}
if (str[len - 1] != '}' || str[len - 2] != '%') return false;

const char *curr_delimiter_start;
long curr_delimiter_len = 0;

for (long i = len - 3; i >= 0; i--) {
char c = str[i];

if (is_word_char(c)) {
curr_delimiter_start = str + i;
curr_delimiter_len++;
} else {
if (curr_delimiter_len > 0) {
match->delimiter_start = curr_delimiter_start;
match->delimiter_len = curr_delimiter_len;
}
curr_delimiter_start = NULL;
curr_delimiter_len = 0;
}

if (c == '%' && match->delimiter_len > 0 &&
i - 1 >= 0 && str[i - 1] == '{') {
match->body_len = i - 1;
return true;
}
}

return false;
}

static VALUE raw_parse_method(VALUE self, VALUE tokens)
{
tokenizer_t *tokenizer;
Tokenizer_Get_Struct(tokens, tokenizer);

token_t token;
struct full_token_possibly_invalid_t match;

VALUE block_delimiter = rb_funcall(self, id_block_delimiter, 0);
Check_Type(block_delimiter, T_STRING);
char *block_delimiter_str = RSTRING_PTR(block_delimiter);
long block_delimiter_len = RSTRING_LEN(block_delimiter);

const char *body = NULL;
long body_len = 0;

while (true) {
tokenizer_next(tokenizer, &token);

if (!token.type) break;

if (body == NULL) {
body = token.str_full;
}

if (match_full_token_possibly_invalid(&token, &match)
&& match.delimiter_len == block_delimiter_len
&& memcmp(match.delimiter_start, block_delimiter_str, block_delimiter_len) == 0) {
body_len += match.body_len;
VALUE body_str = rb_enc_str_new(body, body_len, utf8_encoding);
rb_ivar_set(self, id_ivar_body, body_str);
return Qnil;
}

body_len += token.len_full;
}

rb_funcall(self, id_raise_tag_never_closed, 1, rb_funcall(self, id_block_name, 0));
return Qnil;
}

void init_liquid_raw()
{
id_block_name = rb_intern("block_name");
id_raise_tag_never_closed = rb_intern("raise_tag_never_closed");
id_block_delimiter = rb_intern("block_delimiter");
id_ivar_body = rb_intern("@body");

VALUE cLiquidRaw = rb_const_get(mLiquid, rb_intern("Raw"));

rb_define_method(cLiquidRaw, "c_parse", raw_parse_method, 1);
}
6 changes: 6 additions & 0 deletions ext/liquid_c/raw.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef LIQUID_RAW_H
#define LIQUID_RAW_H

void init_liquid_raw();

#endif
5 changes: 5 additions & 0 deletions ext/liquid_c/stringutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ inline static int not_newline(int c)
return c != '\n';
}

inline static bool is_word_char(char c)
{
return ISALNUM(c) || c == '_';
}

#endif

6 changes: 6 additions & 0 deletions lib/liquid/c.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def self.new(source, line_numbers = false, line_number: nil, for_liquid_tag: fal
end
end

Liquid::Raw.class_eval do
alias_method :ruby_parse, :parse
end

Liquid::ParseContext.class_eval do
alias_method :ruby_new_block_body, :new_block_body

Expand Down Expand Up @@ -156,10 +160,12 @@ def enabled=(value)
if value
Liquid::Context.send(:alias_method, :evaluate, :c_evaluate)
Liquid::Context.send(:alias_method, :find_variable, :c_find_variable_kwarg)
Liquid::Raw.send(:alias_method, :parse, :c_parse)
Liquid::VariableLookup.send(:alias_method, :evaluate, :c_evaluate)
else
Liquid::Context.send(:alias_method, :evaluate, :ruby_evaluate)
Liquid::Context.send(:alias_method, :find_variable, :ruby_find_variable)
Liquid::Raw.send(:alias_method, :parse, :ruby_parse)
Liquid::VariableLookup.send(:alias_method, :evaluate, :ruby_evaluate)
end
end
Expand Down

0 comments on commit 1eccb2c

Please sign in to comment.