require 'erb'
class ERB
  class Compiler # :nodoc:
    class TrimScanner < Scanner # :nodoc:
      def initialize(src, trim_mode, percent)
        super
        @trim_mode = trim_mode
        @percent = percent
        if @trim_mode == '>'
          @scan_line = self.method(:trim_line1)
        elsif @trim_mode == '<>'
          @scan_line = self.method(:trim_line2)
        elsif @trim_mode == '-'
          @scan_line = self.method(:explicit_trim_line)
        else
          @scan_line = self.method(:scan_line)
        end
      end
      attr_accessor :stag

      def scan(&block)
        @stag = nil
        if @percent
          @src.each_line do |line|
            percent_line(line, &block)
          end
        else
          @scan_line.call(@src, &block)
        end
        nil
      end

      def percent_line(line, &block)
        if @stag || line[0] != ?%
          return @scan_line.call(line, &block)
        end

        line[0] = ''
        if line[0] == ?%
          @scan_line.call(line, &block)
        else
          yield(PercentLine.new(line.chomp))
        end
      end

      def scan_line(line)
        line.scan(/(.*?)(<%%|%%>|<%=|<%#|<%b|<%|%>|\n|\z)/m) do |tokens|
          tokens.each do |token|
            next if token.empty?
            yield(token)
          end
        end
      end

      def trim_line1(line)
        line.scan(/(.*?)(<%%|%%>|<%=|<%#|<%b|<%|%>\n|%>|\n|\z)/m) do |tokens|
          tokens.each do |token|
            next if token.empty?
            if token == "%>\n"
              yield('%>')
              yield(:cr)
            else
              yield(token)
            end
          end
        end
      end

      def trim_line2(line)
        head = nil
        line.scan(/(.*?)(<%%|%%>|<%=|<%b|<%#|<%|%>\n|%>|\n|\z)/m) do |tokens|
          tokens.each do |token|
            next if token.empty?
            head = token unless head
            if token == "%>\n"
              yield('%>')
              if is_erb_stag?(head)
                yield(:cr)
              else
                yield("\n")
              end
              head = nil
            else
              yield(token)
              head = nil if token == "\n"
            end
          end
        end
      end

      def explicit_trim_line(line)
        line.scan(/(.*?)(^[ \t]*<%\-|<%\-|<%%|%%>|<%=|<%#|<%b|<%|-%>\n|\n|-%>|%>|\z)/m) do |tokens|
          tokens.each do |token|
            next if token.empty?
            if @stag.nil? && /[ \t]*<%-/ =~ token
              yield('<%')
            elsif @stag && token == "-%>\n"
              yield('%>')
              yield(:cr)
            elsif @stag && token == '-%>'
              yield('%>')
            else
              yield(token)
            end
          end
        end
      end

      remove_const :ERB_STAG
      ERB_STAG = %w(<%b <%= <%# <%)
      def is_erb_stag?(s)
        ERB_STAG.member?(s)
      end
    end

    Scanner.default_scanner = TrimScanner

    class SimpleScanner < Scanner # :nodoc:
      def scan
        @src.scan(/(.*?)(<%%|%%>|<%=|<%#|<%|%>|\n|\z)/m) do |tokens|
          tokens.each do |token|
            next if token.empty?
            yield(token)
          end
        end
      end
    end

    Scanner.regist_scanner(SimpleScanner, nil, false)

    begin
      require 'strscan'
      class SimpleScanner2 < Scanner # :nodoc:
        def scan
          stag_reg = /(.*?)(<%%|<%=|<%#|<%|\z)/m
          etag_reg = /(.*?)(%%>|%>|\z)/m
          scanner = StringScanner.new(@src)
          while ! scanner.eos?
            scanner.scan(@stag ? etag_reg : stag_reg)
            yield(scanner[1])
            yield(scanner[2])
          end
        end
      end
      Scanner.regist_scanner(SimpleScanner2, nil, false)

      class ExplicitScanner < Scanner # :nodoc:
        def scan
          stag_reg = /(.*?)(^[ \t]*<%-|<%%|<%=|<%#|<%-|<%|\z)/m
          etag_reg = /(.*?)(%%>|-%>|%>|\z)/m
          scanner = StringScanner.new(@src)
          while ! scanner.eos?
            scanner.scan(@stag ? etag_reg : stag_reg)
            yield(scanner[1])

            elem = scanner[2]
            if /[ \t]*<%-/ =~ elem
              yield('<%')
            elsif elem == '-%>'
              yield('%>')
              yield(:cr) if scanner.scan(/(\n|\z)/)
            else
              yield(elem)
            end
          end
        end
      end
      Scanner.regist_scanner(ExplicitScanner, '-', false)

    rescue LoadError
    end

    def compile(s)
      enc = s.encoding
      raise ArgumentError, "#{enc} is not ASCII compatible" if enc.dummy?
      s = s.dup.force_encoding("ASCII-8BIT") # don't use constant Enoding::ASCII_8BIT for miniruby
      enc = detect_magic_comment(s) || enc
      out = Buffer.new(self, enc)

      content = ''
      scanner = make_scanner(s)
      scanner.scan do |token|
        next if token.nil?
        next if token == ''
        if scanner.stag.nil?
          case token
          when PercentLine
            add_put_cmd(out, content) if content.size > 0
            content = ''
            out.push(token.to_s)
            out.cr
          when :cr
            out.cr
          when '<%', '<%=', '<%#', '<%b'
            scanner.stag = token
            @line << content if content.size > 0
            #add_put_cmd(out, content) if content.size > 0
            content = ''
          when "\n"
            content << "\n"
            @line << content
            add_put_cmd(out, @line) if @print_line
            @print_line = true
            @line = ''
            content = ''
          when '<%%'
            content << '<%'
          else
            content << token
          end
        else
          case token
          when '%>'
            case scanner.stag
            when '<%'
              if content[-1] == ?\n
                content.chop!
                out.push(content)
                out.cr
              else
                out.push(content)
              end
            when '<%='
              add_put_cmd(out, @line)
              @line = ''
              add_insert_cmd(out, content)
            when '<%b'
              ## ytti
              rslt = eval(content, @binding)
              if rslt
                @line << rslt.to_s
              else
                @print_line = false
              end
            when '<%#'
               out.push("# #{content_dump(content)}")
            end
            scanner.stag = nil
            content = ''
          when '%%>'
            content << '%>'
          else
            content << token
          end
        end
      end
      @line << content if content.size > 0
      out.close
      return out.script, enc
    end

    def initialize(trim_mode, binding=nil)
      @percent, @trim_mode = prepare_trim_mode(trim_mode)
      @put_cmd = 'print'
      @insert_cmd = @put_cmd
      @pre_cmd = []
      @post_cmd = []
      @binding = binding
      @print_line = true
      @line = ''
    end
  end
end

class ERB
  def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout', binding=nil)
    @safe_level = safe_level
    compiler = make_compiler(trim_mode, binding)
    set_eoutvar(compiler, eoutvar)
    @src, @enc = *compiler.compile(str)
    @filename = nil
  end
  def make_compiler(trim_mode, binding=nil)
    ERB::Compiler.new(trim_mode, binding)
  end
end