Files
TransPyC/lib/vast/__init__.py
T
TermiNexus c83fc752c6 fix(sizeof,parse): 尺寸链去静默兜底,解析报错可定位,补 C 解析器缺口
StructGen: _get_struct_size/_get_align 去 opaque→8/ptr_size、未知→4 兜底并补 VectorType;LiteralStructType.fields→.elements(llvmlite 无 fields,必然 AttributeError)

HandlesClassDef: union 载荷尺寸经不存在的 ir.DataLayout 静默退化成 8 字节;HandlesExprCall/ExprBuiltin: sizeof 各静默 0 分支改 fail-fast,数组元素 sizeof 统一走 _get_struct_size

HandlesFor: nonlocal struct/array 堆分配不再退化成 malloc(8)

vast/vast_c: 补一元 * / & 解析,装饰器后 extern/export 分派修复,C 侧报错带 token 与行号;StubGen.Converter: 宏展开丢尾随空行修 .pyi 缩进非法

其余为 rawptr 迁移 / fail-fast 口径等历史改动一并入库
2026-09-26 19:27:22 +08:00

173 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""vast - Viper/Python 自研语法解析器(ast 替代品)
vast 提供与 CPython 3.12 标准库 ``ast`` 完全一致的 API:
- 节点类:``AST`` 及全部 131 个节点类(Name/Call/BinOp/FunctionDef/...)
- 解析入口:``parse(source, filename, mode, ...)`` 产出 ``Module``
- 辅助函数:``dump`` / ``walk`` / ``iter_child_nodes`` / ``iter_fields`` /
``unparse`` / ``copy_location`` / ``fix_missing_locations`` /
``get_source_segment`` / ``literal_eval``
- 类型标注占位:``expr`` / ``stmt`` / ``operator`` / ``cmpop`` 等
编译器侧替换方式(零修改其余代码)::
import vast as ast
vast 原生支持 Viper 软关键字 fn/fun/func/function(并入解析器),无需
ExpandSoftKeywords 预处理。
"""
from __future__ import annotations
import os as _os
import re as _re
import sys as _sys
from lib.vast.Nodes import AST
from lib.vast.Nodes import _NODE_DEFS
# 将全部节点类提升到包命名空间
for _name in _NODE_DEFS:
globals()[_name] = getattr(_sys.modules['lib.vast.Nodes'], _name)
from lib.vast.Parser import Parse # noqa: E402
from lib.vast.Utils import ( # noqa: E402
iter_fields, iter_child_nodes, walk, dump, copy_location,
fix_missing_locations, get_source_segment, literal_eval,
)
from lib.vast.Unparser import Unparse as unparse # noqa: E402
from lib.vast.Tokenizer import SOFT_KEYWORDS # noqa: E402
def _ParsePure(source: str, mode: str) -> AST:
"""纯 Python vast 解析(vast_c 不可用或回退时使用)。"""
tree: AST = Parse(source)
if mode == 'exec':
return tree
if mode == 'eval':
if len(tree.body) != 1 or not isinstance(tree.body[0], _sys.modules['lib.vast.Nodes'].Expr):
raise SyntaxError('invalid syntax for eval mode')
expr_node: AST = _sys.modules['lib.vast.Nodes'].Expression(
body=tree.body[0].value)
return expr_node
if mode == 'single':
interactive: AST = _sys.modules['lib.vast.Nodes'].Interactive(body=tree.body)
return interactive
raise ValueError(f"invalid mode {mode!r}")
# C 加速解析器(vast_c 扩展):可用则优先,输出与 vast.parse 结构完全一致。
# VAST_USE_C=0 禁用 C 路径;=strict 时 C 路径异常直接抛出(fail-fast)。
#
# 回退策略(响亮回退):C 路径抛异常时回退纯 Python,并向 stderr 打印一行可诊断
# 信息(文件名 + 异常)。该回退**不会**改变产出——纯 Python 是本包语义基准,
# 故绝不静默产出与纯 Python 不同的 AST。连续相同的(文件, 异常)只打印一次。
_VAST_C_PARSE = None
try:
from lib.vast_c import parse as _VAST_C_PARSE
except Exception:
_VAST_C_PARSE = None
_CFallbackSeen: set = set()
def _ReportCFallback(filename: str, exc: BaseException) -> None:
"""C 解析失败回退时的可诊断信息(去重,写 stderr)。"""
detail: str = f'{type(exc).__name__}: {exc}'
key = (filename, detail)
if key in _CFallbackSeen:
return
_CFallbackSeen.add(key)
_sys.stderr.write(
f'[vast] C 解析器失败,回退纯 Python:{filename}: {detail}\n')
# vast 语法错误统一报文:解析器原生消息末段带 "at line N"(ExprParser/Parser 各站点同款),
# 取出行号后补齐源码行与列号,用户看到的是单行 [TPC] 报文而不是解析器内部调用链。
_SYNTAX_LINE_RE: _re.Pattern[str] = _re.compile(r'at line (\d+)')
# 原生消息里的 token 原文(如 NAME('foo') / OP('*=') / INDENT(' ')),用于定位列号
_SYNTAX_TOKEN_RE: _re.Pattern[str] = _re.compile(r"\((?:'([^']*)'|\"([^\"]*)\")\)")
def _SourceLine(source: str, lineno: int) -> str:
"""取 1-based 第 lineno 行源码(行号缺失/越界返回空串)。"""
if lineno <= 0:
return ''
lines: list[str] = source.splitlines()
if lineno > len(lines):
return ''
return lines[lineno - 1]
def _LocateToken(msg: str, line_text: str) -> int:
"""定位原生消息里 token 原文在该行的 1-based 列;找不到退回首个非空白列。"""
if not line_text:
return 0
pos: int = -1
matched = _SYNTAX_TOKEN_RE.search(msg)
if matched is not None:
tok_text: str = matched.group(1) if matched.group(1) is not None else matched.group(2)
if tok_text.strip():
pos = line_text.find(tok_text)
if pos < 0:
pos = len(line_text) - len(line_text.lstrip())
return pos + 1
def _HumanizeSyntaxError(exc: SyntaxError, source: str, filename: str) -> SyntaxError:
"""把解析器的 SyntaxError 收口成「行:列 + 源码片段 + 原始消息」的单行报文。
重新抛出的语法错误带 filename/lineno/offset/text 四要素,故上层即使用
traceback.format_exc 也只打印该文件对应行与 caret,不再漏出解析器内部调用链。
"""
msg: str = str(exc)
lineno: int = getattr(exc, 'lineno', 0) or 0
if lineno <= 0:
found = _SYNTAX_LINE_RE.search(msg)
if found is not None:
lineno = int(found.group(1))
line_text: str = getattr(exc, 'text', '') or _SourceLine(source, lineno)
col: int = getattr(exc, 'offset', 0) or 0
if col <= 0:
col = _LocateToken(msg, line_text)
new: SyntaxError = SyntaxError(
f"[TPC] 语法错误({msg}):行 {lineno} 列 {col},源码片段 {line_text.strip()!r}。")
new.filename = filename
new.lineno = lineno
new.offset = col
new.text = line_text
return new
def parse(source: str, filename: str = '<unknown>', mode: str = 'exec', *,
type_comments: bool = False, feature_version: tuple[int, int] | int | None = None) -> AST:
"""解析源码字符串为 AST(与 ast.parse 同签名、同结构)。
优先使用 C 加速解析器 vast_c(输出结构完全一致),异常时回退纯 Python vast
并打印可诊断信息;环境变量 VAST_USE_C:'0' 禁用 C 路径,'strict' 时 C 路径
异常直接抛出。两条路径的 SyntaxError 都收口成带 文件/行/列/源码片段 的统一
报文(不再向用户漏出解析器内部 traceback)。
"""
if _VAST_C_PARSE is not None and _os.environ.get('VAST_USE_C', '1') != '0':
try:
return _VAST_C_PARSE(source, mode)
except SyntaxError as _exc:
if _os.environ.get('VAST_USE_C') == 'strict':
raise _HumanizeSyntaxError(_exc, source, filename) from None
_ReportCFallback(filename, _exc)
except Exception as _exc:
if _os.environ.get('VAST_USE_C') == 'strict':
raise
_ReportCFallback(filename, _exc)
try:
return _ParsePure(source, mode)
except SyntaxError as _exc:
raise _HumanizeSyntaxError(_exc, source, filename) from None
__all__: list[str] = [
'AST', 'parse', 'dump', 'walk', 'iter_fields', 'iter_child_nodes',
'unparse', 'copy_location', 'fix_missing_locations',
'get_source_segment', 'literal_eval', 'SOFT_KEYWORDS',
] + list(_NODE_DEFS.keys())