trenni 1.7.0 → 2.0.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.
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