Files
TransPyC/lib/vast/__init__.py
T
TermiNexus 660a15bef8 refactor(tpc): 删除类型联合语法并将修饰迁移为 AST 字段
类型注解不再接受任何 T1 | T2;const/volatile/static/export/extern/inline/define/typedef/__attribute__/BigEndian/LittleEndian/Bit 改为注解节点字段 Markers/Attributes/ByteOrder/BitWidth;指针与引用改为 CPtr[T]/CRef[T]/CTempRef[T]/CArrayPtr[T]/CAutoPtr[T];Python 与 vast_c 两套解析器、类型解析与声明端同步;C 扩展早于 vast_c.c 时自动回退纯 Python 解析器。
2026-10-18 13:35:34 +08:00

196 lines
8.0 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
_VAST_C_STALE = False
try:
import glob as _glob
_vast_c_dir: str = _os.path.join(_os.path.dirname(_os.path.abspath(__file__)), '..', 'vast_c')
_vast_c_src: str = _os.path.join(_vast_c_dir, 'vast_c.c')
_vast_c_pyd: list[str] = _glob.glob(_os.path.join(_vast_c_dir, '_parser*.pyd'))
if _vast_c_pyd and _os.path.exists(_vast_c_src):
if max(_os.path.getmtime(_f) for _f in _vast_c_pyd) < _os.path.getmtime(_vast_c_src):
_VAST_C_STALE = True
except Exception:
_VAST_C_STALE = False
if not _VAST_C_STALE:
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
# Viper `new` 生命周期软关键字表达式:vast_c(stage 0 子集)不认它,会把 `new` 当普通名
# 产出 Name 节点。命中时强制走纯 Python 解析(语义基准),避免 C 路径产出错误 AST。
_NEW_EXPR_RE: _re.Pattern[str] = _re.compile(r'(?<![\w.])new[ \t]+[A-Za-z_]')
def _SourceUsesNewExpr(source: str) -> bool:
"""源码里是否出现 `new <普通名>` 形态(用于规避 C 解析器对 new 的误解)。"""
return _NEW_EXPR_RE.search(source) is not None
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'
and not _SourceUsesNewExpr(source)):
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())