trenni 1.7.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +7 -3
  4. data/Gemfile +4 -0
  5. data/README.md +72 -10
  6. data/Rakefile +85 -0
  7. data/benchmark/call_vs_yield.rb +51 -0
  8. data/benchmark/interpolation_vs_concat.rb +29 -0
  9. data/benchmark/io_vs_string.rb +14 -4
  10. data/entities.json +2233 -0
  11. data/ext/trenni/extconf.rb +13 -0
  12. data/ext/trenni/markup.c +1981 -0
  13. data/ext/trenni/markup.h +6 -0
  14. data/ext/trenni/markup.rl +223 -0
  15. data/ext/trenni/template.c +1113 -0
  16. data/ext/trenni/template.h +6 -0
  17. data/ext/trenni/template.rl +77 -0
  18. data/ext/trenni/trenni.c +64 -0
  19. data/ext/trenni/trenni.h +28 -0
  20. data/lib/trenni.rb +1 -0
  21. data/lib/trenni/buffer.rb +13 -2
  22. data/lib/trenni/builder.rb +54 -47
  23. data/lib/trenni/entities.rb +2154 -0
  24. data/lib/trenni/entities.trenni +34 -0
  25. data/lib/trenni/fallback/markup.rb +1648 -0
  26. data/lib/trenni/fallback/markup.rl +236 -0
  27. data/lib/trenni/fallback/template.rb +843 -0
  28. data/lib/trenni/fallback/template.rl +97 -0
  29. data/lib/trenni/markup.rb +76 -0
  30. data/lib/trenni/native.rb +28 -0
  31. data/lib/trenni/parse_delegate.rb +34 -0
  32. data/lib/trenni/{scanner.rb → parse_error.rb} +9 -54
  33. data/lib/trenni/parsers.rb +12 -0
  34. data/lib/trenni/substitutions.rb +45 -0
  35. data/lib/trenni/template.rb +52 -135
  36. data/lib/trenni/version.rb +1 -1
  37. data/parsers/trenni/entities.rl +11 -0
  38. data/parsers/trenni/markup.rl +43 -0
  39. data/parsers/trenni/template.rl +60 -0
  40. data/spec/trenni/builder_spec.rb +37 -62
  41. data/spec/trenni/corpus/large.rb +4605 -0
  42. data/spec/trenni/corpus/large.xhtml +726 -0
  43. data/spec/trenni/markup_parser_spec.rb +233 -0
  44. data/spec/trenni/markup_spec.rb +48 -0
  45. data/spec/trenni/parsers_performance_spec.rb +44 -0
  46. data/spec/trenni/template_error_spec.rb +36 -0
  47. data/spec/trenni/template_performance_spec.rb +102 -0
  48. data/spec/trenni/template_spec.rb +90 -64
  49. data/spec/trenni/template_spec/basic.trenni +1 -0
  50. data/spec/trenni/template_spec/capture.trenni +2 -2
  51. data/spec/trenni/template_spec/error.trenni +4 -0
  52. data/trenni.gemspec +9 -6
  53. metadata +57 -15
  54. data/lib/trenni/parser.rb +0 -153
  55. data/spec/trenni/parser_spec.rb +0 -144
@@ -0,0 +1,97 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ %%{
22
+ machine template;
23
+
24
+ action instruction_begin {
25
+ instruction_begin = p
26
+ }
27
+
28
+ action instruction_end {
29
+ instruction_end = p
30
+ }
31
+
32
+ action emit_instruction {
33
+ delegate.instruction(data.byteslice(instruction_begin...instruction_end))
34
+ }
35
+
36
+ action emit_instruction_line {
37
+ delegate.instruction(data.byteslice(instruction_begin...instruction_end), "\n")
38
+ }
39
+
40
+ action instruction_error {
41
+ raise ParseError.new("failed to parse instruction", buffer, p)
42
+ }
43
+
44
+ action expression_begin {
45
+ expression_begin = p
46
+ }
47
+
48
+ action expression_end {
49
+ expression_end = p
50
+ }
51
+
52
+ action emit_expression {
53
+ delegate.expression(data.byteslice(expression_begin...expression_end))
54
+ }
55
+
56
+ action expression_error {
57
+ raise ParseError.new("failed to parse expression", buffer, p)
58
+ }
59
+
60
+ action emit_text {
61
+ delegate.text(data.byteslice(ts...te))
62
+ }
63
+
64
+ # This magic ensures that we process bytes.
65
+ getkey bytes[p];
66
+
67
+ include template "trenni/template.rl";
68
+ }%%
69
+
70
+ require_relative '../parse_error'
71
+
72
+ module Trenni
73
+ module Fallback
74
+ %% write data;
75
+
76
+ def self.parse_template(buffer, delegate)
77
+ data = buffer.read
78
+ bytes = data.bytes
79
+
80
+ p = 0
81
+ pe = eof = data.bytesize
82
+ stack = []
83
+
84
+ expression_begin = expression_end = nil
85
+ instruction_begin = instruction_end = nil
86
+
87
+ %% write init;
88
+ %% write exec;
89
+
90
+ if p != eof
91
+ raise ParseError.new("could not consume all input", buffer, p)
92
+ end
93
+
94
+ return nil
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,76 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'substitutions'
22
+
23
+ module Trenni
24
+ # A wrapper which indicates that `value` can be appended to the output buffer without any changes.
25
+ module Markup
26
+ # Generates a string suitable for concatenating with the output buffer.
27
+ def self.escape(value)
28
+ if value.is_a? Markup
29
+ value
30
+ elsif value
31
+ MarkupString.new(value.to_s)
32
+ else
33
+ # String#<< won't accept nil, so we return an empty string, thus ensuring a fixed point function:
34
+ EMPTY
35
+ end
36
+ end
37
+
38
+ def escape(value)
39
+ Markup.escape(value)
40
+ end
41
+ end
42
+
43
+ # Initialized from text which is escaped to use HTML entities.
44
+ class MarkupString < String
45
+ include Markup
46
+
47
+ # This is only casually related to HTML, it's just enough so that it would not be mis-interpreted by `Trenni::Parser`.
48
+ ESCAPE = Substitutions.new("&" => "&amp;", "<" => "&lt;", ">" => "&gt;", "\"" => "&quot;")
49
+
50
+ # Convert ESCAPE characters into their corresponding entities.
51
+ def initialize(string = nil, escape = true)
52
+ if string
53
+ super(string)
54
+ ESCAPE.gsub!(self) if escape
55
+ else
56
+ super()
57
+ end
58
+ end
59
+
60
+ def self.raw(string)
61
+ self.new(string, false)
62
+ end
63
+ end
64
+
65
+ module Script
66
+ def self.json(value)
67
+ MarkupString.new(JSON.dump(value), false)
68
+ end
69
+ end
70
+
71
+ def self.MarkupString(value)
72
+ Markup.escape(value)
73
+ end
74
+
75
+ Markup::EMPTY = String.new.extend(Markup).freeze
76
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'parse_error'
22
+
23
+ begin
24
+ # Load native code:
25
+ require_relative 'trenni'
26
+ rescue LoadError
27
+ warn "Could not load native implementation: #{$!}" if $VERBOSE
28
+ end
@@ -0,0 +1,34 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Trenni
22
+ # This is a sample delegate for capturing all events. It's only use is for testing.
23
+ class ParseDelegate
24
+ def initialize
25
+ @events = []
26
+ end
27
+
28
+ attr :events
29
+
30
+ def method_missing(*args)
31
+ @events << args
32
+ end
33
+ end
34
+ end
@@ -20,37 +20,24 @@
20
20
 
21
21
  require_relative 'buffer'
22
22
 
23
- require 'strscan'
24
-
25
23
  module Trenni
26
24
  class ParseError < StandardError
27
- def initialize(message, scanner, positions = nil)
25
+ def initialize(message, buffer, offset)
28
26
  super(message)
29
27
 
30
- @path = scanner.path
31
-
32
- @locations = []
33
-
34
- if positions
35
- positions.each do |position|
36
- @locations << Location.new(scanner.string, position)
37
- end
38
- else
39
- @locations = [Location.new(scanner.string, scanner.pos)]
40
- end
41
-
42
- @location = @locations.first
43
-
44
- @input_name = nil
28
+ @buffer = buffer
29
+ @offset = offset
45
30
  end
46
31
 
47
- attr :locations
48
- attr :location
32
+ def location
33
+ @location ||= Location.new(@buffer.read, @offset)
34
+ end
49
35
 
36
+ attr :buffer
50
37
  attr :path
51
38
 
52
39
  def to_s
53
- "#{@path}#{@location}: #{super}\n#{location.line_text}"
40
+ "#{buffer.path}#{location}: #{super}\n#{location.line_text}"
54
41
  end
55
42
  end
56
43
 
@@ -82,7 +69,7 @@ module Trenni
82
69
  end
83
70
 
84
71
  def to_s
85
- "[#{self.line_number}]"
72
+ "[#{self.line_number}:#{self.line_offset}]"
86
73
  end
87
74
 
88
75
  # The line that contains the @offset (base 0 indexing).
@@ -103,36 +90,4 @@ module Trenni
103
90
 
104
91
  attr :line_text
105
92
  end
106
-
107
- class StringScanner < ::StringScanner
108
- def initialize(buffer)
109
- @buffer = buffer
110
-
111
- super(buffer.read)
112
- end
113
-
114
- attr :buffer
115
-
116
- def path
117
- @buffer.path
118
- end
119
-
120
- STUCK_MESSAGE = "Parser is stuck!".freeze
121
-
122
- def stuck?(position)
123
- self.pos == position
124
- end
125
-
126
- def raise_if_stuck(position, message = STUCK_MESSAGE)
127
- if stuck?(position)
128
- parse_error!(message)
129
- end
130
- end
131
-
132
- def parse_error!(message, positions = nil)
133
- positions ||= [self.pos]
134
-
135
- raise ParseError.new(message, self, positions)
136
- end
137
- end
138
93
  end
@@ -0,0 +1,12 @@
1
+
2
+ require_relative 'native'
3
+ require_relative 'parse_delegate'
4
+
5
+ if defined? Trenni::Native and !ENV['TRENNI_PREFER_FALLBACK']
6
+ Trenni::Parsers = Trenni::Native
7
+ else
8
+ require_relative 'fallback/markup'
9
+ require_relative 'fallback/template'
10
+
11
+ Trenni::Parsers = Trenni::Fallback
12
+ end
@@ -0,0 +1,45 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Trenni
22
+ class Substitutions
23
+ def initialize(tokens)
24
+ @tokens = tokens
25
+ end
26
+
27
+ def patterns
28
+ @tokens.keys
29
+ end
30
+
31
+ def pattern
32
+ @pattern ||= Regexp.union(patterns)
33
+ end
34
+
35
+ attr :tokens
36
+
37
+ def gsub!(string)
38
+ string.gsub!(pattern) {|match| @tokens[match]}
39
+ end
40
+
41
+ def gsub(string)
42
+ string.gsub(pattern) {|match| @tokens[match]}
43
+ end
44
+ end
45
+ end
@@ -18,197 +18,114 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require_relative 'scanner'
21
+ require_relative 'parsers'
22
22
 
23
23
  module Trenni
24
24
  # The output variable that will be used in templates:
25
- OUT = '_out'
25
+ OUT = :_out
26
26
 
27
27
  class Template
28
28
  # Returns the output produced by calling the given block.
29
29
  def self.capture(*args, &block)
30
- out = eval(OUT, block.binding)
31
- top = out.size
30
+ scope = block.binding
31
+ output_buffer = scope.local_variable_get(OUT)
32
32
 
33
- block.call(*args)
33
+ capture_buffer = String.new.force_encoding(output_buffer.encoding)
34
+ scope.local_variable_set(OUT, capture_buffer)
34
35
 
35
- return out.pop(out.size - top).join
36
+ begin
37
+ block.call(*args)
38
+ ensure
39
+ scope.local_variable_set(OUT, output_buffer)
40
+ end
41
+
42
+ return capture_buffer
36
43
  end
37
44
 
38
45
  # Returns the buffer used for capturing output.
39
46
  def self.buffer(binding)
40
- eval(OUT, binding)
47
+ binding.local_variable_get(OUT)
41
48
  end
42
49
 
43
50
  class Assembler
44
- def initialize
45
- @parts = []
51
+ def initialize(filter: String, encoding: Encoding::UTF_8)
52
+ @code = String.new.force_encoding(encoding)
53
+ @filter = filter
46
54
  end
47
55
 
48
- attr :parts
56
+ attr :code
49
57
 
50
58
  # Output raw text to the template.
51
59
  def text(text)
52
60
  text = text.gsub("'", "\\\\'")
53
- @parts << "#{OUT}<<'#{text}';"
61
+ @code << "#{OUT}<<'#{text}';"
54
62
 
55
63
  # This is an interesting approach, but it doens't preserve newlines or tabs as raw characters, so template line numbers don't match up.
56
64
  # @parts << "#{OUT}<<#{text.dump};"
57
65
  end
58
66
 
59
67
  # Output a ruby expression (or part of).
60
- def expression(text)
61
- @parts << "#{text};"
68
+ def instruction(text, postfix = nil)
69
+ @code << text << (postfix || ';')
62
70
  end
63
71
 
64
72
  # Output a string interpolation.
65
- def interpolation(text)
66
- @parts << "#{OUT}<<(#{text});"
67
- end
68
-
69
- CODE_PREFIX = "#{OUT}=[];".freeze
70
- CODE_POSTFIX = "#{OUT}".freeze
71
-
72
- def code
73
- return [CODE_PREFIX, *@parts, CODE_POSTFIX].join
73
+ def expression(text)
74
+ # Double brackets are required here to handle expressions like #{foo rescue "bar"}.
75
+ @code << "#{OUT}<<#{@filter}((#{text}));"
74
76
  end
75
77
  end
76
78
 
77
- class Scanner < StringScanner
78
- def initialize(buffer, delegate)
79
- super(buffer)
80
-
81
- @delegate = delegate
82
- end
83
-
84
- def parse!
85
- until eos?
86
- start_pos = self.pos
87
-
88
- scan_text
89
- scan_expression or scan_interpolation
90
-
91
- raise_if_stuck(start_pos)
92
- end
93
- end
94
-
95
- # This is formulated specifically so that it matches up until the start of a code block.
96
- TEXT = /([^<#]|<(?!\?r)|#(?!\{)){1,}/m
97
-
98
- def scan_text
99
- if scan(TEXT)
100
- @delegate.text(self.matched)
101
- end
102
- end
103
-
104
- def scan_expression
105
- start_pos = self.pos
106
-
107
- if scan(/<\?r/)
108
- if scan_until(/(.*?)\?>/m)
109
- @delegate.expression(self[1])
110
- else
111
- parse_error!("Could not find end of expression!", [start_pos, self.pos])
112
- end
113
-
114
- return true
115
- end
116
-
117
- return false
118
- end
119
-
120
- def scan_interpolation
121
- start_pos = self.pos
122
-
123
- if scan(/\#\{/)
124
- level = 1
125
- code = String.new
126
-
127
- until eos?
128
- current_pos = self.pos
129
-
130
- # Scan anything other than something which causes nesting:
131
- if scan(/[^"'\{\}]+/m)
132
- code << matched
133
- end
134
-
135
- # Scan a quoted string:
136
- if scan(/'(\\'|[^'])*'/m) or scan(/"(\\"|[^"])*"/m)
137
- code << matched
138
- end
139
-
140
- # Scan something which nests:
141
- if scan(/\{/)
142
- code << matched
143
- level += 1
144
- end
145
-
146
- if scan(/\}/)
147
- level -= 1
148
- if level == 0
149
- @delegate.interpolation(code)
150
- return true
151
- else
152
- code << matched
153
- end
154
- end
155
-
156
- break if stuck?(current_pos)
157
- end
158
-
159
- parse_error!("Could not find end of interpolation!", [start_pos, self.pos])
160
- end
161
-
162
- return false
163
- end
79
+ def self.load_file(path, **options)
80
+ self.new(FileBuffer.new(path), **options)
164
81
  end
165
82
 
166
- def self.load_file(path)
167
- self.new(FileBuffer.new(path))
168
- end
169
-
170
- def initialize(buffer)
83
+ def initialize(buffer, filter: String)
171
84
  @buffer = buffer
85
+ @filter = filter
172
86
  end
173
87
 
174
- def to_string(scope = Object.new)
175
- to_array(scope).join
88
+ def to_string(scope = Object.new, output = output_buffer)
89
+ output ||= output_buffer
90
+
91
+ scope.instance_exec(output, &to_proc)
176
92
  end
177
93
 
178
94
  def to_buffer(scope)
179
- Buffer.new(to_array(scope).join, path: @buffer.path)
95
+ Buffer.new(to_string(scope), path: @buffer.path)
180
96
  end
181
97
 
182
- # Legacy functions:
183
- alias evaluate to_string
184
- alias result to_string
185
-
186
- def to_array(scope)
187
- if Binding === scope
188
- # Slow code path, evaluate the code string in the given binding (scope).
189
- eval(code, scope, @buffer.path)
190
- else
191
- # Faster code path, use instance_eval on a compiled Proc.
192
- scope.instance_eval(&to_proc)
193
- end
194
- end
195
-
196
- def to_proc
197
- @compiled_proc ||= eval("proc{;#{code};}", binding, @buffer.path)
98
+ def to_proc(scope = nil)
99
+ @compiled_proc ||= eval("proc{|#{OUT}|;#{code};#{OUT}}", scope, @buffer.path)
198
100
  end
199
101
 
200
102
  protected
201
103
 
104
+ def output_buffer
105
+ String.new.force_encoding(code.encoding)
106
+ end
107
+
202
108
  def code
203
109
  @code ||= compile!
204
110
  end
205
111
 
206
112
  def compile!
207
- assembler = Assembler.new
113
+ assembler = Assembler.new(filter: @filter)
208
114
 
209
- Scanner.new(@buffer, assembler).parse!
115
+ Parsers.parse_template(@buffer, assembler)
210
116
 
211
117
  assembler.code
212
118
  end
213
119
  end
120
+
121
+ class MarkupTemplate < Template
122
+ def initialize(buffer, filter: MarkupString)
123
+ super
124
+ end
125
+
126
+ # The output of the markup template is encoded markup (e.g. with entities, tags, etc)
127
+ def output_buffer
128
+ MarkupString.new.force_encoding(code.encoding)
129
+ end
130
+ end
214
131
  end