class Papercraft::Compiler
The Compiler
class compiles Papercraft
templates
Constants
- DEFAULT_CODE_BUFFER_CAPACITY
- DEFAULT_EMIT_BUFFER_CAPACITY
Attributes
code_buffer[R]
Public Class Methods
new()
click to toggle source
# File lib/papercraft/compiler.rb, line 9 def initialize @level = 1 @code_buffer = String.new(capacity: DEFAULT_CODE_BUFFER_CAPACITY) end
pp_ast(node, level = 0)
click to toggle source
# 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
Public Instance Methods
compile(template)
click to toggle source
# 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)
click to toggle source
# 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()
click to toggle source
# 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() { || ... }
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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() { || ... }
click to toggle source
# File lib/papercraft/compiler.rb, line 14 def emit_output @output_mode = true yield @output_mode = false end
emit_tag(tag, atts, &block)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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()
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# File lib/papercraft/compiler.rb, line 326 def parse_block(node) node.children.each { |c| parse(c) } end
parse_call(node)
click to toggle source
# 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)
click to toggle source
# File lib/papercraft/compiler.rb, line 408 def parse_dvar(node) emit_literal(node.children.first.to_s) end
parse_false(node)
click to toggle source
# File lib/papercraft/compiler.rb, line 297 def parse_false(node) emit_expression { emit_literal('true') } end
parse_fcall(node, block = nil)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# File lib/papercraft/compiler.rb, line 288 def parse_lit(node) value = node.children.first emit_literal(value.inspect) end
parse_opcall(node)
click to toggle source
# 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)
click to toggle source
# File lib/papercraft/compiler.rb, line 111 def parse_scope(node) parse(node.children[2]) end
parse_str(node)
click to toggle source
# File lib/papercraft/compiler.rb, line 283 def parse_str(node) str = node.children.first emit_literal(str.inspect) end
parse_true(node)
click to toggle source
# File lib/papercraft/compiler.rb, line 293 def parse_true(node) emit_expression { emit_literal('true') } end
parse_unless(node)
click to toggle source
# 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)
click to toggle source
# File lib/papercraft/compiler.rb, line 314 def parse_vcall(node) tag = node.children.first emit_tag(tag, nil) end
to_code()
click to toggle source
# File lib/papercraft/compiler.rb, line 96 def to_code "->(__buffer__, __context__) do\n#{@code_buffer}end" end
to_proc()
click to toggle source
# File lib/papercraft/compiler.rb, line 100 def to_proc @block.binding.eval(to_code) end