python-library-ralf-model 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.
@@ -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,11 @@
1
+ ralf_model/__init__.py,sha256=k6kVBQxMcUFcdON7X3lVa76vVZrNQ0d0czOIC63sB7k,999
2
+ ralf_model/abc.py,sha256=CjfkbS3dy05uL8vWZzAGQdwMI7CNH3JPeFMdauAa1qI,518
3
+ ralf_model/emit.py,sha256=wEMgPsJxK9H1KbZudCS0obO82w-bwRyUz-dSJukq1UI,2646
4
+ ralf_model/errors.py,sha256=6xoK36ugGOkRLZOvHO9p_kYwHzZoiVNlFSYJL7_NuPk,686
5
+ ralf_model/io.py,sha256=6rIgDZfWcHGTrfLH42cuOhEI0urp5zWeKWf4zbtz73M,1935
6
+ ralf_model/nodes.py,sha256=Wnn3asEeFcUtUFAmnepqC_dJjVcYvFH_0eXv-jg2YUA,2337
7
+ ralf_model/parse.py,sha256=uXGgUZKkFvRMm2Dn1dieRsUKTOHUzEMrVFXJJaqri4w,18823
8
+ ralf_model/source_expand.py,sha256=lBhPLUP3MPTnVzwpW-EU9JMOgSMCStjg99Eg9vUQ_M4,4858
9
+ python_library_ralf_model-0.1.0.dist-info/METADATA,sha256=mrR7oBxPO-cNV8aQN2woQTtGwYHVcM0SPGV7CuXw1d8,2073
10
+ python_library_ralf_model-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
11
+ python_library_ralf_model-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
ralf_model/__init__.py ADDED
@@ -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
+ ]
ralf_model/abc.py ADDED
@@ -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
ralf_model/emit.py ADDED
@@ -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 "")
ralf_model/errors.py ADDED
@@ -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
ralf_model/io.py ADDED
@@ -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)
ralf_model/nodes.py ADDED
@@ -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)
ralf_model/parse.py ADDED
@@ -0,0 +1,554 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+
5
+ from ralf_model.errors import RalfParseError
6
+ from ralf_model.nodes import BlockNode, FieldNode, RalfDocument, RegisterNode
7
+
8
+
9
+ class _Parser:
10
+ def __init__(self, text: str) -> None:
11
+ self._s = text
12
+ self._n = len(text)
13
+ self._i = 0
14
+ self.line = 1
15
+ self.col = 1
16
+
17
+ def _advance(self, n: int = 1) -> None:
18
+ for _ in range(n):
19
+ if self._i >= self._n:
20
+ return
21
+ if self._s[self._i] == "\n":
22
+ self.line += 1
23
+ self.col = 1
24
+ else:
25
+ self.col += 1
26
+ self._i += 1
27
+
28
+ def _peek(self, offset: int = 0) -> str | None:
29
+ j = self._i + offset
30
+ return self._s[j] if j < self._n else None
31
+
32
+ def _error(self, msg: str) -> None:
33
+ raise RalfParseError(msg, line=self.line, col=self.col)
34
+
35
+ def skip_ws_and_comments(self) -> None:
36
+ while self._i < self._n:
37
+ c = self._s[self._i]
38
+ if c in " \t\r\n":
39
+ self._advance()
40
+ elif c == "/" and self._peek(1) == "/":
41
+ while self._i < self._n and self._s[self._i] != "\n":
42
+ self._advance()
43
+ elif c == "/" and self._peek(1) == "*":
44
+ self._advance(2)
45
+ while self._i + 1 < self._n and not (
46
+ self._s[self._i] == "*" and self._s[self._i + 1] == "/"
47
+ ):
48
+ self._advance()
49
+ if self._i + 1 < self._n:
50
+ self._advance(2)
51
+ else:
52
+ self._error("未闭合的块注释")
53
+ else:
54
+ break
55
+
56
+ def expect_char(self, ch: str) -> None:
57
+ self.skip_ws_and_comments()
58
+ if self._peek() != ch:
59
+ self._error(f"期望 {ch!r},实际为 {self._peek()!r}")
60
+ self._advance()
61
+
62
+ def expect_keyword(self, kw: str) -> None:
63
+ self.skip_ws_and_comments()
64
+ start = self._i
65
+ ident = self._read_ident_raw()
66
+ if ident is None or ident.lower() != kw.lower():
67
+ self._i = start
68
+ self._error(f"期望关键字 {kw!r}")
69
+
70
+ def _read_ident_raw(self) -> str | None:
71
+ if self._i >= self._n:
72
+ return None
73
+ c = self._s[self._i]
74
+ if not (c.isalpha() or c == "_"):
75
+ return None
76
+ start = self._i
77
+ self._advance()
78
+ while self._i < self._n:
79
+ c2 = self._s[self._i]
80
+ if c2.isalnum() or c2 == "_":
81
+ self._advance()
82
+ else:
83
+ break
84
+ return self._s[start : self._i]
85
+
86
+ def read_ident(self) -> str:
87
+ self.skip_ws_and_comments()
88
+ ident = self._read_ident_raw()
89
+ if ident is None:
90
+ self._error("期望标识符")
91
+ return ident
92
+
93
+ def parse_integer_value(self) -> int:
94
+ self.skip_ws_and_comments()
95
+ v, end = _parse_int_literal(self._s, self._i)
96
+ if end == self._i:
97
+ self._error("期望整数字面量")
98
+ while self._i < end:
99
+ self._advance()
100
+ return v
101
+
102
+ def read_braced_block(self) -> str:
103
+ """从当前位置的 `{` 起读取一对花括号(含外侧花括号)。"""
104
+ self.skip_ws_and_comments()
105
+ if self._peek() != "{":
106
+ self._error("期望 {")
107
+ start = self._i
108
+ depth = 0
109
+ i = self._i
110
+ while i < self._n:
111
+ c = self._s[i]
112
+ if c == "/" and i + 1 < self._n and self._s[i + 1] == "/":
113
+ while i < self._n and self._s[i] != "\n":
114
+ i += 1
115
+ continue
116
+ if c == "/" and i + 1 < self._n and self._s[i + 1] == "*":
117
+ i += 2
118
+ while i + 1 < self._n and not (
119
+ self._s[i] == "*" and self._s[i + 1] == "/"
120
+ ):
121
+ i += 1
122
+ i = min(i + 2, self._n)
123
+ continue
124
+ if c == "{":
125
+ depth += 1
126
+ i += 1
127
+ elif c == "}":
128
+ depth -= 1
129
+ i += 1
130
+ if depth == 0:
131
+ span = self._s[start:i]
132
+ while self._i < i:
133
+ self._advance()
134
+ return span
135
+ continue
136
+ else:
137
+ i += 1
138
+ self._error("未闭合的 {")
139
+
140
+ def read_until_semicolon(self) -> str:
141
+ """从当前位置读到(不含)`;`,并消费分号。不跨入注释外的换行可简化处理。"""
142
+ self.skip_ws_and_comments()
143
+ start = self._i
144
+ while self._i < self._n:
145
+ c = self._s[self._i]
146
+ if c == "/" and self._peek(1) == "/":
147
+ self._error("行内值不应含注释")
148
+ if c == ";":
149
+ text = self._s[start : self._i].strip()
150
+ self._advance()
151
+ return text
152
+ if c == "\n":
153
+ pass # allow multiline for complex expressions
154
+ self._advance()
155
+ self._error("期望 ;")
156
+
157
+ def parse_document(self) -> RalfDocument:
158
+ blocks: list[BlockNode] = []
159
+ self.skip_ws_and_comments()
160
+ while self._i < self._n:
161
+ kw = self._peek_keyword()
162
+ if kw == "block":
163
+ blocks.append(self.parse_block())
164
+ else:
165
+ self._error(f"顶层期望 block,得到 {kw!r}")
166
+ self.skip_ws_and_comments()
167
+ return RalfDocument(blocks=blocks)
168
+
169
+ def _peek_keyword(self) -> str:
170
+ self.skip_ws_and_comments()
171
+ save = self._i
172
+ ident = self._read_ident_raw()
173
+ self._i = save
174
+ return ident.lower() if ident else ""
175
+
176
+ def read_hierarchical_block_name(self) -> str:
177
+ """实例名:允许段 `ident`、`.` 分层以及后缀 `[ ... ]`(如 `blk_vh[2]`、`dom[*]`)。"""
178
+ name = self.read_ident()
179
+ while True:
180
+ self.skip_ws_and_comments()
181
+ if self._peek() == "[":
182
+ name += self.read_balanced_square_brackets()
183
+ continue
184
+ if self._peek() == ".":
185
+ self._advance()
186
+ name += "." + self.read_ident()
187
+ continue
188
+ break
189
+ return name
190
+
191
+ def read_balanced_square_brackets(self) -> str:
192
+ """从当前 `[` 读到匹配的 `]`(含括号),并前进游标。"""
193
+ if self._peek() != "[":
194
+ self._error("期望 [")
195
+ start = self._i
196
+ depth = 0
197
+ i = self._i
198
+ while i < self._n:
199
+ c = self._s[i]
200
+ if c == "[":
201
+ depth += 1
202
+ i += 1
203
+ elif c == "]":
204
+ depth -= 1
205
+ i += 1
206
+ if depth == 0:
207
+ span = self._s[start:i]
208
+ while self._i < i:
209
+ self._advance()
210
+ return span
211
+ else:
212
+ i += 1
213
+ self._error("未闭合的 [")
214
+
215
+ def read_round_paren_inner(self) -> str:
216
+ """读取一对圆括号内的文本(含嵌套括号),游标停在闭合 `)` 之后。"""
217
+ self.skip_ws_and_comments()
218
+ self.expect_char("(")
219
+ start = self._i
220
+ depth = 1
221
+ while self._i < self._n and depth:
222
+ c = self._s[self._i]
223
+ if c == "(":
224
+ depth += 1
225
+ elif c == ")":
226
+ depth -= 1
227
+ if depth == 0:
228
+ inner = self._s[start : self._i].strip()
229
+ self._advance()
230
+ return inner
231
+ self._advance()
232
+ self._error("未闭合的 (")
233
+
234
+ def parse_block_rhs_after_equals(
235
+ self,
236
+ ) -> tuple[str, str | None, int | None]:
237
+ """解析 ``=`` 之后:``层级名 [ (路径) ] [ @地址 ]``,止于 ``;`` 或 ``{{`` 之前。"""
238
+ head = self.read_hierarchical_block_name()
239
+ self.skip_ws_and_comments()
240
+ paren_inner: str | None = None
241
+ if self._peek() == "(":
242
+ paren_inner = self.read_round_paren_inner()
243
+ self.skip_ws_and_comments()
244
+ addr: int | None = None
245
+ if self._peek() == "@":
246
+ self._advance()
247
+ addr = self.parse_integer_value()
248
+ self.skip_ws_and_comments()
249
+ return head, paren_inner, addr
250
+
251
+ def parse_block(self) -> BlockNode:
252
+ self.expect_keyword("block")
253
+ name = self.read_hierarchical_block_name()
254
+ self.skip_ws_and_comments()
255
+
256
+ if self._peek() == "@":
257
+ self._advance()
258
+ addr = self.parse_integer_value()
259
+ self.skip_ws_and_comments()
260
+ self.expect_char(";")
261
+ return BlockNode(
262
+ name=name,
263
+ base_address=addr,
264
+ has_body=False,
265
+ registers=[],
266
+ blocks=[],
267
+ )
268
+
269
+ if self._peek() == "=":
270
+ self._advance()
271
+ self.skip_ws_and_comments()
272
+ rhs_head, rhs_path, addr_rhs = self.parse_block_rhs_after_equals()
273
+ self.skip_ws_and_comments()
274
+ if self._peek() == ";":
275
+ self._advance()
276
+ return BlockNode(
277
+ name=name,
278
+ rhs_head=rhs_head,
279
+ rhs_paren_path=rhs_path,
280
+ base_address=addr_rhs,
281
+ has_body=False,
282
+ registers=[],
283
+ blocks=[],
284
+ )
285
+ if self._peek() == "{":
286
+ self._advance()
287
+ bw, regs, subs = self._parse_block_body()
288
+ self.expect_char("}")
289
+ return BlockNode(
290
+ name=name,
291
+ rhs_head=rhs_head,
292
+ rhs_paren_path=rhs_path,
293
+ base_address=addr_rhs,
294
+ has_body=True,
295
+ bytes_width=bw,
296
+ registers=regs,
297
+ blocks=subs,
298
+ )
299
+ self._error("block ... = ... 之后应为 ; 或 {")
300
+
301
+ if self._peek() == "{":
302
+ self._advance()
303
+ bw, regs, subs = self._parse_block_body()
304
+ self.expect_char("}")
305
+ return BlockNode(
306
+ name=name,
307
+ has_body=True,
308
+ bytes_width=bw,
309
+ registers=regs,
310
+ blocks=subs,
311
+ )
312
+ self._error("block 名称之后应为 @、= 或 {")
313
+
314
+ def _parse_block_body(self) -> tuple[int | None, list[RegisterNode], list[BlockNode]]:
315
+ bw: int | None = None
316
+ regs: list[RegisterNode] = []
317
+ subs: list[BlockNode] = []
318
+ while True:
319
+ self.skip_ws_and_comments()
320
+ if self._peek() == "}":
321
+ break
322
+ kw = self._peek_keyword()
323
+ if kw == "bytes":
324
+ self.expect_keyword("bytes")
325
+ bw = self.parse_integer_value()
326
+ self.expect_char(";")
327
+ elif kw == "register":
328
+ regs.append(self.parse_register())
329
+ elif kw == "block":
330
+ subs.append(self.parse_block())
331
+ else:
332
+ self._error(f"block 内出现未识别的内容 {kw!r}")
333
+ return bw, regs, subs
334
+
335
+ def parse_register(self) -> RegisterNode:
336
+ self.expect_keyword("register")
337
+ name = self.read_ident()
338
+ self.skip_ws_and_comments()
339
+ offset: int | None = None
340
+ if self._peek() == "@":
341
+ self._advance()
342
+ offset = self.parse_integer_value()
343
+ self.skip_ws_and_comments()
344
+ if self._peek() == ";":
345
+ self._advance()
346
+ return RegisterNode(
347
+ name=name,
348
+ offset_bytes=offset,
349
+ declaration_only=True,
350
+ )
351
+ if self._peek() == "{":
352
+ self._advance()
353
+ rbw, fields = self._parse_register_body()
354
+ self.expect_char("}")
355
+ return RegisterNode(
356
+ name=name,
357
+ offset_bytes=offset,
358
+ bytes_width=rbw,
359
+ fields=fields,
360
+ declaration_only=False,
361
+ )
362
+ self._error("register 后应为 ; 或 {")
363
+
364
+ def _parse_register_body(self) -> tuple[int | None, list[FieldNode]]:
365
+ rbw: int | None = None
366
+ fields: list[FieldNode] = []
367
+ while True:
368
+ self.skip_ws_and_comments()
369
+ if self._peek() == "}":
370
+ break
371
+ kw = self._peek_keyword()
372
+ if kw == "bytes":
373
+ self.expect_keyword("bytes")
374
+ rbw = self.parse_integer_value()
375
+ self.expect_char(";")
376
+ elif kw == "field":
377
+ fields.append(self.parse_field())
378
+ else:
379
+ self._error(f"register 体内期望 field 或 bytes,得到 {kw!r}")
380
+ return rbw, fields
381
+
382
+ def parse_field(self) -> FieldNode:
383
+ self.expect_keyword("field")
384
+ name = self.read_ident()
385
+ self.skip_ws_and_comments()
386
+ off_bits: int | None = None
387
+ if self._peek() == "@":
388
+ self._advance()
389
+ off_bits = self.parse_integer_value()
390
+ self.expect_char("{")
391
+ fn = self._parse_field_body()
392
+ self.expect_char("}")
393
+ return fn.model_copy(update={"name": name, "offset_bits": off_bits})
394
+
395
+ def _parse_field_body(self) -> FieldNode:
396
+ inner: list[str] = []
397
+
398
+ while True:
399
+ self.skip_ws_and_comments()
400
+ if self._peek() == "}":
401
+ break
402
+ kw = self._peek_keyword()
403
+ if kw == "bits":
404
+ self.expect_keyword("bits")
405
+ bits = self.parse_integer_value()
406
+ self.expect_char(";")
407
+ inner.append(f"bits {bits};")
408
+ elif kw == "reset":
409
+ self.expect_keyword("reset")
410
+ reset = self.read_until_semicolon()
411
+ inner.append(f"reset {reset};")
412
+ elif kw == "access":
413
+ self.expect_keyword("access")
414
+ acc = self.read_ident()
415
+ self.expect_char(";")
416
+ inner.append(f"access {acc};")
417
+ elif kw == "volatile":
418
+ self.expect_keyword("volatile")
419
+ self.skip_ws_and_comments()
420
+ if self._peek() == ";":
421
+ self._advance()
422
+ inner.append("volatile;")
423
+ else:
424
+ vol = self.read_ident()
425
+ self.expect_char(";")
426
+ inner.append(f"volatile {vol};")
427
+ else:
428
+ inner.append(self._parse_field_raw_statement())
429
+
430
+ return FieldNode(name="__tmp__", inner_statements=inner)
431
+
432
+ def _parse_field_raw_statement(self) -> str:
433
+ """解析 field 内未知关键字开头的整句,保留原文用于回写。"""
434
+ self.skip_ws_and_comments()
435
+ head = self.read_ident()
436
+ self.skip_ws_and_comments()
437
+ if self._peek() == "{":
438
+ brace = self.read_braced_block()
439
+ self.skip_ws_and_comments()
440
+ self.expect_char(";")
441
+ return f"{head} {brace};"
442
+ rest = self.read_until_semicolon()
443
+ if rest:
444
+ return f"{head} {rest};"
445
+ return f"{head};"
446
+
447
+
448
+ def _parse_int_literal(s: str, start: int) -> tuple[int, int]:
449
+ """自 `start` 起解析 Verilog 风格整数,返回 (值, 结束下标)。"""
450
+ n = len(s)
451
+ i = start
452
+ while i < n and s[i] in " \t\r\n":
453
+ i += 1
454
+ if i >= n:
455
+ return 0, start
456
+ j = i
457
+ if s[i] == "'":
458
+ return _parse_verilog_unsized(s, i)
459
+ # decimal width? digits then '
460
+ k = i
461
+ while k < n and s[k].isdigit():
462
+ k += 1
463
+ if k < n and s[k] == "'":
464
+ return _parse_verilog_sized(s, i)
465
+ # plain decimal
466
+ if s[i].isdigit():
467
+ while j < n and s[j].isdigit():
468
+ j += 1
469
+ return int(s[i:j]), j
470
+ return 0, start
471
+
472
+
473
+ def _parse_verilog_unsized(s: str, i: int) -> tuple[int, int]:
474
+ """如 'hFF"""
475
+ n = len(s)
476
+ if i >= n or s[i] != "'":
477
+ return 0, i
478
+ i += 1
479
+ if i >= n:
480
+ return 0, i
481
+ base_ch = s[i].lower()
482
+ i += 1
483
+ if i < n and s[i] in "sS":
484
+ i += 1
485
+ while i < n and s[i] in " \t":
486
+ i += 1
487
+ start_digits = i
488
+ while i < n:
489
+ c = s[i]
490
+ if c == "_" or c.isalnum() or c in "?xzXZ":
491
+ i += 1
492
+ else:
493
+ break
494
+ digits = s[start_digits:i].replace("_", "")
495
+ if not digits:
496
+ return 0, start_digits
497
+ if base_ch == "h":
498
+ return int(digits, 16), i
499
+ if base_ch in ("d",):
500
+ return int(digits, 10), i
501
+ if base_ch == "b":
502
+ return int(digits.replace("?", "0").replace("z", "0").replace("x", "0"), 2), i
503
+ if base_ch == "o":
504
+ return int(digits, 8), i
505
+ return int(digits, 16), i
506
+
507
+
508
+ def _parse_verilog_sized(s: str, i: int) -> tuple[int, int]:
509
+ """如 8'hFF"""
510
+ n = len(s)
511
+ j = i
512
+ while j < n and s[j].isdigit():
513
+ j += 1
514
+ if j >= n or s[j] != "'":
515
+ return 0, i
516
+ return _parse_verilog_unsized(s, j)
517
+
518
+
519
+ _VER_WS = re.compile(r"\s+")
520
+
521
+
522
+ def parse_ralf(text: str) -> RalfDocument:
523
+ """将 RALF 源文本解析为 `RalfDocument`。"""
524
+ p = _Parser(text)
525
+ doc = p.parse_document()
526
+ return doc
527
+
528
+
529
+ def normalize_ralf_whitespace(text: str) -> str:
530
+ """去掉注释与多余空白,用于测试比对(不保证与工具链字节级一致)。"""
531
+ p = _Parser(text)
532
+ p.skip_ws_and_comments()
533
+ out: list[str] = []
534
+ while p._i < p._n:
535
+ c = p._s[p._i]
536
+ if c in " \t\r\n":
537
+ if out and out[-1] != " ":
538
+ out.append(" ")
539
+ p._advance()
540
+ elif c == "/" and p._peek(1) == "/":
541
+ while p._i < p._n and p._s[p._i] != "\n":
542
+ p._advance()
543
+ elif c == "/" and p._peek(1) == "*":
544
+ p._advance(2)
545
+ while p._i + 1 < p._n and not (
546
+ p._s[p._i] == "*" and p._s[p._i + 1] == "/"
547
+ ):
548
+ p._advance()
549
+ p._advance(min(2, p._n - p._i))
550
+ else:
551
+ out.append(c)
552
+ p._advance()
553
+ s = "".join(out).strip()
554
+ return _VER_WS.sub(" ", s)
@@ -0,0 +1,145 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Sequence
6
+
7
+ from ralf_model.errors import RalfSourceError
8
+
9
+ # RALF 常作为 Tcl 脚本;顶层 ``source path`` 常见单独成行(可有尾随 ``;``、``#`` 注释)。
10
+ _SOURCE_HEAD = re.compile(r"^source\s+", re.IGNORECASE)
11
+
12
+
13
+ def resolve_source_path(
14
+ spec: str,
15
+ *,
16
+ base_dir: Path,
17
+ include_paths: Sequence[Path],
18
+ ) -> Path:
19
+ """按 Synopsys ``ralgen -I`` 语义解析 ``source`` 路径。
20
+
21
+ 顺序:``base_dir / spec``(当前文件所在目录),然后各 ``include_paths / spec``。
22
+ ``spec`` 为绝对路径时直接查找该路径。
23
+ """
24
+ raw = spec.strip()
25
+ if not raw:
26
+ raise RalfSourceError("source 路径为空")
27
+
28
+ p = Path(raw)
29
+ if p.is_absolute():
30
+ if p.is_file():
31
+ return p.resolve()
32
+ raise RalfSourceError(f"找不到 source 文件: {p}", path=p)
33
+
34
+ candidates = [base_dir / raw, *[inc / raw for inc in include_paths]]
35
+ for c in candidates:
36
+ if c.is_file():
37
+ return c.resolve()
38
+ searched = [str(x) for x in candidates]
39
+ raise RalfSourceError(
40
+ f"找不到 source 文件: {raw!r},已搜索: {searched}",
41
+ path=base_dir / raw,
42
+ )
43
+
44
+
45
+ def _strip_line_comment(line: str) -> str:
46
+ if "#" not in line:
47
+ return line
48
+ i = line.index("#")
49
+ # Tcl 里字符串中的 # 不一定是注释;简化处理:引号外的第一个 #
50
+ in_dq = False
51
+ j = 0
52
+ while j < len(line):
53
+ c = line[j]
54
+ if c == '"' and (j == 0 or line[j - 1] != "\\"):
55
+ in_dq = not in_dq
56
+ elif c == "#" and not in_dq:
57
+ return line[:j].rstrip()
58
+ j += 1
59
+ return line
60
+
61
+
62
+ def _parse_source_argument(line: str) -> str | None:
63
+ """若整行(去掉注释后)为 ``source <path>``,返回路径规格,否则返回 None。"""
64
+ s = _strip_line_comment(line).strip()
65
+ if not s:
66
+ return None
67
+ s = s.rstrip(";").strip()
68
+ m = _SOURCE_HEAD.match(s)
69
+ if not m:
70
+ return None
71
+ rest = s[m.end() :].strip()
72
+ if not rest:
73
+ return None
74
+ if rest.startswith('"'):
75
+ end = rest.find('"', 1)
76
+ if end == -1:
77
+ raise RalfSourceError(f"source 双引号路径未闭合: {line!r}")
78
+ inner = rest[1:end]
79
+ tail = rest[end + 1 :].strip().rstrip(";").strip()
80
+ if tail:
81
+ raise RalfSourceError(f"source 行含多余内容: {line!r}")
82
+ return inner
83
+ if rest.startswith("{"):
84
+ depth = 0
85
+ i = 0
86
+ while i < len(rest):
87
+ if rest[i] == "{":
88
+ depth += 1
89
+ elif rest[i] == "}":
90
+ depth -= 1
91
+ if depth == 0:
92
+ inner = rest[1:i]
93
+ tail = rest[i + 1 :].strip().rstrip(";").strip()
94
+ if tail:
95
+ raise RalfSourceError(f"source 行含多余内容: {line!r}")
96
+ return inner.strip()
97
+ i += 1
98
+ raise RalfSourceError(f"source 花括号路径未闭合: {line!r}")
99
+ parts = rest.split()
100
+ if len(parts) != 1:
101
+ raise RalfSourceError(f"无法解析的 source 行(期望单个路径): {line!r}")
102
+ return parts[0]
103
+
104
+
105
+ def expand_ralf_sources(
106
+ text: str,
107
+ *,
108
+ current_file: Path,
109
+ include_paths: Sequence[Path] = (),
110
+ encoding: str = "utf-8",
111
+ _chain: tuple[Path, ...] = (),
112
+ ) -> str:
113
+ """将 Tcl 风格 ``source path`` 递归展开为单段 RALF 文本后再交给 ``parse_ralf``。
114
+
115
+ ``current_file`` 用于确定相对路径的基准目录(通常为 ``path.parent``),并参与循环检测。
116
+ 从内存加载字符串时可使用 ``base_dir / \"__inline__.ralf\"`` 这类占位路径。
117
+ """
118
+ cf = current_file.resolve()
119
+ if cf in _chain:
120
+ raise RalfSourceError(f"source 形成循环: {' -> '.join(str(p) for p in _chain)} -> {cf}", path=cf)
121
+
122
+ chain = _chain + (cf,)
123
+ inc = tuple(Path(p).resolve() for p in include_paths)
124
+
125
+ out: list[str] = []
126
+ for line in text.splitlines(keepends=True):
127
+ spec = _parse_source_argument(line)
128
+ if spec is None:
129
+ out.append(line)
130
+ continue
131
+
132
+ inner_path = resolve_source_path(spec, base_dir=cf.parent, include_paths=inc)
133
+ inner_text = inner_path.read_text(encoding=encoding)
134
+ expanded_inner = expand_ralf_sources(
135
+ inner_text,
136
+ current_file=inner_path,
137
+ include_paths=inc,
138
+ encoding=encoding,
139
+ _chain=chain,
140
+ )
141
+ out.append(expanded_inner)
142
+ if expanded_inner and not expanded_inner.endswith("\n"):
143
+ out.append("\n")
144
+
145
+ return "".join(out)