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 **
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(" ")
254 if (ch == '<') out.print("<")
255 else if (ch == '>') out.print(">")
256 else if (ch == '&') out.print("&")
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 }