| 1 | #!/usr/bin/env lua |
| 2 | |
| 3 | -- Copyright (C) 2008-2009 Matthew Wild |
| 4 | -- |
| 5 | -- This project is MIT/X11 licensed. Please see the |
| 6 | -- COPYING file in the source package for more information. |
| 7 | |
| 8 | local keywords = { "function", "for", "if", "elseif", "then", "else", "do", "repeat", "until", "end", "return", "true", "false", "and", "not", "or", "local", "nil", "break" } |
| 9 | for _, keyword in ipairs(keywords) do keywords[keyword] = true; end |
| 10 | |
| 11 | local ops = {}; |
| 12 | |
| 13 | local bytelisting, err = io.popen("luac -l -p "..arg[1]); |
| 14 | if not bytelisting then print(err); return 1; end |
| 15 | |
| 16 | for line in bytelisting:lines() do |
| 17 | local opnum, linenum, opname, comments = line:match("^\t(%d+)%s*%[(%d+)%]%s*(%u+)[^;]*;? ?(.*)$"); |
| 18 | if opnum then |
| 19 | linenum = tonumber(linenum); |
| 20 | -- print(string.format("opnum: %d line: %d op: %s ; %s", opnum, linenum, opname, comments)); |
| 21 | if not ops[linenum] then |
| 22 | ops[linenum] = { }; |
| 23 | if ops[linenum-1] then |
| 24 | table.sort(ops[linenum-1], function (a,b) return a.comment and b.comment and #a.comment > #b.comment; end); |
| 25 | end |
| 26 | end |
| 27 | table.insert(ops[linenum], { name = opname, comment = comments }); |
| 28 | end |
| 29 | end |
| 30 | |
| 31 | local test_coverage = {}; |
| 32 | |
| 33 | local test_report, test_report_err = io.open("tests/reports/coverage_"..arg[1]:gsub("^%W", ""):gsub("%W+", "_")..".report"); |
| 34 | if test_report then |
| 35 | for line in test_report:lines() do |
| 36 | local f, linenum, test, success = line:match("^(.-)|(.-)|(.-)|(.-)$"); |
| 37 | if linenum and success then |
| 38 | test_coverage[tonumber(linenum)] = success; |
| 39 | else |
| 40 | io.stderr:write("Warning: Can't parse this: [[", line, "]] in test report\n"); |
| 41 | end |
| 42 | end |
| 43 | else |
| 44 | test_coverage = nil; |
| 45 | end |
| 46 | |
| 47 | |
| 48 | |
| 49 | local linenum = 1; |
| 50 | local html_spaces = { ["\t"] = " ", [" "] = " " }; |
| 51 | function escape_html(s) |
| 52 | return (s and s:gsub("[^%w]", { ["<"] = "<", [">"] = ">", ["&"] = "&", ["\""] = """, ["'"] = "'" })) or ""; |
| 53 | end |
| 54 | |
| 55 | function escape_pattern(s) |
| 56 | return s:gsub("(%p)", "%%%1"); |
| 57 | end |
| 58 | |
| 59 | local sub; |
| 60 | |
| 61 | print("<html><head><title>"..escape_html(arg[1]).."</title><style type='text/css'>"); |
| 62 | print [[ |
| 63 | .getglobal { color:#cc0000 !important; text-decoration:underline; } |
| 64 | .setglobal { color:#ff0000; font-weight:bold; text-decoration:underline; } |
| 65 | .getupval { color:#00cc00; } |
| 66 | .nop { color:#cccccc; } |
| 67 | .keyword { color:#0000cc; } |
| 68 | .comment, .comment .keyword { color:#007733; } |
| 69 | .string, .string * { color:#007755; } |
| 70 | |
| 71 | td .test-false, .test-false { background-color:#ffa6be !important; display:block; width:100%; } |
| 72 | td .test-true, .test-true { background-color:#beffa6; display:block; width:100%; } |
| 73 | td .test-miss, .test-miss { background-color:#a6beff; display:block; width:100%; } |
| 74 | |
| 75 | table { border-collapse: collapse; } |
| 76 | span, tr, *, table { padding:2px; } |
| 77 | td { padding:0px; padding-right:2em; } |
| 78 | ]] |
| 79 | print("</style></head><body>\n<br/>\n<table>"); |
| 80 | for line in io.lines(arg[1]) do |
| 81 | line = " "..line:gsub("%s", html_spaces).." "; |
| 82 | line = line:gsub("FIXME:?", "<b>%1</b>"); |
| 83 | line = line:gsub("TODO:?", "<b>%1</b>"); |
| 84 | line = line:gsub("%-%-.*$", "<span class='comment'>%1</span>"); |
| 85 | line = line:gsub("\".-[^\\]\"", "<span class='string'>%1</span>"); |
| 86 | if ops[linenum] then |
| 87 | for _, op in pairs(ops[linenum]) do |
| 88 | if op.comment and #op.comment > 0 and op.name ~= "JMP" then |
| 89 | --print("Replacing "..escape_pattern(op.comment).."("..op.comment..") for "..op.name); |
| 90 | line, sub = line:gsub("([^%w_.])("..escape_pattern(op.comment)..")([^%w_])", "%1<span class='"..op.name:lower().."'>"..escape_html(op.comment).."</span>%3"); |
| 91 | if sub == 0 and op.name == "GETTABLE" then |
| 92 | --print("Trying again..."); |
| 93 | local id = op.comment:match("[%w_]+"); |
| 94 | if id then |
| 95 | --print("Got "..id); |
| 96 | line = line:gsub(escape_pattern("."..id), ".<span class='"..op.name:lower().."'>"..escape_html(id).."</span>"); |
| 97 | end |
| 98 | end |
| 99 | else |
| 100 | --print("??", tostring(op.comment), tostring(#op.comment)); |
| 101 | end |
| 102 | end |
| 103 | line = line:gsub("[%w_]+", function (id) if keywords[id] then return "<span class='keyword'>"..id.."</span>"; end end); |
| 104 | local classes = { "line" }; |
| 105 | |
| 106 | if test_coverage and test_coverage[linenum] ~= nil then |
| 107 | table.insert(classes, "test-"..test_coverage[linenum]); |
| 108 | end |
| 109 | print("<tr><td><span class='"..table.concat(classes, " ").."'>"..linenum.."</span></td><td>", line, "</td></tr>"); |
| 110 | else |
| 111 | line = line:gsub("[%w_]+", function (id) if keywords[id] then return "<span class='keyword'>"..id.."</span>"; end end); |
| 112 | print("<tr><td><span class='line nop'>"..linenum.."</span></td><td>", line, "</td></tr>"); |
| 113 | end |
| 114 | linenum = linenum + 1; |
| 115 | end |
| 116 | |
| 117 | print("</table>\n</body>\n</html>"); |