socx-cli 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.
- socx_cli-0.1.0.dist-info/METADATA +34 -0
- socx_cli-0.1.0.dist-info/RECORD +41 -0
- socx_cli-0.1.0.dist-info/WHEEL +4 -0
- socx_cli-0.1.0.dist-info/entry_points.txt +2 -0
- socx_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- src/plugins/config.py +92 -0
- src/plugins/convert/lst.py +12 -0
- src/plugins/example.py +43 -0
- src/plugins/rgr.py +28 -0
- src/socx/__init__.py +132 -0
- src/socx/__main__.py +5 -0
- src/socx/cli.py +169 -0
- src/socx/config/__init__.py +88 -0
- src/socx/config/_config.py +357 -0
- src/socx/console.py +8 -0
- src/socx/converter.py +71 -0
- src/socx/core/__init__.py +2 -0
- src/socx/core/_uid.py +36 -0
- src/socx/core/mixins.py +34 -0
- src/socx/formatter.py +61 -0
- src/socx/log.py +224 -0
- src/socx/memory.py +86 -0
- src/socx/parser.py +153 -0
- src/socx/reader.py +47 -0
- src/socx/regression/__init__.py +14 -0
- src/socx/regression/regression.py +129 -0
- src/socx/regression/test.py +244 -0
- src/socx/static/toml/convert.local.toml +2 -0
- src/socx/static/toml/converter.toml +119 -0
- src/socx/static/toml/filetypes.toml +104 -0
- src/socx/static/toml/plugins.toml +5 -0
- src/socx/static/toml/regression.toml +47 -0
- src/socx/static/toml/settings.toml +6 -0
- src/socx/static/toml/test.toml +9 -0
- src/socx/tokenizer.py +63 -0
- src/socx/validators.py +57 -0
- src/socx/visitor.py +70 -0
- src/socx/writer.py +58 -0
- src/socx-tui/__init__.py +0 -0
- src/socx-tui/app.py +7 -0
- src/socx-tui/regression/table.py +0 -0
src/socx/core/_uid.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from weakref import WeakValueDictionary
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
from threading import RLock
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _UIDMeta(type):
|
|
9
|
+
__lock: ClassVar[RLock] = RLock()
|
|
10
|
+
__uid_map: ClassVar[dict[str, int]] = {}
|
|
11
|
+
__handles: ClassVar[dict[int, object]] = WeakValueDictionary()
|
|
12
|
+
|
|
13
|
+
def __call__(cls, *args, **kwargs) -> _UIDMeta:
|
|
14
|
+
inst = super().__call__(*args, **kwargs)
|
|
15
|
+
inst.__uid = cls._next_uid(inst)
|
|
16
|
+
cls.__handles[inst.__uid] = inst
|
|
17
|
+
return inst
|
|
18
|
+
|
|
19
|
+
def ref(cls) -> _UIDMeta:
|
|
20
|
+
return cls.__uid
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def dref(cls, handle) -> _UIDMeta:
|
|
24
|
+
return cls.__handles.get(handle)
|
|
25
|
+
|
|
26
|
+
def _next_uid(cls, inst) -> int:
|
|
27
|
+
name = inst.__class__.__name__
|
|
28
|
+
with cls.__lock:
|
|
29
|
+
if name not in cls.__uid_map:
|
|
30
|
+
cls.__uid_map[name] = 0
|
|
31
|
+
rv = cls.__uid_map[name]
|
|
32
|
+
cls.__uid_map[name] += 1
|
|
33
|
+
return rv
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
src/socx/core/mixins.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import field
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from ._uid import _UIDMeta
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TypedPtrMixin(metaclass=_UIDMeta):
|
|
9
|
+
"""
|
|
10
|
+
It is probably very unsafe in context of software-security, use this
|
|
11
|
+
for ez compute and lazy eval but using it in an object that also encrypts
|
|
12
|
+
ur password would probably be a rly bad idea.
|
|
13
|
+
|
|
14
|
+
Anyway I dont even think I need this I just thought it would be cool to
|
|
15
|
+
write so I did lol.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def ref(self) -> int:
|
|
20
|
+
return _UIDMeta.ref(self)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def dref(cls, ref: int) -> TypedPtrMixin:
|
|
24
|
+
return _UIDMeta.dref(ref)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass # cuz a true alpha man never implements __repr__ themselves
|
|
28
|
+
class UIDMixin(TypedPtrMixin):
|
|
29
|
+
"""
|
|
30
|
+
A mixin class that generates unique instance ids for instances of the same
|
|
31
|
+
class.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
uid: int = field(init=False, default=property(lambda self: self.ref))
|
src/socx/formatter.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import abc
|
|
3
|
+
from typing import override
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from jinja2 import Environment
|
|
6
|
+
|
|
7
|
+
from dynaconf.utils.boxing import DynaBox
|
|
8
|
+
|
|
9
|
+
from .parser import SymbolTable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Formatter(abc.ABC):
|
|
14
|
+
@abc.abstractmethod
|
|
15
|
+
def format(
|
|
16
|
+
self, tokens: dict[str, DynaBox], matches: list[re.Match]
|
|
17
|
+
) -> str:
|
|
18
|
+
"""Format matches as text."""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SystemVerilogFormatter(Formatter):
|
|
23
|
+
@override
|
|
24
|
+
def format(
|
|
25
|
+
self,
|
|
26
|
+
tokens: dict[str, DynaBox],
|
|
27
|
+
matches: list[re.Match],
|
|
28
|
+
sym_table: SymbolTable,
|
|
29
|
+
) -> str:
|
|
30
|
+
state = True
|
|
31
|
+
env = Environment()
|
|
32
|
+
base = None
|
|
33
|
+
scope = None
|
|
34
|
+
header = ""
|
|
35
|
+
footer = ""
|
|
36
|
+
output = ""
|
|
37
|
+
|
|
38
|
+
for match in matches:
|
|
39
|
+
name = match.lastgroup
|
|
40
|
+
tok = tokens[name]
|
|
41
|
+
|
|
42
|
+
if tok.starts_scope:
|
|
43
|
+
output += header if state else footer
|
|
44
|
+
scope = match.expand(r"\g<device>_\g<mem>")
|
|
45
|
+
header = match.expand(tok.subst)
|
|
46
|
+
footer = match.expand(tok.scope_ender)
|
|
47
|
+
if scope and base and not state:
|
|
48
|
+
template = env.from_string(output)
|
|
49
|
+
output = template.render(base=base)
|
|
50
|
+
state = not state
|
|
51
|
+
base = None
|
|
52
|
+
|
|
53
|
+
if base is None:
|
|
54
|
+
base = match.group("addr")
|
|
55
|
+
|
|
56
|
+
output += match.expand(tok.subst)
|
|
57
|
+
|
|
58
|
+
output += footer
|
|
59
|
+
template = env.from_string(output)
|
|
60
|
+
output = template.render(base=base)
|
|
61
|
+
return output
|
src/socx/log.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import logging
|
|
6
|
+
from weakref import proxy
|
|
7
|
+
from typing import Final, NewType
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.logging import RichHandler
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
# types
|
|
15
|
+
"Level",
|
|
16
|
+
# API
|
|
17
|
+
"log",
|
|
18
|
+
"info",
|
|
19
|
+
"warn",
|
|
20
|
+
"error",
|
|
21
|
+
"fatal",
|
|
22
|
+
"debug",
|
|
23
|
+
"warning",
|
|
24
|
+
"exception",
|
|
25
|
+
"critical",
|
|
26
|
+
"level",
|
|
27
|
+
"set_level",
|
|
28
|
+
"has_handlers",
|
|
29
|
+
"add_handler",
|
|
30
|
+
"get_handler",
|
|
31
|
+
"remove_handler",
|
|
32
|
+
"get_handler_names",
|
|
33
|
+
"add_filter",
|
|
34
|
+
"remove_filter",
|
|
35
|
+
"is_enabled_for",
|
|
36
|
+
# defaults
|
|
37
|
+
"DEFAULT_LEVEL",
|
|
38
|
+
"DEFAULT_FORMAT",
|
|
39
|
+
"DEFAULT_HANDLERS",
|
|
40
|
+
"DEFAULT_TIME_FORMAT",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
Level: NewType = NewType("Level", int | str)
|
|
44
|
+
"""
|
|
45
|
+
Union type definition of `int | str` for annotating level arguments.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
DEFAULT_LEVEL: Final[Level] = os.environ.get("SOCX_VERBOSITY", logging.WARN)
|
|
49
|
+
"""
|
|
50
|
+
Default logger level, a.k.a verbosity.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
DEFAULT_FORMAT: Final[str] = os.environ.get("SOCX_LOG_FORMAT", "%(message)s")
|
|
54
|
+
"""
|
|
55
|
+
Default logger message format.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
DEFAULT_TIME_FORMAT: Final[str] = os.environ.get("SOCX_TIME_FORMAT", "[%X]")
|
|
59
|
+
"""
|
|
60
|
+
Default logger date format logs.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
DEFAULT_CHILD_FORMAT: Final[str] = os.environ.get(
|
|
64
|
+
"SOCX_LOG_FORMAT",
|
|
65
|
+
"%(asctime)s %(levelname)5s - %(filename)5s:%(lineno)-4d - %(message)s",
|
|
66
|
+
)
|
|
67
|
+
"""
|
|
68
|
+
Default logger message format.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
DEFAULT_CHILD_FORMATTER: Final[str] = logging.Formatter(
|
|
72
|
+
DEFAULT_CHILD_FORMAT, DEFAULT_TIME_FORMAT
|
|
73
|
+
)
|
|
74
|
+
"""
|
|
75
|
+
Default logger message format.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
DEFAULT_HANDLERS: Final[list[logging.Handler]] = [
|
|
79
|
+
RichHandler(
|
|
80
|
+
level=DEFAULT_LEVEL,
|
|
81
|
+
console=Console(tab_size=4, markup=True, force_terminal=True),
|
|
82
|
+
show_time=True,
|
|
83
|
+
show_level=True,
|
|
84
|
+
rich_tracebacks=False,
|
|
85
|
+
omit_repeated_times=False,
|
|
86
|
+
tracebacks_word_wrap=False,
|
|
87
|
+
tracebacks_show_locals=False,
|
|
88
|
+
log_time_format=DEFAULT_TIME_FORMAT,
|
|
89
|
+
tracebacks_theme="monokai",
|
|
90
|
+
locals_max_string=None,
|
|
91
|
+
locals_max_length=None,
|
|
92
|
+
),
|
|
93
|
+
]
|
|
94
|
+
"""
|
|
95
|
+
Default logging handlers of this module's default `logger`.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _get_logger() -> logging.Logger:
|
|
100
|
+
logging.basicConfig(
|
|
101
|
+
level=DEFAULT_LEVEL,
|
|
102
|
+
format=DEFAULT_FORMAT,
|
|
103
|
+
datefmt=DEFAULT_TIME_FORMAT,
|
|
104
|
+
handlers=DEFAULT_HANDLERS,
|
|
105
|
+
)
|
|
106
|
+
return logging.getLogger(__package__.partition(".")[0])
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
_logger = _get_logger()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_logger(name: str, filename: str | None = None) -> logging.Logger:
|
|
113
|
+
"""
|
|
114
|
+
Get a pretty printing log handler.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
name: str
|
|
119
|
+
Name of the logger.
|
|
120
|
+
|
|
121
|
+
filename: str
|
|
122
|
+
Specifies that a FileHandler be created, using the specified filename
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
A pretty printing logging.Logger instance.
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
rv = _logger.getChild(name)
|
|
130
|
+
if filename is not None:
|
|
131
|
+
handler = logging.FileHandler(
|
|
132
|
+
filename=filename, mode="w", encoding="utf-8"
|
|
133
|
+
)
|
|
134
|
+
handler.setFormatter(DEFAULT_CHILD_FORMATTER)
|
|
135
|
+
rv.addHandler(handler)
|
|
136
|
+
return rv
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def log(level: Level, msg: str, *args, **kwargs) -> None:
|
|
140
|
+
_logger.log(msg, *args, **kwargs)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def info(msg: str, *args, **kwargs) -> None:
|
|
144
|
+
_logger.info(msg, *args, **kwargs)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def warn(msg: str, *args, **kwargs) -> None:
|
|
148
|
+
_logger.warn(msg, *args, **kwargs)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def error(msg: str, *args, **kwargs) -> None:
|
|
152
|
+
_logger.error(msg, *args, **kwargs)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def fatal(msg: str, *args, **kwargs) -> None:
|
|
156
|
+
_logger.fatal(msg, *args, **kwargs)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def debug(msg: str, *args, **kwargs) -> None:
|
|
160
|
+
_logger.debug(msg, *args, **kwargs)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def warning(msg: str, *args, **kwargs) -> None:
|
|
164
|
+
_logger.warning(msg, *args, **kwargs)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def exception(msg: str, *args, **kwargs) -> None:
|
|
168
|
+
_logger.exception(msg, *args, **kwargs)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def critical(msg: str, *args, **kwargs) -> None:
|
|
172
|
+
_logger.critical(msg, *args, **kwargs)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def level() -> str:
|
|
176
|
+
return _logger.getEffectiveLevel()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def set_level(level: Level) -> None:
|
|
180
|
+
_logger.setLevel(level)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def has_handlers() -> None:
|
|
184
|
+
_logger.hasHandlers()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def add_handler(handler: logging.Handler) -> None:
|
|
188
|
+
_logger.addHandler(handler)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def get_handler(name: str) -> logging.Handler:
|
|
192
|
+
return logging.getHandlerByName(name)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def remove_handler(handler: logging.Handler) -> None:
|
|
196
|
+
_logger.removeHandler(handler)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_handler_names() -> logging.Handlers:
|
|
200
|
+
return logging.getHandlerNames()
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def add_filter(filter: logging.Filter) -> None: # noqa: A002
|
|
204
|
+
_logger.addFilter(filter)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def remove_filter(filter: logging.Filter) -> None: # noqa: A002
|
|
208
|
+
_logger.removeFilter(filter)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def is_enabled_for(level: Level) -> bool:
|
|
212
|
+
if isinstance(level, str):
|
|
213
|
+
level = logging.getLevelName(level)
|
|
214
|
+
return _logger.isEnabledFor(level)
|
|
215
|
+
|
|
216
|
+
logger = proxy(_logger)
|
|
217
|
+
"""
|
|
218
|
+
Default logging handler.
|
|
219
|
+
|
|
220
|
+
Can be used for default logging when no custom behavior is required.
|
|
221
|
+
|
|
222
|
+
If custom logging is needed, use `get_logger` method to get a custom handler
|
|
223
|
+
instead.
|
|
224
|
+
"""
|
src/socx/memory.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
import dataclasses as dc
|
|
5
|
+
|
|
6
|
+
import rich as rich
|
|
7
|
+
import rich.table as table
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dc.dataclass(frozen=True)
|
|
11
|
+
class MemorySegment:
|
|
12
|
+
origin: int
|
|
13
|
+
"""
|
|
14
|
+
Origin/Base address of the address space.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
length: int
|
|
18
|
+
"""
|
|
19
|
+
Length/Size of the address space in bytes.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
end: int
|
|
23
|
+
"""
|
|
24
|
+
End/Highest memory address of the address space.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dc.dataclass(frozen=True)
|
|
29
|
+
class DynamicSymbol:
|
|
30
|
+
name: str
|
|
31
|
+
"""
|
|
32
|
+
Name identifier aliased with the symbol.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
addr: int
|
|
36
|
+
"""
|
|
37
|
+
Symbol start address in the relevant address space.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
length: int
|
|
41
|
+
"""
|
|
42
|
+
Total byte length symbol takes up in memory address space.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
index: int
|
|
46
|
+
"""
|
|
47
|
+
Index of symbol in symbol table.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dc.dataclass
|
|
52
|
+
class SymbolTable(t.TypedDict):
|
|
53
|
+
device: str
|
|
54
|
+
"""
|
|
55
|
+
Name identifier of the device associated with the address space.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
segment: MemorySegment
|
|
59
|
+
"""
|
|
60
|
+
Address space memory adress specification.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
symbols: tuple[DynamicSymbol, ...] | None = None
|
|
64
|
+
"""
|
|
65
|
+
Tuple listing of all dynamic symbols associated with the device and their
|
|
66
|
+
mapping within the address space.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@table.dataclass
|
|
71
|
+
class RichSymTable:
|
|
72
|
+
device: str
|
|
73
|
+
"""
|
|
74
|
+
Name identifier of the device associated with the address space.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
segment: MemorySegment
|
|
78
|
+
"""
|
|
79
|
+
Address space memory adress specification.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
symbols: tuple[DynamicSymbol, ...] | None = None
|
|
83
|
+
"""
|
|
84
|
+
Tuple listing of all dynamic symbols associated with the device and their
|
|
85
|
+
mapping within the address space.
|
|
86
|
+
"""
|
src/socx/parser.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
__all__ = ("Parser", "LstParser", "parse")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import re as re
|
|
7
|
+
import abc as abc
|
|
8
|
+
import json as json
|
|
9
|
+
import typing as t
|
|
10
|
+
import pathlib as pathlib
|
|
11
|
+
import dataclasses as dc
|
|
12
|
+
from pathlib import Path as Path
|
|
13
|
+
|
|
14
|
+
import rich as rich
|
|
15
|
+
import click as click
|
|
16
|
+
from dynaconf.utils.boxing import DynaBox
|
|
17
|
+
|
|
18
|
+
from .console import console as console
|
|
19
|
+
from .config import settings as settings
|
|
20
|
+
from .memory import SymbolTable as SymbolTable
|
|
21
|
+
from .memory import MemorySegment as MemorySegment
|
|
22
|
+
from .memory import DynamicSymbol as DynamicSymbol
|
|
23
|
+
from .tokenizer import Tokenizer as Tokenizer
|
|
24
|
+
from .tokenizer import LstTokenizer as LstTokenizer
|
|
25
|
+
from .validators import PathValidator as PathValidator
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Parser(abc.ABC):
|
|
29
|
+
@abc.abstractmethod
|
|
30
|
+
def parse(self) -> None:
|
|
31
|
+
"""Start the parser."""
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
@abc.abstractmethod
|
|
36
|
+
def lang(self) -> DynaBox:
|
|
37
|
+
"""Return the 'lang' configuration of the parser's source language."""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dc.dataclass
|
|
42
|
+
class LstParser(Parser):
|
|
43
|
+
"""
|
|
44
|
+
Parses .lst files to functions definitions represented as a
|
|
45
|
+
python object.
|
|
46
|
+
|
|
47
|
+
See DynamicSymbol, MemorySegment, SymbolTable.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
options: dict[str, str] | None = None
|
|
51
|
+
includes: set[pathlib.Path] | None = None
|
|
52
|
+
excludes: set[pathlib.Path] | None = None
|
|
53
|
+
source_dir: pathlib.Path | None = None
|
|
54
|
+
target_dir: pathlib.Path | None = None
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self: t.Self,
|
|
58
|
+
options: dict[str, str] | None = None,
|
|
59
|
+
includes: set[pathlib.Path] | None = None,
|
|
60
|
+
excludes: set[pathlib.Path] | None = None,
|
|
61
|
+
source_dir: pathlib.Path | None = None,
|
|
62
|
+
target_dir: pathlib.Path | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Initialize the parser.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
source_dir
|
|
70
|
+
Source directory from which sources are included and parsed by the
|
|
71
|
+
parser.
|
|
72
|
+
target_dir
|
|
73
|
+
Target directory to which parsed sources will be saved with as
|
|
74
|
+
configured in convert.toml
|
|
75
|
+
options
|
|
76
|
+
Options for handling the conversion operation. See `convert.toml`
|
|
77
|
+
for additional info.
|
|
78
|
+
"""
|
|
79
|
+
if options is None:
|
|
80
|
+
options = self.cfg.options
|
|
81
|
+
if includes is None:
|
|
82
|
+
includes = self.cfg.includes
|
|
83
|
+
if excludes is None:
|
|
84
|
+
excludes = self.cfg.excludes
|
|
85
|
+
if source_dir is None:
|
|
86
|
+
source_dir = self.cfg.source
|
|
87
|
+
if target_dir is None:
|
|
88
|
+
target_dir = self.cfg.target
|
|
89
|
+
self.options = options
|
|
90
|
+
self.includes = includes
|
|
91
|
+
self.excludes = excludes
|
|
92
|
+
self.source_dir = source_dir
|
|
93
|
+
self.target_dir = target_dir
|
|
94
|
+
self.includes = PathValidator._extract_includes(
|
|
95
|
+
self.source_dir, includes, excludes
|
|
96
|
+
)
|
|
97
|
+
self.sym_table = SymbolTable()
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def cfg(self) -> DynaBox:
|
|
101
|
+
return settings.convert[self.lang]
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def lang(self) -> DynaBox:
|
|
105
|
+
return "lst"
|
|
106
|
+
|
|
107
|
+
def parse(self) -> None:
|
|
108
|
+
"""Parse the sources according to initialization configuration."""
|
|
109
|
+
self._parse_sym_table()
|
|
110
|
+
|
|
111
|
+
def _parse_sym_table(self: t.Self) -> SymbolTable:
|
|
112
|
+
memory_map = {}
|
|
113
|
+
base_addr_file = self.cfg.base_addr_map
|
|
114
|
+
base_addr_path = pathlib.Path(self.source_dir) / base_addr_file
|
|
115
|
+
field_names = tuple([field.name for field in dc.fields(MemorySegment)])
|
|
116
|
+
base_addr_map = json.loads(base_addr_path.read_text())
|
|
117
|
+
for name in field_names:
|
|
118
|
+
for device_field, value in base_addr_map.items():
|
|
119
|
+
if name not in device_field:
|
|
120
|
+
continue
|
|
121
|
+
device = str(device_field).removesuffix(f"_{name}")
|
|
122
|
+
if device not in memory_map:
|
|
123
|
+
memory_map[device] = {}
|
|
124
|
+
memory_map[device][name] = int(value, self.cfg.base_addr_base)
|
|
125
|
+
self.sym_table.clear()
|
|
126
|
+
for device in memory_map:
|
|
127
|
+
if all(name in memory_map[device] for name in field_names):
|
|
128
|
+
space = MemorySegment(**dict(memory_map[device].items()))
|
|
129
|
+
self.sym_table[device] = (space, None)
|
|
130
|
+
self.sym_table = self.sym_table
|
|
131
|
+
return self.sym_table
|
|
132
|
+
|
|
133
|
+
def __hash__(self) -> int:
|
|
134
|
+
return hash(tuple(self.includes))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@click.pass_context
|
|
138
|
+
def parse(ctx: click.Context) -> SymbolTable:
|
|
139
|
+
src = ctx.source_dir if hasattr(ctx, "source_dir") else None
|
|
140
|
+
target = ctx.target_dir if hasattr(ctx, "target_dir") else None
|
|
141
|
+
parser = LstParser(src, target)
|
|
142
|
+
parser.parse()
|
|
143
|
+
table = parser.sym_table
|
|
144
|
+
rich.print(parser)
|
|
145
|
+
rich.print(table)
|
|
146
|
+
return table
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def write(ctx: click.Context) -> None:
|
|
150
|
+
for asm in ctx.mods:
|
|
151
|
+
output_file = ctx.output / asm.file.with_suffix(".sv")
|
|
152
|
+
output_file.write_text(asm.to_sv())
|
|
153
|
+
print(f"SystemVerilog output successfuly written to {output_file}")
|
src/socx/reader.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import override
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from .validators import PathValidator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Reader(abc.ABC):
|
|
11
|
+
"""
|
|
12
|
+
Reads input from one or more sources.
|
|
13
|
+
|
|
14
|
+
Attributes
|
|
15
|
+
----------
|
|
16
|
+
source: Path
|
|
17
|
+
Source of input to read from.
|
|
18
|
+
|
|
19
|
+
includes: set[Path]
|
|
20
|
+
Names or patterns to include in the reading list.
|
|
21
|
+
|
|
22
|
+
exludes: set[Path]
|
|
23
|
+
Names or patterns to exclude from the reading list.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
source: Path | None = None
|
|
27
|
+
includes: set[Path] | None = None
|
|
28
|
+
excludes: set[Path] | None = None
|
|
29
|
+
|
|
30
|
+
@abc.abstractmethod
|
|
31
|
+
def read(self) -> dict[Path, str]:
|
|
32
|
+
"""Read text from one or more sources into a dictionary."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class FileReader(Reader):
|
|
38
|
+
@override
|
|
39
|
+
def read(self) -> dict[Path, str]:
|
|
40
|
+
return {path: path.read_text() for path in self.paths}
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def paths(self) -> set[Path]:
|
|
44
|
+
return PathValidator._extract_includes(
|
|
45
|
+
src=self.source, includes=self.includes, excludes=self.excludes
|
|
46
|
+
)
|
|
47
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
__all__ = (
|
|
2
|
+
"Test",
|
|
3
|
+
"TestStatus",
|
|
4
|
+
"TestCommand",
|
|
5
|
+
"Regression",
|
|
6
|
+
"RegressionStatus",
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from .test import Test as Test
|
|
10
|
+
from .test import Status as TestStatus
|
|
11
|
+
from .test import Command as TestCommand
|
|
12
|
+
|
|
13
|
+
from .regression import Status as RegressionStatus
|
|
14
|
+
from .regression import Regression as Regression
|