class Haml::Compiler

def build_script_formatter(text, opts)

def build_script_formatter(text, opts)
  text = "(#{text}).to_s"
  if opts[:escape_html]
    text = "::Haml::Helpers.html_escape(#{text})"
  end
  if opts[:nuke_inner_whitespace]
    text = "(#{text}).strip"
  end
  if opts[:preserve_tag]
    text = "_hamlout.fix_textareas!(::Haml::Helpers.preserve(#{text}))"
  elsif opts[:preserve_script]
    text = "_hamlout.fix_textareas!(::Haml::Helpers.find_and_preserve(#{text}, _hamlout.options[:preserve]))"
  end
  "#{text};"
end

def call(node)

def call(node)
  compile(node)
  @temple
end

def compile(node)

def compile(node)
  parent, @node = @node, node
  if node.children.empty?
    send(:"compile_#{node.type}")
  else
    send(:"compile_#{node.type}") {node.children.each {|c| compile c}}
  end
ensure
  @node = parent
end

def compile_comment

def compile_comment
  condition = "#{@node.value[:conditional]}>" if @node.value[:conditional]
  revealed = @node.value[:revealed]
  open = "<!--#{condition}#{'<!-->' if revealed}"
  close = "#{'<!--' if revealed}#{'<![endif]' if condition}-->"
  unless block_given?
    push_text("#{open} ")
    if @node.value[:parse]
      push_script(@node.value[:text], :in_tag => true, :nuke_inner_whitespace => true)
    else
      push_text(@node.value[:text])
    end
    push_text(" #{close}\n")
    return
  end
  push_text("#{open}\n")
  yield if block_given?
  push_text("#{close}\n")
end

def compile_doctype

def compile_doctype
  doctype = text_for_doctype
  push_text("#{doctype}\n") if doctype
end

def compile_filter

def compile_filter
  unless (filter = @filters[@node.value[:name]])
    name = @node.value[:name]
    if ["maruku", "textile"].include?(name)
      raise Error.new(Error.message(:install_haml_contrib, name), @node.line - 1)
    else
      raise Error.new(Error.message(:filter_not_defined, name), @node.line - 1)
    end
  end
  filter.internal_compile(self, @node.value[:text])
end

def compile_haml_comment; end

def compile_haml_comment; end

def compile_plain

def compile_plain
  push_text("#{@node.value[:text]}\n")
end

def compile_root

def compile_root
  @output_line = 1
  yield if block_given?
  flush_merged_text
end

def compile_script(&block)

def compile_script(&block)
  push_script(@node.value[:text],
              :preserve_script       => @node.value[:preserve],
              :escape_html           => @node.value[:escape_html],
              :nuke_inner_whitespace => nuke_inner_whitespace?(@node),
              &block)
end

def compile_silent_script

def compile_silent_script
  return if @options.suppress_eval
  push_silent(@node.value[:text])
  keyword = @node.value[:keyword]
  if block_given?
    yield
    push_silent("end", :can_suppress) unless @node.value[:dont_push_end]
  elsif keyword == "end"
    if @node.parent.children.last.equal?(@node)
      # Since this "end" is ending the block,
      # we don't need to generate an additional one
      @node.parent.value[:dont_push_end] = true
    end
    # Don't restore dont_* for end because it isn't a conditional branch.
  end
end

def compile_tag

def compile_tag
  t = @node.value
  # Get rid of whitespace outside of the tag if we need to
  rstrip_buffer! if t[:nuke_outer_whitespace]
  if @options.suppress_eval
    object_ref = :nil
    parse = false
    value = t[:parse] ? nil : t[:value]
    dynamic_attributes = Haml::Parser::DynamicAttributes.new
  else
    object_ref = t[:object_ref]
    parse = t[:parse]
    value = t[:value]
    dynamic_attributes = t[:dynamic_attributes]
  end
  if @options[:trace]
    t[:attributes].merge!({"data-trace" => @options.filename.split('/views').last << ":" << @node.line.to_s})
  end
  push_text("<#{t[:name]}")
  push_temple(@attribute_compiler.compile(t[:attributes], object_ref, dynamic_attributes))
  push_text(
    if t[:self_closing] && @options.xhtml?
      " />#{"\n" unless t[:nuke_outer_whitespace]}"
    else
      ">#{"\n" unless (t[:self_closing] && @options.html?) ? t[:nuke_outer_whitespace] : (!block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace])}"
    end
  )
  if value && !parse
    push_text("#{value}</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
  end
  return if t[:self_closing]
  if value.nil?
    yield if block_given?
    rstrip_buffer! if t[:nuke_inner_whitespace]
    push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
    return
  end
  if parse
    push_script(value, t.merge(:in_tag => true))
    push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
  end
end

def flush_merged_text

def flush_merged_text
  return if @to_merge.empty?
  @to_merge.each do |type, val|
    case type
    when :text
      @temple << [:static, val]
    when :script
      @temple << [:dynamic, val]
    else
      raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Compiler@to_merge.")
    end
  end
  @to_merge = []
end

def initialize(options)

def initialize(options)
  @options     = Options.wrap(options)
  @to_merge    = []
  @temple      = [:multi]
  @node        = nil
  @filters     = Filters.defined.merge(options[:filters])
  @attribute_compiler = AttributeCompiler.new(@options)
end

def nuke_inner_whitespace?(node)

def nuke_inner_whitespace?(node)
  if node.value && node.value[:nuke_inner_whitespace]
    true
  elsif node.parent
    nuke_inner_whitespace?(node.parent)
  else
    false
  end
end

def push_generated_script(text)

def push_generated_script(text)
  @to_merge << [:script, resolve_newlines + text]
  @output_line += text.count("\n")
end

def push_script(text, opts = {})

the result before it is added to `@buffer`
If `opts[:preserve_script]` is true, Haml::Helpers#find_and_preserve is run on

the scope object and the result to be added to `@buffer`.
Causes `text` to be evaluated in the context of
def push_script(text, opts = {})
  return if @options.suppress_eval?
  no_format = !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
  unless block_given?
    push_generated_script(no_format ? "(#{text}\n).to_s" : build_script_formatter("(#{text}\n)", opts))
    push_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
    return
  end
  flush_merged_text
  push_silent "haml_temp = #{text}"
  yield
  push_silent('end', :can_suppress) unless @node.value[:dont_push_end]
  @temple << [:dynamic, no_format ? 'haml_temp.to_s;' : build_script_formatter('haml_temp', opts)]
end

def push_silent(text, can_suppress = false)

does not output the result.
Evaluates `text` in the context of the scope object, but
def push_silent(text, can_suppress = false)
  flush_merged_text
  return if can_suppress && @options.suppress_eval?
  newline = (text == "end") ? ";" : "\n"
  @temple << [:code, "#{resolve_newlines}#{text}#{newline}"]
  @output_line = @output_line + text.count("\n") + newline.count("\n")
end

def push_temple(temple)

def push_temple(temple)
  flush_merged_text
  @temple.concat([[:newline]] * resolve_newlines.count("\n"))
  @temple << temple
  @output_line += TempleLineCounter.count_lines(temple)
end

def push_text(text)

Adds `text` to `@buffer`.
def push_text(text)
  @to_merge << [:text, text]
end

def resolve_newlines

def resolve_newlines
  diff = @node.line - @output_line
  return "" if diff <= 0
  @output_line = @node.line
  "\n" * diff
end

def rstrip_buffer!(index = -1)

or the merged text
Get rid of and whitespace at the end of the buffer
def rstrip_buffer!(index = -1)
  last = @to_merge[index]
  if last.nil?
    push_silent("_hamlout.rstrip!", false)
    return
  end
  case last.first
  when :text
    last[1] = last[1].rstrip
    if last[1].empty?
      @to_merge.slice! index
      rstrip_buffer! index
    end
  when :script
    last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
    rstrip_buffer! index - 1
  else
    raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Compiler@to_merge.")
  end
end

def text_for_doctype

def text_for_doctype
  if @node.value[:type] == "xml"
    return nil if @options.html?
    wrapper = @options.attr_wrapper
    return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{@node.value[:encoding] || "utf-8"}#{wrapper} ?>"
  end
  if @options.html5?
    '<!DOCTYPE html>'
  elsif @options.xhtml?
    if @node.value[:version] == "1.1"
      '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
    elsif @node.value[:version] == "5"
      '<!DOCTYPE html>'
    else
      case @node.value[:type]
      when "strict";   '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
      when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
      when "mobile";   '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
      when "rdfa";     '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
      when "basic";    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
      else             '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
      end
    end
  elsif @options.html4?
    case @node.value[:type]
    when "strict";   '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
    when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
    else             '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
    end
  end
end