Skip to content

Commit 7eaab36

Browse files
committed
Add helper class to generate HTML
1 parent cbb52d5 commit 7eaab36

File tree

15 files changed

+351
-256
lines changed

15 files changed

+351
-256
lines changed

ppci/cli/compile_base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from ..wasm import ir_to_wasm
1010
from .base import out_parser
1111

12+
logger = logging.getLogger("base")
1213
compile_parser = argparse.ArgumentParser(add_help=False, parents=[out_parser])
1314
compile_parser.add_argument(
1415
"-g", help="create debug information", action="store_true", default=False
@@ -86,4 +87,4 @@ def do_compile(ir_modules, march, reporter, args):
8687
obj.save(output)
8788

8889
# TODO: link objects together?
89-
logging.warning("TODO: Linking with stdlibs")
90+
logger.warning("TODO: Linking with stdlibs")

ppci/lang/ocaml/cmo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
def read_file(filename):
1414
"""Read a cmo or bytecode file."""
1515
if isinstance(filename, str):
16-
logging.info("Processing %s", filename)
16+
logger.info("Processing %s", filename)
1717
with open(filename, "rb") as f:
1818
return read_file(f)
1919
else:

ppci/utils/htmlgen.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
Helper utilities to create html reports.
3+
"""
4+
5+
import html
6+
from contextlib import contextmanager
7+
8+
9+
class Base:
10+
"""Base HTML output"""
11+
12+
def __init__(self, f):
13+
self._f = f
14+
15+
def print(self, text):
16+
print(text, file=self._f)
17+
18+
@contextmanager
19+
def tag(self, name):
20+
self.print(f"<{name}>")
21+
yield self
22+
self.print(f"</{name}>")
23+
24+
25+
class Document(Base):
26+
def __init__(self, f: str):
27+
super().__init__(f)
28+
29+
def __enter__(self):
30+
self.print("<!DOCTYPE html><html>")
31+
return self
32+
33+
def __exit__(self, *exc):
34+
self.print("</html>")
35+
36+
def head(self) -> "Head":
37+
return Head(self._f)
38+
39+
def body(self) -> "Body":
40+
return Body(self._f)
41+
42+
43+
class Head(Base):
44+
def __enter__(self):
45+
self.print("<head>")
46+
return self
47+
48+
def __exit__(self, *exc):
49+
self.print("</head>")
50+
51+
def title(self, caption: str):
52+
with self.tag("title"):
53+
self.print(caption)
54+
55+
def style(self, content: str):
56+
with self.tag("style"):
57+
self.print(content)
58+
59+
60+
class Body(Base):
61+
"""HTML body"""
62+
63+
def __enter__(self):
64+
self.print("<body>")
65+
return self
66+
67+
def __exit__(self, *exc):
68+
self.print("</body>")
69+
70+
def h1(self, caption: str):
71+
self.header(caption, level=1)
72+
73+
def h2(self, caption: str):
74+
self.header(caption, level=2)
75+
76+
def h3(self, caption: str):
77+
self.header(caption, level=3)
78+
79+
def h4(self, caption: str):
80+
self.header(caption, level=4)
81+
82+
def header(self, caption: str, level=1):
83+
with self.tag(f"h{level}"):
84+
self.print(caption)
85+
86+
def paragraph(self, text: str):
87+
with self.tag("p"):
88+
self.print(text)
89+
90+
def pre(self, text: str):
91+
with self.tag("pre"):
92+
self.print(html.escape(text))
93+
94+
def table(self) -> "Table":
95+
return Table(self._f)
96+
97+
98+
class Table(Base):
99+
"""Table item"""
100+
101+
def __enter__(self):
102+
self.print('<table border="1">')
103+
return self
104+
105+
def __exit__(self, exc_type, exc_value, traceback):
106+
self.print("</table>")
107+
108+
def header(self, *texts):
109+
self.row(*texts, tag="th")
110+
111+
def row(self, *texts, tag="td", escape=True):
112+
with self.tag("tr"):
113+
for text in texts:
114+
with self.tag(tag):
115+
text = html.escape(text) if escape else text
116+
self.print(text)

ppci/wasm/util.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"has_node",
2424
]
2525

26+
logger = logging.getLogger("wasm")
2627
this_dir = Path(__file__).resolve().parent
2728
PAGE_SIZE = 64 * 1024 # 64 KiB
2829
hex_prog = re.compile(r"-?0x[0-9a-fA-F]+")
@@ -218,7 +219,7 @@ def export_wasm_example(filename, code, wasm, main_js=""):
218219

219220
# Export HTML file
220221
filename.write_text(html)
221-
logging.info("Wrote example HTML to %s", filename)
222+
logger.info("Wrote example HTML to %s", filename)
222223

223224

224225
_nb_output = 0

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ extend-select = [
7979
"UP", # pyupgrade
8080
"I", # isort
8181
"C4", # flake8-comprehensions
82-
# "LOG",
83-
# "N",
84-
# "PTH",
82+
"LOG", # flake8-logging
83+
# "N", # Naming conventions
84+
# "PTH", # Pathlib usage
8585
]
8686
extend-ignore = [
8787
"E203", # Conflicts with black formatting

test/lang/c/test_c.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,14 @@ def test_struct(self):
270270
"""
271271
self.do(src)
272272

273+
def test_incomplete_struct(self):
274+
"""Test usage of incomplete struct"""
275+
src = """
276+
struct st;
277+
struct st a;
278+
"""
279+
self.expect_error(src, 3, "Type of variable 'a' is incomplete")
280+
273281
def test_tag_scoping(self):
274282
src = """
275283
void f(int n) {
@@ -739,6 +747,16 @@ def test_afterwards_declaration(self):
739747
"""
740748
self.do(src)
741749

750+
@unittest.skip("TODO")
751+
def test_extern_after_static(self):
752+
"""Test redeclaration"""
753+
src = """
754+
static int i4; // internal linkage
755+
extern int i4; // keep internal linkage
756+
int i4; // illegal: try to change linkage to external
757+
"""
758+
self.expect_error(src, 4, "Invalid re-declaration")
759+
742760
def test_variable_double_definition(self):
743761
"""Test double definition raises an error."""
744762
src = """

tools/analyze_c_code.py

Lines changed: 75 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from ppci.common import CompilerError
1919
from ppci.lang.c import COptions, create_ast
2020
from ppci.lang.c.nodes import declarations
21+
from ppci.utils.htmlgen import Document
2122

2223
this_path = Path(__file__).resolve().parent
2324
root_path = this_path.parent
@@ -35,14 +36,30 @@ def main():
3536
analyze_sources(args.source_dir, defines)
3637

3738

38-
def get_tag(filename: Path) -> str:
39-
return filename.stem
40-
41-
4239
def analyze_sources(source_dir: Path, defines):
4340
"""Analyze a directory with sourcecode"""
4441

4542
# Phase 1: acquire ast's:
43+
asts = read_sources(source_dir, defines)
44+
45+
# Phase 2: do some bad-ass analysis:
46+
global_variables = []
47+
functions = []
48+
for _source_filename, _source_code, ast in asts:
49+
for decl in ast.declarations:
50+
if isinstance(decl, declarations.VariableDeclaration):
51+
global_variables.append(decl)
52+
elif isinstance(decl, declarations.FunctionDeclaration):
53+
if decl.body is not None and decl.storage_class != "static":
54+
functions.append(decl)
55+
56+
functions.sort(key=lambda d: d.name)
57+
58+
# Phase 3: generate html report?
59+
gen_report(functions, asts)
60+
61+
62+
def read_sources(source_dir: Path, defines):
4663
arch_info = get_arch("x86_64").info
4764
coptions = COptions()
4865
# TODO: infer defines from code:
@@ -65,100 +82,73 @@ def analyze_sources(source_dir: Path, defines):
6582
else:
6683
asts.append((source_filename, source_code, ast))
6784
logger.info("Got %s ast's", len(asts))
85+
return asts
6886

69-
# Phase 2: do some bad-ass analysis:
70-
global_variables = []
71-
functions = []
72-
for _source_filename, _source_code, ast in asts:
73-
for decl in ast.declarations:
74-
if isinstance(decl, declarations.VariableDeclaration):
75-
global_variables.append(decl)
76-
elif isinstance(decl, declarations.FunctionDeclaration):
77-
if decl.body is not None and decl.storage_class != "static":
78-
functions.append(decl)
7987

80-
functions.sort(key=lambda d: d.name)
88+
def get_tag(filename: Path) -> str:
89+
return filename.stem
8190

82-
# Phase 3: generate html report?
91+
92+
def gen_report(functions, asts):
8393
html_filename = "analyze_report.html"
8494
html_path = (
8595
build_path / html_filename
8696
if build_path.exists()
8797
else Path(html_filename)
8898
)
8999
logger.info(f"Creating {html_path}")
90-
with open(html_path, "w") as f:
91-
c_lexer = CLexer()
100+
with open(html_path, "w") as f, Document(f) as doc:
92101
formatter = HtmlFormatter(lineanchors="fubar", linenos="inline")
93-
print(
94-
f"""<html>
95-
<head>
96-
<style>
97-
{formatter.get_style_defs()}
98-
</style>
99-
</head>
100-
<body>
101-
""",
102-
file=f,
103-
)
104-
105-
print("<h1>Overview</h1>", file=f)
106-
print("<table>", file=f)
107-
print("<tr><th>Name</th><th>Location</th><th>typ</th></tr>", file=f)
108-
for func in functions:
109-
tagname = get_tag(func.location.filename)
110-
name = f'<a href="#{tagname}-{func.location.row}">{func.name}</a>'
111-
print(
112-
"<tr><td>{}</td><td>{}</td><td>{}</td></tr>".format(
113-
name, "", ""
114-
),
115-
file=f,
102+
with doc.head() as head:
103+
head.title("Analyzed C code")
104+
head.style(formatter.get_style_defs())
105+
106+
with doc.body() as body:
107+
body.h1("Overview")
108+
with body.table() as table:
109+
table.header("Name", "Location")
110+
for func in functions:
111+
tagname = get_tag(func.location.filename)
112+
tagname = f"{tagname}-{func.location.row}"
113+
name = f'<a href="#{tagname}">{func.name}</a>'
114+
location = str(func.location)
115+
table.row(name, location, escape=False)
116+
117+
body.h1("Files")
118+
for source_filename, source_code, ast in asts:
119+
report_single_file(body, source_filename, source_code, ast)
120+
121+
122+
def report_single_file(body, source_filename, source_code, ast):
123+
tagname = get_tag(source_filename)
124+
body.h2(str(source_filename))
125+
with body.table() as table:
126+
table.header("Name", "Location", "typ", "storage_class")
127+
for decl in ast.declarations:
128+
if isinstance(decl, declarations.VariableDeclaration):
129+
tp = "var"
130+
elif isinstance(decl, declarations.FunctionDeclaration):
131+
tp = "func"
132+
else:
133+
tp = "other"
134+
135+
if source_filename == decl.location.filename:
136+
anchor = f"{tagname}-{decl.location.row}"
137+
name = f'<a href="#{anchor}">{decl.name}</a>'
138+
else:
139+
name = decl.name
140+
table.row(
141+
name,
142+
str(decl.location),
143+
tp,
144+
str(decl.storage_class),
145+
escape=False,
116146
)
117147

118-
print("</table>", file=f)
119-
print("<h1>Files</h1>", file=f)
120-
121-
for source_filename, source_code, ast in asts:
122-
tagname = get_tag(source_filename)
123-
formatter = HtmlFormatter(lineanchors=tagname, linenos="inline")
124-
print(f"<h2>{source_filename}</h2>", file=f)
125-
print("<table>", file=f)
126-
print(
127-
"<tr><th>Name</th><th>Location</th><th>typ</th></tr>", file=f
128-
)
129-
for decl in ast.declarations:
130-
if isinstance(decl, declarations.VariableDeclaration):
131-
tp = "var"
132-
elif isinstance(decl, declarations.FunctionDeclaration):
133-
tp = "func"
134-
else:
135-
tp = "other"
136-
137-
tp += str(decl.storage_class)
138-
139-
if source_filename == decl.location.filename:
140-
anchor = f"{tagname}-{decl.location.row}"
141-
name = f'<a href="#{anchor}">{decl.name}</a>'
142-
else:
143-
name = decl.name
144-
145-
print(
146-
f"<tr><td>{name}</td>"
147-
f"<td>{decl.location}</td>"
148-
f"<td>{tp}</td></tr>",
149-
file=f,
150-
)
151-
print("</table>", file=f)
152-
print(""" <div>""", file=f)
153-
print(highlight(source_code, c_lexer, formatter), file=f)
154-
print(""" </div>""", file=f)
155-
156-
print(
157-
"""</body>
158-
</html>
159-
""",
160-
file=f,
161-
)
148+
c_lexer = CLexer()
149+
formatter = HtmlFormatter(lineanchors=tagname, linenos="inline")
150+
with body.tag("div"):
151+
print(highlight(source_code, c_lexer, formatter), file=body._f)
162152

163153

164154
if __name__ == "__main__":

0 commit comments

Comments
 (0)