MXVM 1.8.1
Virtual Machine, Compiler, and Pascal Frontend
Loading...
Searching...
No Matches
html_gen.cpp
Go to the documentation of this file.
1
6#include "mxvm/html_gen.hpp"
7#include "mxvm/instruct.hpp"
8#include <algorithm>
9#include <cctype>
10#include <unordered_set>
11
12namespace {
13 inline bool isIdentStart(char c) { return std::isalpha(static_cast<unsigned char>(c)) || c == '_'; }
14 inline bool isIdentChar(char c) { return std::isalnum(static_cast<unsigned char>(c)) || c == '_'; }
15 std::string lower(std::string s) {
16 std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
17 return s;
18 }
19 std::string html_escape(const std::string &s) {
20 std::string out;
21 out.reserve(s.size() * 12 / 10 + 8);
22 for (char c : s) {
23 switch (c) {
24 case '&':
25 out += "&amp;";
26 break;
27 case '<':
28 out += "&lt;";
29 break;
30 case '>':
31 out += "&gt;";
32 break;
33 case '"':
34 out += "&quot;";
35 break;
36 case '\'':
37 out += "&#39;";
38 break;
39 case '\n':
40 out += "\\n";
41 break;
42 case '\t':
43 out += "\\t";
44 break;
45 default:
46 out.push_back(c);
47 break;
48 }
49 }
50 return out;
51 }
52} // namespace
53
54namespace mxvm {
55
56 static std::unordered_set<std::string> buildInstructionSet() {
57 std::unordered_set<std::string> s;
58 for (const auto &name : IncType) {
59 if (name == "NULL")
60 continue;
61 s.insert(lower(name));
62 }
63 return s;
64 }
65
66 static std::unordered_set<std::string> buildKeywordSet() {
67 std::unordered_set<std::string> s;
68 for (const auto &k : keywords)
69 s.insert(lower(k));
70 const char *extra[] = {
71 "function", "export", "return", "start", "done", "true", "false", "null",
72 "string", "int", "byte", "float", "ptr", "object", "program", "module", "data", "code", "section"};
73 for (auto *p : extra)
74 s.insert(p);
75 return s;
76 }
77
78 static const std::unordered_set<std::string> kInstructions = buildInstructionSet();
79 static const std::unordered_set<std::string> kKeywords = buildKeywordSet();
80 static const std::unordered_set<std::string> kTypes = [] {
81 return std::unordered_set<std::string>{"string", "int", "byte", "float", "ptr"};
82 }();
83
84 HTMLGen::HTMLGen(const std::string &text) : source(text) {}
85
86 void HTMLGen::output(std::ostream &out, std::string filename) {
87 out << "<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">";
88 out << "<title>" << filename << " - [ MXVM Source ]</title>";
89 out << "<style>";
90 out << "body{background:#FFFFFF;color:#000000;margin:0;font:13px/1.6 \"Courier New\",Courier,monospace}";
91 out << ".wrap{max-width:1100px;margin:0 auto;padding:24px}";
92 out << "pre.code{white-space:pre-wrap;word-wrap:break-word;background:#FFFFFF;padding:16px;border:1px solid #CCCCCC;border-radius:2px}";
93 out << ".kw{color:#000080;font-weight:bold}";
94 out << ".op{color:#000080;font-weight:bold}";
95 out << ".id{color:#000000}";
96 out << ".num{color:#800000}";
97 out << ".str{color:#008080}";
98 out << ".com{color:#808080;font-style:italic}";
99 out << ".lbl{color:#008000;font-weight:bold}";
100 out << ".typ{color:#000080;font-weight:bold}";
101 out << "</style></head><body><div class=\"wrap\"><pre class=\"code\"><code>";
102 const std::string &s = source;
103 size_t i = 0, n = s.size();
104
105 // State for suppressing "string" highlighting inside section module { … }
106 enum class ModState { None, SawSection, SawModule, Inside };
107 ModState modState = ModState::None;
108
109 while (i < n) {
110 char c = s[i];
111 if (c == '\r') {
112 ++i;
113 continue;
114 }
115 if (c == '\n') {
116 out << "\n";
117 ++i;
118 continue;
119 }
120 if (c == '\t') {
121 out << "\t";
122 ++i;
123 continue;
124 }
125 if (c == ' ') {
126 out << " ";
127 ++i;
128 continue;
129 }
130
131 if (c == '#') {
132 size_t j = i;
133 while (j < n && s[j] != '\n')
134 ++j;
135 std::string t = s.substr(i, j - i);
136 out << "<span class=\"com\">" << html_escape(t) << "</span>";
137 i = j;
138 continue;
139 }
140
141 if (c == '"') {
142 size_t j = i + 1;
143 bool esc = false;
144 while (j < n) {
145 if (!esc && s[j] == '"') {
146 ++j;
147 break;
148 }
149 esc = (!esc && s[j] == '\\');
150 ++j;
151 }
152 std::string t = s.substr(i, j - i);
153 out << "<span class=\"str\">" << html_escape(t) << "</span>";
154 i = j;
155 continue;
156 }
157
158 if (std::isdigit(static_cast<unsigned char>(c)) || (c == '-' && i + 1 < n && std::isdigit(static_cast<unsigned char>(s[i + 1])))) {
159 size_t j = i;
160 if (s[j] == '-')
161 ++j;
162 if (j + 1 < n && s[j] == '0' && (s[j + 1] == 'x' || s[j + 1] == 'X')) {
163 j += 2;
164 while (j < n && std::isxdigit(static_cast<unsigned char>(s[j])))
165 ++j;
166 } else {
167 while (j < n && std::isdigit(static_cast<unsigned char>(s[j])))
168 ++j;
169 if (j < n && s[j] == '.') {
170 ++j;
171 while (j < n && std::isdigit(static_cast<unsigned char>(s[j])))
172 ++j;
173 }
174 }
175 std::string t = s.substr(i, j - i);
176 out << "<span class=\"num\">" << html_escape(t) << "</span>";
177 i = j;
178 continue;
179 }
180
181 if (isIdentStart(c)) {
182 size_t j = i + 1;
183 while (j < n && isIdentChar(s[j]))
184 ++j;
185 bool is_label = (j < n && s[j] == ':');
186 std::string ident = s.substr(i, j - i);
187 std::string lid = lower(ident);
188 if (is_label) {
189 out << "<span class=\"lbl\">" << html_escape(ident) << "</span>:";
190 i = j + 1;
191 modState = ModState::None;
192 continue;
193 }
194
195 // Advance the section module { … } state machine
196 if (lid == "section" && modState == ModState::None) {
197 modState = ModState::SawSection;
198 } else if (lid == "module" && modState == ModState::SawSection) {
199 modState = ModState::SawModule;
200 } else if (modState != ModState::Inside) {
201 modState = ModState::None;
202 }
203
204 // Inside a module section, suppress type/keyword highlighting
205 // so that module names like "string" render as plain identifiers.
206 bool suppress = (modState == ModState::Inside);
207
208 if (kInstructions.count(lid)) {
209 out << "<span class=\"op\">" << html_escape(ident) << "</span>";
210 } else if (!suppress && (kTypes.count(lid) || kKeywords.count(lid))) {
211 if (kTypes.count(lid))
212 out << "<span class=\"typ\">" << html_escape(ident) << "</span>";
213 else
214 out << "<span class=\"kw\">" << html_escape(ident) << "</span>";
215 } else {
216 out << "<span class=\"id\">" << html_escape(ident) << "</span>";
217 }
218 i = j;
219 continue;
220 }
221
222 if (c == '{' || c == '}' || c == ',' || c == '(' || c == ')' || c == '[' || c == ']') {
223 if (c == '{' && modState == ModState::SawModule) {
224 modState = ModState::Inside;
225 } else if (c == '}' && modState == ModState::Inside) {
226 modState = ModState::None;
227 }
228 out << html_escape(std::string(1, c));
229 ++i;
230 continue;
231 }
232
233 out << html_escape(std::string(1, c));
234 ++i;
235 }
236 out << "</code></pre></div></body></html>";
237 }
238
239} // namespace mxvm
240
241// ============================================================
242// Pascal highlighter
243// ============================================================
244
245namespace {
246
248 std::string pas_html_escape(const std::string &s) {
249 std::string out;
250 out.reserve(s.size() + 16);
251 for (char c : s) {
252 switch (c) {
253 case '&': out += "&amp;"; break;
254 case '<': out += "&lt;"; break;
255 case '>': out += "&gt;"; break;
256 case '"': out += "&quot;"; break;
257 case '\'': out += "&#39;"; break;
258 default: out.push_back(c); break;
259 }
260 }
261 return out;
262 }
263
264 // ---- Pascal keyword sets ----------------------------------------
265
267 const std::unordered_set<std::string> &kPasKeywords() {
268 static const std::unordered_set<std::string> s = {
269 "program", "unit", "interface", "implementation", "uses",
270 "var", "const", "type", "procedure", "function", "forward",
271 "begin", "end",
272 "if", "then", "else", "while", "do",
273 "for", "to", "downto", "repeat", "until",
274 "case", "of", "with", "goto", "label",
275 "in", "and", "or", "not", "div", "mod", "xor", "shl", "shr",
276 "packed", "set", "file", "array", "record",
277 };
278 return s;
279 }
280
282 const std::unordered_set<std::string> &kPasTypes() {
283 static const std::unordered_set<std::string> s = {
284 "integer", "real", "boolean", "char",
285 "byte", "word", "longint", "shortint", "smallint", "cardinal",
286 "string", "text", "double", "single", "extended",
287 "comp", "currency", "ptr", "pointer",
288 };
289 return s;
290 }
291
293 const std::unordered_set<std::string> &kPasBuiltins() {
294 static const std::unordered_set<std::string> s = {
295 // I/O
296 "writeln", "write", "readln", "read",
297 "assign", "reset", "rewrite", "append", "close", "eof", "eoln",
298 // memory / dynamic
299 "new", "dispose", "setlength", "high", "low",
300 // control
301 "exit", "break", "continue", "halt",
302 // predefined consts
303 "nil", "true", "false",
304 // special function return var
305 "result",
306 // math / conversion
307 "ord", "chr", "succ", "pred",
308 "inc", "dec",
309 "abs", "sqr", "sqrt", "trunc", "round",
310 "odd", "upcase",
311 "random", "randomize", "seed_random", "rand_number",
312 // string
313 "length", "copy", "pos", "concat", "delete", "insert", "str", "val",
314 // set
315 "include", "exclude",
316 };
317 return s;
318 }
319
320} // anonymous namespace
321
322namespace mxvm {
323
324 PasHTMLGen::PasHTMLGen(const std::string &src) : source(src) {}
325
327 return
328 "/* mxvm-highlight.css — syntax highlight stylesheet for mxvm-html\n"
329 " Inspired by the Borland Delphi 7 editor default colour scheme.\n"
330 " Edit this file to change colours; it takes precedence over the\n"
331 " inline fallback styles that are embedded in every HTML file. */\n"
332 "\n"
333 "body {\n"
334 " background: #FFFFFF;\n"
335 " color: #000000;\n"
336 " margin: 0;\n"
337 " font: 13px/1.6 \"Courier New\", Courier, monospace;\n"
338 "}\n"
339 ".wrap {\n"
340 " max-width: 1100px;\n"
341 " margin: 0 auto;\n"
342 " padding: 24px;\n"
343 "}\n"
344 "pre.code {\n"
345 " white-space: pre-wrap;\n"
346 " word-wrap: break-word;\n"
347 " background: #FFFFFF;\n"
348 " padding: 16px;\n"
349 " border: 1px solid #CCCCCC;\n"
350 " border-radius: 2px;\n"
351 "}\n"
352 "\n"
353 "/* ---- token spans ---- */\n"
354 "\n"
355 "/* reserved keywords — bold navy (classic Delphi blue) */\n"
356 ".kw { color: #000080; font-weight: bold; }\n"
357 "/* built-in types — same as keywords */\n"
358 ".typ { color: #000080; font-weight: bold; }\n"
359 "/* built-in routines and predefined constants */\n"
360 ".bi { color: #000080; }\n"
361 "/* string / char literals — teal */\n"
362 ".str { color: #008080; }\n"
363 "/* numeric literals — maroon */\n"
364 ".num { color: #800000; }\n"
365 "/* comments — gray italic */\n"
366 ".com { color: #808080; font-style: italic; }\n"
367 "/* plain identifiers — black */\n"
368 ".id { color: #000000; }\n"
369 "\n"
370 "/* ---- mxvm-specific (also used by .mxvm output) ---- */\n"
371 "\n"
372 "/* VM instructions */\n"
373 ".op { color: #000080; font-weight: bold; }\n"
374 "/* VM labels */\n"
375 ".lbl { color: #008000; font-weight: bold; }\n";
376 }
377
378 void PasHTMLGen::output(std::ostream &out, const std::string &filename,
379 const std::string &cssHref) {
380 out << "<!doctype html><html><head>"
381 << "<meta charset=\"utf-8\">"
382 << "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
383 << "<title>" << pas_html_escape(filename) << " - [ Pascal Source ]</title>"
384 // Inline fallback: guarantees highlighting even if the CSS file is absent
385 << "<style>\n" << defaultCss() << "</style>"
386 // External file: user edits this to customise; it overrides the inline block
387 << "<link rel=\"stylesheet\" href=\"" << pas_html_escape(cssHref) << "\">"
388 << "</head><body><div class=\"wrap\"><pre class=\"code\"><code>";
389
390 const std::string &s = source;
391 size_t i = 0, n = s.size();
392
393 while (i < n) {
394 char c = s[i];
395
396 // -- whitespace --------------------------------------------------
397 if (c == '\r') { ++i; continue; }
398 if (c == '\n') { out << '\n'; ++i; continue; }
399 if (c == '\t') { out << '\t'; ++i; continue; }
400 if (c == ' ') { out << ' '; ++i; continue; }
401
402 // -- line comment // ... ----------------------------------------
403 if (c == '/' && i + 1 < n && s[i + 1] == '/') {
404 size_t j = i;
405 while (j < n && s[j] != '\n') ++j;
406 out << "<span class=\"com\">" << pas_html_escape(s.substr(i, j - i)) << "</span>";
407 i = j;
408 continue;
409 }
410
411 // -- block comment { ... } --------------------------------------
412 if (c == '{') {
413 size_t j = i + 1;
414 while (j < n && s[j] != '}') ++j;
415 if (j < n) ++j; // include '}'
416 out << "<span class=\"com\">" << pas_html_escape(s.substr(i, j - i)) << "</span>";
417 i = j;
418 continue;
419 }
420
421 // -- block comment (* ... *) ------------------------------------
422 if (c == '(' && i + 1 < n && s[i + 1] == '*') {
423 size_t j = i + 2;
424 while (j + 1 < n && !(s[j] == '*' && s[j + 1] == ')')) ++j;
425 if (j + 1 < n) j += 2; // include '*)'
426 out << "<span class=\"com\">" << pas_html_escape(s.substr(i, j - i)) << "</span>";
427 i = j;
428 continue;
429 }
430
431 // -- Pascal char literal #65 or #$41 --------------------------
432 if (c == '#') {
433 size_t j = i + 1;
434 if (j < n && s[j] == '$') {
435 ++j;
436 while (j < n && std::isxdigit(static_cast<unsigned char>(s[j]))) ++j;
437 } else {
438 while (j < n && std::isdigit(static_cast<unsigned char>(s[j]))) ++j;
439 }
440 out << "<span class=\"num\">" << pas_html_escape(s.substr(i, j - i)) << "</span>";
441 i = j;
442 continue;
443 }
444
445 // -- string literal 'hello' with '' escape -------------------
446 if (c == '\'') {
447 size_t j = i + 1;
448 while (j < n) {
449 if (s[j] == '\'') {
450 ++j;
451 if (j < n && s[j] == '\'') { ++j; continue; } // '' → single quote
452 break;
453 }
454 ++j;
455 }
456 out << "<span class=\"str\">" << pas_html_escape(s.substr(i, j - i)) << "</span>";
457 i = j;
458 continue;
459 }
460
461 // -- hex literal $FF --------------------------------------------
462 if (c == '$') {
463 size_t j = i + 1;
464 while (j < n && std::isxdigit(static_cast<unsigned char>(s[j]))) ++j;
465 out << "<span class=\"num\">" << pas_html_escape(s.substr(i, j - i)) << "</span>";
466 i = j;
467 continue;
468 }
469
470 // -- decimal / float (don't consume '..' as a decimal point) ----
471 if (std::isdigit(static_cast<unsigned char>(c))) {
472 size_t j = i;
473 while (j < n && std::isdigit(static_cast<unsigned char>(s[j]))) ++j;
474 if (j < n && s[j] == '.' && (j + 1 >= n || s[j + 1] != '.')) {
475 ++j;
476 while (j < n && std::isdigit(static_cast<unsigned char>(s[j]))) ++j;
477 }
478 if (j < n && (s[j] == 'e' || s[j] == 'E')) {
479 ++j;
480 if (j < n && (s[j] == '+' || s[j] == '-')) ++j;
481 while (j < n && std::isdigit(static_cast<unsigned char>(s[j]))) ++j;
482 }
483 out << "<span class=\"num\">" << pas_html_escape(s.substr(i, j - i)) << "</span>";
484 i = j;
485 continue;
486 }
487
488 // -- identifier / keyword ----------------------------------------
489 if (isIdentStart(c)) {
490 size_t j = i + 1;
491 while (j < n && isIdentChar(s[j])) ++j;
492 std::string ident = s.substr(i, j - i);
493 std::string lid = lower(ident);
494 i = j;
495
496 if (kPasKeywords().count(lid))
497 out << "<span class=\"kw\">" << pas_html_escape(ident) << "</span>";
498 else if (kPasTypes().count(lid))
499 out << "<span class=\"typ\">" << pas_html_escape(ident) << "</span>";
500 else if (kPasBuiltins().count(lid))
501 out << "<span class=\"bi\">" << pas_html_escape(ident) << "</span>";
502 else
503 out << "<span class=\"id\">" << pas_html_escape(ident) << "</span>";
504 continue;
505 }
506
507 // -- everything else (operators, punctuation) --------------------
508 out << pas_html_escape(std::string(1, c));
509 ++i;
510 }
511
512 out << "</code></pre></div></body></html>";
513 }
514
515} // namespace mxvm
void output(std::ostream &out, std::string filename)
Write the generated HTML to an output stream.
Definition html_gen.cpp:86
HTMLGen(const std::string &src)
Construct an HTML generator from MXVM source text.
Definition html_gen.cpp:84
std::string source
the MXVM source text to convert
Definition html_gen.hpp:31
std::string source
Definition html_gen.hpp:70
PasHTMLGen(const std::string &src)
Construct from Pascal source text.
Definition html_gen.cpp:324
void output(std::ostream &out, const std::string &filename, const std::string &cssHref="mxvm-highlight.css")
Write generated HTML to an output stream.
Definition html_gen.cpp:378
static std::string defaultCss()
Return the default stylesheet text.
Definition html_gen.cpp:326
HTML documentation generator from MXVM source code.
Instruction set enum, operand/instruction structs, variable types, and Variable/Variable_Value defini...
std::vector< std::string > IncType
String representations of Inc opcodes, indexed by enum value.
Definition instruct.hpp:82
Definition ast.hpp:14
@ None
exact fixed operand count required
Definition valid.hpp:49
static const std::unordered_set< std::string > kInstructions
Definition html_gen.cpp:78
static std::unordered_set< std::string > buildKeywordSet()
Definition html_gen.cpp:66
static bool is_label(const std::string &line)
Definition icode_opt.cpp:33
static std::unordered_set< std::string > buildInstructionSet()
Definition html_gen.cpp:56
static const std::unordered_set< std::string > kKeywords
Definition html_gen.cpp:79
static const std::unordered_set< std::string > kTypes
Definition html_gen.cpp:80
std::vector< std::string > keywords
String representations of Keywords enum values.
Definition instruct.cpp:10