@@ -0,0 +1,359 @@
|
||||
"""Shared recognition of TSL codegen API headings and declaration lines."""
|
||||
|
||||
from bisect import bisect_right
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
|
||||
|
||||
TOP_LEVEL_RE = re.compile(r"^##(?!#)\s+`(.+?)`\s*$")
|
||||
PLAIN_H2_RE = re.compile(r"^##(?!#)\s+")
|
||||
BARE_API_RE = re.compile(r"^(#{3,5})(?!#)\s+`(.+?)`\s*$")
|
||||
LEGACY_TYPED_RE = re.compile(
|
||||
r"^(#{3,5})(?!#)\s+"
|
||||
r"(class function|static function|static field|static const|"
|
||||
r"function|property|field|const|var|class)\s+`(.+?)`\s*$"
|
||||
)
|
||||
DECLARATION_LINE_RE = re.compile(r"^声明:(.*?)\s*$")
|
||||
DECLARATION_PREFIX_RE = re.compile(r"^声明[::]", re.IGNORECASE)
|
||||
LOOSE_DECLARATION_RE = re.compile(r"^声明[::]\s*(.*?)\s*$", re.IGNORECASE)
|
||||
TOP_LEVEL_TYPES = {"function", "class", "unit"}
|
||||
CLASS_DECLARATIONS = {
|
||||
"function": ("method", "instance"),
|
||||
"class function": ("method", "class"),
|
||||
"property": ("property", "instance"),
|
||||
"field": ("field", "instance"),
|
||||
"static field": ("field", "static"),
|
||||
"const": ("constant", "instance"),
|
||||
"static const": ("constant", "static"),
|
||||
}
|
||||
UNIT_DECLARATIONS = {
|
||||
"function": ("function", "unit"),
|
||||
"var": ("variable", "unit"),
|
||||
"const": ("constant", "unit"),
|
||||
"class": ("class", "unit"),
|
||||
}
|
||||
HEADING_RE = re.compile(r"^(#{1,6})(?!#)\s+")
|
||||
FENCE_RE = re.compile(r"^\s*(```|~~~)")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ApiHeading:
|
||||
level: int
|
||||
kind: str
|
||||
binding: str
|
||||
name: str
|
||||
signature: str
|
||||
visible_title: str
|
||||
root_kind: str
|
||||
valid: bool = True
|
||||
error: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ApiEntry:
|
||||
heading: ApiHeading
|
||||
start: int
|
||||
end: int
|
||||
|
||||
|
||||
def simple_name(signature):
|
||||
return signature.split("(", 1)[0].strip()
|
||||
|
||||
|
||||
def fence_flags(lines):
|
||||
flags = []
|
||||
in_fence = False
|
||||
marker = ""
|
||||
for line in lines:
|
||||
stripped = line.lstrip()
|
||||
if not in_fence and FENCE_RE.match(line):
|
||||
marker = stripped[:3]
|
||||
flags.append(True)
|
||||
in_fence = True
|
||||
continue
|
||||
flags.append(in_fence)
|
||||
if in_fence and stripped.startswith(marker):
|
||||
in_fence = False
|
||||
marker = ""
|
||||
return flags
|
||||
|
||||
|
||||
def heading_body_end(lines, start, flags=None):
|
||||
if flags is None:
|
||||
flags = fence_flags(lines)
|
||||
for index in range(start + 1, len(lines)):
|
||||
if flags[index]:
|
||||
continue
|
||||
if HEADING_RE.match(lines[index]):
|
||||
return index
|
||||
return len(lines)
|
||||
|
||||
|
||||
def top_level_header_end(lines, start, flags=None):
|
||||
return heading_body_end(lines, start, flags)
|
||||
|
||||
|
||||
def _declaration(lines, start, end, flags):
|
||||
content = []
|
||||
declaration_lines = []
|
||||
for index in range(start + 1, end):
|
||||
if flags[index]:
|
||||
continue
|
||||
stripped = lines[index].strip()
|
||||
if not stripped:
|
||||
continue
|
||||
content.append((index, stripped))
|
||||
if DECLARATION_PREFIX_RE.match(stripped):
|
||||
declaration_lines.append((index, stripped))
|
||||
|
||||
if not content:
|
||||
return "", "API 标题后的第一条非空正文必须是 `声明:...`"
|
||||
first_index, first = content[0]
|
||||
match = DECLARATION_LINE_RE.fullmatch(first)
|
||||
if not match:
|
||||
loose = LOOSE_DECLARATION_RE.fullmatch(first)
|
||||
if loose and loose.group(1).strip():
|
||||
expected = f"声明:{loose.group(1).strip().casefold()}"
|
||||
return "", f"声明行必须精确写为 `{expected}`"
|
||||
return (
|
||||
"",
|
||||
"API 标题后的第一条非空正文必须是规范的 `声明:...` 行",
|
||||
)
|
||||
value = match.group(1).strip().casefold()
|
||||
expected = f"声明:{value}"
|
||||
if first != expected:
|
||||
return "", f"声明行必须精确写为 `{expected}`"
|
||||
if len(declaration_lines) != 1 or declaration_lines[0][0] != first_index:
|
||||
return "", "每个 API 必须且只能包含一行声明"
|
||||
return value, ""
|
||||
|
||||
|
||||
def top_level_type(lines, start, end):
|
||||
return _declaration(lines, start, end, fence_flags(lines))
|
||||
|
||||
|
||||
def invalid_heading(level, name, signature, visible_title, root_kind, message):
|
||||
return ApiHeading(
|
||||
level,
|
||||
"invalid",
|
||||
"",
|
||||
name,
|
||||
signature,
|
||||
visible_title,
|
||||
root_kind,
|
||||
False,
|
||||
message,
|
||||
)
|
||||
|
||||
|
||||
def _valid_heading(level, kind, binding, signature, root_kind):
|
||||
return ApiHeading(
|
||||
level,
|
||||
kind,
|
||||
binding,
|
||||
simple_name(signature),
|
||||
signature,
|
||||
signature,
|
||||
root_kind,
|
||||
)
|
||||
|
||||
|
||||
def top_level_heading(signature, declaration, error):
|
||||
if error:
|
||||
return invalid_heading(
|
||||
2,
|
||||
simple_name(signature),
|
||||
signature,
|
||||
signature,
|
||||
"",
|
||||
error,
|
||||
)
|
||||
if declaration not in TOP_LEVEL_TYPES:
|
||||
return invalid_heading(
|
||||
2,
|
||||
simple_name(signature),
|
||||
signature,
|
||||
signature,
|
||||
"",
|
||||
f"顶级 API 声明必须是 function、class 或 unit,实为 {declaration}",
|
||||
)
|
||||
return _valid_heading(2, declaration, "", signature, declaration)
|
||||
|
||||
|
||||
def member_heading(
|
||||
level,
|
||||
declaration,
|
||||
signature,
|
||||
root_kind,
|
||||
unit_class_open,
|
||||
error,
|
||||
):
|
||||
name = simple_name(signature)
|
||||
if error:
|
||||
return invalid_heading(
|
||||
level, name, signature, signature, root_kind, error
|
||||
)
|
||||
if declaration == "static function":
|
||||
return invalid_heading(
|
||||
level,
|
||||
name,
|
||||
signature,
|
||||
signature,
|
||||
root_kind,
|
||||
"不存在 static function,请使用 class function",
|
||||
)
|
||||
|
||||
if root_kind == "class":
|
||||
if level != 3 or declaration not in CLASS_DECLARATIONS:
|
||||
return invalid_heading(
|
||||
level,
|
||||
name,
|
||||
signature,
|
||||
signature,
|
||||
root_kind,
|
||||
"class API 成员必须使用约定的 H3 声明",
|
||||
)
|
||||
kind, binding = CLASS_DECLARATIONS[declaration]
|
||||
return _valid_heading(level, kind, binding, signature, root_kind)
|
||||
|
||||
if root_kind == "unit":
|
||||
if level == 3:
|
||||
if declaration not in UNIT_DECLARATIONS:
|
||||
return invalid_heading(
|
||||
level,
|
||||
name,
|
||||
signature,
|
||||
signature,
|
||||
root_kind,
|
||||
"unit interface 成员必须使用约定的 H3 声明",
|
||||
)
|
||||
kind, binding = UNIT_DECLARATIONS[declaration]
|
||||
return _valid_heading(level, kind, binding, signature, root_kind)
|
||||
if (
|
||||
level == 4
|
||||
and unit_class_open
|
||||
and declaration in CLASS_DECLARATIONS
|
||||
):
|
||||
kind, binding = CLASS_DECLARATIONS[declaration]
|
||||
return _valid_heading(level, kind, binding, signature, root_kind)
|
||||
return invalid_heading(
|
||||
level,
|
||||
name,
|
||||
signature,
|
||||
signature,
|
||||
root_kind,
|
||||
"unit class 成员必须位于所属 class 的 H4",
|
||||
)
|
||||
|
||||
return invalid_heading(
|
||||
level,
|
||||
name,
|
||||
signature,
|
||||
signature,
|
||||
root_kind,
|
||||
"function 顶级 API 不能包含 API 成员",
|
||||
)
|
||||
|
||||
|
||||
def collect_headings_and_boundaries(lines):
|
||||
flags = fence_flags(lines)
|
||||
headings = []
|
||||
h2_boundaries = []
|
||||
root_kind = ""
|
||||
unit_class_open = False
|
||||
|
||||
for index, line in enumerate(lines):
|
||||
if flags[index]:
|
||||
continue
|
||||
|
||||
if PLAIN_H2_RE.match(line):
|
||||
h2_boundaries.append(index)
|
||||
root_kind = ""
|
||||
unit_class_open = False
|
||||
top_level = TOP_LEVEL_RE.match(line)
|
||||
if not top_level:
|
||||
continue
|
||||
signature = top_level.group(1)
|
||||
end = heading_body_end(lines, index, flags)
|
||||
declaration, error = _declaration(
|
||||
lines, index, end, flags
|
||||
)
|
||||
heading = top_level_heading(signature, declaration, error)
|
||||
if heading.valid:
|
||||
root_kind = heading.kind
|
||||
headings.append((index, heading))
|
||||
continue
|
||||
|
||||
if not root_kind:
|
||||
continue
|
||||
|
||||
heading_match = HEADING_RE.match(line)
|
||||
if root_kind == "unit" and heading_match:
|
||||
if len(heading_match.group(1)) == 3:
|
||||
unit_class_open = False
|
||||
|
||||
legacy = LEGACY_TYPED_RE.match(line)
|
||||
if legacy:
|
||||
level = len(legacy.group(1))
|
||||
signature = legacy.group(3)
|
||||
headings.append(
|
||||
(
|
||||
index,
|
||||
invalid_heading(
|
||||
level,
|
||||
simple_name(signature),
|
||||
signature,
|
||||
line.lstrip("# "),
|
||||
root_kind,
|
||||
"API 标题只写名称或调用签名,声明种类写在声明行",
|
||||
),
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
bare = BARE_API_RE.match(line)
|
||||
if not bare:
|
||||
continue
|
||||
level = len(bare.group(1))
|
||||
signature = bare.group(2)
|
||||
end = heading_body_end(lines, index, flags)
|
||||
declaration, error = _declaration(lines, index, end, flags)
|
||||
heading = member_heading(
|
||||
level,
|
||||
declaration,
|
||||
signature,
|
||||
root_kind,
|
||||
unit_class_open,
|
||||
error,
|
||||
)
|
||||
if root_kind == "unit" and level == 3:
|
||||
unit_class_open = heading.valid and heading.kind == "class"
|
||||
headings.append((index, heading))
|
||||
|
||||
return headings, h2_boundaries
|
||||
|
||||
|
||||
def collect_headings(lines):
|
||||
headings, _ = collect_headings_and_boundaries(lines)
|
||||
return headings
|
||||
|
||||
|
||||
def iter_api_entries(lines):
|
||||
headings, h2_boundaries = collect_headings_and_boundaries(lines)
|
||||
for index, (start, heading) in enumerate(headings):
|
||||
next_heading = (
|
||||
headings[index + 1][0]
|
||||
if index + 1 < len(headings)
|
||||
else len(lines)
|
||||
)
|
||||
boundary_index = bisect_right(h2_boundaries, start)
|
||||
next_h2 = (
|
||||
h2_boundaries[boundary_index]
|
||||
if boundary_index < len(h2_boundaries)
|
||||
else len(lines)
|
||||
)
|
||||
yield ApiEntry(heading, start, min(next_heading, next_h2))
|
||||
|
||||
|
||||
def slug(text):
|
||||
return re.sub(r"[^a-z0-9_]", "", text.casefold())
|
||||
Reference in New Issue
Block a user