Skip to content

Commit

Permalink
Add freeze_template_literals option to avoid String#freeze
Browse files Browse the repository at this point in the history
`"foo".freeze` generates an `opt_str_freeze` instruction while
`"foo"` when frozen string literals is enabled generated a
`putobject` instruction.

The performance difference between the two is not that big,
but it's still less work as it doesn't have to check wether
String#freeze was redefined.

It also generate much less native code with YJIT.
  • Loading branch information
byroot committed Jul 27, 2022
1 parent d8a3d0a commit a9a2d35
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 4 deletions.
9 changes: 7 additions & 2 deletions lib/erubi.rb
Expand Up @@ -12,7 +12,6 @@ module Erubi
RANGE_LAST = -1..-1
end

TEXT_END = RUBY_VERSION >= '2.1' ? "'.freeze" : "'"
MATCH_METHOD = RUBY_VERSION >= '2.4' ? :match? : :match
SKIP_DEFINED_FOR_INSTANCE_VARIABLE = RUBY_VERSION > '3'
# :nocov:
Expand Down Expand Up @@ -69,6 +68,7 @@ class Engine
# +:escape_html+ :: Same as +:escape+, with lower priority.
# +:filename+ :: The filename for the template.
# +:freeze+ :: Whether to enable frozen string literals in the resulting source code.
# +:freeze_template_literals+ :: Wether suffix all literal strings with <tt>.freeze</tt> (default: <tt>true</tt> on Ruby 2.1+, <tt>false</tt> on Ruby2.0 and older).
# +:literal_prefix+ :: The prefix to output when using escaped tag delimiters (default <tt>'<%'</tt>).
# +:literal_postfix+ :: The postfix to output when using escaped tag delimiters (default <tt>'%>'</tt>).
# +:outvar+ :: Same as +:bufvar+, with lower priority.
Expand All @@ -89,6 +89,11 @@ def initialize(input, properties={})
preamble = properties[:preamble] || "#{bufvar} = #{bufval};"
postamble = properties[:postamble] || "#{bufvar}.to_s\n"
@chain_appends = properties[:chain_appends]
@text_end = if properties.fetch(:freeze_template_literals, RUBY_VERSION >= '2.1')
"'.freeze"
else
"'"
end

@buffer_on_stack = false
@src = src = properties[:src] || String.new
Expand Down Expand Up @@ -200,7 +205,7 @@ def add_text(text)
text.gsub!(/['\\]/, '\\\\\&')
end

with_buffer{@src << " << '" << text << TEXT_END}
with_buffer{@src << " << '" << text << @text_end}
end

# Add ruby code to the template
Expand Down
92 changes: 90 additions & 2 deletions test/test.rb
Expand Up @@ -35,11 +35,11 @@
@options = {}
end

def check_output(input, src, result, &block)
def check_output(input, src, result, strip_freeze: RUBY_VERSION >= '2.1', &block)
t = (@options[:engine] || Erubi::Engine).new(input, @options)
tsrc = t.src
eval(tsrc, block.binding).must_equal result
tsrc = tsrc.gsub(/\.freeze/, '') if RUBY_VERSION >= '2.1'
tsrc = tsrc.gsub(/\.freeze/, '') if strip_freeze
tsrc.must_equal src
end

Expand Down Expand Up @@ -336,6 +336,94 @@ def self.quux
END3
end

it "should handle :freeze_template_literals => true option" do
@options[:freeze_template_literals] = true
list = list = ['&\'<>"2']
check_output(<<END1, <<END2, <<END3, strip_freeze: false){}
<table>
<tbody>
<% i = 0
list.each_with_index do |item, i| %>
<tr>
<td><%= i+1 %></td>
<td><%== item %></td>
</tr>
<% end %>
</tbody>
</table>
<%== i+1 %>
END1
_buf = ::String.new; _buf << '<table>
<tbody>
'.freeze; i = 0
list.each_with_index do |item, i|
_buf << ' <tr>
<td>'.freeze; _buf << ( i+1 ).to_s; _buf << '</td>
<td>'.freeze; _buf << ::Erubi.h(( item )); _buf << '</td>
</tr>
'.freeze; end
_buf << ' </tbody>
</table>
'.freeze; _buf << ::Erubi.h(( i+1 )); _buf << '
'.freeze;
_buf.to_s
END2
<table>
<tbody>
<tr>
<td>1</td>
<td>&amp;&#39;&lt;&gt;&quot;2</td>
</tr>
</tbody>
</table>
1
END3
end

it "should handle :freeze_template_literals => false option" do
@options[:freeze_template_literals] = false
list = list = ['&\'<>"2']
check_output(<<END1, <<END2, <<END3, strip_freeze: false){}
<table>
<tbody>
<% i = 0
list.each_with_index do |item, i| %>
<tr>
<td><%= i+1 %></td>
<td><%== item %></td>
</tr>
<% end %>
</tbody>
</table>
<%== i+1 %>
END1
_buf = ::String.new; _buf << '<table>
<tbody>
'; i = 0
list.each_with_index do |item, i|
_buf << ' <tr>
<td>'; _buf << ( i+1 ).to_s; _buf << '</td>
<td>'; _buf << ::Erubi.h(( item )); _buf << '</td>
</tr>
'; end
_buf << ' </tbody>
</table>
'; _buf << ::Erubi.h(( i+1 )); _buf << '
';
_buf.to_s
END2
<table>
<tbody>
<tr>
<td>1</td>
<td>&amp;&#39;&lt;&gt;&quot;2</td>
</tr>
</tbody>
</table>
1
END3
end

it "should handle ensure option with no bufvar" do
list = list = ['&\'<>"2']
@options[:ensure] = true
Expand Down

0 comments on commit a9a2d35

Please sign in to comment.