haml 5.2.2 → 6.0.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/test.yml +13 -9
- data/.gitignore +16 -16
- data/CHANGELOG.md +13 -3
- data/Gemfile +18 -11
- data/MIT-LICENSE +1 -1
- data/README.md +13 -19
- data/Rakefile +95 -93
- data/bin/bench +66 -0
- data/bin/console +11 -0
- data/bin/ruby +3 -0
- data/bin/setup +7 -0
- data/bin/stackprof +27 -0
- data/bin/test +24 -0
- data/exe/haml +6 -0
- data/ext/haml/extconf.rb +10 -0
- data/ext/haml/haml.c +537 -0
- data/ext/haml/hescape.c +108 -0
- data/ext/haml/hescape.h +20 -0
- data/haml.gemspec +39 -37
- data/lib/haml/ambles.rb +20 -0
- data/lib/haml/attribute_builder.rb +135 -179
- data/lib/haml/attribute_compiler.rb +85 -194
- data/lib/haml/attribute_parser.rb +86 -126
- data/lib/haml/cli.rb +154 -0
- data/lib/haml/compiler/children_compiler.rb +126 -0
- data/lib/haml/compiler/comment_compiler.rb +39 -0
- data/lib/haml/compiler/doctype_compiler.rb +46 -0
- data/lib/haml/compiler/script_compiler.rb +116 -0
- data/lib/haml/compiler/silent_script_compiler.rb +24 -0
- data/lib/haml/compiler/tag_compiler.rb +76 -0
- data/lib/haml/compiler.rb +63 -296
- data/lib/haml/dynamic_merger.rb +67 -0
- data/lib/haml/engine.rb +42 -227
- data/lib/haml/error.rb +3 -52
- data/lib/haml/escapable.rb +6 -70
- data/lib/haml/filters/base.rb +12 -0
- data/lib/haml/filters/cdata.rb +20 -0
- data/lib/haml/filters/coffee.rb +17 -0
- data/lib/haml/filters/css.rb +33 -0
- data/lib/haml/filters/erb.rb +10 -0
- data/lib/haml/filters/escaped.rb +22 -0
- data/lib/haml/filters/javascript.rb +33 -0
- data/lib/haml/filters/less.rb +20 -0
- data/lib/haml/filters/markdown.rb +11 -0
- data/lib/haml/filters/plain.rb +29 -0
- data/lib/haml/filters/preserve.rb +22 -0
- data/lib/haml/filters/ruby.rb +10 -0
- data/lib/haml/filters/sass.rb +15 -0
- data/lib/haml/filters/scss.rb +15 -0
- data/lib/haml/filters/text_base.rb +25 -0
- data/lib/haml/filters/tilt_base.rb +49 -0
- data/lib/haml/filters.rb +54 -378
- data/lib/haml/force_escapable.rb +29 -0
- data/lib/haml/haml_error.rb +66 -0
- data/lib/haml/helpers.rb +3 -697
- data/lib/haml/html.rb +22 -0
- data/lib/haml/identity.rb +13 -0
- data/lib/haml/object_ref.rb +30 -0
- data/lib/haml/parser.rb +179 -49
- data/lib/haml/rails_helpers.rb +51 -0
- data/lib/haml/rails_template.rb +55 -0
- data/lib/haml/railtie.rb +7 -45
- data/lib/haml/ruby_expression.rb +32 -0
- data/lib/haml/string_splitter.rb +20 -0
- data/lib/haml/template.rb +15 -34
- data/lib/haml/temple_line_counter.rb +2 -1
- data/lib/haml/util.rb +17 -15
- data/lib/haml/version.rb +1 -2
- data/lib/haml.rb +8 -20
- metadata +211 -57
- data/.gitmodules +0 -3
- data/.yardopts +0 -22
- data/TODO +0 -24
- data/benchmark.rb +0 -70
- data/bin/haml +0 -9
- data/lib/haml/.gitattributes +0 -1
- data/lib/haml/buffer.rb +0 -182
- data/lib/haml/exec.rb +0 -347
- data/lib/haml/generator.rb +0 -42
- data/lib/haml/helpers/action_view_extensions.rb +0 -60
- data/lib/haml/helpers/action_view_mods.rb +0 -132
- data/lib/haml/helpers/action_view_xss_mods.rb +0 -60
- data/lib/haml/helpers/safe_erubi_template.rb +0 -20
- data/lib/haml/helpers/safe_erubis_template.rb +0 -33
- data/lib/haml/helpers/xss_mods.rb +0 -114
- data/lib/haml/options.rb +0 -273
- data/lib/haml/plugin.rb +0 -54
- data/lib/haml/sass_rails_filter.rb +0 -47
- data/lib/haml/template/options.rb +0 -27
- data/lib/haml/temple_engine.rb +0 -124
- data/yard/default/.gitignore +0 -1
- data/yard/default/fulldoc/html/css/common.sass +0 -15
- data/yard/default/layout/html/footer.erb +0 -12
data/lib/haml/compiler.rb
CHANGED
@@ -1,329 +1,96 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'haml/
|
4
|
-
require 'haml/
|
5
|
-
require 'haml/
|
2
|
+
require 'haml/compiler/children_compiler'
|
3
|
+
require 'haml/compiler/comment_compiler'
|
4
|
+
require 'haml/compiler/doctype_compiler'
|
5
|
+
require 'haml/compiler/script_compiler'
|
6
|
+
require 'haml/compiler/silent_script_compiler'
|
7
|
+
require 'haml/compiler/tag_compiler'
|
8
|
+
require 'haml/filters'
|
9
|
+
require 'haml/identity'
|
6
10
|
|
7
11
|
module Haml
|
8
12
|
class Compiler
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
18
|
-
@filters = Filters.defined.merge(options[:filters])
|
19
|
-
@attribute_compiler = AttributeCompiler.new(@options)
|
13
|
+
def initialize(options = {})
|
14
|
+
identity = Identity.new
|
15
|
+
@children_compiler = ChildrenCompiler.new
|
16
|
+
@comment_compiler = CommentCompiler.new
|
17
|
+
@doctype_compiler = DoctypeCompiler.new(options)
|
18
|
+
@filter_compiler = Filters.new(options)
|
19
|
+
@script_compiler = ScriptCompiler.new(identity, options)
|
20
|
+
@silent_script_compiler = SilentScriptCompiler.new
|
21
|
+
@tag_compiler = TagCompiler.new(identity, options)
|
20
22
|
end
|
21
23
|
|
22
|
-
def call(
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def compile(node)
|
28
|
-
parent, @node = @node, node
|
29
|
-
if node.children.empty?
|
30
|
-
send(:"compile_#{node.type}")
|
31
|
-
else
|
32
|
-
send(:"compile_#{node.type}") {node.children.each {|c| compile c}}
|
33
|
-
end
|
34
|
-
ensure
|
35
|
-
@node = parent
|
24
|
+
def call(ast)
|
25
|
+
return runtime_error(ast) if ast.is_a?(HamlError)
|
26
|
+
compile(ast)
|
27
|
+
rescue Error => e
|
28
|
+
runtime_error(e)
|
36
29
|
end
|
37
30
|
|
38
31
|
private
|
39
32
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
33
|
+
def compile(node)
|
34
|
+
case node.type
|
35
|
+
when :root
|
36
|
+
compile_children(node)
|
37
|
+
when :comment
|
38
|
+
compile_comment(node)
|
39
|
+
when :doctype
|
40
|
+
compile_doctype(node)
|
41
|
+
when :filter
|
42
|
+
compile_filter(node)
|
43
|
+
when :plain
|
44
|
+
compile_plain(node)
|
45
|
+
when :script
|
46
|
+
compile_script(node)
|
47
|
+
when :silent_script
|
48
|
+
compile_silent_script(node)
|
49
|
+
when :tag
|
50
|
+
compile_tag(node)
|
51
|
+
when :haml_comment
|
52
|
+
[:multi]
|
55
53
|
else
|
56
|
-
|
54
|
+
raise InternalError.new("Unexpected node type: #{node.type}")
|
57
55
|
end
|
58
56
|
end
|
59
57
|
|
60
|
-
def
|
61
|
-
|
62
|
-
:preserve_script => @node.value[:preserve],
|
63
|
-
:escape_html => @node.value[:escape_html],
|
64
|
-
:nuke_inner_whitespace => nuke_inner_whitespace?(@node),
|
65
|
-
&block)
|
58
|
+
def compile_children(node)
|
59
|
+
@children_compiler.compile(node) { |n| compile(n) }
|
66
60
|
end
|
67
61
|
|
68
|
-
def
|
69
|
-
|
70
|
-
push_silent(@node.value[:text])
|
71
|
-
keyword = @node.value[:keyword]
|
72
|
-
|
73
|
-
if block_given?
|
74
|
-
yield
|
75
|
-
push_silent("end", :can_suppress) unless @node.value[:dont_push_end]
|
76
|
-
elsif keyword == "end"
|
77
|
-
if @node.parent.children.last.equal?(@node)
|
78
|
-
# Since this "end" is ending the block,
|
79
|
-
# we don't need to generate an additional one
|
80
|
-
@node.parent.value[:dont_push_end] = true
|
81
|
-
end
|
82
|
-
# Don't restore dont_* for end because it isn't a conditional branch.
|
83
|
-
end
|
62
|
+
def compile_comment(node)
|
63
|
+
@comment_compiler.compile(node) { |n| compile_children(n) }
|
84
64
|
end
|
85
65
|
|
86
|
-
def
|
87
|
-
|
88
|
-
def compile_tag
|
89
|
-
t = @node.value
|
90
|
-
|
91
|
-
# Get rid of whitespace outside of the tag if we need to
|
92
|
-
rstrip_buffer! if t[:nuke_outer_whitespace]
|
93
|
-
|
94
|
-
if @options.suppress_eval
|
95
|
-
object_ref = :nil
|
96
|
-
parse = false
|
97
|
-
value = t[:parse] ? nil : t[:value]
|
98
|
-
dynamic_attributes = Haml::Parser::DynamicAttributes.new
|
99
|
-
else
|
100
|
-
object_ref = t[:object_ref]
|
101
|
-
parse = t[:parse]
|
102
|
-
value = t[:value]
|
103
|
-
dynamic_attributes = t[:dynamic_attributes]
|
104
|
-
end
|
105
|
-
|
106
|
-
if @options[:trace]
|
107
|
-
t[:attributes].merge!({"data-trace" => @options.filename.split('/views').last << ":" << @node.line.to_s})
|
108
|
-
end
|
109
|
-
|
110
|
-
push_text("<#{t[:name]}")
|
111
|
-
push_temple(@attribute_compiler.compile(t[:attributes], object_ref, dynamic_attributes))
|
112
|
-
push_text(
|
113
|
-
if t[:self_closing] && @options.xhtml?
|
114
|
-
" />#{"\n" unless t[:nuke_outer_whitespace]}"
|
115
|
-
else
|
116
|
-
">#{"\n" unless (t[:self_closing] && @options.html?) ? t[:nuke_outer_whitespace] : (!block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace])}"
|
117
|
-
end
|
118
|
-
)
|
119
|
-
|
120
|
-
if value && !parse
|
121
|
-
push_text("#{value}</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
|
122
|
-
end
|
123
|
-
|
124
|
-
return if t[:self_closing]
|
125
|
-
|
126
|
-
if value.nil?
|
127
|
-
yield if block_given?
|
128
|
-
rstrip_buffer! if t[:nuke_inner_whitespace]
|
129
|
-
push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
|
130
|
-
return
|
131
|
-
end
|
132
|
-
|
133
|
-
if parse
|
134
|
-
push_script(value, t.merge(:in_tag => true))
|
135
|
-
push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
|
136
|
-
end
|
66
|
+
def compile_doctype(node)
|
67
|
+
@doctype_compiler.compile(node)
|
137
68
|
end
|
138
69
|
|
139
|
-
def
|
140
|
-
|
141
|
-
revealed = @node.value[:revealed]
|
142
|
-
|
143
|
-
open = "<!--#{condition}#{'<!-->' if revealed}"
|
144
|
-
|
145
|
-
close = "#{'<!--' if revealed}#{'<![endif]' if condition}-->"
|
146
|
-
|
147
|
-
unless block_given?
|
148
|
-
push_text("#{open} ")
|
149
|
-
|
150
|
-
if @node.value[:parse]
|
151
|
-
push_script(@node.value[:text], :in_tag => true, :nuke_inner_whitespace => true)
|
152
|
-
else
|
153
|
-
push_text(@node.value[:text])
|
154
|
-
end
|
155
|
-
|
156
|
-
push_text(" #{close}\n")
|
157
|
-
return
|
158
|
-
end
|
159
|
-
|
160
|
-
push_text("#{open}\n")
|
161
|
-
yield if block_given?
|
162
|
-
push_text("#{close}\n")
|
163
|
-
end
|
164
|
-
|
165
|
-
def compile_doctype
|
166
|
-
doctype = text_for_doctype
|
167
|
-
push_text("#{doctype}\n") if doctype
|
168
|
-
end
|
169
|
-
|
170
|
-
def compile_filter
|
171
|
-
unless (filter = @filters[@node.value[:name]])
|
172
|
-
name = @node.value[:name]
|
173
|
-
if ["maruku", "textile"].include?(name)
|
174
|
-
raise Error.new(Error.message(:install_haml_contrib, name), @node.line - 1)
|
175
|
-
else
|
176
|
-
raise Error.new(Error.message(:filter_not_defined, name), @node.line - 1)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
filter.internal_compile(self, @node.value[:text])
|
180
|
-
end
|
181
|
-
|
182
|
-
def text_for_doctype
|
183
|
-
if @node.value[:type] == "xml"
|
184
|
-
return nil if @options.html?
|
185
|
-
wrapper = @options.attr_wrapper
|
186
|
-
return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{@node.value[:encoding] || "utf-8"}#{wrapper} ?>"
|
187
|
-
end
|
188
|
-
|
189
|
-
if @options.html5?
|
190
|
-
'<!DOCTYPE html>'
|
191
|
-
elsif @options.xhtml?
|
192
|
-
if @node.value[:version] == "1.1"
|
193
|
-
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
|
194
|
-
elsif @node.value[:version] == "5"
|
195
|
-
'<!DOCTYPE html>'
|
196
|
-
else
|
197
|
-
case @node.value[:type]
|
198
|
-
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
199
|
-
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
200
|
-
when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
|
201
|
-
when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
|
202
|
-
when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
|
203
|
-
else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
elsif @options.html4?
|
208
|
-
case @node.value[:type]
|
209
|
-
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
|
210
|
-
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
|
211
|
-
else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
|
212
|
-
end
|
213
|
-
end
|
70
|
+
def compile_filter(node)
|
71
|
+
@filter_compiler.compile(node)
|
214
72
|
end
|
215
73
|
|
216
|
-
|
217
|
-
|
218
|
-
def push_silent(text, can_suppress = false)
|
219
|
-
flush_merged_text
|
220
|
-
return if can_suppress && @options.suppress_eval?
|
221
|
-
newline = (text == "end") ? ";" : "\n"
|
222
|
-
@temple << [:code, "#{resolve_newlines}#{text}#{newline}"]
|
223
|
-
@output_line = @output_line + text.count("\n") + newline.count("\n")
|
74
|
+
def compile_plain(node)
|
75
|
+
[:static, node.value[:text]]
|
224
76
|
end
|
225
77
|
|
226
|
-
|
227
|
-
|
228
|
-
@to_merge << [:text, text]
|
78
|
+
def compile_script(node)
|
79
|
+
@script_compiler.compile(node) { |n| compile_children(n) }
|
229
80
|
end
|
230
81
|
|
231
|
-
def
|
232
|
-
|
233
|
-
@temple.concat([[:newline]] * resolve_newlines.count("\n"))
|
234
|
-
@temple << temple
|
235
|
-
@output_line += TempleLineCounter.count_lines(temple)
|
82
|
+
def compile_silent_script(node)
|
83
|
+
@silent_script_compiler.compile(node) { |n| compile_children(n) }
|
236
84
|
end
|
237
85
|
|
238
|
-
def
|
239
|
-
|
240
|
-
|
241
|
-
@to_merge.each do |type, val|
|
242
|
-
case type
|
243
|
-
when :text
|
244
|
-
@temple << [:static, val]
|
245
|
-
when :script
|
246
|
-
@temple << [:dynamic, val]
|
247
|
-
else
|
248
|
-
raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Compiler@to_merge.")
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
@to_merge = []
|
86
|
+
def compile_tag(node)
|
87
|
+
@tag_compiler.compile(node) { |n| compile_children(n) }
|
253
88
|
end
|
254
89
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
# the result before it is added to `@buffer`
|
260
|
-
def push_script(text, opts = {})
|
261
|
-
return if @options.suppress_eval?
|
262
|
-
|
263
|
-
no_format = !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
|
264
|
-
|
265
|
-
unless block_given?
|
266
|
-
push_generated_script(no_format ? "(#{text}\n).to_s" : build_script_formatter("(#{text}\n)", opts))
|
267
|
-
push_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
|
268
|
-
return
|
269
|
-
end
|
270
|
-
|
271
|
-
flush_merged_text
|
272
|
-
push_silent "haml_temp = #{text}"
|
273
|
-
yield
|
274
|
-
push_silent('end', :can_suppress) unless @node.value[:dont_push_end]
|
275
|
-
@temple << [:dynamic, no_format ? 'haml_temp.to_s;' : build_script_formatter('haml_temp', opts)]
|
276
|
-
end
|
277
|
-
|
278
|
-
def build_script_formatter(text, opts)
|
279
|
-
text = "(#{text}).to_s"
|
280
|
-
if opts[:escape_html]
|
281
|
-
text = "::Haml::Helpers.html_escape(#{text})"
|
282
|
-
end
|
283
|
-
if opts[:nuke_inner_whitespace]
|
284
|
-
text = "(#{text}).strip"
|
285
|
-
end
|
286
|
-
if opts[:preserve_tag]
|
287
|
-
text = "_hamlout.fix_textareas!(::Haml::Helpers.preserve(#{text}))"
|
288
|
-
elsif opts[:preserve_script]
|
289
|
-
text = "_hamlout.fix_textareas!(::Haml::Helpers.find_and_preserve(#{text}, _hamlout.options[:preserve]))"
|
290
|
-
end
|
291
|
-
"#{text};"
|
292
|
-
end
|
293
|
-
|
294
|
-
def push_generated_script(text)
|
295
|
-
@to_merge << [:script, resolve_newlines + text]
|
296
|
-
@output_line += text.count("\n")
|
297
|
-
end
|
298
|
-
|
299
|
-
def resolve_newlines
|
300
|
-
diff = @node.line - @output_line
|
301
|
-
return "" if diff <= 0
|
302
|
-
@output_line = @node.line
|
303
|
-
"\n" * diff
|
304
|
-
end
|
305
|
-
|
306
|
-
# Get rid of and whitespace at the end of the buffer
|
307
|
-
# or the merged text
|
308
|
-
def rstrip_buffer!(index = -1)
|
309
|
-
last = @to_merge[index]
|
310
|
-
if last.nil?
|
311
|
-
push_silent("_hamlout.rstrip!", false)
|
312
|
-
return
|
313
|
-
end
|
314
|
-
|
315
|
-
case last.first
|
316
|
-
when :text
|
317
|
-
last[1] = last[1].rstrip
|
318
|
-
if last[1].empty?
|
319
|
-
@to_merge.slice! index
|
320
|
-
rstrip_buffer! index
|
321
|
-
end
|
322
|
-
when :script
|
323
|
-
last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
|
324
|
-
rstrip_buffer! index - 1
|
325
|
-
else
|
326
|
-
raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Compiler@to_merge.")
|
90
|
+
def runtime_error(error)
|
91
|
+
[:multi].tap do |temple|
|
92
|
+
error.line.times { temple << [:newline] } if error.line
|
93
|
+
temple << [:code, %Q[raise #{error.class}.new(%q[#{error.message}], #{error.line.inspect})]]
|
327
94
|
end
|
328
95
|
end
|
329
96
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Haml
|
3
|
+
# Compile [:multi, [:static, 'foo'], [:dynamic, 'bar']] to [:dynamic, '"foo#{bar}"']
|
4
|
+
class DynamicMerger < Temple::Filter
|
5
|
+
def on_multi(*exps)
|
6
|
+
exps = exps.dup
|
7
|
+
result = [:multi]
|
8
|
+
buffer = []
|
9
|
+
|
10
|
+
until exps.empty?
|
11
|
+
type, arg = exps.first
|
12
|
+
if type == :dynamic && arg.count("\n") == 0
|
13
|
+
buffer << exps.shift
|
14
|
+
elsif type == :static && exps.size > (count = arg.count("\n")) &&
|
15
|
+
exps[1, count].all? { |e| e == [:newline] }
|
16
|
+
(1 + count).times { buffer << exps.shift }
|
17
|
+
elsif type == :newline && exps.size > (count = count_newline(exps)) &&
|
18
|
+
exps[count].first == :static && count == exps[count].last.count("\n")
|
19
|
+
(count + 1).times { buffer << exps.shift }
|
20
|
+
else
|
21
|
+
result.concat(merge_dynamic(buffer))
|
22
|
+
buffer = []
|
23
|
+
result << compile(exps.shift)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
result.concat(merge_dynamic(buffer))
|
27
|
+
|
28
|
+
result.size == 2 ? result[1] : result
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def merge_dynamic(exps)
|
34
|
+
# Merge exps only when they have both :static and :dynamic
|
35
|
+
unless exps.any? { |type,| type == :static } && exps.any? { |type,| type == :dynamic }
|
36
|
+
return exps
|
37
|
+
end
|
38
|
+
|
39
|
+
strlit_body = String.new
|
40
|
+
exps.each do |type, arg|
|
41
|
+
case type
|
42
|
+
when :static
|
43
|
+
strlit_body << arg.dump.sub!(/\A"/, '').sub!(/"\z/, '').gsub('\n', "\n")
|
44
|
+
when :dynamic
|
45
|
+
strlit_body << "\#{#{arg}}"
|
46
|
+
when :newline
|
47
|
+
# newline is added by `gsub('\n', "\n")`
|
48
|
+
else
|
49
|
+
raise "unexpected type #{type.inspect} is given to #merge_dynamic"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
[[:dynamic, "%Q\0#{strlit_body}\0"]]
|
53
|
+
end
|
54
|
+
|
55
|
+
def count_newline(exps)
|
56
|
+
count = 0
|
57
|
+
exps.each do |exp|
|
58
|
+
if exp == [:newline]
|
59
|
+
count += 1
|
60
|
+
else
|
61
|
+
return count
|
62
|
+
end
|
63
|
+
end
|
64
|
+
return count
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|