svdoc 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- svdoc/__init__.py +0 -0
- svdoc/build.py +71 -0
- svdoc/cli.py +123 -0
- svdoc/fixer.py +91 -0
- svdoc/ir.py +166 -0
- svdoc/parser.py +455 -0
- svdoc/render_html.py +219 -0
- svdoc/render_md.py +127 -0
- svdoc-0.1.0.dist-info/METADATA +119 -0
- svdoc-0.1.0.dist-info/RECORD +14 -0
- svdoc-0.1.0.dist-info/WHEEL +5 -0
- svdoc-0.1.0.dist-info/entry_points.txt +2 -0
- svdoc-0.1.0.dist-info/licenses/LICENSE +21 -0
- svdoc-0.1.0.dist-info/top_level.txt +1 -0
svdoc/__init__.py
ADDED
|
File without changes
|
svdoc/build.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""svdoc build: a flat multi-page HTML site (one page per construct) with a
|
|
2
|
+
navigable index, so cross-links between pages always resolve correctly --
|
|
3
|
+
avoids the relative-path problem of generating standalone pages into
|
|
4
|
+
whatever directories the source files happen to live in.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pathlib
|
|
8
|
+
import re
|
|
9
|
+
from html import escape
|
|
10
|
+
|
|
11
|
+
from . import render_html
|
|
12
|
+
from .ir import InterfaceDoc, ModuleDoc, PackageDoc
|
|
13
|
+
from .parser import parse_file, resolve_types
|
|
14
|
+
|
|
15
|
+
_UNSAFE_NAME_CHARS = re.compile(r"[^\w.-]")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _safe_page_name(name: str) -> str:
|
|
19
|
+
"""Sanitize a construct name for use as an output filename. SV escaped
|
|
20
|
+
identifiers (``\\../../foo``) parse without error and can contain path
|
|
21
|
+
separators, so page filenames can't trust ``doc.name`` directly."""
|
|
22
|
+
return _UNSAFE_NAME_CHARS.sub("_", name)
|
|
23
|
+
|
|
24
|
+
_RENDERERS = {
|
|
25
|
+
ModuleDoc: render_html.render,
|
|
26
|
+
InterfaceDoc: render_html.render_interface,
|
|
27
|
+
PackageDoc: render_html.render_package,
|
|
28
|
+
}
|
|
29
|
+
_KIND_LABELS = {
|
|
30
|
+
ModuleDoc: "Module",
|
|
31
|
+
InterfaceDoc: "Interface",
|
|
32
|
+
PackageDoc: "Package",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def build_site(paths: list, out_dir: str) -> str:
|
|
37
|
+
"""Parse every file in ``paths`` and write one HTML page per construct
|
|
38
|
+
into a single flat ``out_dir``, plus an ``index.html`` linking to all of
|
|
39
|
+
them. Because every page lands in the same directory, the convention-based
|
|
40
|
+
cross-links in :mod:`svdoc.render_html` (``pkg_name.html#member``) always
|
|
41
|
+
resolve correctly, regardless of where the source ``.sv`` files live.
|
|
42
|
+
|
|
43
|
+
:param paths: All ``.sv`` files to document. Each is parsed independently
|
|
44
|
+
(one construct per file, same as :func:`svdoc.parser.parse_file`);
|
|
45
|
+
cross-file type resolution runs against the full set for every module.
|
|
46
|
+
:param out_dir: Directory to write the site into. Created if missing.
|
|
47
|
+
:returns: Path to the written ``index.html``.
|
|
48
|
+
:raises ValueError: If any file fails to parse cleanly.
|
|
49
|
+
"""
|
|
50
|
+
out = pathlib.Path(out_dir)
|
|
51
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
|
|
53
|
+
docs = [parse_file(p) for p in paths]
|
|
54
|
+
for doc in docs:
|
|
55
|
+
if isinstance(doc, ModuleDoc):
|
|
56
|
+
resolve_types(doc, paths)
|
|
57
|
+
|
|
58
|
+
index_lines = ["<h1>svdoc site</h1>", "<ul>"]
|
|
59
|
+
for doc in sorted(docs, key=lambda d: (_KIND_LABELS[type(d)], d.name)):
|
|
60
|
+
page_name = f"{_safe_page_name(doc.name)}.html"
|
|
61
|
+
(out / page_name).write_text(_RENDERERS[type(doc)](doc))
|
|
62
|
+
label = _KIND_LABELS[type(doc)]
|
|
63
|
+
index_lines.append(
|
|
64
|
+
f'<li>{label}: <a href="{escape(page_name)}">{escape(doc.name)}</a></li>'
|
|
65
|
+
)
|
|
66
|
+
index_lines.append("</ul>")
|
|
67
|
+
|
|
68
|
+
index_html = render_html.page("svdoc site", index_lines)
|
|
69
|
+
index_path = out / "index.html"
|
|
70
|
+
index_path.write_text(index_html)
|
|
71
|
+
return str(index_path)
|
svdoc/cli.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""svdoc CLI (v1): `svdoc <file.sv>` prints to terminal; `--out md`/`--out html`
|
|
2
|
+
writes a file; `svdoc build <files...>` writes a multi-page linked HTML site.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import pathlib
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from . import render_html, render_md
|
|
10
|
+
from .build import build_site
|
|
11
|
+
from .fixer import fix_file
|
|
12
|
+
from .ir import InterfaceDoc, ModuleDoc, PackageDoc
|
|
13
|
+
from .parser import parse_file, resolve_types
|
|
14
|
+
|
|
15
|
+
_RENDERERS = {
|
|
16
|
+
"md": {
|
|
17
|
+
ModuleDoc: render_md.render,
|
|
18
|
+
InterfaceDoc: render_md.render_interface,
|
|
19
|
+
PackageDoc: render_md.render_package,
|
|
20
|
+
},
|
|
21
|
+
"html": {
|
|
22
|
+
ModuleDoc: render_html.render,
|
|
23
|
+
InterfaceDoc: render_html.render_interface,
|
|
24
|
+
PackageDoc: render_html.render_package,
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
_EXTENSIONS = {"md": ".md", "html": ".html"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _run_single(args):
|
|
31
|
+
if args.fix:
|
|
32
|
+
changed = fix_file(args.file)
|
|
33
|
+
print(f"{'fixed' if changed else 'no changes needed for'} {args.file}")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
doc = parse_file(args.file)
|
|
37
|
+
if args.more_files and isinstance(doc, ModuleDoc):
|
|
38
|
+
resolve_types(doc, [args.file] + args.more_files)
|
|
39
|
+
|
|
40
|
+
if args.out:
|
|
41
|
+
text = _RENDERERS[args.out][type(doc)](doc)
|
|
42
|
+
out_path = pathlib.Path(args.file).with_suffix(_EXTENSIONS[args.out])
|
|
43
|
+
out_path.write_text(text)
|
|
44
|
+
print(f"wrote {out_path}")
|
|
45
|
+
else:
|
|
46
|
+
print(_RENDERERS["md"][type(doc)](doc))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _run_build(args):
|
|
50
|
+
index_path = build_site(args.files, args.out_dir)
|
|
51
|
+
print(f"wrote {len(args.files)} file(s) to a site at {index_path}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _build_parser():
|
|
55
|
+
ap = argparse.ArgumentParser(prog="svdoc build")
|
|
56
|
+
ap.add_argument("files", nargs="+", help=".sv files to document")
|
|
57
|
+
ap.add_argument("--out-dir", required=True, help="directory to write the site into")
|
|
58
|
+
return ap
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _single_parser():
|
|
62
|
+
ap = argparse.ArgumentParser(
|
|
63
|
+
prog="svdoc",
|
|
64
|
+
epilog="Also available: `svdoc build <files...> --out-dir <dir>` to "
|
|
65
|
+
"generate a multi-page linked HTML site.",
|
|
66
|
+
)
|
|
67
|
+
ap.add_argument(
|
|
68
|
+
"file",
|
|
69
|
+
help="path to the .sv file containing the module/interface/package to document",
|
|
70
|
+
)
|
|
71
|
+
ap.add_argument(
|
|
72
|
+
"more_files",
|
|
73
|
+
nargs="*",
|
|
74
|
+
help="additional .sv files (e.g. packages it depends on) used only to "
|
|
75
|
+
"resolve cross-file types -- not documented themselves",
|
|
76
|
+
)
|
|
77
|
+
ap.add_argument(
|
|
78
|
+
"--out",
|
|
79
|
+
choices=["md", "html"],
|
|
80
|
+
help="write rendered doc to a file in this format instead of printing to stdout",
|
|
81
|
+
)
|
|
82
|
+
ap.add_argument(
|
|
83
|
+
"--fix",
|
|
84
|
+
action="store_true",
|
|
85
|
+
help="insert ///< TODO stubs next to undocumented ports/params in place",
|
|
86
|
+
)
|
|
87
|
+
return ap
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main(argv=None):
|
|
91
|
+
"""Entry point for the ``svdoc`` command (registered via ``pyproject.toml``).
|
|
92
|
+
|
|
93
|
+
Two modes, dispatched on whether the first argument is the literal
|
|
94
|
+
``build`` subcommand (argparse subparsers don't mix cleanly with a
|
|
95
|
+
same-position positional argument, so this is checked manually rather
|
|
96
|
+
than via ``add_subparsers``):
|
|
97
|
+
|
|
98
|
+
- ``svdoc <file.sv> [more_files...]`` — document a single module/interface/
|
|
99
|
+
package, optionally resolving cross-file types against ``more_files``,
|
|
100
|
+
printing Markdown to stdout or writing a single Markdown/HTML file
|
|
101
|
+
(``--out``), or scaffolding missing doc comments in place (``--fix``).
|
|
102
|
+
- ``svdoc build <files...> --out-dir <dir>`` — document every given file
|
|
103
|
+
into a single flat multi-page HTML site (one page per construct plus
|
|
104
|
+
an ``index.html``), so cross-links between pages always resolve
|
|
105
|
+
correctly regardless of where the source files live on disk.
|
|
106
|
+
|
|
107
|
+
:param argv: Argument list to parse (as :func:`sys.argv`\\ ``[1:]`` would
|
|
108
|
+
appear). Defaults to ``sys.argv[1:]`` when ``None``.
|
|
109
|
+
"""
|
|
110
|
+
if argv is None:
|
|
111
|
+
argv = sys.argv[1:]
|
|
112
|
+
|
|
113
|
+
if argv and argv[0] == "build":
|
|
114
|
+
args = _build_parser().parse_args(argv[1:])
|
|
115
|
+
_run_build(args)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
args = _single_parser().parse_args(argv)
|
|
119
|
+
_run_single(args)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
sys.exit(main())
|
svdoc/fixer.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""svdoc --fix: insert `///< TODO` next to undocumented ports/params, and a
|
|
2
|
+
`/** @brief TODO */` stub above an undocumented module, in place. Mirrors
|
|
3
|
+
`ruff --fix` — scaffolds missing docs rather than guessing their content.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pyslang
|
|
7
|
+
|
|
8
|
+
from .parser import _leading_doc, _trailing_doc
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _end_offset(node) -> int:
|
|
12
|
+
"""node may be a raw Token (comma, closing paren) or a syntax node."""
|
|
13
|
+
tok = node.getLastToken() if hasattr(node, "getLastToken") else node
|
|
14
|
+
return tok.range.end.offset
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def fix_file(path: str) -> bool:
|
|
18
|
+
"""Scaffold missing doc comments in place, in ``path``, and save the result.
|
|
19
|
+
|
|
20
|
+
Inserts ``///< TODO`` next to any undocumented port/parameter, and a
|
|
21
|
+
``/** @brief TODO */`` stub above the module/interface/package itself if
|
|
22
|
+
it has no doc comment. Idempotent: re-running on an already-documented
|
|
23
|
+
file makes no changes.
|
|
24
|
+
|
|
25
|
+
:param path: Path to the ``.sv`` file to fix in place.
|
|
26
|
+
:returns: ``True`` if the file was modified, ``False`` if it was already
|
|
27
|
+
fully documented.
|
|
28
|
+
:raises ValueError: If the file fails to parse cleanly.
|
|
29
|
+
"""
|
|
30
|
+
tree = pyslang.syntax.SyntaxTree.fromFile(path, pyslang.SourceManager())
|
|
31
|
+
if tree.diagnostics:
|
|
32
|
+
raise ValueError(f"parse errors in {path}: {list(tree.diagnostics)}")
|
|
33
|
+
|
|
34
|
+
# module and interface declarations share the same syntax shape
|
|
35
|
+
# (ModuleDeclarationSyntax, differentiated only by .kind) -- --fix scaffolds
|
|
36
|
+
# their header (params/ports) doc comments identically. Body constructs
|
|
37
|
+
# (interface signals/modports) aren't scaffolded here yet.
|
|
38
|
+
mod = next(m for m in tree.root.members if hasattr(m, "header"))
|
|
39
|
+
header = mod.header
|
|
40
|
+
|
|
41
|
+
# (offset, text_to_insert), applied back-to-front so earlier offsets stay valid.
|
|
42
|
+
insertions = []
|
|
43
|
+
|
|
44
|
+
if _leading_doc(mod) is None:
|
|
45
|
+
offset = mod.getFirstToken().range.start.offset
|
|
46
|
+
insertions.append((offset, "/**\n * @brief TODO\n */\n"))
|
|
47
|
+
|
|
48
|
+
if header.parameters:
|
|
49
|
+
raw = list(header.parameters.declarations)
|
|
50
|
+
for i, node in enumerate(raw):
|
|
51
|
+
if node.kind != pyslang.syntax.SyntaxKind.ParameterDeclaration:
|
|
52
|
+
continue
|
|
53
|
+
has_comma = i + 1 < len(raw)
|
|
54
|
+
# trailing ///< doc for this item is leading trivia on the node
|
|
55
|
+
# AFTER its separator (see parser._trailing_doc) -- for the last
|
|
56
|
+
# item there's no comma, so the lookup and insertion point are
|
|
57
|
+
# both the item itself, right before the closing paren.
|
|
58
|
+
lookup = (
|
|
59
|
+
raw[i + 2]
|
|
60
|
+
if has_comma and i + 2 < len(raw)
|
|
61
|
+
else header.parameters.closeParen
|
|
62
|
+
)
|
|
63
|
+
if _trailing_doc(lookup) is None:
|
|
64
|
+
offset = _end_offset(raw[i + 1]) if has_comma else _end_offset(node)
|
|
65
|
+
insertions.append((offset, " ///< TODO"))
|
|
66
|
+
|
|
67
|
+
if header.ports:
|
|
68
|
+
raw = list(header.ports.ports)
|
|
69
|
+
for i, node in enumerate(raw):
|
|
70
|
+
if not hasattr(node, "declarator"):
|
|
71
|
+
continue
|
|
72
|
+
has_comma = i + 1 < len(raw)
|
|
73
|
+
lookup = (
|
|
74
|
+
raw[i + 2]
|
|
75
|
+
if has_comma and i + 2 < len(raw)
|
|
76
|
+
else header.ports.closeParen
|
|
77
|
+
)
|
|
78
|
+
if _trailing_doc(lookup) is None:
|
|
79
|
+
offset = _end_offset(raw[i + 1]) if has_comma else _end_offset(node)
|
|
80
|
+
insertions.append((offset, " ///< TODO"))
|
|
81
|
+
|
|
82
|
+
if not insertions:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
with open(path) as f:
|
|
86
|
+
text = f.read()
|
|
87
|
+
for offset, snippet in sorted(insertions, reverse=True):
|
|
88
|
+
text = text[:offset] + snippet + text[offset:]
|
|
89
|
+
with open(path, "w") as f:
|
|
90
|
+
f.write(text)
|
|
91
|
+
return True
|
svdoc/ir.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Doc IR (v1): module-level only. See PLAN.md Phase 1."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Param:
|
|
9
|
+
"""A single module/interface/subroutine parameter.
|
|
10
|
+
|
|
11
|
+
:ivar name: Parameter identifier.
|
|
12
|
+
:ivar type: Declared type as written in source (e.g. ``"int"``).
|
|
13
|
+
:ivar default: Default value expression, or ``None`` if unset.
|
|
14
|
+
:ivar doc: Extracted doc comment, or ``None`` if undocumented.
|
|
15
|
+
:ivar type_ref: Fully-qualified ``"package::type"`` name if resolved
|
|
16
|
+
cross-file via :func:`svdoc.parser.resolve_types`; ``None`` otherwise.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name: str
|
|
20
|
+
type: str
|
|
21
|
+
default: Optional[str]
|
|
22
|
+
doc: Optional[str]
|
|
23
|
+
type_ref: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Port:
|
|
28
|
+
"""A single module/interface/subroutine port or argument.
|
|
29
|
+
|
|
30
|
+
Also reused for subroutine arguments (see :attr:`Subroutine.args`), since
|
|
31
|
+
the shape (name, direction, type, doc) is identical.
|
|
32
|
+
|
|
33
|
+
:ivar name: Port identifier.
|
|
34
|
+
:ivar direction: ``"input"``, ``"output"``, ``"inout"``, etc.
|
|
35
|
+
:ivar type: Declared type as written in source, including any packed
|
|
36
|
+
dimensions (e.g. ``"logic [7:0]"``).
|
|
37
|
+
:ivar doc: Extracted doc comment, or ``None`` if undocumented.
|
|
38
|
+
:ivar type_ref: Fully-qualified ``"package::type"`` name if resolved
|
|
39
|
+
cross-file via :func:`svdoc.parser.resolve_types`; ``None`` otherwise.
|
|
40
|
+
:ivar modport_preview: For an interface-typed port (``direction ==
|
|
41
|
+
"interface"``), the resolved :class:`Modport` from the interface file,
|
|
42
|
+
if it was found among the files passed to
|
|
43
|
+
:func:`svdoc.parser.resolve_types`; ``None`` otherwise.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
name: str
|
|
47
|
+
direction: str
|
|
48
|
+
type: str
|
|
49
|
+
doc: Optional[str]
|
|
50
|
+
type_ref: Optional[str] = None
|
|
51
|
+
modport_preview: Optional["Modport"] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class ModuleDoc:
|
|
56
|
+
"""Doc IR for a single SystemVerilog module."""
|
|
57
|
+
|
|
58
|
+
name: str
|
|
59
|
+
doc: Optional[str]
|
|
60
|
+
params: List[Param] = field(default_factory=list)
|
|
61
|
+
ports: List[Port] = field(default_factory=list)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class Signal:
|
|
66
|
+
"""A variable declared in an interface body (outside any modport)."""
|
|
67
|
+
|
|
68
|
+
name: str
|
|
69
|
+
type: str
|
|
70
|
+
doc: Optional[str]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class ModportPortGroup:
|
|
75
|
+
"""One direction-grouped clause within a modport, e.g. ``output valid, data``."""
|
|
76
|
+
|
|
77
|
+
direction: str
|
|
78
|
+
signals: List[str]
|
|
79
|
+
doc: Optional[str]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class Modport:
|
|
84
|
+
"""A single ``modport`` declaration inside an interface."""
|
|
85
|
+
|
|
86
|
+
name: str
|
|
87
|
+
doc: Optional[str]
|
|
88
|
+
port_groups: List[ModportPortGroup] = field(default_factory=list)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class InterfaceDoc:
|
|
93
|
+
"""Doc IR for a single SystemVerilog interface, including its modports."""
|
|
94
|
+
|
|
95
|
+
name: str
|
|
96
|
+
doc: Optional[str]
|
|
97
|
+
params: List[Param] = field(default_factory=list)
|
|
98
|
+
ports: List[Port] = field(default_factory=list)
|
|
99
|
+
signals: List[Signal] = field(default_factory=list)
|
|
100
|
+
modports: List[Modport] = field(default_factory=list)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class EnumValue:
|
|
105
|
+
"""A single named value within an ``enum`` typedef."""
|
|
106
|
+
|
|
107
|
+
name: str
|
|
108
|
+
value: Optional[str]
|
|
109
|
+
doc: Optional[str]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class StructField:
|
|
114
|
+
"""A single field within a packed ``struct`` typedef."""
|
|
115
|
+
|
|
116
|
+
name: str
|
|
117
|
+
type: str
|
|
118
|
+
doc: Optional[str]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class Typedef:
|
|
123
|
+
"""A single ``typedef`` declared in a package: enum, packed struct, or alias.
|
|
124
|
+
|
|
125
|
+
:ivar kind: One of ``"enum"``, ``"struct"``, or ``"alias"`` — determines
|
|
126
|
+
which of ``base_type``/``values``, ``fields``, or ``alias_type`` is populated.
|
|
127
|
+
:ivar base_type: For ``kind="enum"``, the underlying integer type
|
|
128
|
+
(e.g. ``"logic [1:0]"``).
|
|
129
|
+
:ivar alias_type: For ``kind="alias"``, the type being aliased.
|
|
130
|
+
:ivar values: For ``kind="enum"``, the enum's named values in declaration order.
|
|
131
|
+
:ivar fields: For ``kind="struct"``, the struct's fields in declaration order.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
name: str
|
|
135
|
+
doc: Optional[str]
|
|
136
|
+
kind: str
|
|
137
|
+
base_type: Optional[str] = None
|
|
138
|
+
alias_type: Optional[str] = None
|
|
139
|
+
values: List[EnumValue] = field(default_factory=list)
|
|
140
|
+
fields: List[StructField] = field(default_factory=list)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class Subroutine:
|
|
145
|
+
"""A ``function`` or ``task`` declared in a package.
|
|
146
|
+
|
|
147
|
+
:ivar kind: ``"function"`` or ``"task"``.
|
|
148
|
+
:ivar return_type: Declared return type; always ``None`` for tasks.
|
|
149
|
+
:ivar args: Arguments, reusing :class:`Port`'s name/direction/type/doc shape.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
name: str
|
|
153
|
+
doc: Optional[str]
|
|
154
|
+
kind: str
|
|
155
|
+
return_type: Optional[str] = None
|
|
156
|
+
args: List[Port] = field(default_factory=list)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class PackageDoc:
|
|
161
|
+
"""Doc IR for a single SystemVerilog package: its typedefs and subroutines."""
|
|
162
|
+
|
|
163
|
+
name: str
|
|
164
|
+
doc: Optional[str]
|
|
165
|
+
typedefs: List[Typedef] = field(default_factory=list)
|
|
166
|
+
subroutines: List[Subroutine] = field(default_factory=list)
|