python-library-ralf-model 0.1.0__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.
@@ -0,0 +1,12 @@
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/
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-library-ralf-model
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.10
5
+ Requires-Dist: pydantic<3,>=2.0
6
+ Description-Content-Type: text/markdown
7
+
8
+ # ralf_model
9
+
10
+ 在 **RALF(Register Abstraction Layer Format)源文本** 与 **`RalfDocument` / 抽象节点类型**(`BlockNode`、`RegisterNode`、`FieldNode`,并实现 `AbstractRalf*`)之间做解析与生成。
11
+
12
+ ## 用法
13
+
14
+ ```python
15
+ from pathlib import Path
16
+
17
+ from ralf_model import dump_ralf, load_ralf_file, parse_ralf
18
+
19
+ doc = parse_ralf(Path("chip.ralf").read_text(encoding="utf-8"))
20
+ text = dump_ralf(doc)
21
+ ```
22
+
23
+ 亦可使用 `loads_ralf` / `dumps_ralf`、`load_ralf_file` / `dump_ralf_file`(见 `ralf_model.io`)。加载时可传入 **`include_paths`**(类似 ``ralgen -I dir``),配合 **`source`** 行递归展开后再解析;相对路径先相对**当前文件目录**,再依次在各 include 目录检索。
24
+
25
+ ### source 与 include
26
+
27
+ RALF 常作为 Tcl 脚本。单独成行的 ``source "f.ralf"``、``source {path}``、``source name.ralf``(可有行尾 ``;``、``#`` 注释)会在解析前被展开。不需要展开时:`loads_ralf(..., expand_source=False)` 或 `load_ralf_file(..., expand_source=False)`。
28
+
29
+ ```python
30
+ from pathlib import Path
31
+ from ralf_model import load_ralf_file
32
+
33
+ doc = load_ralf_file(
34
+ "top.ralf",
35
+ include_paths=[Path("ralf_inc"), Path("../shared")],
36
+ )
37
+ ```
38
+
39
+ 也可直接使用 ``expand_ralf_sources``、``resolve_source_path``(见 `ralf_model.source_expand`)。
40
+
41
+ ## 能力范围
42
+
43
+ - **block**:定义 ``block 名 { ... }``;简单映射 ``block 名 @地址;``;赋值与可选路径、地址 ``block 左名 = 右名``、``block 左名 = 右名 (hdl路径)``、``... @地址``,可与 ``{ ... }`` 组合。
44
+ - `field` 花括号内按**源顺序**保留各条语句(含 `enum { ... };` 等),便于往返。
45
+ - `@` 后的偏移在写出时统一为 Verilog 风格十六进制字面量(如 `'h5`);`bytes` 等为十进制。
46
+
47
+ 与 Synopsys `ralgen` 全语法并非字节级兼容;复杂构造若解析失败可再扩展解析器。
@@ -0,0 +1,40 @@
1
+ # ralf_model
2
+
3
+ 在 **RALF(Register Abstraction Layer Format)源文本** 与 **`RalfDocument` / 抽象节点类型**(`BlockNode`、`RegisterNode`、`FieldNode`,并实现 `AbstractRalf*`)之间做解析与生成。
4
+
5
+ ## 用法
6
+
7
+ ```python
8
+ from pathlib import Path
9
+
10
+ from ralf_model import dump_ralf, load_ralf_file, parse_ralf
11
+
12
+ doc = parse_ralf(Path("chip.ralf").read_text(encoding="utf-8"))
13
+ text = dump_ralf(doc)
14
+ ```
15
+
16
+ 亦可使用 `loads_ralf` / `dumps_ralf`、`load_ralf_file` / `dump_ralf_file`(见 `ralf_model.io`)。加载时可传入 **`include_paths`**(类似 ``ralgen -I dir``),配合 **`source`** 行递归展开后再解析;相对路径先相对**当前文件目录**,再依次在各 include 目录检索。
17
+
18
+ ### source 与 include
19
+
20
+ RALF 常作为 Tcl 脚本。单独成行的 ``source "f.ralf"``、``source {path}``、``source name.ralf``(可有行尾 ``;``、``#`` 注释)会在解析前被展开。不需要展开时:`loads_ralf(..., expand_source=False)` 或 `load_ralf_file(..., expand_source=False)`。
21
+
22
+ ```python
23
+ from pathlib import Path
24
+ from ralf_model import load_ralf_file
25
+
26
+ doc = load_ralf_file(
27
+ "top.ralf",
28
+ include_paths=[Path("ralf_inc"), Path("../shared")],
29
+ )
30
+ ```
31
+
32
+ 也可直接使用 ``expand_ralf_sources``、``resolve_source_path``(见 `ralf_model.source_expand`)。
33
+
34
+ ## 能力范围
35
+
36
+ - **block**:定义 ``block 名 { ... }``;简单映射 ``block 名 @地址;``;赋值与可选路径、地址 ``block 左名 = 右名``、``block 左名 = 右名 (hdl路径)``、``... @地址``,可与 ``{ ... }`` 组合。
37
+ - `field` 花括号内按**源顺序**保留各条语句(含 `enum { ... };` 等),便于往返。
38
+ - `@` 后的偏移在写出时统一为 Verilog 风格十六进制字面量(如 `'h5`);`bytes` 等为十进制。
39
+
40
+ 与 Synopsys `ralgen` 全语法并非字节级兼容;复杂构造若解析失败可再扩展解析器。
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ from ralf_model import dump_ralf, parse_ralf
6
+
7
+ _SAMPLE = """
8
+ block b1 {
9
+ bytes 1;
10
+ register r {
11
+ bytes 1;
12
+ field WDT_EN @'h5 {
13
+ bits 1;
14
+ reset 'h0;
15
+ access rw;
16
+ enum { ENABLE = 1, DISABLE = 0 };
17
+ }
18
+ }
19
+ }
20
+ """
21
+
22
+
23
+ def main() -> None:
24
+ if hasattr(sys.stdout, "reconfigure"):
25
+ try:
26
+ sys.stdout.reconfigure(encoding="utf-8")
27
+ except OSError:
28
+ pass
29
+ doc = parse_ralf(_SAMPLE)
30
+ print(dump_ralf(doc))
31
+
32
+
33
+ if __name__ == "__main__":
34
+ main()
@@ -0,0 +1,10 @@
1
+ @echo off
2
+ cd /d %~dp0
3
+
4
+ if not exist .venv (
5
+ python -m venv .venv
6
+ )
7
+
8
+ call .venv\Scripts\activate.bat
9
+ python -m pip install -e .
10
+ python -m example
@@ -0,0 +1,15 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "python-library-ralf-model"
7
+ version = "0.1.0"
8
+ readme = "README.md"
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "pydantic>=2.0,<3",
12
+ ]
13
+
14
+ [tool.hatch.build.targets.wheel]
15
+ packages = ["ralf_model"]
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from ralf_model.abc import AbstractRalfBlock, AbstractRalfField, AbstractRalfRegister
4
+ from ralf_model.emit import dump_ralf
5
+ from ralf_model.errors import RalfError, RalfParseError, RalfSourceError
6
+ from ralf_model.io import dump_ralf_file, dumps_ralf, load_ralf_file, loads_ralf
7
+ from ralf_model.nodes import BlockNode, FieldNode, RalfDocument, RegisterNode
8
+ from ralf_model.parse import normalize_ralf_whitespace, parse_ralf
9
+ from ralf_model.source_expand import expand_ralf_sources, resolve_source_path
10
+
11
+ __all__ = [
12
+ "AbstractRalfBlock",
13
+ "AbstractRalfField",
14
+ "AbstractRalfRegister",
15
+ "BlockNode",
16
+ "FieldNode",
17
+ "RalfDocument",
18
+ "RegisterNode",
19
+ "RalfError",
20
+ "RalfParseError",
21
+ "RalfSourceError",
22
+ "dump_ralf",
23
+ "dumps_ralf",
24
+ "dump_ralf_file",
25
+ "expand_ralf_sources",
26
+ "load_ralf_file",
27
+ "loads_ralf",
28
+ "parse_ralf",
29
+ "normalize_ralf_whitespace",
30
+ "resolve_source_path",
31
+ ]
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Protocol, runtime_checkable
4
+
5
+
6
+ @runtime_checkable
7
+ class AbstractRalfField(Protocol):
8
+ """RALF `field` 对应的对象应至少具备 `name`。"""
9
+
10
+ name: str
11
+
12
+
13
+ @runtime_checkable
14
+ class AbstractRalfRegister(Protocol):
15
+ """RALF `register` 对应的对象应至少具备 `name`。"""
16
+
17
+ name: str
18
+
19
+
20
+ @runtime_checkable
21
+ class AbstractRalfBlock(Protocol):
22
+ """RALF `block` 对应的对象应至少具备 `name`。"""
23
+
24
+ name: str
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ from ralf_model.nodes import BlockNode, FieldNode, RalfDocument, RegisterNode
4
+
5
+
6
+ def _fmt_at_int(v: int) -> str:
7
+ """`@` 后的偏移(字节或位),采用 Verilog 无尺寸十六进制字面量。"""
8
+ if v < 0:
9
+ return str(v)
10
+ return f"'h{v:x}"
11
+
12
+
13
+ def _emit_field(f: FieldNode, indent: str) -> list[str]:
14
+ head = f"{indent}field {f.name}"
15
+ if f.offset_bits is not None:
16
+ head += f" @{_fmt_at_int(f.offset_bits)}"
17
+ head += " {"
18
+ lines = [head]
19
+ inner = indent + " "
20
+ for stmt in f.inner_statements:
21
+ for line in stmt.splitlines():
22
+ lines.append(f"{inner}{line.strip()}")
23
+ lines.append(indent + "}")
24
+ return lines
25
+
26
+
27
+ def _emit_register(r: RegisterNode, indent: str) -> list[str]:
28
+ head = f"{indent}register {r.name}"
29
+ if r.offset_bytes is not None:
30
+ head += f" @{_fmt_at_int(r.offset_bytes)}"
31
+ if r.declaration_only:
32
+ return [f"{head};"]
33
+ lines = [head + " {"]
34
+ inner = indent + " "
35
+ if r.bytes_width is not None:
36
+ lines.append(f"{inner}bytes {r.bytes_width};")
37
+ for f in r.fields:
38
+ lines.extend(_emit_field(f, inner))
39
+ lines.append(indent + "}")
40
+ return lines
41
+
42
+
43
+ def _emit_block_open_line(b: BlockNode, indent: str) -> str:
44
+ """``block ...`` 行中 `{` 之前的部分(含 `@` / `=` 等)。"""
45
+ line = f"{indent}block {b.name}"
46
+ if b.rhs_head is not None:
47
+ line += f" = {b.rhs_head}"
48
+ if b.rhs_paren_path is not None:
49
+ line += f" ({b.rhs_paren_path})"
50
+ if b.base_address is not None:
51
+ line += f" @{_fmt_at_int(b.base_address)}"
52
+ elif b.base_address is not None:
53
+ line += f" @{_fmt_at_int(b.base_address)}"
54
+ return line
55
+
56
+
57
+ def _emit_block(b: BlockNode, indent: str) -> list[str]:
58
+ head_line = _emit_block_open_line(b, indent)
59
+ if not b.has_body:
60
+ return [f"{head_line};"]
61
+ lines = [head_line + " {"]
62
+ inner = indent + " "
63
+ if b.bytes_width is not None:
64
+ lines.append(f"{inner}bytes {b.bytes_width};")
65
+ for r in b.registers:
66
+ lines.extend(_emit_register(r, inner))
67
+ for sub in b.blocks:
68
+ lines.extend(_emit_block(sub, inner))
69
+ lines.append(indent + "}")
70
+ return lines
71
+
72
+
73
+ def dump_ralf(doc: RalfDocument) -> str:
74
+ """将 `RalfDocument` 序列化为 RALF 源文本(规范化排版)。"""
75
+ out: list[str] = []
76
+ for i, b in enumerate(doc.blocks):
77
+ if i:
78
+ out.append("")
79
+ out.extend(_emit_block(b, ""))
80
+ return "\n".join(out) + ("\n" if out else "")
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ class RalfError(Exception):
7
+ """RALF 解析或生成错误基类。"""
8
+
9
+
10
+ class RalfParseError(RalfError):
11
+ """文本不符合预期语法或词法。"""
12
+
13
+ def __init__(self, message: str, *, line: int, col: int) -> None:
14
+ super().__init__(f"{message} (行 {line}, 列 {col})")
15
+ self.line = line
16
+ self.col = col
17
+
18
+
19
+ class RalfSourceError(RalfError):
20
+ """source 展开或路径解析失败(含循环引用、找不到文件)。"""
21
+
22
+ def __init__(self, message: str, *, path: Path | None = None) -> None:
23
+ super().__init__(message)
24
+ self.path = path
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from pathlib import Path
5
+
6
+ from ralf_model.emit import dump_ralf
7
+ from ralf_model.nodes import RalfDocument
8
+ from ralf_model.parse import parse_ralf
9
+ from ralf_model.source_expand import expand_ralf_sources
10
+
11
+
12
+ def load_ralf_file(
13
+ path: str | Path,
14
+ *,
15
+ encoding: str = "utf-8",
16
+ include_paths: Sequence[str | Path] | None = None,
17
+ expand_source: bool = True,
18
+ ) -> RalfDocument:
19
+ """从文件加载;若 ``expand_source`` 为真,先按 Tcl ``source`` 语义展开(含 ``include_paths`` 检索)。"""
20
+ p = Path(path).resolve()
21
+ text = p.read_text(encoding=encoding)
22
+ inc = tuple(Path(x).resolve() for x in (include_paths or ()))
23
+ if expand_source:
24
+ text = expand_ralf_sources(text, current_file=p, include_paths=inc, encoding=encoding)
25
+ return parse_ralf(text)
26
+
27
+
28
+ def loads_ralf(
29
+ text: str,
30
+ *,
31
+ encoding: str = "utf-8",
32
+ base_dir: str | Path | None = None,
33
+ include_paths: Sequence[str | Path] | None = None,
34
+ expand_source: bool = True,
35
+ ) -> RalfDocument:
36
+ """自字符串解析。展开 ``source`` 时相对路径相对 ``base_dir``(默认当前工作目录)。"""
37
+ bd = Path(base_dir).resolve() if base_dir is not None else Path.cwd()
38
+ virtual = bd / "__inline__.ralf"
39
+ inc = tuple(Path(x).resolve() for x in (include_paths or ()))
40
+ if expand_source:
41
+ text = expand_ralf_sources(
42
+ text,
43
+ current_file=virtual,
44
+ include_paths=inc,
45
+ encoding=encoding,
46
+ )
47
+ return parse_ralf(text)
48
+
49
+
50
+ def dump_ralf_file(doc: RalfDocument, path: str | Path, *, encoding: str = "utf-8") -> None:
51
+ Path(path).write_text(dump_ralf(doc), encoding=encoding)
52
+
53
+
54
+ def dumps_ralf(doc: RalfDocument) -> str:
55
+ """序列化为字符串,等价于 `dump_ralf`。"""
56
+ return dump_ralf(doc)
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class FieldNode(BaseModel):
7
+ """单个 field:`inner_statements` 按源顺序保存每条完整语句(含分号)。"""
8
+
9
+ model_config = ConfigDict(extra="forbid")
10
+
11
+ name: str
12
+ offset_bits: int | None = Field(default=None, description="field 内 `@` 后的位偏移")
13
+ inner_statements: list[str] = Field(
14
+ default_factory=list,
15
+ description='field 花括号内语句,如 bits 1;、reset \'h0;、enum { ... };',
16
+ )
17
+
18
+
19
+ class RegisterNode(BaseModel):
20
+ """寄存器:可为完整 `{ ... }` 定义,或仅 `register name;` 的前向引用。"""
21
+
22
+ model_config = ConfigDict(extra="forbid")
23
+
24
+ name: str
25
+ offset_bytes: int | None = Field(default=None, description="register 后的 `@` 字节偏移")
26
+ bytes_width: int | None = None
27
+ fields: list[FieldNode] = Field(default_factory=list)
28
+ declaration_only: bool = False
29
+
30
+
31
+ class BlockNode(BaseModel):
32
+ """层次化 block。
33
+
34
+ 典型形态:
35
+ - 定义体:``block 名 { ... }``
36
+ - 简单映射:``block 名 @地址;``
37
+ - 实例化:``block 左名 = 右名 [ (路径) ] [ @地址 ] ;`` 或带 ``{ ... }``
38
+ """
39
+
40
+ model_config = ConfigDict(extra="forbid")
41
+
42
+ name: str
43
+ rhs_head: str | None = Field(
44
+ default=None,
45
+ description="`=` 右侧起始的层级名(含可能的 `[..]` 后缀)",
46
+ )
47
+ rhs_paren_path: str | None = Field(
48
+ default=None,
49
+ description="紧跟在 rhs_head 后的圆括号路径内容(不含括号)",
50
+ )
51
+ base_address: int | None = Field(
52
+ default=None,
53
+ description="`@` 后的地址;可出现于简单 `block 名 @addr` 或 `=` 形式末尾",
54
+ )
55
+ has_body: bool = Field(
56
+ default=True,
57
+ description="是否带有 `{ ... }`;仅分号结尾的声明为 False",
58
+ )
59
+ bytes_width: int | None = None
60
+ registers: list[RegisterNode] = Field(default_factory=list)
61
+ blocks: list[BlockNode] = Field(default_factory=list)
62
+
63
+
64
+ class RalfDocument(BaseModel):
65
+ """顶层 RALF 文件内容(当前实现要求顶层为若干 `block`)。"""
66
+
67
+ model_config = ConfigDict(extra="forbid")
68
+
69
+ blocks: list[BlockNode] = Field(default_factory=list)