%% name = Dang::Parser

%% {

def initialize(str, debug=false)
  setup_parser(str, debug)
  @doctype = nil
  @output = ""
end

DOC_TYPES = {
  "html"                 => "<!doctype html>",
  "html5"                => "<!doctype html>",
  "html4"                => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
  "html4 transitional"   => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
  "html4 strict"         => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
  "html4 frameset"       => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',

  "xhtml 1"              => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
  "xhtml 1 transitional" => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
  "xhtml 1 strict"       => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
  "xhtml 1 frameset"     => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
  "xhtml 1.1"            => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
  "xhtml 1.1 basic"      => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
  "xhtml 1.2 mobile"     => '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">',
  "xhtml rdfa"           => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">',
  "xhtml 5"              => '<!DOCTYPE html>',

  "xml iso-8859-1"       => "<?xml version='1.0' encoding='iso-8859-1' ?>"
}

def html_doctype
  return "" unless @doctype

  unless DOC_TYPES.key? @doctype
    warn "doctype '#{@doctype}' not understood, using 'html'"
    @doctype = "html"
  end

  DOC_TYPES[@doctype].dup
end

def compile
  strings = @output.flatten.map do |i|
    case i
    when Literal
      "_out << #{i.str.dump}"
    when Code
      if i.print
        "_out << (#{i.str}).to_s"
      else
        i.str
      end
    when Filter
      "_out << Dang.run_filter('#{i.name}', #{i.str.dump}).to_s"
    end
  end

  "_out = '';\n" + strings.join(";") + ";_out"
end

def output(env=nil)
  out = eval(compile, env || binding).strip

  doctype = html_doctype

  if doctype.empty?
    str = out
  else
    if out.empty?
      str = doctype
    else
      str = doctype << "\n" << out
    end
  end

  str
end

def attrs(at,sel=[])
  out = []
  classes = []

  (at+sel).flatten.each do |pair|
    key = pair.key
    val = pair.value

    if key == "class"
      val = val.str if val.kind_of? Literal
      classes.unshift val
    elsif val == true
      out << "#{key}"
      out << " "
    else
      out << "#{key}='"
      out << val
      out << "'"
      out << " "
    end
  end

  unless classes.empty?
    expanded = ["class='"]
    classes.each do |c|
      expanded << c
      expanded << " "
    end

    expanded[-1] = "'"

    classes = expanded

    if out.empty?
      return classes
    end

    out = classes + [" "] + out
  end

  if out.last == " "
    out.pop
  end

  out
end

class Literal
  def initialize(str)
    @str = str
  end

  attr_reader :str
end

class Code
  def initialize(str, print)
    @str = str
    @print = print
  end

  attr_reader :str, :print
end

class Filter
  def initialize(name, str)
    @name = name
    @str = str
  end

  attr_reader :name, :str
end

def joinm(*elems)
  elems.flatten.map do |i|
    if i.kind_of? String
      Literal.new(i)
    else
      i
    end
  end
end

Pair = Struct.new(:key, :value)

def join(f,b)
  f = Literal.new(f) if f.kind_of? String
  b = Literal.new(b) if b.kind_of? String

  if b.kind_of? Array
    [f] + b
  else
    [f,b]
  end
end

def code(str, print=true)
  Code.new(str, print)
end

}

space = " " | "\t"
   bs = " " | "\t" | "\n"
    - = bs+
  eol = "\n"
  eof = !.
 rest = < (!eol .)* > (eol | eof) { text }

doctype = “!!!” space* rest:r { @doctype = r.empty? ? “html” : r }

  name = < /[a-zA-Z0-9_\-:]+/ > { text }
 start = "<" name:n { n }
   pts = space+ { "" }
       | < eol bs* > { text }

   end = name:n ">" { n }
 slash = - "/>"

marker = start | "<!" | "<|" | "<:" | (- end)
 chunk = < (!marker .)* > { text }

rclose = “:>”

ruby = "<:" bs+ < (!rclose .)* > rclose { code(text, false) }

pclose = “|>”

 puby = "<|" < (!pclose .)* > pclose { code(text) }

  part = ruby | puby| filter | comment | tag | chunk
  body = part:p body:b { join(p,b) }
       | part

   key = name
       | "'" < /[^'\n]*/ > "'" { text }

valsrt = "]" | "<|"
valchk = < (!valsrt .)* > { text }
valprt = puby | valchk
valraw = valprt:p valraw:b { join(p,b) }
       | valprt

   val = "'" < /[^'\n]*/ > "'" { text }
       | valraw

 dattr = "[" key:k "=" val:v "]" { Pair.new("data-#{k}", v) }
dattrs = dattr:a dattrs:l { [a] + l }
       | dattr:a { [a] }

  attr = "[data" dattrs:t "]" { t }
       | "[" key:k "=" val:v "]" { Pair.new(k, v) }
       | "[" key:k "]" { Pair.new(k,true) }
 attrs = attr:a attrs:l { [a] + l }
       | attr:a { [a] }

 cc_if = /[iI][fF]/
cc_end = /[eE][nN][dD][iI][fF]/

comment = “<!” space+ < “[” space* cc_if (!“]” .)* “]” > space+ “!>”

         { "<!--#{text}>" }
       |  "<!" space+ < "[" space* cc_end (!"]" .)* "]" > space+ "!>"
         { "<!#{text}-->" }
       | "<!" < (!"!>" .)*  > "!>"
         { "<!--#{text}-->" }

simple = /[a-zA-Z0-9_\-]+/
select = "#" < simple > { Pair.new("id", text) }
       | "." < simple > { Pair.new("class", text) }

selects = select:s selects:t { [s] + t }

| select:s { [s] }

end_filter(n) = bs* < /[a-zA-Z]+/ > &{ n == text } “:>”

filter = “<:” name:n bs* < (!end_filter(n) .)* > end_filter(n)

       { Filter.new(n, text) }

  tag = start:l slash { "<#{l} />" }
      | start:l space+ end:r { "<#{l}></#{r}>" }
      | start:l attrs:a slash
        { joinm "<#{l} ", attrs(a), " />" }
      | start:l selects:t slash
        { joinm "<#{l} ",  attrs(t), " />" }
      | start:l selects:t attrs:a slash
        { joinm "<#{l} ", attrs(t,a), " />" }
      | start:l attrs:a space+ end:r
        { joinm "<#{l} ", attrs(a), "></#{r}>" }
      | start:l selects:t space+ end:r
        { joinm "<#{l} ", attrs(t), "></#{r}>" }
      | start:l selects:t attrs:a space+ end:r
        { joinm "<#{l} ", attrs(t,a), "></#{r}>" }
      | start:l selects:t attrs:a pts body:b pts:es end:r
        { joinm "<#{l} ", attrs(a,t), ">",b,es,"</#{r}>" }
      | start:l attrs:a pts body:b pts:es end:r
        { joinm "<#{l} ", attrs(a), ">", b, es, "</#{r}>" }
      | start:l selects:t pts:s body:b pts:es end:r
        { joinm "<#{l} ", attrs(t), ">",s, b, es, "</#{r}>" }
      | start:l pts:s body:b pts:es end:r
        { joinm "<#{l}>", s, b, es, "</#{r}>" }

root = doctype? body:b eof { @output = join(b,"") }