- GenApi:新增 compute_life_anno / _RetKindFromTag 与 RET_KIND_* 编码, 提供 get_ret_kind / get_ret_kind_method / lookup_ret_kind_via_vpi; _WillFreeMergeTag 不再拦截 ret(改由 compute_life_anno 收集校验)。 - StructGenMixin:EscDirTable 由 3 槽扩到 4 槽(新增 ret_kind 列), compute_escape_summary 把契约落在 box[5] 并登记。 - Stub:.vpi 行尾新增 escr=<ret_kind>(Converter 写、VpiReader 读 _g_vpi_esd_r)。 - AssignGenMixin.life_of_expr(Call):@life(ret(...)) 声明优先于任何推断; frame/heap/static/borrows=none 直接映射,borrows=x 取对应实参生命期, 方法调用按接收者静态类型解析类名。 - StmtGenCifMixin:同批移除定义端 return <本帧地址> 的 fail-fast, 返回值去向改由调用点按被调者契约判定。
1014 lines
56 KiB
Plaintext
1014 lines
56 KiB
Plaintext
import lib.core.Gen.GenApi as GenApi
|
||
import lib.core.VLogger as VLogger
|
||
import t, c
|
||
from stdint import *
|
||
import ast
|
||
import string
|
||
import memhub
|
||
import viperlib
|
||
import stdlib
|
||
import sys
|
||
|
||
# ============================================================
|
||
# StubGen.Converter - .vpi stub file generator
|
||
|
||
|
||
# _eval_binop_int / _eval_binop_valid - recursively evaluate BinOp constant values.
|
||
# Handles BitOr/BitAnd/Add/Sub and nested Name refs to registered CDefine constants.
|
||
def _eval_binop_valid(node: ast.AST*) -> int:
|
||
"""Return 1 if a BinOp node is fully computable from constants or registered CDefines"""
|
||
if node is None: return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Constant:
|
||
cn: ast.Constant* = ast.Constant*(node)
|
||
if cn.const_kind == ast.CONST_INT: return 1
|
||
return 0
|
||
if k == ast.ASTKind.UnaryOp:
|
||
uop: ast.UnaryOp* = ast.UnaryOp*(node)
|
||
if uop.operand is not None: return _eval_binop_valid(uop.operand)
|
||
return 0
|
||
if k == ast.ASTKind.BinOp:
|
||
bop: ast.BinOp* = ast.BinOp*(node)
|
||
if bop.left is not None and bop.right is not None:
|
||
if _eval_binop_valid(bop.left) != 0:
|
||
if _eval_binop_valid(bop.right) != 0: return 1
|
||
return 0
|
||
if k == ast.ASTKind.Name:
|
||
# Name reference: check whether it is a registered CDefine constant
|
||
nm: ast.Name* = ast.Name*(node)
|
||
if nm.id is not None:
|
||
# Look up in the local CDefine table
|
||
val: int = _lookup_cdefine_for_pyi(nm.id)
|
||
if _pyi_cdefine_found() != 0: return 1
|
||
return 0
|
||
return 0
|
||
|
||
|
||
def _eval_binop_int(node: ast.AST*) -> int64:
|
||
"""Recursively evaluate a BinOp node as a 64-bit integer value"""
|
||
if node is None: return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Constant:
|
||
cn: ast.Constant* = ast.Constant*(node)
|
||
if cn.const_kind == ast.CONST_INT: return cn.int_val
|
||
return 0
|
||
if k == ast.ASTKind.UnaryOp:
|
||
uop: ast.UnaryOp* = ast.UnaryOp*(node)
|
||
if uop.op == ast.OpKind.USub and uop.operand is not None: return -_eval_binop_int(uop.operand)
|
||
if uop.op == ast.OpKind.UAdd and uop.operand is not None: return _eval_binop_int(uop.operand)
|
||
if uop.op == ast.OpKind.Invert and uop.operand is not None: return ~_eval_binop_int(uop.operand)
|
||
return 0
|
||
if k == ast.ASTKind.BinOp:
|
||
bop: ast.BinOp* = ast.BinOp*(node)
|
||
if bop.left is None or bop.right is None: return 0
|
||
lv: int64 = _eval_binop_int(bop.left)
|
||
rv: int64 = _eval_binop_int(bop.right)
|
||
if bop.op == ast.OpKind.BitOr: return lv | rv
|
||
if bop.op == ast.OpKind.BitAnd: return lv & rv
|
||
if bop.op == ast.OpKind.BitXor: return lv ^ rv
|
||
if bop.op == ast.OpKind.Add: return lv + rv
|
||
if bop.op == ast.OpKind.Sub: return lv - rv
|
||
if bop.op == ast.OpKind.Mult: return lv * rv
|
||
if bop.op == ast.OpKind.Div: return lv // rv
|
||
if bop.op == ast.OpKind.FloorDiv: return lv // rv
|
||
if bop.op == ast.OpKind.Mod: return lv % rv
|
||
if bop.op == ast.OpKind.LShift: return lv << rv
|
||
if bop.op == ast.OpKind.RShift: return lv >> rv
|
||
return 0
|
||
if k == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(node)
|
||
if nm.id is not None: return _lookup_cdefine_for_pyi(nm.id)
|
||
return 0
|
||
return 0
|
||
|
||
|
||
# Forward declaration for CDefine lookup (real impl in HandlesType);
|
||
# weak refs avoid circular dependencies.
|
||
_g_pyi_cdefine_found: int = 0
|
||
|
||
# Per-file CDefine value cache (vpi only): vpi runs before the translate phase, so
|
||
# _GeneratePyiFromAst registers in order; C arrays dodge list.get misdispatch.
|
||
_g_pyi_cdef_names: str* = None
|
||
_g_pyi_cdef_vals: int64* = None
|
||
_g_pyi_cdef_count: int = 0
|
||
|
||
|
||
def _lookup_cdefine_for_pyi(name: str) -> int64:
|
||
"""Look up a constant value from this file's CDefine cache or the local CDefine table"""
|
||
global _g_pyi_cdefine_found
|
||
# Look up this file's CDefine cache first (globals are empty during vpi gen)
|
||
if _g_pyi_cdef_names is not None and _g_pyi_cdef_vals is not None:
|
||
# Bound loop by _g_pyi_cdef_count; never len()/.get on i8** (len falls back
|
||
# to strlen, .get misdispatches to HashTable.get) -- index via t.CPtr(base + i*8).
|
||
pn: sizet = sizet(_g_pyi_cdef_count)
|
||
for pi in range(pn):
|
||
entry_pc: str* = (str*)(uint64(_g_pyi_cdef_names) + pi * 8)
|
||
pnm: str = entry_pc[0]
|
||
if pnm is not None and string.strcmp(pnm, name) == 0:
|
||
_g_pyi_cdefine_found = 1
|
||
return _g_pyi_cdef_vals[pi]
|
||
val: int64 = GenApi.lookup_cdefine_constant(name)
|
||
if GenApi.is_cdefine_found() != 0:
|
||
_g_pyi_cdefine_found = 1
|
||
return val
|
||
_g_pyi_cdefine_found = 0
|
||
return 0
|
||
|
||
|
||
def _eval_cdefine_value_cache(node: ast.AST*) -> int64:
|
||
"""Evaluate a CDefine value for preprocessing registration (Constant/UnaryOp/BinOp/Call/Name)"""
|
||
if node is None: return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Constant:
|
||
cn_c: ast.Constant* = ast.Constant*(node)
|
||
if cn_c.const_kind == ast.CONST_INT: return cn_c.int_val
|
||
if cn_c.const_kind == ast.CONST_BOOL: return cn_c.int_val
|
||
# STR/FLOAT/NONE do not participate in integer BinOp refs (registering 0 is harmless)
|
||
return 0
|
||
if k == ast.ASTKind.UnaryOp: return _eval_binop_int(node)
|
||
if k == ast.ASTKind.BinOp: return _eval_binop_int(node)
|
||
if k == ast.ASTKind.Call:
|
||
cl_c: ast.Call* = ast.Call*(node)
|
||
if cl_c.args is not None and len(cl_c.args) > 0: return _eval_cdefine_value_cache(cl_c.args.get(0))
|
||
return 0
|
||
if k == ast.ASTKind.Name:
|
||
nm_c: ast.Name* = ast.Name*(node)
|
||
if nm_c.id is not None: return _lookup_cdefine_for_pyi(nm_c.id)
|
||
return 0
|
||
return 0
|
||
|
||
|
||
def _pyi_cdefine_found() -> int:
|
||
"""Return the result status of the last _lookup_cdefine_for_pyi call"""
|
||
return _g_pyi_cdefine_found
|
||
|
||
|
||
# ============================================================
|
||
# _PyiEmit - append text to buf at pos, returning the new pos (truncates on overflow)
|
||
def _PyiEmit(buf: bytes, buf_size: sizet, pos: sizet, text: str) -> sizet:
|
||
"""Append text to buf at pos and return the new pos"""
|
||
if buf is None or text is None or pos >= buf_size: return pos
|
||
tlen: sizet = string.strlen(text)
|
||
if tlen == 0: return pos
|
||
remain: sizet = buf_size - pos
|
||
if tlen >= remain: tlen = remain - 1
|
||
if tlen > 0: string.memcpy(buf + pos, text, tlen)
|
||
return pos + tlen
|
||
|
||
|
||
# ============================================================
|
||
# _PyiIndent - append level*4 spaces to buf at pos
|
||
def _PyiIndent(buf: bytes, buf_size: sizet, pos: sizet, level: int) -> sizet:
|
||
"""Append level*4 spaces to buf at pos and return the new pos"""
|
||
for i in range(level):
|
||
pos = _PyiEmit(buf, buf_size, pos, " ")
|
||
return pos
|
||
|
||
|
||
# _PyiBinOpOpText - emit the " OP " text for a binary operator.
|
||
# Kept byte-identical to TPC's BinOp operator table; unknown ops fail fast.
|
||
def _PyiBinOpOpText(buf: bytes, buf_size: sizet, pos: sizet, op: int, node: ast.AST*) -> sizet:
|
||
"""Emit the " OP " text for a binary operator; unknown operators fail fast"""
|
||
if op == ast.OpKind.BitOr: return _PyiEmit(buf, buf_size, pos, " | ")
|
||
if op == ast.OpKind.Add: return _PyiEmit(buf, buf_size, pos, " + ")
|
||
if op == ast.OpKind.Sub: return _PyiEmit(buf, buf_size, pos, " - ")
|
||
if op == ast.OpKind.Mult: return _PyiEmit(buf, buf_size, pos, " * ")
|
||
if op == ast.OpKind.Div: return _PyiEmit(buf, buf_size, pos, " / ")
|
||
if op == ast.OpKind.Mod: return _PyiEmit(buf, buf_size, pos, " % ")
|
||
if op == ast.OpKind.BitAnd: return _PyiEmit(buf, buf_size, pos, " & ")
|
||
if op == ast.OpKind.RShift: return _PyiEmit(buf, buf_size, pos, " >> ")
|
||
GenApi.fatal_error(node, "unknown vpi type-annotation node type", "TypeError")
|
||
return pos # unreachable (fatal_error already exited)
|
||
|
||
|
||
# _PyiTypeStr - recursively render an AST annotation node into buf at pos.
|
||
# Support set matches TPC; TPV additionally accepts a bare Tuple.
|
||
def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, annot: ast.AST*, depth: int = 0) -> sizet:
|
||
"""Recursively render a type annotation node into buf and return the new pos"""
|
||
# Recursion guard: a self-referential AST would recurse until stack exhaustion.
|
||
# Fail fast instead of silently falling back.
|
||
if depth > 64:
|
||
VLogger.error("vpi type annotation recursion too deep (AST cycle or nesting over 64 levels)", "StubGen")
|
||
sys.exit(1)
|
||
if annot is None: return _PyiEmit(buf, buf_size, pos, "int")
|
||
|
||
k: int = annot.kind()
|
||
|
||
# Name: simple type name (e.g. int, str, MyClass)
|
||
if k == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(annot)
|
||
if nm.id is not None: return _PyiEmit(buf, buf_size, pos, nm.id)
|
||
return _PyiEmit(buf, buf_size, pos, "int")
|
||
|
||
# Attribute: attribute reference (e.g. int, memhub.MemBuddy)
|
||
if k == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(annot)
|
||
pos = _PyiTypeStr(buf, buf_size, pos, at.value, depth + 1)
|
||
pos = _PyiEmit(buf, buf_size, pos, ".")
|
||
if at.attr is not None: pos = _PyiEmit(buf, buf_size, pos, at.attr)
|
||
return pos
|
||
|
||
# Subscript: subscripted type (e.g. list[int], t.CArray[elem, count])
|
||
if k == ast.ASTKind.Subscript:
|
||
sub: ast.Subscript* = ast.Subscript*(annot)
|
||
pos = _PyiTypeStr(buf, buf_size, pos, sub.value, depth + 1)
|
||
pos = _PyiEmit(buf, buf_size, pos, "[")
|
||
pos = _PyiTypeStr(buf, buf_size, pos, sub.slice, depth + 1)
|
||
pos = _PyiEmit(buf, buf_size, pos, "]")
|
||
return pos
|
||
|
||
# BinOp: union types and CArray count expressions (MAX_ORDER + 1,
|
||
# 2 * ZHUFF_MAX_CODES); operator text via _PyiBinOpOpText, byte-identical to TPC.
|
||
if k == ast.ASTKind.BinOp:
|
||
bop: ast.BinOp* = ast.BinOp*(annot)
|
||
pos = _PyiTypeStr(buf, buf_size, pos, bop.left, depth + 1)
|
||
pos = _PyiBinOpOpText(buf, buf_size, pos, bop.op, annot)
|
||
pos = _PyiTypeStr(buf, buf_size, pos, bop.right, depth + 1)
|
||
return pos
|
||
|
||
# UnaryOp: count expressions (-1 / ~0) and macro args (not ct.isptr(x)).
|
||
# Not/Invert/USub match TPC; other unary ops fail fast.
|
||
if k == ast.ASTKind.UnaryOp:
|
||
uop_t: ast.UnaryOp* = ast.UnaryOp*(annot)
|
||
if uop_t.op == ast.OpKind.Not: pos = _PyiEmit(buf, buf_size, pos, "not ")
|
||
elif uop_t.op == ast.OpKind.Invert: pos = _PyiEmit(buf, buf_size, pos, "~")
|
||
elif uop_t.op == ast.OpKind.USub: pos = _PyiEmit(buf, buf_size, pos, "-")
|
||
else:
|
||
GenApi.fatal_error(annot, "unknown vpi type-annotation node type", "TypeError")
|
||
return pos # unreachable (fatal_error already exited)
|
||
return _PyiTypeStr(buf, buf_size, pos, uop_t.operand, depth + 1)
|
||
|
||
# Compare: comparison expressions (MAX_SIZE > 512 / val == 1).
|
||
# == != < <= > >= match TPC; unknown ops emit " ? ".
|
||
if k == ast.ASTKind.Compare:
|
||
cmp_t: ast.Compare* = ast.Compare*(annot)
|
||
pos = _PyiTypeStr(buf, buf_size, pos, cmp_t.left, depth + 1)
|
||
ops_ct: list[int]* = (list[int]*)(cmp_t.ops)
|
||
cmps_ct: list[ast.AST*]* = cmp_t.comparators
|
||
if ops_ct is not None:
|
||
on_ct: sizet = len(ops_ct)
|
||
for oi_ct in range(on_ct):
|
||
oid_ct: int = ops_ct.get(oi_ct)
|
||
if oid_ct == ast.OpKind.Eq: pos = _PyiEmit(buf, buf_size, pos, " == ")
|
||
elif oid_ct == ast.OpKind.Ne: pos = _PyiEmit(buf, buf_size, pos, " != ")
|
||
elif oid_ct == ast.OpKind.Lt: pos = _PyiEmit(buf, buf_size, pos, " < ")
|
||
elif oid_ct == ast.OpKind.Le: pos = _PyiEmit(buf, buf_size, pos, " <= ")
|
||
elif oid_ct == ast.OpKind.Gt: pos = _PyiEmit(buf, buf_size, pos, " > ")
|
||
elif oid_ct == ast.OpKind.Ge: pos = _PyiEmit(buf, buf_size, pos, " >= ")
|
||
else: pos = _PyiEmit(buf, buf_size, pos, " ? ")
|
||
# TPC indexes comparators[i] by ops; guard the length here so a
|
||
# malformed AST (unequal ops/comparators) cannot overrun.
|
||
if cmps_ct is not None and oi_ct < len(cmps_ct): pos = _PyiTypeStr(buf, buf_size, pos, cmps_ct.get(oi_ct), depth + 1)
|
||
return pos
|
||
|
||
# BoolOp: boolean ops (ENABLED and MAX_SIZE > 0 / A or B).
|
||
# Matches TPC: and -> " and ", or -> " or ".
|
||
if k == ast.ASTKind.BoolOp:
|
||
bool_t: ast.BoolOp* = ast.BoolOp*(annot)
|
||
vals_bt: list[ast.AST*]* = bool_t.values
|
||
if vals_bt is not None:
|
||
vn_bt: sizet = len(vals_bt)
|
||
for vi_bt in range(vn_bt):
|
||
if vi_bt > 0:
|
||
if bool_t.op == ast.OpKind.Or: pos = _PyiEmit(buf, buf_size, pos, " or ")
|
||
else: pos = _PyiEmit(buf, buf_size, pos, " and ")
|
||
pos = _PyiTypeStr(buf, buf_size, pos, vals_bt.get(vi_bt), depth + 1)
|
||
return pos
|
||
|
||
# Constant: string forward refs (e.g. 'MyClass'), None or numeric constants
|
||
if k == ast.ASTKind.Constant:
|
||
cn: ast.Constant* = ast.Constant*(annot)
|
||
if cn.const_kind == ast.CONST_STR:
|
||
pos = _PyiEmit(buf, buf_size, pos, "'")
|
||
if cn.str_val is not None: pos = _PyiEmit(buf, buf_size, pos, cn.str_val)
|
||
pos = _PyiEmit(buf, buf_size, pos, "'")
|
||
return pos
|
||
if cn.const_kind == ast.CONST_NONE: return _PyiEmit(buf, buf_size, pos, "None")
|
||
# Numeric constants (e.g. the count in t.CArray[uint32, 64]): emit the value
|
||
# itself; falling back to int makes stub loading raise TypeError.
|
||
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_CHAR:
|
||
num_buf_ct: str = stdlib.malloc(24)
|
||
if num_buf_ct is not None:
|
||
viperlib.snprintf(num_buf_ct, 24, "%d", cn.int_val)
|
||
pos = _PyiEmit(buf, buf_size, pos, num_buf_ct)
|
||
stdlib.free(num_buf_ct)
|
||
return pos
|
||
return _PyiEmit(buf, buf_size, pos, "int")
|
||
|
||
# List: function pointer parameter table (e.g. the inner [t.CPtr, ULONG] of
|
||
# t.Callable[[t.CPtr, ULONG], BOOL]); must emit "[a, b]" or it fails fast.
|
||
if k == ast.ASTKind.List:
|
||
lst: ast.List* = ast.List*(annot)
|
||
pos = _PyiEmit(buf, buf_size, pos, "[")
|
||
if lst.elts is not None:
|
||
lst_elts: list[ast.AST*]* = lst.elts
|
||
ln: sizet = len(lst_elts)
|
||
for li in range(ln):
|
||
if li > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||
le: ast.AST* = lst_elts.get(li)
|
||
pos = _PyiTypeStr(buf, buf_size, pos, le, depth + 1)
|
||
pos = _PyiEmit(buf, buf_size, pos, "]")
|
||
return pos
|
||
|
||
# Tuple: multi-argument subscript (e.g. Dict[str, int])
|
||
if k == ast.ASTKind.Tuple:
|
||
tup: ast.Tuple* = ast.Tuple*(annot)
|
||
if tup.elts is not None:
|
||
tup_elts: list[ast.AST*]* = tup.elts
|
||
tn: sizet = len(tup_elts)
|
||
for ti in range(tn):
|
||
if ti > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||
el: ast.AST* = tup_elts.get(ti)
|
||
pos = _PyiTypeStr(buf, buf_size, pos, el, depth + 1)
|
||
return pos
|
||
|
||
# Call: call expression (used by decorators such as @t.NoVTable())
|
||
if k == ast.ASTKind.Call:
|
||
cl: ast.Call* = ast.Call*(annot)
|
||
pos = _PyiTypeStr(buf, buf_size, pos, cl.func, depth + 1)
|
||
pos = _PyiEmit(buf, buf_size, pos, "(")
|
||
if cl.args is not None:
|
||
cl_args: list[ast.AST*]* = cl.args
|
||
can: sizet = len(cl_args)
|
||
for cai in range(can):
|
||
if cai > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||
ca: ast.AST* = cl_args.get(cai)
|
||
pos = _PyiTypeStr(buf, buf_size, pos, ca, depth + 1)
|
||
pos = _PyiEmit(buf, buf_size, pos, ")")
|
||
return pos
|
||
|
||
# Unknown node type: fail fast rather than silently writing "int" and producing a wrong signature.
|
||
GenApi.fatal_error(annot, "unknown vpi type-annotation node type", "TypeError")
|
||
return _PyiEmit(buf, buf_size, pos, "int") # unreachable (fatal_error already exited)
|
||
|
||
|
||
# _PyiDefaultStr - render a default-value expression for '= <default>' in vpi.
|
||
# Stubs must keep defaults so VpiReader can register the default-parameter table.
|
||
def _PyiDefaultStr(buf: bytes, buf_size: sizet, pos: sizet, node: ast.AST*, depth: int = 0) -> sizet:
|
||
"""Render a default-value expression into buf and return the new pos"""
|
||
# Recursion guard: fail fast on a self-referential AST (same as _PyiTypeStr)
|
||
if depth > 64:
|
||
VLogger.error("vpi default-value expression recursion too deep (AST cycle or nesting over 64 levels)", "StubGen")
|
||
sys.exit(1)
|
||
if node is None: return pos
|
||
|
||
k: int = node.kind()
|
||
# Constant: emit numeric/string/bool/None literals directly
|
||
if k == ast.ASTKind.Constant:
|
||
cn: ast.Constant* = ast.Constant*(node)
|
||
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_CHAR:
|
||
num_buf_dv: str = stdlib.malloc(24)
|
||
if num_buf_dv is not None:
|
||
viperlib.snprintf(num_buf_dv, 24, "%d", cn.int_val)
|
||
pos = _PyiEmit(buf, buf_size, pos, num_buf_dv)
|
||
stdlib.free(num_buf_dv)
|
||
return pos
|
||
if cn.const_kind == ast.CONST_STR:
|
||
pos = _PyiEmit(buf, buf_size, pos, "\"")
|
||
if cn.str_val is not None: pos = _PyiEmit(buf, buf_size, pos, cn.str_val)
|
||
return _PyiEmit(buf, buf_size, pos, "\"")
|
||
if cn.const_kind == ast.CONST_BOOL:
|
||
if cn.int_val != 0: return _PyiEmit(buf, buf_size, pos, "True")
|
||
return _PyiEmit(buf, buf_size, pos, "False")
|
||
if cn.const_kind == ast.CONST_NONE: return _PyiEmit(buf, buf_size, pos, "None")
|
||
return pos
|
||
|
||
# Name: CDefine constant name (e.g. share: ULONG = SHARE_READ)
|
||
if k == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(node)
|
||
if nm.id is not None: return _PyiEmit(buf, buf_size, pos, nm.id)
|
||
return pos
|
||
|
||
# UnaryOp: unary operation (e.g. -1, ~0)
|
||
if k == ast.ASTKind.UnaryOp:
|
||
uop: ast.UnaryOp* = ast.UnaryOp*(node)
|
||
if uop.op == ast.OpKind.USub: pos = _PyiEmit(buf, buf_size, pos, "-")
|
||
elif uop.op == ast.OpKind.UAdd: pos = _PyiEmit(buf, buf_size, pos, "+")
|
||
elif uop.op == ast.OpKind.Invert: pos = _PyiEmit(buf, buf_size, pos, "~")
|
||
else: return pos
|
||
if uop.operand is not None: return _PyiDefaultStr(buf, buf_size, pos, uop.operand, depth + 1)
|
||
return pos
|
||
|
||
# Other complex expressions (BinOp etc.): emit nothing; caller enforces fail-fast
|
||
return pos
|
||
|
||
|
||
# _PyiRejectCReturnDecorator - fail if @c.CReturn appears in the decorator list.
|
||
# The decorator was removed; a stale stub must be regenerated. 0 = none found.
|
||
def _PyiRejectCReturnDecorator(node: ast.AST*, decorators: list[ast.AST*]*) -> int:
|
||
"""Fail fast on a @c.CReturn decorator (returns 0 when absent)"""
|
||
if decorators is None: return 0
|
||
dn: sizet = len(decorators)
|
||
di: sizet = 0
|
||
while di < dn:
|
||
dnode: ast.AST* = decorators.get(di)
|
||
di += 1
|
||
if dnode is None: continue
|
||
# Handles both @c.CReturn(...) (Call with func=Attribute) and bare @c.CReturn
|
||
dattr: ast.Attribute* = None
|
||
if dnode.kind() == ast.ASTKind.Call:
|
||
dcall: ast.Call* = ast.Call*(dnode)
|
||
if dcall.func is not None and dcall.func.kind() == ast.ASTKind.Attribute: dattr = ast.Attribute*(dcall.func)
|
||
elif dnode.kind() == ast.ASTKind.Attribute: dattr = ast.Attribute*(dnode)
|
||
if dattr is None or dattr.attr is None: continue
|
||
if string.strcmp(dattr.attr, "CReturn") == 0:
|
||
GenApi.fatal_error(node, "vpi still contains `@c.CReturn` (removed): regenerate the .vpi using a return annotation `-> tuple[ty1, ty2, ...]` (you may add --clean)", "TypeError")
|
||
return 1
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# 逃逸摘要计算用的复用临时区(74 槽:gset[0]=global 计数、gset[1..65)=名哈希、box=gset+65)。
|
||
# 只分配一次,避免每个 def 都分配;布局与 StructGen 的 EscScratch 完全一致。
|
||
_g_pyi_esc_scratch: sizet* = None
|
||
|
||
|
||
def _PyiEnsureEscScratch() -> int:
|
||
"""惰性分配摘要临时区;1=可用 / 0=失败"""
|
||
# 必须声明 global:否则赋值会落到**局部**,调用方读到的仍是 NULL 全局,
|
||
# box 指针算成 0+520 → ASan: WRITE on 0x208(本文件它处同款写法见 _g_pyi_cdefine_found)。
|
||
global _g_pyi_esc_scratch
|
||
if _g_pyi_esc_scratch is not None: return 1
|
||
_g_pyi_esc_scratch = sizet*(stdlib.malloc(74 * 8))
|
||
if _g_pyi_esc_scratch is None: return 0
|
||
string.memset(_g_pyi_esc_scratch, 0, 74 * 8)
|
||
return 1
|
||
|
||
|
||
# _PyiFunction - emit a function signature (def name(...) -> Ret: pass)
|
||
def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*, indent_level: int, class_name: str) -> sizet:
|
||
"""Emit a function signature into buf and return the new pos"""
|
||
# Decorators
|
||
if fn.decorator_list is not None:
|
||
fn_decorator_list: list[ast.AST*]* = fn.decorator_list
|
||
dn: sizet = len(fn_decorator_list)
|
||
for di in range(dn):
|
||
dec: ast.AST* = fn_decorator_list.get(di)
|
||
if dec is not None:
|
||
pos = _PyiIndent(buf, buf_size, pos, indent_level)
|
||
pos = _PyiEmit(buf, buf_size, pos, "@")
|
||
pos = _PyiTypeStr(buf, buf_size, pos, dec)
|
||
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||
|
||
pos = _PyiIndent(buf, buf_size, pos, indent_level)
|
||
pos = _PyiEmit(buf, buf_size, pos, "def ")
|
||
if fn.name is not None: pos = _PyiEmit(buf, buf_size, pos, fn.name)
|
||
pos = _PyiEmit(buf, buf_size, pos, "(")
|
||
|
||
# Parameter list
|
||
args_node: ast.AST* = fn.args
|
||
if args_node is not None:
|
||
args: ast.Arguments* = ast.Arguments*(args_node)
|
||
first: int = 1
|
||
|
||
# Positional parameters
|
||
if args.args is not None:
|
||
args_args: list[ast.AST*]* = args.args
|
||
an: sizet = len(args_args)
|
||
# Defaults right-align to args tail: defaults[i] maps to args[an - dn + i];
|
||
# emit '= <default>' and declare arg_defaults inline to avoid .get misdispatch.
|
||
arg_defaults: list[ast.AST*]* = args.defaults
|
||
dn: sizet = 0
|
||
if arg_defaults is not None: dn = len(arg_defaults)
|
||
ai: sizet = 0
|
||
while ai < an:
|
||
if first == 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||
first = 0
|
||
ag: ast.Arg* = ast.Arg*(args_args.get(ai))
|
||
if ag.arg is not None: pos = _PyiEmit(buf, buf_size, pos, ag.arg)
|
||
if ag.annotation is not None:
|
||
pos = _PyiEmit(buf, buf_size, pos, ": ")
|
||
pos = _PyiTypeStr(buf, buf_size, pos, ag.annotation)
|
||
if arg_defaults is not None and an - dn <= ai:
|
||
di: sizet = ai - (an - dn)
|
||
dnode: ast.AST* = arg_defaults.get(di)
|
||
if dnode is not None:
|
||
pos = _PyiEmit(buf, buf_size, pos, " = ")
|
||
pos = _PyiDefaultStr(buf, buf_size, pos, dnode)
|
||
ai += 1
|
||
|
||
# *args
|
||
if args.vararg is not None:
|
||
if first == 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||
first = 0
|
||
pos = _PyiEmit(buf, buf_size, pos, "*")
|
||
va: ast.Arg* = ast.Arg*(args.vararg)
|
||
if va.arg is not None: pos = _PyiEmit(buf, buf_size, pos, va.arg)
|
||
|
||
# **kwargs
|
||
if args.kwarg is not None:
|
||
if first == 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||
first = 0
|
||
pos = _PyiEmit(buf, buf_size, pos, "**")
|
||
kw: ast.Arg* = ast.Arg*(args.kwarg)
|
||
if kw.arg is not None: pos = _PyiEmit(buf, buf_size, pos, kw.arg)
|
||
|
||
pos = _PyiEmit(buf, buf_size, pos, ")")
|
||
|
||
# Return type
|
||
if fn.returns is not None:
|
||
pos = _PyiEmit(buf, buf_size, pos, " -> ")
|
||
pos = _PyiTypeStr(buf, buf_size, pos, fn.returns)
|
||
else:
|
||
# Without a `->` annotation, @c.CReturn is removed: fail fast to regenerate vpi
|
||
_PyiRejectCReturnDecorator(fn, fn.decorator_list)
|
||
|
||
pos = _PyiEmit(buf, buf_size, pos, ": pass")
|
||
# v3 跨模块逃逸摘要:把形参逃逸掩码与返回借用位写成**同一行**的行尾注释,供 VpiReader
|
||
# 解析后在别的模块的调用点查询(跨模块摘要表按文件重置,只能靠 vpi 传递)。
|
||
# 只在真有逃逸/返回借用时写,保持 vpi 干净:无标记 = 无摘要 = 调用点保守放行。
|
||
# P2 willFree:同行走 `# ... wf=<mask>`(第 k 位=第 k 个形参在本调用后失效),
|
||
# 纯声明式、不依赖逃逸扫描的 scratch,所以单独算、独立决定是否落盘。
|
||
wf_mask_c: int = GenApi.compute_willfree_mask(fn)
|
||
esc_v_c: int = 0
|
||
ret_v_c: int = 0
|
||
ef_v_c: int = 0
|
||
ne_v_c: int = 0
|
||
rk_v_c: int = 0
|
||
if _PyiEnsureEscScratch() != 0:
|
||
esc_box_c: sizet* = sizet*(uint64(_g_pyi_esc_scratch) + 520)
|
||
if GenApi.compute_escape_summary(None, fn, _g_pyi_esc_scratch, esc_box_c) == 0:
|
||
esc_v_c = int(esc_box_c[0])
|
||
ret_v_c = int(esc_box_c[1])
|
||
# 声明式指令(@life(escape(...)) / @life(noescape(...))):compute_escape_summary 把
|
||
# 契约掩码落在 box[3]/box[4],随同行走 esce=/escn= 落盘,跨模块调用点才能最后套用。
|
||
ef_v_c = int(esc_box_c[3])
|
||
ne_v_c = int(esc_box_c[4])
|
||
# A2(§14)`@life(ret(...))`:返回值生命期契约落 box[5],随同行走 escr= 落盘。
|
||
rk_v_c = int(esc_box_c[5])
|
||
# 跨模块常量特化:算"哪些常量绑定能清掉哪些逃逸位"(每条 = 打包的 p|k|m),随同行走 escg= 落盘。
|
||
# 只在真有逃逸位时才有意义;算出来 0 条就不写(保持 vpi 干净)。
|
||
clears_c: sizet* = None
|
||
n_clears_c: int = 0
|
||
if esc_v_c != 0:
|
||
clears_c = sizet*(stdlib.malloc(4 * 8))
|
||
if clears_c is not None:
|
||
n_clears_c = GenApi.compute_escape_clears(fn, clears_c, 4)
|
||
if esc_v_c != 0 or ret_v_c != 0 or wf_mask_c != 0 or n_clears_c > 0 or ef_v_c != 0 or ne_v_c != 0 or rk_v_c != 0:
|
||
tmp_c: bytes = stdlib.malloc(96)
|
||
if tmp_c is not None:
|
||
if esc_v_c != 0 or ret_v_c != 0:
|
||
if wf_mask_c != 0: viperlib.snprintf(tmp_c, 96, " # esc=%d ret=%d wf=%d", esc_v_c, ret_v_c, wf_mask_c)
|
||
else: viperlib.snprintf(tmp_c, 96, " # esc=%d ret=%d", esc_v_c, ret_v_c)
|
||
elif wf_mask_c != 0: viperlib.snprintf(tmp_c, 96, " # wf=%d", wf_mask_c)
|
||
# 只有声明式指令(既无逃逸/借用也无 willFree)时也要落一个 '#' 注释段:
|
||
# 读侧是"见到 '#' 才去找 esce=/escn=",没有 '#' 就整行都不看。
|
||
else: viperlib.snprintf(tmp_c, 96, " # esc=0 ret=0")
|
||
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
|
||
# 跨模块常量特化:每条"常量守卫"落一个 escg= 字段(打包 p|k|m,见
|
||
# StructGenMixin._PackEscapeClear)。同一函数多条 → 重复该字段,读侧按同键全部收集。
|
||
if clears_c is not None:
|
||
ci_c: int = 0
|
||
while ci_c < n_clears_c:
|
||
viperlib.snprintf(tmp_c, 96, " escg=%d", int(clears_c[ci_c]))
|
||
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
|
||
ci_c += 1
|
||
# 声明式指令契约:escape(...) → esce=,noescape(...) → escn=(各自独立字段,
|
||
# 避免和 esc=/ret=/wf= 挤在同一个 96 字节 snprintf 里)。
|
||
if ef_v_c != 0:
|
||
viperlib.snprintf(tmp_c, 96, " esce=%d", ef_v_c)
|
||
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
|
||
if ne_v_c != 0:
|
||
viperlib.snprintf(tmp_c, 96, " escn=%d", ne_v_c)
|
||
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
|
||
# A2(§14):`@life(ret(...))` 的返回值生命期契约 → escr=(编码见 GenApi.RET_KIND_*)。
|
||
if rk_v_c != 0:
|
||
viperlib.snprintf(tmp_c, 96, " escr=%d", rk_v_c)
|
||
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
|
||
stdlib.free(tmp_c)
|
||
if clears_c is not None: stdlib.free(clears_c)
|
||
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||
return pos
|
||
|
||
|
||
# _PyiClass - emit a class definition (fields and method stubs).
|
||
def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, cls: ast.ClassDef*, indent_level: int) -> sizet:
|
||
"""Emit a class definition into buf and return the new pos"""
|
||
# Decorators
|
||
if cls.decorator_list is not None:
|
||
cls_decorator_list: list[ast.AST*]* = cls.decorator_list
|
||
dcn: sizet = len(cls_decorator_list)
|
||
for dci in range(dcn):
|
||
ddec: ast.AST* = cls_decorator_list.get(dci)
|
||
if ddec is not None:
|
||
pos = _PyiIndent(buf, buf_size, pos, indent_level)
|
||
pos = _PyiEmit(buf, buf_size, pos, "@")
|
||
pos = _PyiTypeStr(buf, buf_size, pos, ddec)
|
||
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||
|
||
pos = _PyiIndent(buf, buf_size, pos, indent_level)
|
||
pos = _PyiEmit(buf, buf_size, pos, "class ")
|
||
if cls.name is not None: pos = _PyiEmit(buf, buf_size, pos, cls.name)
|
||
|
||
# PEP 695 type parameters (class Foo[T]:)
|
||
if cls.type_params is not None:
|
||
# ClassDef.type_params is really rawptr[list[str]] (a list of name strings),
|
||
# not list[rawptr[AST]] (that is decorator_list's type); align with the rest of the repo.
|
||
cls_type_params: list[str]* = cls.type_params
|
||
tpn: sizet = len(cls_type_params)
|
||
if tpn > 0:
|
||
pos = _PyiEmit(buf, buf_size, pos, "[")
|
||
for tpi in range(tpn):
|
||
if tpi > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||
tpname: str = cls_type_params.get(tpi)
|
||
if tpname is not None: pos = _PyiEmit(buf, buf_size, pos, tpname)
|
||
pos = _PyiEmit(buf, buf_size, pos, "]")
|
||
|
||
# Base classes
|
||
if cls.bases is not None:
|
||
cls_bases: list[ast.AST*]* = cls.bases
|
||
bn: sizet = len(cls_bases)
|
||
if bn > 0:
|
||
pos = _PyiEmit(buf, buf_size, pos, "(")
|
||
for bi in range(bn):
|
||
if bi > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||
base: ast.AST* = cls_bases.get(bi)
|
||
pos = _PyiTypeStr(buf, buf_size, pos, base)
|
||
pos = _PyiEmit(buf, buf_size, pos, ")")
|
||
|
||
pos = _PyiEmit(buf, buf_size, pos, ":\n")
|
||
|
||
# Class members
|
||
has_member: int = 0
|
||
|
||
if cls.children is not None:
|
||
cls_children: list[ast.AST*]* = cls.children
|
||
cn: sizet = len(cls_children)
|
||
ci: sizet = 0
|
||
while ci < cn:
|
||
stmt: ast.AST* = cls_children.get(ci)
|
||
if stmt is not None:
|
||
sk: int = stmt.kind()
|
||
|
||
# Methods
|
||
if sk == ast.ASTKind.FunctionDef:
|
||
mfn: ast.FunctionDef* = ast.FunctionDef*(stmt)
|
||
# __init__ method: first extract self.x: Type attributes
|
||
if mfn.name is not None and string.strcmp(mfn.name, "__init__") == 0:
|
||
if mfn.children is not None:
|
||
mfn_children: list[ast.AST*]* = mfn.children
|
||
icn: sizet = len(mfn_children)
|
||
ici: sizet = 0
|
||
while ici < icn:
|
||
ibody: ast.AST* = mfn_children.get(ici)
|
||
if ibody is not None and ibody.kind() == ast.ASTKind.AnnAssign:
|
||
iaa: ast.AnnAssign* = ast.AnnAssign*(ibody)
|
||
if iaa.target is not None and iaa.target.kind() == ast.ASTKind.Attribute:
|
||
iat: ast.Attribute* = ast.Attribute*(iaa.target)
|
||
# Check whether target.value is Name("self")
|
||
if iat.value is not None and iat.value.kind() == ast.ASTKind.Name:
|
||
iavn: ast.Name* = ast.Name*(iat.value)
|
||
if iavn.id is None or string.strcmp(iavn.id, "self") != 0:
|
||
ici += 1
|
||
continue
|
||
pos = _PyiIndent(buf, buf_size, pos, indent_level + 1)
|
||
if iat.attr is not None: pos = _PyiEmit(buf, buf_size, pos, iat.attr)
|
||
pos = _PyiEmit(buf, buf_size, pos, ": ")
|
||
pos = _PyiTypeStr(buf, buf_size, pos, iaa.annotation)
|
||
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||
has_member = 1
|
||
ici += 1
|
||
pos = _PyiFunction(buf, buf_size, pos, mfn, indent_level + 1, cls.name)
|
||
has_member = 1
|
||
|
||
# Annotated attributes
|
||
elif sk == ast.ASTKind.AnnAssign:
|
||
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
|
||
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name:
|
||
tn: ast.Name* = ast.Name*(aa.target)
|
||
pos = _PyiIndent(buf, buf_size, pos, indent_level + 1)
|
||
if tn.id is not None: pos = _PyiEmit(buf, buf_size, pos, tn.id)
|
||
pos = _PyiEmit(buf, buf_size, pos, ": ")
|
||
pos = _PyiTypeStr(buf, buf_size, pos, aa.annotation)
|
||
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||
has_member = 1
|
||
|
||
# Nested classes
|
||
elif sk == ast.ASTKind.ClassDef:
|
||
ncls: ast.ClassDef* = ast.ClassDef*(stmt)
|
||
pos = _PyiClass(buf, buf_size, pos, ncls, indent_level + 1)
|
||
has_member = 1
|
||
ci += 1
|
||
|
||
# Emit pass for an empty class
|
||
if has_member == 0:
|
||
pos = _PyiIndent(buf, buf_size, pos, indent_level + 1)
|
||
pos = _PyiEmit(buf, buf_size, pos, "pass\n")
|
||
|
||
return pos
|
||
|
||
|
||
# _GeneratePyiFromAst - walk the AST Module and emit .vpi stub content into buf.
|
||
# Returns bytes written (0 on failure); rel_path should be absolute for error attribution.
|
||
def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf: bytes, buf_size: sizet) -> sizet:
|
||
"""Generate .vpi stub content from an AST into buf"""
|
||
if tree is None or buf is None or buf_size == 0: return 0
|
||
|
||
# Error attribution: point "current file" at the file being stubbed. Callers must pass
|
||
# an absolute path (fatal_error opens it directly). Set once at entry to cover all branches.
|
||
if rel_path is not None: GenApi.set_current_file_type(rel_path)
|
||
|
||
pos: sizet = 0
|
||
|
||
# Pre-pass: register this file's CDefines into the _g_pyi cache in declaration order
|
||
# (vpi runs before the translate phase; without this, BinOp Name refs degrade to 0).
|
||
global _g_pyi_cdef_names, _g_pyi_cdef_vals, _g_pyi_cdef_count
|
||
if tree.children is not None:
|
||
pre_children: list[ast.AST*]* = tree.children
|
||
pcn: sizet = len(pre_children)
|
||
# Pass 1: count t.CDefine declarations (C arrays need a fixed size first)
|
||
_g_pyi_cdef_count = 0
|
||
for pci0 in range(pcn):
|
||
stmt_pc0: ast.AST* = pre_children.get(pci0)
|
||
if stmt_pc0 is not None and stmt_pc0.kind() == ast.ASTKind.AnnAssign:
|
||
aa_pc0: ast.AnnAssign* = ast.AnnAssign*(stmt_pc0)
|
||
if aa_pc0.target is not None and aa_pc0.target.kind() == ast.ASTKind.Name and aa_pc0.annotation is not None:
|
||
tgt_pc0: ast.Name* = ast.Name*(aa_pc0.target)
|
||
if tgt_pc0.id is not None and aa_pc0.annotation.kind() == ast.ASTKind.Attribute:
|
||
ann_pc0: ast.Attribute* = ast.Attribute*(aa_pc0.annotation)
|
||
if ann_pc0.attr is not None and string.strcmp(ann_pc0.attr, "CDefine") == 0: _g_pyi_cdef_count += 1
|
||
if _g_pyi_cdef_count > 0:
|
||
_g_pyi_cdef_names = (str*)(mb.alloc(sizet(_g_pyi_cdef_count) * 8))
|
||
_g_pyi_cdef_vals = (int64*)(mb.alloc(sizet(_g_pyi_cdef_count) * 8))
|
||
# Pass 2: register in declaration order (BinOp Name refs depend on earlier entries)
|
||
pci: sizet = 0
|
||
cdi: int = 0
|
||
for pci in range(pcn):
|
||
stmt_pc: ast.AST* = pre_children.get(pci)
|
||
if stmt_pc is not None and stmt_pc.kind() == ast.ASTKind.AnnAssign:
|
||
aa_pc: ast.AnnAssign* = ast.AnnAssign*(stmt_pc)
|
||
if aa_pc.target is not None and aa_pc.target.kind() == ast.ASTKind.Name and aa_pc.annotation is not None:
|
||
tgt_pc: ast.Name* = ast.Name*(aa_pc.target)
|
||
if tgt_pc.id is not None and aa_pc.annotation.kind() == ast.ASTKind.Attribute:
|
||
ann_pc: ast.Attribute* = ast.Attribute*(aa_pc.annotation)
|
||
if ann_pc.attr is not None and string.strcmp(ann_pc.attr, "CDefine") == 0:
|
||
if _g_pyi_cdef_names is not None and _g_pyi_cdef_vals is not None:
|
||
slot_pc: str* = (str*)(uint64(_g_pyi_cdef_names) + cdi * 8)
|
||
slot_pc[0] = tgt_pc.id
|
||
_g_pyi_cdef_vals[cdi] = _eval_cdefine_value_cache(aa_pc.value)
|
||
cdi += 1
|
||
|
||
# File header
|
||
pos = _PyiEmit(buf, buf_size, pos, "\"\"\"Auto-generated Python stub file\"\"\"\n\n")
|
||
|
||
if tree.children is None:
|
||
if pos < buf_size: buf[pos] = 0
|
||
return pos
|
||
|
||
tree_children: list[ast.AST*]* = tree.children
|
||
n: sizet = len(tree_children)
|
||
|
||
# Pass 1: detect import t/c
|
||
has_import_t: int = 0
|
||
has_import_c: int = 0
|
||
for i in range(n):
|
||
stmt: ast.AST* = tree_children.get(i)
|
||
if stmt is not None and stmt.kind() == ast.ASTKind.Import:
|
||
imp: ast.Import* = ast.Import*(stmt)
|
||
if imp.names is not None:
|
||
imp_names: list[ast.AST*]* = imp.names
|
||
an: sizet = len(imp_names)
|
||
for ai in range(an):
|
||
al: ast.Alias* = ast.Alias*(imp_names.get(ai))
|
||
if al is not None and al.name is not None:
|
||
if string.strcmp(al.name, "t") == 0: has_import_t = 1
|
||
elif string.strcmp(al.name, "c") == 0: has_import_c = 1
|
||
|
||
# Add missing import t/c
|
||
if has_import_t == 0 and has_import_c == 0: pos = _PyiEmit(buf, buf_size, pos, "import t, c\n\n")
|
||
elif has_import_t == 0: pos = _PyiEmit(buf, buf_size, pos, "import t\n\n")
|
||
elif has_import_c == 0: pos = _PyiEmit(buf, buf_size, pos, "import c\n\n")
|
||
else: pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||
|
||
# Pass 2: emit declarations
|
||
for i in range(n):
|
||
stmt = tree_children.get(i)
|
||
if stmt is not None:
|
||
k: int = stmt.kind()
|
||
|
||
# Import statement
|
||
if k == ast.ASTKind.Import:
|
||
imp2: ast.Import* = ast.Import*(stmt)
|
||
pos = _PyiEmit(buf, buf_size, pos, "import ")
|
||
# fail-fast: an empty alias list would write a broken `import ` line and
|
||
# lose all names, breaking package re-export registration.
|
||
if imp2.names is None or len(imp2.names) == 0:
|
||
GenApi.fatal_error(stmt, "vpi generation: the import statement alias list is empty (expected >=1 alias) -- AST construction/parse anomaly", "NameError")
|
||
if imp2.names is not None:
|
||
imp2_names: list[ast.AST*]* = imp2.names
|
||
an2: sizet = len(imp2_names)
|
||
for ai2 in range(an2):
|
||
if ai2 > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||
al2: ast.Alias* = ast.Alias*(imp2_names.get(ai2))
|
||
# fail-fast: never silently write "import , ". Import names are the
|
||
# only input for package re-export registration. Report the two causes separately.
|
||
if al2 is None:
|
||
GenApi.fatal_error(stmt, "vpi generation: import alias **node pointer is null** (Alias(...) construction produced no object: " "pool.alloc failed or __init__ was not run) -- losing the import name breaks package re-export registration", "NameError")
|
||
if al2.name is None or al2.name[0] == '\0':
|
||
GenApi.fatal_error(stmt, "vpi generation: the import alias node exists but **name is empty** (_copy_str returned None / " "__init__ did not set name) -- losing the import name breaks package re-export registration", "NameError")
|
||
pos = _PyiEmit(buf, buf_size, pos, al2.name)
|
||
if al2.asname is not None and string.strlen(al2.asname) > 0:
|
||
pos = _PyiEmit(buf, buf_size, pos, " as ")
|
||
pos = _PyiEmit(buf, buf_size, pos, al2.asname)
|
||
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||
|
||
# ImportFrom statement
|
||
elif k == ast.ASTKind.ImportFrom:
|
||
ifr: ast.ImportFrom* = ast.ImportFrom*(stmt)
|
||
pos = _PyiEmit(buf, buf_size, pos, "from ")
|
||
lvl: int = ifr.level
|
||
while lvl > 0:
|
||
pos = _PyiEmit(buf, buf_size, pos, ".")
|
||
lvl -= 1
|
||
if ifr.module is not None: pos = _PyiEmit(buf, buf_size, pos, ifr.module)
|
||
pos = _PyiEmit(buf, buf_size, pos, " import ")
|
||
# fail-fast: same as Import; `from X import *` uses Alias("*"). An empty
|
||
# list or empty name means a broken AST and would break re-export registration.
|
||
if ifr.names is None or len(ifr.names) == 0:
|
||
GenApi.fatal_error(stmt, "vpi generation: the from-import statement alias list is empty (expected >=1 alias, including star)" "-- AST construction/parse anomaly", "NameError")
|
||
if ifr.names is not None:
|
||
ifr_names: list[ast.AST*]* = ifr.names
|
||
an3: sizet = len(ifr_names)
|
||
for ai3 in range(an3):
|
||
if ai3 > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||
al3: ast.Alias* = ast.Alias*(ifr_names.get(ai3))
|
||
# fail-fast: same as Import (report the two causes separately).
|
||
if al3 is None:
|
||
GenApi.fatal_error(stmt, "vpi generation: from-import alias **node pointer is null** (Alias(...) construction produced no object: " "pool.alloc failed or __init__ was not run) -- package re-export registration will therefore fail", "NameError")
|
||
if al3.name is None or al3.name[0] == '\0':
|
||
GenApi.fatal_error(stmt, "vpi generation: the from-import alias node exists but **name is empty** (_copy_str returned None / " "__init__ did not set name) -- package re-export registration will therefore fail", "NameError")
|
||
pos = _PyiEmit(buf, buf_size, pos, al3.name)
|
||
if al3.asname is not None and string.strlen(al3.asname) > 0:
|
||
pos = _PyiEmit(buf, buf_size, pos, " as ")
|
||
pos = _PyiEmit(buf, buf_size, pos, al3.asname)
|
||
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||
|
||
# Function definition (_PyiFunction already ends with a newline)
|
||
elif k == ast.ASTKind.FunctionDef:
|
||
fn: ast.FunctionDef* = ast.FunctionDef*(stmt)
|
||
# Generic function templates (def f[T]) emit no vpi: the signature has an
|
||
# unbound T. Specializations are monomorphized per call site, not here.
|
||
if fn.type_params is not None: continue
|
||
pos = _PyiFunction(buf, buf_size, pos, fn, 0, "")
|
||
|
||
# Class definition
|
||
elif k == ast.ASTKind.ClassDef:
|
||
cls: ast.ClassDef* = ast.ClassDef*(stmt)
|
||
pos = _PyiClass(buf, buf_size, pos, cls, 0)
|
||
|
||
# Annotated variable declaration
|
||
elif k == ast.ASTKind.AnnAssign:
|
||
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
|
||
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(aa.target)
|
||
if nm.id is not None:
|
||
pos = _PyiEmit(buf, buf_size, pos, nm.id)
|
||
pos = _PyiEmit(buf, buf_size, pos, ": ")
|
||
pos = _PyiTypeStr(buf, buf_size, pos, aa.annotation)
|
||
# If there is a value (e.g. a CDefine constant) emit = value.
|
||
# CTypedef is special: its value is a type expression, rendered by _PyiTypeStr.
|
||
is_ctypedef_ann: int = 0
|
||
if aa.annotation is not None and aa.annotation.kind() == ast.ASTKind.Attribute:
|
||
ctd_at: ast.Attribute* = ast.Attribute*(aa.annotation)
|
||
if ctd_at.attr is not None and string.strcmp(ctd_at.attr, "CTypedef") == 0:
|
||
if ctd_at.value is not None and ctd_at.value.kind() == ast.ASTKind.Name:
|
||
ctd_mod: ast.Name* = ast.Name*(ctd_at.value)
|
||
if ctd_mod.id is not None and string.strcmp(ctd_mod.id, "t") == 0: is_ctypedef_ann = 1
|
||
if is_ctypedef_ann != 0 and aa.value is not None:
|
||
pos = _PyiEmit(buf, buf_size, pos, " = ")
|
||
pos = _PyiTypeStr(buf, buf_size, pos, aa.value)
|
||
elif aa.value is not None and aa.value.kind() == ast.ASTKind.Constant:
|
||
cv: ast.Constant* = ast.Constant*(aa.value)
|
||
num_buf: bytes = mb.alloc(32)
|
||
if cv.const_kind == ast.CONST_INT and num_buf is not None:
|
||
viperlib.snprintf(num_buf, 32, " = %lld", cv.int_val)
|
||
pos = _PyiEmit(buf, buf_size, pos, num_buf)
|
||
elif cv.const_kind == ast.CONST_FLOAT:
|
||
# Float CDefines (e.g. numpy pi/e) must emit a value or read-back
|
||
# degrades to a global pointer; %.17g round-trips double exactly.
|
||
fbuf_c: bytes = mb.alloc(64)
|
||
if fbuf_c is not None:
|
||
viperlib.snprintf(fbuf_c, 64, " = %.17g", cv.float_val)
|
||
pos = _PyiEmit(buf, buf_size, pos, fbuf_c)
|
||
elif cv.const_kind == ast.CONST_BOOL:
|
||
if cv.int_val != 0: pos = _PyiEmit(buf, buf_size, pos, " = True")
|
||
else: pos = _PyiEmit(buf, buf_size, pos, " = False")
|
||
elif cv.const_kind == ast.CONST_STR:
|
||
# String CDefines (e.g. pyzlib.ZLIB_VERSION) must emit a value so the stub is self-contained
|
||
sbuf_c: bytes = mb.alloc(string.strlen(cv.str_val) + 6)
|
||
if sbuf_c is not None:
|
||
viperlib.snprintf(sbuf_c, string.strlen(cv.str_val) + 6, " = \"%s\"", cv.str_val)
|
||
pos = _PyiEmit(buf, buf_size, pos, sbuf_c)
|
||
elif cv.const_kind == ast.CONST_NONE: pos = _PyiEmit(buf, buf_size, pos, " = None")
|
||
elif aa.value is not None and aa.value.kind() == ast.ASTKind.UnaryOp:
|
||
# Negative constant (e.g. INVALID_HANDLE_VALUE = -1):
|
||
# UnaryOp(USub, Constant(1)) -> -1
|
||
uop_cv: ast.UnaryOp* = ast.UnaryOp*(aa.value)
|
||
num_buf_u: bytes = mb.alloc(32)
|
||
if uop_cv.op == ast.OpKind.USub and uop_cv.operand is not None and uop_cv.operand.kind() == ast.ASTKind.Constant:
|
||
uop_const: ast.Constant* = ast.Constant*(uop_cv.operand)
|
||
if uop_const.const_kind == ast.CONST_INT and num_buf_u is not None:
|
||
neg_val: int64 = -uop_const.int_val
|
||
viperlib.snprintf(num_buf_u, 32, " = %lld", neg_val)
|
||
pos = _PyiEmit(buf, buf_size, pos, num_buf_u)
|
||
elif aa.value is not None and aa.value.kind() == ast.ASTKind.Call:
|
||
# Type-constructor constant (e.g. t.CUnsignedLong(-11)):
|
||
# extract and emit the integer value of the first argument.
|
||
call_cv: ast.Call* = ast.Call*(aa.value)
|
||
if call_cv.args is not None:
|
||
call_args_cv: list[ast.AST*]* = call_cv.args
|
||
if len(call_args_cv) > 0:
|
||
first_arg: ast.AST* = call_args_cv.get(0)
|
||
if first_arg is not None:
|
||
arg_kind: int = first_arg.kind()
|
||
if arg_kind == ast.ASTKind.Constant:
|
||
ca_cv: ast.Constant* = ast.Constant*(first_arg)
|
||
num_buf_c: bytes = mb.alloc(32)
|
||
if ca_cv.const_kind == ast.CONST_INT and num_buf_c is not None:
|
||
viperlib.snprintf(num_buf_c, 32, " = %lld", ca_cv.int_val)
|
||
pos = _PyiEmit(buf, buf_size, pos, num_buf_c)
|
||
elif ca_cv.const_kind == ast.CONST_FLOAT:
|
||
# Call-wrapped float constant (e.g. float(3.14159...)): emit the
|
||
# value or read-back degrades to 0.0; %.17g round-trips double.
|
||
fbuf_cc: bytes = mb.alloc(64)
|
||
if fbuf_cc is not None:
|
||
viperlib.snprintf(fbuf_cc, 64, " = %.17g", ca_cv.float_val)
|
||
pos = _PyiEmit(buf, buf_size, pos, fbuf_cc)
|
||
elif arg_kind == ast.ASTKind.UnaryOp:
|
||
uop_ca: ast.UnaryOp* = ast.UnaryOp*(first_arg)
|
||
num_buf_cu: bytes = mb.alloc(32)
|
||
if uop_ca.op == ast.OpKind.USub and uop_ca.operand is not None and uop_ca.operand.kind() == ast.ASTKind.Constant:
|
||
uoc: ast.Constant* = ast.Constant*(uop_ca.operand)
|
||
if uoc.const_kind == ast.CONST_INT and num_buf_cu is not None:
|
||
neg_c_val: int64 = -uoc.int_val
|
||
viperlib.snprintf(num_buf_cu, 32, " = %lld", neg_c_val)
|
||
pos = _PyiEmit(buf, buf_size, pos, num_buf_cu)
|
||
elif uoc.const_kind == ast.CONST_FLOAT:
|
||
# Negative Call-wrapped float (e.g. float(-1.5)):
|
||
# same as CONST_FLOAT, %.17g round-trips exactly.
|
||
neg_cf: double = -uoc.float_val
|
||
fbuf_cu: bytes = mb.alloc(64)
|
||
if fbuf_cu is not None:
|
||
viperlib.snprintf(fbuf_cu, 64, " = %.17g", neg_cf)
|
||
pos = _PyiEmit(buf, buf_size, pos, fbuf_cu)
|
||
elif arg_kind == ast.ASTKind.BinOp:
|
||
# BinOp inside a Call (e.g. t.CUnsignedLong(A | B)):
|
||
# recursively evaluate the BinOp value.
|
||
binop_val_bo: int64 = _eval_binop_int(first_arg)
|
||
num_buf_bo: bytes = mb.alloc(32)
|
||
if (binop_val_bo != 0 or _eval_binop_valid(first_arg) != 0) and num_buf_bo is not None:
|
||
viperlib.snprintf(num_buf_bo, 32, " = %lld", binop_val_bo)
|
||
pos = _PyiEmit(buf, buf_size, pos, num_buf_bo)
|
||
elif aa.value is not None and aa.value.kind() == ast.ASTKind.BinOp:
|
||
# BinOp constant (e.g. FOREGROUND_COMBO = FOREGROUND_RED | FOREGROUND_GREEN):
|
||
# recursively evaluate and emit it.
|
||
binop_val_av: int64 = _eval_binop_int(aa.value)
|
||
if binop_val_av != 0 or _eval_binop_valid(aa.value) != 0:
|
||
num_buf_b: bytes = mb.alloc(32)
|
||
if num_buf_b is not None:
|
||
viperlib.snprintf(num_buf_b, 32, " = %lld", binop_val_av)
|
||
pos = _PyiEmit(buf, buf_size, pos, num_buf_b)
|
||
elif aa.value is not None and aa.value.kind() == ast.ASTKind.Name:
|
||
# Name reference constant (e.g. ALIAS = OTHER_CONST):
|
||
# emit the referenced name for the consumer to resolve locally.
|
||
name_val: ast.Name* = ast.Name*(aa.value)
|
||
if name_val.id is not None:
|
||
pos = _PyiEmit(buf, buf_size, pos, " = ")
|
||
pos = _PyiEmit(buf, buf_size, pos, name_val.id)
|
||
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||
|
||
# Global assignment (no annotation) - infer type from the Constant value
|
||
elif k == ast.ASTKind.Assign:
|
||
asg: ast.Assign* = ast.Assign*(stmt)
|
||
if asg.targets is not None and asg.value is not None:
|
||
asg_targets: list[ast.AST*]* = asg.targets
|
||
atn: sizet = len(asg_targets)
|
||
if atn == 1:
|
||
atg: ast.AST* = asg_targets.get(0)
|
||
if atg is not None and atg.kind() == ast.ASTKind.Name:
|
||
agn: ast.Name* = ast.Name*(atg)
|
||
aval: ast.AST* = asg.value
|
||
avk: int = aval.kind()
|
||
inferred: int = 0
|
||
if avk == ast.ASTKind.Constant:
|
||
acv: ast.Constant* = ast.Constant*(aval)
|
||
if acv.const_kind == ast.CONST_INT: inferred = 1
|
||
elif acv.const_kind == ast.CONST_FLOAT: inferred = 2
|
||
elif acv.const_kind == ast.CONST_STR: inferred = 3
|
||
elif acv.const_kind == ast.CONST_BOOL: inferred = 4
|
||
elif acv.const_kind == ast.CONST_NONE: inferred = 5
|
||
if inferred > 0 and agn.id is not None:
|
||
pos = _PyiEmit(buf, buf_size, pos, agn.id)
|
||
pos = _PyiEmit(buf, buf_size, pos, ": ")
|
||
if inferred == 1: pos = _PyiEmit(buf, buf_size, pos, "int")
|
||
elif inferred == 2: pos = _PyiEmit(buf, buf_size, pos, "double")
|
||
elif inferred == 3: pos = _PyiEmit(buf, buf_size, pos, "str")
|
||
elif inferred == 4: pos = _PyiEmit(buf, buf_size, pos, "bool")
|
||
elif inferred == 5: pos = _PyiEmit(buf, buf_size, pos, "None")
|
||
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||
|
||
# NUL terminator
|
||
if pos < buf_size: buf[pos] = 0
|
||
else: buf[buf_size - 1] = 0
|
||
return pos
|