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.
Files changed (19) hide show
  1. python_library_ralf_model-0.1.1/.gitignore +21 -0
  2. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/PKG-INFO +1 -1
  3. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/pyproject.toml +1 -1
  4. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/errors.py +14 -2
  5. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/io.py +9 -4
  6. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/parse.py +22 -4
  7. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/source_expand.py +9 -3
  8. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/tests/test_ralf_model.py +27 -1
  9. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/tests/test_source_include.py +1 -1
  10. python_library_ralf_model-0.1.0/.gitignore +0 -12
  11. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/README.md +0 -0
  12. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/example/__main__.py +0 -0
  13. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/example.bat +0 -0
  14. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/__init__.py +0 -0
  15. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/abc.py +0 -0
  16. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/emit.py +0 -0
  17. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/ralf_model/nodes.py +0 -0
  18. {python_library_ralf_model-0.1.0 → python_library_ralf_model-0.1.1}/test.bat +0 -0
  19. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-library-ralf-model
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: pydantic<3,>=2.0
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-library-ralf-model"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -10,10 +10,22 @@ class RalfError(Exception):
10
10
  class RalfParseError(RalfError):
11
11
  """文本不符合预期语法或词法。"""
12
12
 
13
- def __init__(self, message: str, *, line: int, col: int) -> None:
14
- super().__init__(f"{message} (行 {line}, 列 {col})")
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(text, current_file=p, include_paths=inc, encoding=encoding)
25
- return parse_ralf(text)
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
- return parse_ralf(text)
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__(self, text: str) -> None:
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
- raise RalfParseError(msg, line=self.line, col=self.col)
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(text: str) -> RalfDocument:
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
 
@@ -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
@@ -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()
@@ -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=(),
@@ -1,12 +0,0 @@
1
- __pycache__/
2
- *.pyc
3
- *.pyo
4
- *.egg-info/
5
- dist/
6
- build/
7
- .venv/
8
- .env
9
- .pytest_cache/
10
- config.yaml
11
- logs/
12
- .cursor/