Class: Papercraft::Compiler
- Inherits:
-
Object
- Object
- Papercraft::Compiler
- Defined in:
- lib/papercraft/compiler.rb
Overview
The Compiler class compiles Papercraft templates
Constant Summary collapse
- DEFAULT_CODE_BUFFER_CAPACITY =
8192
- DEFAULT_EMIT_BUFFER_CAPACITY =
4096
Instance Attribute Summary collapse
-
#code_buffer ⇒ Object
readonly
Returns the value of attribute code_buffer.
Class Method Summary collapse
Instance Method Summary collapse
- #compile(template) ⇒ Object
- #emit_code(code) ⇒ Object
- #emit_code_line_break ⇒ Object
- #emit_expression ⇒ Object
- #emit_if_code(cond, then_branch, else_branch) ⇒ Object
- #emit_if_output(cond, then_branch, else_branch) ⇒ Object
- #emit_literal(lit) ⇒ Object
- #emit_output ⇒ Object
- #emit_tag(tag, atts, &block) ⇒ Object
- #emit_tag_attribute_key(key) ⇒ Object
- #emit_tag_attribute_value(value, key) ⇒ Object
- #emit_tag_attributes(atts) ⇒ Object
- #emit_text(str, encoding: :html) ⇒ Object
- #emit_unless_code(cond, then_branch, else_branch) ⇒ Object
- #emit_unless_output(cond, then_branch, else_branch) ⇒ Object
- #encode(str, encoding) ⇒ Object
- #fcall_attributes_from_args(args) ⇒ Object
- #fcall_inner_text_from_args(args) ⇒ Object
- #flush_emit_buffer ⇒ Object
-
#initialize ⇒ Compiler
constructor
A new instance of Compiler.
- #parse(node) ⇒ Object
- #parse_block(node) ⇒ Object
- #parse_call(node) ⇒ Object
- #parse_dvar(node) ⇒ Object
- #parse_false(node) ⇒ Object
- #parse_fcall(node, block = nil) ⇒ Object
- #parse_if(node) ⇒ Object
- #parse_iter(node) ⇒ Object
- #parse_ivar(node) ⇒ Object
- #parse_list(node) ⇒ Object
- #parse_lit(node) ⇒ Object
- #parse_opcall(node) ⇒ Object
- #parse_scope(node) ⇒ Object
- #parse_str(node) ⇒ Object
- #parse_true(node) ⇒ Object
- #parse_unless(node) ⇒ Object
- #parse_vcall(node) ⇒ Object
- #to_code ⇒ Object
- #to_proc ⇒ Object
Constructor Details
#initialize ⇒ Compiler
Returns a new instance of Compiler.
9 10 11 12 |
# File 'lib/papercraft/compiler.rb', line 9 def initialize @level = 1 @code_buffer = String.new(capacity: DEFAULT_CODE_BUFFER_CAPACITY) end |
Instance Attribute Details
#code_buffer ⇒ Object (readonly)
Returns the value of attribute code_buffer.
94 95 96 |
# File 'lib/papercraft/compiler.rb', line 94 def code_buffer @code_buffer end |
Class Method Details
.pp_ast(node, level = 0) ⇒ Object
413 414 415 416 417 418 419 420 421 422 423 424 425 426 |
# File 'lib/papercraft/compiler.rb', line 413 def self.pp_ast(node, level = 0) case node when RubyVM::AbstractSyntaxTree::Node puts "#{' ' * level}#{node.type.inspect}" node.children.each { |c| pp_ast(c, level + 1) } when Array puts "#{' ' * level}[" node.each { |c| pp_ast(c, level + 1) } puts "#{' ' * level}]" else puts "#{' ' * level}#{node.inspect}" return end end |
Instance Method Details
#compile(template) ⇒ Object
85 86 87 88 89 90 91 92 |
# File 'lib/papercraft/compiler.rb', line 85 def compile(template) @block = template.to_proc ast = RubyVM::AbstractSyntaxTree.of(@block) # Compiler.pp_ast(ast) parse(ast) flush_emit_buffer self end |
#emit_code(code) ⇒ Object
72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/papercraft/compiler.rb', line 72 def emit_code(code) if flush_emit_buffer || @line_break emit_code_line_break if @line_break @code_buffer << "#{' ' * @level}#{code}" else if @code_buffer.empty? || (@code_buffer[-1] == "\n") @code_buffer << "#{' ' * @level}#{code}" else @code_buffer << "#{code}" end end end |
#emit_code_line_break ⇒ Object
20 21 22 23 24 25 |
# File 'lib/papercraft/compiler.rb', line 20 def emit_code_line_break return if @code_buffer.empty? @code_buffer << "\n" if @code_buffer[-1] != "\n" @line_break = nil end |
#emit_expression ⇒ Object
54 55 56 57 58 59 60 61 62 |
# File 'lib/papercraft/compiler.rb', line 54 def emit_expression if @output_mode emit_literal('#{__html_encode__(') yield emit_literal(')}') else yield end end |
#emit_if_code(cond, then_branch, else_branch) ⇒ Object
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/papercraft/compiler.rb', line 372 def emit_if_code(cond, then_branch, else_branch) emit_code('if ') parse(cond) emit_code("\n") @level += 1 parse(then_branch) flush_emit_buffer @level -= 1 if else_branch emit_code("else\n") @level += 1 parse(else_branch) flush_emit_buffer @level -= 1 end emit_code("end\n") end |
#emit_if_output(cond, then_branch, else_branch) ⇒ Object
348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/papercraft/compiler.rb', line 348 def emit_if_output(cond, then_branch, else_branch) parse(cond) emit_literal(" ? ") parse(then_branch) emit_literal(" : ") if else_branch parse(else_branch) else emit_literal(nil) end end |
#emit_literal(lit) ⇒ Object
27 28 29 30 31 32 33 34 35 |
# File 'lib/papercraft/compiler.rb', line 27 def emit_literal(lit) if @output_mode emit_code_line_break if @line_break @emit_buffer ||= String.new(capacity: DEFAULT_EMIT_BUFFER_CAPACITY) @emit_buffer << lit else emit_code(lit) end end |
#emit_output ⇒ Object
14 15 16 17 18 |
# File 'lib/papercraft/compiler.rb', line 14 def emit_output @output_mode = true yield @output_mode = false end |
#emit_tag(tag, atts, &block) ⇒ Object
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/papercraft/compiler.rb', line 183 def emit_tag(tag, atts, &block) emit_output do if atts emit_literal("<#{tag}") emit_tag_attributes(atts) emit_literal(block ? '>' : '/>') else emit_literal(block ? "<#{tag}>" : "<#{tag}/>") end end if block block.call emit_output { emit_literal("</#{tag}>") } end end |
#emit_tag_attribute_key(key) ⇒ Object
222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/papercraft/compiler.rb', line 222 def emit_tag_attribute_key(key) case key.type when :STR emit_literal(key.children.first) when :LIT emit_literal(key.children.first.to_s) when :NIL emit_literal('nil') else emit_expression { parse(key) } end end |
#emit_tag_attribute_value(value, key) ⇒ Object
235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/papercraft/compiler.rb', line 235 def emit_tag_attribute_value(value, key) case value.type when :STR encoding = (key.type == :LIT) && (key.children.first == :href) ? :uri : :html emit_text(value.children.first, encoding: encoding) when :LIT emit_text(value.children.first.to_s) else parse(value) end end |
#emit_tag_attributes(atts) ⇒ Object
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/papercraft/compiler.rb', line 199 def emit_tag_attributes(atts) list = atts.children.first.children while true key = list.shift break unless key value = list.shift value_type = value.type case value_type when :FALSE, :NIL next end emit_literal(' ') emit_tag_attribute_key(key) next if value_type == :TRUE emit_literal('=\"') emit_tag_attribute_value(value, key) emit_literal('\"') end end |
#emit_text(str, encoding: :html) ⇒ Object
37 38 39 40 41 |
# File 'lib/papercraft/compiler.rb', line 37 def emit_text(str, encoding: :html) emit_code_line_break if @line_break @emit_buffer ||= String.new(capacity: DEFAULT_EMIT_BUFFER_CAPACITY) @emit_buffer << encode(str, encoding).inspect[1..-2] end |
#emit_unless_code(cond, then_branch, else_branch) ⇒ Object
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
# File 'lib/papercraft/compiler.rb', line 390 def emit_unless_code(cond, then_branch, else_branch) emit_code('unless ') parse(cond) emit_code("\n") @level += 1 parse(then_branch) flush_emit_buffer @level -= 1 if else_branch emit_code("else\n") @level += 1 parse(else_branch) flush_emit_buffer @level -= 1 end emit_code("end\n") end |
#emit_unless_output(cond, then_branch, else_branch) ⇒ Object
360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/papercraft/compiler.rb', line 360 def emit_unless_output(cond, then_branch, else_branch) parse(cond) emit_literal(" ? ") if else_branch parse(else_branch) else emit_literal(nil) end emit_literal(" : ") parse(then_branch) end |
#encode(str, encoding) ⇒ Object
43 44 45 46 47 48 49 50 51 52 |
# File 'lib/papercraft/compiler.rb', line 43 def encode(str, encoding) case encoding when :html __html_encode__(str) when :uri __uri_encode__(str) else raise "Invalid encoding #{encoding.inspect}" end end |
#fcall_attributes_from_args(args) ⇒ Object
176 177 178 179 180 181 |
# File 'lib/papercraft/compiler.rb', line 176 def fcall_attributes_from_args(args) return nil if !args last = args.last (last.type == :HASH) ? last : nil end |
#fcall_inner_text_from_args(args) ⇒ Object
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/papercraft/compiler.rb', line 160 def fcall_inner_text_from_args(args) return nil if !args first = args.first case first.type when :STR first.children.first when :LIT first.children.first.to_s when :HASH nil else first end end |
#flush_emit_buffer ⇒ Object
64 65 66 67 68 69 70 |
# File 'lib/papercraft/compiler.rb', line 64 def flush_emit_buffer return if !@emit_buffer @code_buffer << "#{' ' * @level}__buffer__ << \"#{@emit_buffer}\"\n" @emit_buffer = nil true end |
#parse(node) ⇒ Object
104 105 106 107 108 109 |
# File 'lib/papercraft/compiler.rb', line 104 def parse(node) @line_break = @last_node && node.first_lineno != @last_node.first_lineno @last_node = node # puts "- parse(#{node.type}) (break: #{@line_break.inspect})" send(:"parse_#{node.type.downcase}", node) end |
#parse_block(node) ⇒ Object
326 327 328 |
# File 'lib/papercraft/compiler.rb', line 326 def parse_block(node) node.children.each { |c| parse(c) } end |
#parse_call(node) ⇒ Object
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/papercraft/compiler.rb', line 247 def parse_call(node) receiver, method, args = node.children if receiver.type == :VCALL && receiver.children == [:context] emit_literal('__context__') else parse(receiver) end if method == :[] emit_literal('[') args = args.children.compact while true arg = args.shift break unless arg parse(arg) emit_literal(', ') if !args.empty? end emit_literal(']') else emit_literal('.') emit_literal(method.to_s) if args emit_literal('(') args = args.children.compact while true arg = args.shift break unless arg parse(arg) emit_literal(', ') if !args.empty? end emit_literal(')') end end end |
#parse_dvar(node) ⇒ Object
408 409 410 411 |
# File 'lib/papercraft/compiler.rb', line 408 def parse_dvar(node) emit_literal(node.children.first.to_s) end |
#parse_false(node) ⇒ Object
297 298 299 |
# File 'lib/papercraft/compiler.rb', line 297 def parse_false(node) emit_expression { emit_literal('true') } end |
#parse_fcall(node, block = nil) ⇒ Object
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/papercraft/compiler.rb', line 138 def parse_fcall(node, block = nil) tag, args = node.children args = args.children.compact if args text = fcall_inner_text_from_args(args) atts = fcall_attributes_from_args(args) if block emit_tag(tag, atts) { parse(block) } elsif text emit_tag(tag, atts) do emit_output do if text.is_a?(String) emit_text(text) else emit_expression { parse(text) } end end end else emit_tag(tag, atts) end end |
#parse_if(node) ⇒ Object
330 331 332 333 334 335 336 337 |
# File 'lib/papercraft/compiler.rb', line 330 def parse_if(node) cond, then_branch, else_branch = node.children if @output_mode emit_if_output(cond, then_branch, else_branch) else emit_if_code(cond, then_branch, else_branch) end end |
#parse_iter(node) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/papercraft/compiler.rb', line 115 def parse_iter(node) call, scope = node.children if call.type == :FCALL parse_fcall(call, scope) else parse(call) emit_code(" do") args = scope.children[0] emit_code(" |#{args.join(', ')}|") if args emit_code("\n") @level += 1 parse(scope) flush_emit_buffer @level -= 1 emit_code("end\n") end end |
#parse_ivar(node) ⇒ Object
133 134 135 136 |
# File 'lib/papercraft/compiler.rb', line 133 def parse_ivar(node) ivar = node.children.first.match(/^@(.+)*/)[1] emit_literal("__context__[:#{ivar}]") end |
#parse_list(node) ⇒ Object
301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/papercraft/compiler.rb', line 301 def parse_list(node) emit_literal('[') items = node.children.compact while true item = items.shift break unless item parse(item) emit_literal(', ') if !items.empty? end emit_literal(']') end |
#parse_lit(node) ⇒ Object
288 289 290 291 |
# File 'lib/papercraft/compiler.rb', line 288 def parse_lit(node) value = node.children.first emit_literal(value.inspect) end |
#parse_opcall(node) ⇒ Object
319 320 321 322 323 324 |
# File 'lib/papercraft/compiler.rb', line 319 def parse_opcall(node) left, op, right = node.children parse(left) emit_literal(" #{op} ") right.children.compact.each { |c| parse(c) } end |
#parse_scope(node) ⇒ Object
111 112 113 |
# File 'lib/papercraft/compiler.rb', line 111 def parse_scope(node) parse(node.children[2]) end |
#parse_str(node) ⇒ Object
283 284 285 286 |
# File 'lib/papercraft/compiler.rb', line 283 def parse_str(node) str = node.children.first emit_literal(str.inspect) end |
#parse_true(node) ⇒ Object
293 294 295 |
# File 'lib/papercraft/compiler.rb', line 293 def parse_true(node) emit_expression { emit_literal('true') } end |
#parse_unless(node) ⇒ Object
339 340 341 342 343 344 345 346 |
# File 'lib/papercraft/compiler.rb', line 339 def parse_unless(node) cond, then_branch, else_branch = node.children if @output_mode emit_unless_output(cond, then_branch, else_branch) else emit_unless_code(cond, then_branch, else_branch) end end |
#parse_vcall(node) ⇒ Object
314 315 316 317 |
# File 'lib/papercraft/compiler.rb', line 314 def parse_vcall(node) tag = node.children.first emit_tag(tag, nil) end |
#to_code ⇒ Object
96 97 98 |
# File 'lib/papercraft/compiler.rb', line 96 def to_code "->(__buffer__, __context__) do\n#{@code_buffer}end" end |
#to_proc ⇒ Object
100 101 102 |
# File 'lib/papercraft/compiler.rb', line 100 def to_proc @block.binding.eval(to_code) end |