python-library-ralf-model 0.1.0__tar.gz → 0.1.1__tar.gz
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.
- python_library_ralf_model-0.1.1/.gitignore +21 -0
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/PKG-INFO +1 -1
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/pyproject.toml +1 -1
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/errors.py +14 -2
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/io.py +9 -4
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/parse.py +22 -4
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/source_expand.py +9 -3
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/tests/test_ralf_model.py +27 -1
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/tests/test_source_include.py +1 -1
- python_library_ralf_model-0.1.0/.gitignore +0 -12
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/README.md +0 -0
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/example/__main__.py +0 -0
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/example.bat +0 -0
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/__init__.py +0 -0
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/abc.py +0 -0
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/emit.py +0 -0
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/nodes.py +0 -0
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/test.bat +0 -0
- {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/tests/__init__.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
*.pyo
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
.env
|
|
9
|
+
.env.*
|
|
10
|
+
!.env.example
|
|
11
|
+
# packages 示例本地密钥(*.example 为模板,可提交)
|
|
12
|
+
packages/**/examples/**/.env
|
|
13
|
+
!packages/**/examples/**/.env.example
|
|
14
|
+
packages/**/examples/**/mcp.json
|
|
15
|
+
!packages/**/examples/**/mcp.json.example
|
|
16
|
+
packages/**/examples/**/.sandbox/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
config.yaml
|
|
19
|
+
logs/
|
|
20
|
+
.cursor/
|
|
21
|
+
uv.lock
|
|
@@ -10,10 +10,22 @@ class RalfError(Exception):
|
|
|
10
10
|
class RalfParseError(RalfError):
|
|
11
11
|
"""文本不符合预期语法或词法。"""
|
|
12
12
|
|
|
13
|
-
def __init__(
|
|
14
|
-
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
message: str,
|
|
16
|
+
*,
|
|
17
|
+
line: int,
|
|
18
|
+
col: int,
|
|
19
|
+
path: Path | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
loc = f"行 {line}, 列 {col}"
|
|
22
|
+
if path is not None:
|
|
23
|
+
loc = f"{path}: {loc}"
|
|
24
|
+
super().__init__(f"{message} ({loc})")
|
|
25
|
+
self.message = message
|
|
15
26
|
self.line = line
|
|
16
27
|
self.col = col
|
|
28
|
+
self.path = path
|
|
17
29
|
|
|
18
30
|
|
|
19
31
|
class RalfSourceError(RalfError):
|
|
@@ -20,9 +20,13 @@ def load_ralf_file(
|
|
|
20
20
|
p = Path(path).resolve()
|
|
21
21
|
text = p.read_text(encoding=encoding)
|
|
22
22
|
inc = tuple(Path(x).resolve() for x in (include_paths or ()))
|
|
23
|
+
line_sources: list[Path] | None = None
|
|
23
24
|
if expand_source:
|
|
24
|
-
text = expand_ralf_sources(
|
|
25
|
-
|
|
25
|
+
text, line_sources = expand_ralf_sources(
|
|
26
|
+
text, current_file=p, include_paths=inc, encoding=encoding
|
|
27
|
+
)
|
|
28
|
+
return parse_ralf(text, line_sources=line_sources)
|
|
29
|
+
return parse_ralf(text, path=p)
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
def loads_ralf(
|
|
@@ -38,13 +42,14 @@ def loads_ralf(
|
|
|
38
42
|
virtual = bd / "__inline__.ralf"
|
|
39
43
|
inc = tuple(Path(x).resolve() for x in (include_paths or ()))
|
|
40
44
|
if expand_source:
|
|
41
|
-
text = expand_ralf_sources(
|
|
45
|
+
text, line_sources = expand_ralf_sources(
|
|
42
46
|
text,
|
|
43
47
|
current_file=virtual,
|
|
44
48
|
include_paths=inc,
|
|
45
49
|
encoding=encoding,
|
|
46
50
|
)
|
|
47
|
-
|
|
51
|
+
return parse_ralf(text, line_sources=line_sources)
|
|
52
|
+
return parse_ralf(text, path=virtual)
|
|
48
53
|
|
|
49
54
|
|
|
50
55
|
def dump_ralf_file(doc: RalfDocument, path: str | Path, *, encoding: str = "utf-8") -> None:
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
|
|
5
7
|
from ralf_model.errors import RalfParseError
|
|
6
8
|
from ralf_model.nodes import BlockNode, FieldNode, RalfDocument, RegisterNode
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class _Parser:
|
|
10
|
-
def __init__(
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
text: str,
|
|
15
|
+
*,
|
|
16
|
+
path: Path | None = None,
|
|
17
|
+
line_sources: Sequence[Path] | None = None,
|
|
18
|
+
) -> None:
|
|
11
19
|
self._s = text
|
|
12
20
|
self._n = len(text)
|
|
13
21
|
self._i = 0
|
|
14
22
|
self.line = 1
|
|
15
23
|
self.col = 1
|
|
24
|
+
self._path = path
|
|
25
|
+
self._line_sources = line_sources
|
|
16
26
|
|
|
17
27
|
def _advance(self, n: int = 1) -> None:
|
|
18
28
|
for _ in range(n):
|
|
@@ -30,7 +40,10 @@ class _Parser:
|
|
|
30
40
|
return self._s[j] if j < self._n else None
|
|
31
41
|
|
|
32
42
|
def _error(self, msg: str) -> None:
|
|
33
|
-
|
|
43
|
+
path = self._path
|
|
44
|
+
if self._line_sources and 0 < self.line <= len(self._line_sources):
|
|
45
|
+
path = self._line_sources[self.line - 1]
|
|
46
|
+
raise RalfParseError(msg, line=self.line, col=self.col, path=path)
|
|
34
47
|
|
|
35
48
|
def skip_ws_and_comments(self) -> None:
|
|
36
49
|
while self._i < self._n:
|
|
@@ -519,9 +532,14 @@ def _parse_verilog_sized(s: str, i: int) -> tuple[int, int]:
|
|
|
519
532
|
_VER_WS = re.compile(r"\s+")
|
|
520
533
|
|
|
521
534
|
|
|
522
|
-
def parse_ralf(
|
|
535
|
+
def parse_ralf(
|
|
536
|
+
text: str,
|
|
537
|
+
*,
|
|
538
|
+
path: Path | None = None,
|
|
539
|
+
line_sources: Sequence[Path] | None = None,
|
|
540
|
+
) -> RalfDocument:
|
|
523
541
|
"""将 RALF 源文本解析为 `RalfDocument`。"""
|
|
524
|
-
p = _Parser(text)
|
|
542
|
+
p = _Parser(text, path=path, line_sources=line_sources)
|
|
525
543
|
doc = p.parse_document()
|
|
526
544
|
return doc
|
|
527
545
|
|
{python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/source_expand.py
RENAMED
|
@@ -109,11 +109,13 @@ def expand_ralf_sources(
|
|
|
109
109
|
include_paths: Sequence[Path] = (),
|
|
110
110
|
encoding: str = "utf-8",
|
|
111
111
|
_chain: tuple[Path, ...] = (),
|
|
112
|
-
) -> str:
|
|
112
|
+
) -> tuple[str, list[Path]]:
|
|
113
113
|
"""将 Tcl 风格 ``source path`` 递归展开为单段 RALF 文本后再交给 ``parse_ralf``。
|
|
114
114
|
|
|
115
115
|
``current_file`` 用于确定相对路径的基准目录(通常为 ``path.parent``),并参与循环检测。
|
|
116
116
|
从内存加载字符串时可使用 ``base_dir / \"__inline__.ralf\"`` 这类占位路径。
|
|
117
|
+
|
|
118
|
+
返回 ``(展开后文本, 行来源)``:``行来源[i]`` 对应展开结果第 ``i+1`` 行的源文件路径。
|
|
117
119
|
"""
|
|
118
120
|
cf = current_file.resolve()
|
|
119
121
|
if cf in _chain:
|
|
@@ -123,15 +125,17 @@ def expand_ralf_sources(
|
|
|
123
125
|
inc = tuple(Path(p).resolve() for p in include_paths)
|
|
124
126
|
|
|
125
127
|
out: list[str] = []
|
|
128
|
+
line_sources: list[Path] = []
|
|
126
129
|
for line in text.splitlines(keepends=True):
|
|
127
130
|
spec = _parse_source_argument(line)
|
|
128
131
|
if spec is None:
|
|
129
132
|
out.append(line)
|
|
133
|
+
line_sources.append(cf)
|
|
130
134
|
continue
|
|
131
135
|
|
|
132
136
|
inner_path = resolve_source_path(spec, base_dir=cf.parent, include_paths=inc)
|
|
133
137
|
inner_text = inner_path.read_text(encoding=encoding)
|
|
134
|
-
expanded_inner = expand_ralf_sources(
|
|
138
|
+
expanded_inner, inner_sources = expand_ralf_sources(
|
|
135
139
|
inner_text,
|
|
136
140
|
current_file=inner_path,
|
|
137
141
|
include_paths=inc,
|
|
@@ -139,7 +143,9 @@ def expand_ralf_sources(
|
|
|
139
143
|
_chain=chain,
|
|
140
144
|
)
|
|
141
145
|
out.append(expanded_inner)
|
|
146
|
+
line_sources.extend(inner_sources)
|
|
142
147
|
if expanded_inner and not expanded_inner.endswith("\n"):
|
|
143
148
|
out.append("\n")
|
|
149
|
+
line_sources.append(inner_sources[-1] if inner_sources else inner_path)
|
|
144
150
|
|
|
145
|
-
return "".join(out)
|
|
151
|
+
return "".join(out), line_sources
|
{python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/tests/test_ralf_model.py
RENAMED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import tempfile
|
|
3
4
|
import unittest
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
|
|
5
|
-
from ralf_model import dump_ralf, parse_ralf
|
|
7
|
+
from ralf_model import RalfParseError, dump_ralf, load_ralf_file, parse_ralf
|
|
6
8
|
from ralf_model.parse import normalize_ralf_whitespace
|
|
7
9
|
|
|
8
10
|
|
|
@@ -176,5 +178,29 @@ block a {
|
|
|
176
178
|
self.assertNotIn("//", n)
|
|
177
179
|
|
|
178
180
|
|
|
181
|
+
class ParseErrorTests(unittest.TestCase):
|
|
182
|
+
def test_parse_error_includes_file_path(self) -> None:
|
|
183
|
+
with tempfile.TemporaryDirectory() as td:
|
|
184
|
+
bad = Path(td) / "bad.ralf"
|
|
185
|
+
bad.write_text("block x {\n ???\n}\n", encoding="utf-8")
|
|
186
|
+
with self.assertRaises(RalfParseError) as ctx:
|
|
187
|
+
load_ralf_file(bad, expand_source=False)
|
|
188
|
+
self.assertIn(str(bad.resolve()), str(ctx.exception))
|
|
189
|
+
|
|
190
|
+
def test_parse_error_in_included_file(self) -> None:
|
|
191
|
+
with tempfile.TemporaryDirectory() as td:
|
|
192
|
+
root = Path(td)
|
|
193
|
+
inc = root / "inc"
|
|
194
|
+
inc.mkdir()
|
|
195
|
+
(inc / "broken.ralf").write_text("block y {\n ???\n}\n", encoding="utf-8")
|
|
196
|
+
top = root / "top.ralf"
|
|
197
|
+
top.write_text('source "inc/broken.ralf"\n', encoding="utf-8")
|
|
198
|
+
broken = (inc / "broken.ralf").resolve()
|
|
199
|
+
with self.assertRaises(RalfParseError) as ctx:
|
|
200
|
+
load_ralf_file(top)
|
|
201
|
+
self.assertIn(str(broken), str(ctx.exception))
|
|
202
|
+
self.assertNotIn(str(top.resolve()), str(ctx.exception))
|
|
203
|
+
|
|
204
|
+
|
|
179
205
|
if __name__ == "__main__":
|
|
180
206
|
unittest.main()
|
{python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/tests/test_source_include.py
RENAMED
|
@@ -51,7 +51,7 @@ class SourceIncludeTests(unittest.TestCase):
|
|
|
51
51
|
top = root / "top.ralf"
|
|
52
52
|
top.write_text('source "mid.ralf"\n', encoding="utf-8")
|
|
53
53
|
|
|
54
|
-
out = expand_ralf_sources(
|
|
54
|
+
out, _line_sources = expand_ralf_sources(
|
|
55
55
|
top.read_text(encoding="utf-8"),
|
|
56
56
|
current_file=top.resolve(),
|
|
57
57
|
include_paths=(),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|