logo

class

docCompiler::SourceToHtmlGenerator

sys::Obj
  fandoc::HtmlDocWriter
    docCompiler::HtmlGenerator
      docCompiler::ApiToHtmlGenerator
        docCompiler::SourceToHtmlGenerator
   1  //
   2  // Copyright (c) 2007, Brian Frank and Andy Frank
   3  // Licensed under the Academic Free License version 3.0
   4  //
   5  // History:
   6  //   9 May 07  Andy Frank  Creation
   7  //
   8  
   9  using compiler
  10  using fandoc
  11  
  12  **
  13  ** SourceToHtmlGenerator generates an syntax color coded HTML
  14  ** file for a Type's source code.
  15  **
  16  class SourceToHtmlGenerator : ApiToHtmlGenerator
  17  {
  18  
  19  //////////////////////////////////////////////////////////////////////////
  20  // Constructor
  21  //////////////////////////////////////////////////////////////////////////
  22  
  23    new make(DocCompiler compiler, Location loc, OutStream out, Type t, File srcFile)
  24      : super(compiler, loc, out, t)
  25    {
  26      this.srcFile = srcFile
  27    }
  28  
  29  //////////////////////////////////////////////////////////////////////////
  30  // ApiToHtmlGenerator
  31  //////////////////////////////////////////////////////////////////////////
  32  
  33    **
  34    ** Generate the main content.
  35    **
  36    override Void content()
  37    {
  38      out.print("<div class='type'>\n")
  39      typeOverview
  40      out.print("</div>\n")
  41      out.print("<div class='src'>\n")
  42      out.print("<pre>")
  43      srcFileFacet := t->sourceFile
  44      file := t.pod.files["/src/$srcFileFacet".toUri]
  45      convert(file.in.readAllLines, out)
  46      out.print("</pre>\n")
  47      out.print("</div>\n")
  48    }
  49  
  50    **
  51    ** Generate the sidebar.
  52    **
  53    override Void sidebar()
  54    {
  55      out.print("<h2>More Info</h2>\n")
  56      out.print("<ul>\n")
  57      out.print("  <li><a href='${t.name}.html'>View Fandoc</a></li>\n")
  58      out.print("</ul>\n")
  59      slotsOverview(false)
  60    }
  61  
  62  //////////////////////////////////////////////////////////////////////////
  63  // FanToHtml
  64  //////////////////////////////////////////////////////////////////////////
  65  
  66    **
  67    ** Convert the Fan source code to syntax highlighted HTML markup.
  68    **
  69    Void convert(Str[] lines, OutStream out)
  70    {
  71      max := lines.size.toStr.size
  72      inBlockComment := false
  73  
  74  
  75      lines.each |Str line, Int i|
  76      {
  77        num := i+1
  78        pad := max - num.toStr.size + 1
  79  
  80        out.print("<span class='a'")
  81        slot := t.slots.find |Slot s -> Bool| // TODO: this nested loop sucks
  82        {
  83          return s.parent == t && s->lineNumber == num
  84        }
  85        if (slot != null && slot.name != "instance\$init")
  86          out.print(" id='$slot.name'")
  87        out.print(">")
  88        out.print(Str.spaces(pad))
  89        out.print("${num} </span>")
  90        out.print(" ")
  91        inBlockComment = markup(line, out, inBlockComment)
  92  
  93        out.print("\n")
  94      }
  95    }
  96  
  97  //////////////////////////////////////////////////////////////////////////
  98  // Support Methods
  99  //////////////////////////////////////////////////////////////////////////
 100  
 101    **
 102    ** Markup the given line with syntax highlighting.
 103    **
 104    private Bool markup(Str line, OutStream out, Bool inBlockComment := false)
 105    {
 106      inStr := false
 107      buf   := Buf.make
 108  
 109      if (inBlockComment) out.print("<span class='b'") // block comment
 110  
 111      for (i := 0; i<line.size; i++)
 112      {
 113        curr := line[i]
 114        peek := (i < line.size-1) ? line[i+1] : -1
 115        pre  := (i > 0) ? line[i-1] : -1
 116  
 117        // Currenty in a block comment, keep looping till we find the end
 118        if (inBlockComment)
 119        {
 120          safe(out, curr)
 121          if (curr == '*' && peek == '/')
 122          {
 123            inBlockComment = false
 124            safe(out, peek)
 125            out.print("</span>")
 126            i++
 127          }
 128          continue
 129        }
 130  
 131        // Currently in a Str or Uri, keep looping till we find the end
 132        if (inStr)
 133        {
 134          safe(out, curr)
 135          if ((curr == '"' && pre != '\\') || curr == '`')
 136          {
 137            inStr = false
 138            out.print("</span>")
 139          }
 140          continue
 141        }
 142  
 143        // We're starting a Str
 144        if (curr == '"')
 145        {
 146          inStr = true
 147          out.print("<span class='g'>").writeChar(curr)  // string
 148          continue
 149        }
 150  
 151        // We're starting a Uri
 152        if (curr == '`')
 153        {
 154          inStr = true
 155          out.print("<span class='i'>").writeChar(curr) // uri
 156          continue
 157        }
 158  
 159        // We're starting a Char
 160        if (curr == '\'')
 161        {
 162          out.print("<span class='h'>").writeChar(curr) // char
 163          safe(out, peek)
 164          if (peek == '\\')
 165          {
 166            safe(out, line[i+2])
 167            i++
 168          }
 169          i += 2
 170          out.writeChar('\'')
 171          out.print("</span>")
 172          continue
 173        }
 174  
 175        // Block comment
 176        if (curr == '/' && peek == '*')
 177        {
 178          out.print("<span class='b'>") // block comment
 179          inBlockComment = true
 180          out.writeChar(curr).writeChar(peek)
 181          i++
 182          continue
 183        }
 184  
 185        // Line comment
 186        if (curr == '/' && peek == '/')
 187        {
 188          out.print("<span class='c'>") // line comment
 189          safeStr(out, line[i..-1])
 190          out.print("</span>")
 191          return false
 192        }
 193  
 194        // Fandoc comment
 195        if (curr == '*' && peek == '*')
 196        {
 197          out.print("<span class='d'>") // fandoc comment
 198          safeStr(out, line[i..-1])
 199          out.print("</span>")
 200          return false
 201        }
 202  
 203        if (isAlphaNum(curr))
 204        {
 205          // Build up buffer to check if this is a keyword
 206          buf.writeChar(curr)
 207          continue
 208        }
 209        else if (!buf.empty)
 210        {
 211          // Word is over, write out checking for keywords
 212          doBuf(buf, out)
 213  
 214          // Reset buf for next time
 215          buf.clear
 216        }
 217  
 218        // Single character
 219        if (curr == '(' || curr == ')' ||
 220            curr == '{' || curr == '}' ||
 221            curr == '[' || curr == ']')
 222        {
 223          out.print("<span class='e'>").writeChar(curr) // bracket
 224          out.print("</span>")
 225        }
 226        else
 227        {
 228          safe(out, curr)
 229        }
 230      }
 231  
 232      // If buf ended the line, make sure we still check for a keyword
 233      if (!buf.empty) doBuf(buf, out)
 234  
 235      // If still in block comment, close tag for this line
 236      if (inBlockComment) out.print("</span>")
 237      return inBlockComment
 238    }
 239  
 240    **
 241    ** Convenience for s.each |Int ch| { safe(out, ch) }
 242    **
 243    private Void safeStr(OutStream out, Str s)
 244    {
 245      s.each |Int ch| { safe(out, ch) }
 246    }
 247  
 248    **
 249    ** Escape <, &, and > characters.
 250    **
 251    private Void safe(OutStream out, Int ch)
 252    {
 253      //if (ch == ' ') out.print("&nbsp;")
 254      if (ch == '<') out.print("&lt;")
 255      else if (ch == '>') out.print("&gt;")
 256      else if (ch == '&') out.print("&amp;")
 257      else out.writeChar(ch)
 258    }
 259  
 260    **
 261    ** Handle writing the buf, checking for keywords. This method
 262    ** assumes the buf is still in write mode.
 263    **
 264    private Void doBuf(Buf buf, OutStream out)
 265    {
 266      buf.flip
 267      s := buf.readAllStr
 268  
 269      if (isKeyword(s))
 270      {
 271        // Keyword
 272        out.print("<span class='f'>$s</span>") // keyword
 273      }
 274      else
 275      {
 276        // Indentifier
 277        out.print(s)
 278      }
 279    }
 280  
 281    **
 282    ** Return true if the buf contains only alphabetic characters,
 283    ** numerals or underscores.
 284    **
 285    private Bool isAlphaNum(Int ch)
 286    {
 287      return ch.isAlphaNum || ch === '_'
 288    }
 289  
 290    **
 291    ** Return true if this Str matches a keyword
 292    **
 293    private Bool isKeyword(Str s)
 294    {
 295      return keywords.get(s, false)
 296    }
 297  
 298    static const Str:Bool keywords
 299    static
 300    {
 301      list :=
 302     // TODO: this should really map to a bitmask per language
 303      [ "abstract",   "as",          "assert",      "boolean",
 304        "break",      "case",        "catch",       "class",
 305        "const",      "continue",    "default",     "delegate",
 306        "do",         "else",        "enum",        "event",
 307        "explicit",   "extends",     "extern",      "false",
 308        "final",      "finally",     "fixed",       "for",
 309        "foreach",    "goto",        "if",          "implements",
 310        "implicit",   "import",      "instanceof",  "interface",
 311        "internal",   "is",          "lock",        "mixin",
 312        "native",     "new",         "null",        "object",
 313        "operator",   "override",    "package",     "private",
 314        "protected",  "public",      "readonly",    "return",
 315        "sealed",     "sizeof",      "static",      "strictfp",
 316        "struct",     "super",       "switch",      "synchronized",
 317        "this",       "throw",       "throws",      "transient",
 318        "true",       "try",         "typeof",      "unchecked",
 319        "unsafe",     "using",       "virtual",     "void",
 320        "volatile",   "while"]
 321  
 322       map := Str:Bool[:]
 323       list.each |Str s| { map[s] = true }
 324       keywords = map.toImmutable
 325    }
 326  
 327  //////////////////////////////////////////////////////////////////////////
 328  // Fields
 329  //////////////////////////////////////////////////////////////////////////
 330  
 331    File srcFile
 332  }