944 lines
52 KiB
Plaintext
944 lines
52 KiB
Plaintext
from stdint import *
|
|
import ast
|
|
import memhub
|
|
import string
|
|
import stdlib
|
|
import viperlib
|
|
import lib.core.Gen.Translator as HT
|
|
import lib.core.CompilerFactory as CompilerFactory
|
|
import lib.core.Gen.GenTypes as GenTypes
|
|
import lib.core.Gen.Generator as Generator
|
|
|
|
|
|
# ============================================================
|
|
# Local constants: metadata/decorator/exception table sizes and method metadata flag bits.
|
|
define META_TLS = 32
|
|
# Class compile-time metadata table capacity (__provides__/__requires__/__require_must__).
|
|
define CLASS_META_MAX = 256
|
|
# Custom-decorator wrapper table capacity (number of @decor functions).
|
|
define DECOR_FUNC_MAX = 64
|
|
# Decorator table slot length for function/wrapper names (incl. NUL).
|
|
define DECOR_NAME_LEN = 128
|
|
# Exception class registry capacity (user classes subclassing Exception).
|
|
define EXCEPTION_MAX = 64
|
|
# Exception table slot length for class/parent names (incl. NUL).
|
|
define EXCEPTION_NAME_LEN = 128
|
|
# Builtin exception codes (mirrors TPC EXCEPTION_CODE_MAP); user classes start at 100.
|
|
# KeyError=6 IOError=7 OSError=8 AssertionError=9 Exception=99.
|
|
define BUILTIN_EH_VALUE_ERROR = 1
|
|
define BUILTIN_EH_RUNTIME_ERROR = 3
|
|
define BUILTIN_EH_EXCEPTION = 99
|
|
define EH_USER_CODE_START = 100
|
|
# Method metadata table sizes (formerly HandlesClassDef.META_MAX/META_KEY_LEN).
|
|
define META_MAX = 512
|
|
define META_KEY_LEN = 128
|
|
|
|
|
|
# ============================================================
|
|
# ClassGenBase - fields plus metadata/exception/inheritance/decorator helpers.
|
|
novirtual class ClassGenBase:
|
|
"""Fields plus method-metadata, exception, inheritance and decorator helpers."""
|
|
|
|
# Method metadata table (formerly _g_method_meta_keys/_flags/_count).
|
|
Gen: Generator.Generator*
|
|
MethodMetaKeys: bytes
|
|
MethodMetaFlags: bytes
|
|
MethodMetaCount: int
|
|
|
|
# REnum variant table (formerly _renum_class_names/_variant_counts/_variant_names/_variant_tags/_renum_class_count).
|
|
RenumClassNames: sizet*
|
|
RenumClassVariantCounts: sizet*
|
|
RenumVariantNames: sizet*
|
|
RenumVariantTags: sizet*
|
|
RenumClassCount: int
|
|
|
|
# REnum field alias table (formerly _renum_alias_class/_alias_field/_alias_index/_alias_count).
|
|
RenumAliasClass: sizet*
|
|
RenumAliasField: sizet*
|
|
RenumAliasIndex: sizet*
|
|
# REnum variant member LLVM types (mirrors TPC class_members): deep-copied into perm_pool.
|
|
# match binds slots by the real member type, not the raw i64 slot width.
|
|
RenumAliasTy: sizet*
|
|
# Alias variant name (perm_pool copy): the same payload offset has a different type
|
|
# per variant, so match looks up the member type by (class, variant, offset).
|
|
RenumAliasVariant: sizet*
|
|
RenumAliasCount: int
|
|
|
|
# --- Generic specialization cache (formerly _spec_keys/_spec_names/_spec_count). ---
|
|
SpecKeys: sizet*
|
|
SpecNames: sizet*
|
|
SpecCount: int
|
|
|
|
# --- Class compile-time metadata table (__provides__/__requires__/__require_must__).
|
|
# perm_pool-allocated: kind(4B) + cls(64B) + field(64B) per slot, e.g. FakeDuck __requires__.
|
|
ClassMetaKinds: int*
|
|
ClassMetaClss: bytes
|
|
ClassMetaFields: bytes
|
|
ClassMetaCount: int
|
|
|
|
# --- Custom decorator wrapper table (@log/@skip/@repeat -> __decor_wrap_{name}).
|
|
# translate_call rewrites the original name to the wrapper via lookup_decor_wrapper.
|
|
DecoratorFuncCount: int
|
|
DecoratorFuncNames: bytes
|
|
DecoratorWrapNames: bytes
|
|
# First constant decorator arg (the 3 in @repeat(3)); 0 when none (8B i64 per slot).
|
|
DecoratorArgs: int64*
|
|
|
|
# --- Exception class registry for try/except (mirrors TPC HandlesClassDef).
|
|
# perm_pool-allocated slots: class name(128B) + parent(128B) + code(4B); user codes from 100.
|
|
ExceptionCount: int
|
|
ExceptionNames: bytes
|
|
ExceptionParents: bytes
|
|
ExceptionCodes: int*
|
|
# Next exception code to assign (user classes, starting at 100).
|
|
NextExceptionCode: int
|
|
# Temp counter for collect_descendant_codes (invalid after it returns).
|
|
# Must be declared at class top; mid-method field annotations break Mixin layout.
|
|
_exception_codes_tmp_count: int
|
|
|
|
# --- Generic specialization recursion depth (guards against cyclic specialization).
|
|
# Incremented on entry, decremented on exit; exceeding the limit aborts.
|
|
GenericSpecDepth: int
|
|
|
|
# --- Generic template tables (formerly HandlesClassDef._generic_class_names/
|
|
# _generic_class_nodes/_generic_class_sha1s). ---
|
|
GenericClassNames: list[str]*
|
|
GenericClassNodes: list[ast.ClassDef*]*
|
|
GenericClassSha1s: list[str]*
|
|
# Source file path per template (same order as above): specialization temporarily
|
|
# switches CurrentFile to it so template line numbers in errors stay correct.
|
|
GenericClassFiles: list[str]*
|
|
# Template type-param names (same order as GenericClassNames, deep-copied to stdlib):
|
|
# Phase 1 type_params may expire across phases, so they are flattened here.
|
|
GenericClassTpParams: sizet*
|
|
|
|
# --- Generic function template tables, keyed "{sha1}.{fname}" to template
|
|
# node/SHA1/file/type-params; must stay at class top for Mixin layout. ---
|
|
GenericFuncKeys: list[str]*
|
|
GenericFuncNodes: list[ast.FunctionDef*]*
|
|
GenericFuncSha1s: list[str]*
|
|
GenericFuncFiles: list[str]*
|
|
GenericFuncTpParams: sizet*
|
|
|
|
# --- vtable method collection tables (formerly _g_vt_full_names/
|
|
# _g_vt_method_names/_g_vt_parent_sha1s/_g_vt_method_count). ---
|
|
VtFullNames: sizet*
|
|
VtMethodNames: sizet*
|
|
VtParentSha1s: sizet*
|
|
VtMethodCount: int
|
|
# ============================================================
|
|
# Methods below migrated in P5-2 from HandlesClassDef.vp; intra-module calls use self.
|
|
|
|
def _is_cenum_base(self, base_node: ast.AST*, trans: HT.Translator* = None) -> int:
|
|
"""Return 1 if the base node is t.CEnum, else 0."""
|
|
if base_node is None: return 0
|
|
k: int = base_node.kind()
|
|
# Name('CEnum') form (from t import CEnum).
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(base_node)
|
|
if nm.id is None: return 0
|
|
if string.strcmp(nm.id, "CEnum") == 0: return 1
|
|
# Alias form: from t import CEnum as en; class Color(en).
|
|
# Resolve the original name (en -> CEnum) before comparing.
|
|
if trans is not None and trans._from_imports is not None:
|
|
alias_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, nm.id)
|
|
if alias_orig is not None:
|
|
og_len_ce: sizet = 0
|
|
while alias_orig[og_len_ce] != ' ' and alias_orig[og_len_ce] != '\0': og_len_ce += 1
|
|
if og_len_ce == 5 and string.strncmp(alias_orig, "CEnum", 5) == 0: return 1
|
|
return 0
|
|
# Attribute form was retired together with the t module prefix.
|
|
return 0
|
|
def _is_enum_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
|
|
"""Return 1 if the ClassDef is a CEnum (subclasses t.CEnum or uses @t.CEnum)."""
|
|
if cd is None: return 0
|
|
# Decorator form: @t.CEnum class Color: (equivalent to subclassing t.CEnum).
|
|
if cd.decorator_list is not None:
|
|
if self._has_decorator(cd.decorator_list, "CEnum") == 1: return 1
|
|
if cd.bases is None: return 0
|
|
bases: list[ast.AST*]* = cd.bases
|
|
bn: sizet = len(bases)
|
|
if bn == 0: return 0
|
|
for bi in range(int(bn)):
|
|
base_node: ast.AST* = bases.get(bi)
|
|
if base_node is None: continue
|
|
if self._is_cenum_base(base_node, trans) == 1: return 1
|
|
return 0
|
|
def _is_cunion_base(self, base_node: ast.AST*, trans: HT.Translator* = None) -> int:
|
|
"""Return 1 if the base node is t.CUnion, else 0."""
|
|
if base_node is None: return 0
|
|
k: int = base_node.kind()
|
|
# Name('CUnion') form (from t import CUnion).
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(base_node)
|
|
if nm.id is None: return 0
|
|
if string.strcmp(nm.id, "CUnion") == 0: return 1
|
|
# Alias form: from t import CUnion as un; class Data(un).
|
|
if trans is not None and trans._from_imports is not None:
|
|
alias_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, nm.id)
|
|
if alias_orig is not None:
|
|
og_len_cu: sizet = 0
|
|
while alias_orig[og_len_cu] != ' ' and alias_orig[og_len_cu] != '\0': og_len_cu += 1
|
|
if og_len_cu == 6 and string.strncmp(alias_orig, "CUnion", 6) == 0: return 1
|
|
return 0
|
|
# Attribute form was retired together with the t module prefix.
|
|
return 0
|
|
def _is_union_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
|
|
"""Return 1 if the ClassDef is a CUnion (subclasses t.CUnion or uses @t.CUnion)."""
|
|
if cd is None: return 0
|
|
# Decorator form: @t.CUnion class Data:
|
|
if cd.decorator_list is not None:
|
|
if self._has_decorator(cd.decorator_list, "CUnion") == 1: return 1
|
|
if cd.bases is None: return 0
|
|
bases: list[ast.AST*]* = cd.bases
|
|
bn: sizet = len(bases)
|
|
if bn == 0: return 0
|
|
for bi in range(int(bn)):
|
|
base_node: ast.AST* = bases.get(bi)
|
|
if base_node is None: continue
|
|
if self._is_cunion_base(base_node, trans) == 1: return 1
|
|
return 0
|
|
def _is_crenum_base(self, base_node: ast.AST*, trans: HT.Translator* = None) -> int:
|
|
"""Return 1 if the base node is t.REnum, else 0."""
|
|
if base_node is None: return 0
|
|
k: int = base_node.kind()
|
|
# Name('REnum') form (from t import REnum).
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(base_node)
|
|
if nm.id is None: return 0
|
|
if string.strcmp(nm.id, "REnum") == 0: return 1
|
|
# Alias form: from t import REnum as ren; class Opt(ren).
|
|
if trans is not None and trans._from_imports is not None:
|
|
alias_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, nm.id)
|
|
if alias_orig is not None:
|
|
og_len_cr: sizet = 0
|
|
while alias_orig[og_len_cr] != ' ' and alias_orig[og_len_cr] != '\0': og_len_cr += 1
|
|
if og_len_cr == 5 and string.strncmp(alias_orig, "REnum", 5) == 0: return 1
|
|
return 0
|
|
# Attribute form was retired together with the t module prefix.
|
|
return 0
|
|
def _is_renum_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
|
|
"""Return 1 if the ClassDef is a REnum (subclasses t.REnum or uses @t.REnum)."""
|
|
if cd is None: return 0
|
|
# Decorator form: @t.REnum class X:
|
|
if cd.decorator_list is not None:
|
|
if self._has_decorator(cd.decorator_list, "REnum") == 1: return 1
|
|
if cd.bases is None: return 0
|
|
bases: list[ast.AST*]* = cd.bases
|
|
bn: sizet = len(bases)
|
|
if bn == 0: return 0
|
|
for bi in range(int(bn)):
|
|
base_node: ast.AST* = bases.get(bi)
|
|
if base_node is None: continue
|
|
if self._is_crenum_base(base_node, trans) == 1: return 1
|
|
return 0
|
|
def _has_decorator(self, decorator_list: list[ast.AST*]*, deco_name: str) -> int:
|
|
"""Return 1 if decorator_list contains the given bare builtin decorator name."""
|
|
if decorator_list is None: return 0
|
|
dn: sizet = len(decorator_list)
|
|
if dn == 0: return 0
|
|
for di in range(int(dn)):
|
|
deco: ast.AST* = decorator_list.get(di)
|
|
if deco is None: continue
|
|
k: int = deco.kind()
|
|
# @NoVTable / @CVTable / @TLS / @CStatic form (bare builtin Name).
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(deco)
|
|
if nm.id is not None and string.strcmp(nm.id, deco_name) == 0: return 1
|
|
return 0
|
|
def _tattr_name(self, node: ast.AST*, attr_name: str) -> int:
|
|
"""Extract the outermost attribute name from t.attr.<name> / t.attr.<name>(args) and compare it."""
|
|
if node is None: return 0
|
|
if node.kind() == ast.ASTKind.Call:
|
|
cl2: ast.Call* = ast.Call*(node)
|
|
if cl2.func is None: return 0
|
|
return self._tattr_name(cl2.func, attr_name)
|
|
if node.kind() == ast.ASTKind.Attribute:
|
|
at2: ast.Attribute* = ast.Attribute*(node)
|
|
if at2.attr is None: return 0
|
|
return 1 if string.strcmp(at2.attr, attr_name) == 0 else 0
|
|
return 0
|
|
def _class_has_tattr(self, decorator_list: list[ast.AST*]*, attr_name: str) -> int:
|
|
"""Return 1 if decorator_list contains t.attr.<attr_name> (@c.Attribute(t.attr.xxx) or @t.attr.xxx)."""
|
|
if decorator_list is None: return 0
|
|
dn: sizet = len(decorator_list)
|
|
if dn == 0: return 0
|
|
for di in range(int(dn)):
|
|
deco: ast.AST* = decorator_list.get(di)
|
|
if deco is None: continue
|
|
k: int = deco.kind()
|
|
if k == ast.ASTKind.Call:
|
|
cl: ast.Call* = ast.Call*(deco)
|
|
# @Attribute(attr.xxx) form: func is the bare builtin Name("Attribute").
|
|
if cl.func is None or cl.func.kind() != ast.ASTKind.Name: continue
|
|
at0: ast.Name* = ast.Name*(cl.func)
|
|
if at0.id is None or string.strcmp(at0.id, "Attribute") != 0: continue
|
|
if cl.args is None: continue
|
|
an: sizet = len(cl.args)
|
|
for ai in range(int(an)):
|
|
ag: ast.AST* = cl.args.get(ai)
|
|
if ag is not None and self._tattr_name(ag, attr_name) != 0: return 1
|
|
else:
|
|
# Direct @t.attr.xxx form.
|
|
if self._tattr_name(deco, attr_name) != 0: return 1
|
|
return 0
|
|
def _tattr_aligned_int(self, node: ast.AST*) -> int:
|
|
"""Return N for t.attr.aligned(N); 0 when not aligned or the argument is missing."""
|
|
if node is None: return 0
|
|
if node.kind() == ast.ASTKind.Call:
|
|
cl2: ast.Call* = ast.Call*(node)
|
|
if cl2.func is None or cl2.func.kind() != ast.ASTKind.Attribute: return 0
|
|
at2: ast.Attribute* = ast.Attribute*(cl2.func)
|
|
if at2.attr is None or string.strcmp(at2.attr, "aligned") != 0: return 0
|
|
if cl2.args is None or len(cl2.args) == 0: return 0
|
|
a0: ast.AST* = cl2.args.get(0)
|
|
if a0 is None or a0.kind() != ast.ASTKind.Constant: return 0
|
|
cn: ast.Constant* = ast.Constant*(a0)
|
|
if cn.const_kind != ast.CONST_INT: return 0
|
|
return int(cn.int_val)
|
|
return 0
|
|
def _class_aligned_value(self, decorator_list: list[ast.AST*]*) -> int:
|
|
"""Return the alignment bytes from @Attribute(attr.aligned(N)); 0 when absent."""
|
|
if decorator_list is None: return 0
|
|
dn: sizet = len(decorator_list)
|
|
if dn == 0: return 0
|
|
for di in range(int(dn)):
|
|
deco: ast.AST* = decorator_list.get(di)
|
|
if deco is None: continue
|
|
if deco.kind() == ast.ASTKind.Call:
|
|
cl: ast.Call* = ast.Call*(deco)
|
|
if cl.func is None or cl.func.kind() != ast.ASTKind.Name: continue
|
|
at0: ast.Name* = ast.Name*(cl.func)
|
|
if at0.id is None or string.strcmp(at0.id, "Attribute") != 0: continue
|
|
if cl.args is None: continue
|
|
an: sizet = len(cl.args)
|
|
for ai in range(int(an)):
|
|
ag: ast.AST* = cl.args.get(ai)
|
|
if ag is not None:
|
|
av: int = self._tattr_aligned_int(ag)
|
|
if av > 0: return av
|
|
else:
|
|
av2: int = self._tattr_aligned_int(deco)
|
|
if av2 > 0: return av2
|
|
return 0
|
|
def _extract_func_meta(self, decorator_list: list[ast.AST*]*) -> int:
|
|
"""Build the method-metadata bitmask from decorators; 0=no decorator metadata."""
|
|
meta: int = 0
|
|
if decorator_list is None: return meta
|
|
dn: sizet = len(decorator_list)
|
|
if dn == 0: return meta
|
|
for di in range(int(dn)):
|
|
deco: ast.AST* = decorator_list.get(di)
|
|
if deco is None: continue
|
|
k: int = deco.kind()
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(deco)
|
|
if nm.id is not None:
|
|
if string.strcmp(nm.id, "staticmethod") == 0: meta |= GenTypes.META_STATIC
|
|
elif string.strcmp(nm.id, "property") == 0: meta |= GenTypes.META_PROP_GETTER
|
|
elif string.strcmp(nm.id, "classmethod") == 0: meta |= GenTypes.META_CLASS
|
|
elif string.strcmp(nm.id, "TLS") == 0: meta |= META_TLS
|
|
elif k == ast.ASTKind.Attribute:
|
|
at: ast.Attribute* = ast.Attribute*(deco)
|
|
if at.attr is not None and at.value is not None:
|
|
if at.value.kind() == ast.ASTKind.Name:
|
|
vn: ast.Name* = ast.Name*(at.value)
|
|
is_prop_name: int = 0
|
|
if vn.id is not None and string.strcmp(vn.id, "property") == 0: is_prop_name = 1
|
|
# Recognize @property.setter and @x.setter/getter/deleter.
|
|
if is_prop_name != 0 or string.strcmp(at.attr, "setter") == 0 or string.strcmp(at.attr, "getter") == 0 or string.strcmp(at.attr, "deleter") == 0:
|
|
if string.strcmp(at.attr, "setter") == 0: meta |= GenTypes.META_PROP_SETTER
|
|
elif string.strcmp(at.attr, "getter") == 0: meta |= GenTypes.META_PROP_GETTER
|
|
elif string.strcmp(at.attr, "deleter") == 0: meta |= GenTypes.META_PROP_DELETER
|
|
return meta
|
|
def register_class_meta(self, pool: memhub.MemBuddy*, kind: int, cls: str, field: str) -> int:
|
|
"""Register a class compile-time metadata field (__provides__/__requires__/__require_must__), idempotent."""
|
|
if pool is None or cls is None or field is None: return -1
|
|
if self.ClassMetaKinds is None:
|
|
self.ClassMetaKinds = CompilerFactory.factory.PermPool.alloc(CLASS_META_MAX * 4)
|
|
self.ClassMetaClss = CompilerFactory.factory.PermPool.alloc(CLASS_META_MAX * 64)
|
|
self.ClassMetaFields = CompilerFactory.factory.PermPool.alloc(CLASS_META_MAX * 64)
|
|
if self.ClassMetaKinds is None or self.ClassMetaClss is None or self.ClassMetaFields is None: return -1
|
|
string.memset(self.ClassMetaKinds, 0, CLASS_META_MAX * 4)
|
|
string.memset(self.ClassMetaClss, 0, CLASS_META_MAX * 64)
|
|
string.memset(self.ClassMetaFields, 0, CLASS_META_MAX * 64)
|
|
self.ClassMetaCount = 0
|
|
if self.ClassMetaCount >= CLASS_META_MAX: return -1
|
|
# Idempotent: skip duplicate (kind, cls, field).
|
|
for i in range(int(self.ClassMetaCount)):
|
|
if self.ClassMetaKinds[i] == kind:
|
|
if string.strcmp(self.ClassMetaClss + i * 64, cls) == 0:
|
|
if string.strcmp(self.ClassMetaFields + i * 64, field) == 0: return 0
|
|
idx_cm: sizet = sizet(self.ClassMetaCount) * 64
|
|
self.ClassMetaKinds[self.ClassMetaCount] = kind
|
|
string.strncpy(self.ClassMetaClss + idx_cm, cls, 63)
|
|
self.ClassMetaClss[idx_cm + 63] = '\0'
|
|
string.strncpy(self.ClassMetaFields + idx_cm, field, 63)
|
|
self.ClassMetaFields[idx_cm + 63] = '\0'
|
|
self.ClassMetaCount += 1
|
|
return 0
|
|
def class_meta_count(self, kind: int, cls: str) -> int:
|
|
"""Return the field count of the given metadata kind for a class."""
|
|
if self.ClassMetaKinds is None or cls is None: return 0
|
|
n_cm: int = 0
|
|
for i in range(int(self.ClassMetaCount)):
|
|
if self.ClassMetaKinds[i] == kind:
|
|
if string.strcmp(self.ClassMetaClss + i * 64, cls) == 0: n_cm += 1
|
|
return n_cm
|
|
def class_meta_field_at(self, kind: int, cls: str, idx: int) -> str:
|
|
"""Return the idx-th field name of the given metadata kind for a class."""
|
|
if self.ClassMetaKinds is None or cls is None: return None
|
|
n_cm2: int = 0
|
|
for i in range(int(self.ClassMetaCount)):
|
|
if self.ClassMetaKinds[i] == kind:
|
|
if string.strcmp(self.ClassMetaClss + i * 64, cls) == 0:
|
|
if n_cm2 == idx: return self.ClassMetaFields + i * 64
|
|
n_cm2 += 1
|
|
return None
|
|
def register_decorator(self, pool: memhub.MemBuddy*, func_name: str, wrapper_name: str, arg0: int64) -> int:
|
|
"""Register a custom decorator function (@log/@skip/@repeat(3) -> __decor_wrap_{name}); arg0 is its first constant arg (0 when none)."""
|
|
if pool is None or func_name is None or wrapper_name is None: return -1
|
|
if self.DecoratorFuncNames is None:
|
|
self.DecoratorFuncNames = CompilerFactory.factory.PermPool.alloc(DECOR_FUNC_MAX * DECOR_NAME_LEN)
|
|
self.DecoratorWrapNames = CompilerFactory.factory.PermPool.alloc(DECOR_FUNC_MAX * DECOR_NAME_LEN)
|
|
self.DecoratorArgs = CompilerFactory.factory.PermPool.alloc(DECOR_FUNC_MAX * 8)
|
|
if self.DecoratorFuncNames is None or self.DecoratorWrapNames is None or self.DecoratorArgs is None: return -1
|
|
string.memset(self.DecoratorFuncNames, 0, DECOR_FUNC_MAX * DECOR_NAME_LEN)
|
|
string.memset(self.DecoratorWrapNames, 0, DECOR_FUNC_MAX * DECOR_NAME_LEN)
|
|
string.memset(self.DecoratorArgs, 0, DECOR_FUNC_MAX * 8)
|
|
self.DecoratorFuncCount = 0
|
|
if self.DecoratorFuncCount >= DECOR_FUNC_MAX: return -1
|
|
# Idempotent: skip an already-registered function name (multi-pass scan).
|
|
for i in range(int(self.DecoratorFuncCount)):
|
|
if string.strcmp(self.DecoratorFuncNames + i * DECOR_NAME_LEN, func_name) == 0: return 0
|
|
idx_dc: sizet = sizet(self.DecoratorFuncCount) * DECOR_NAME_LEN
|
|
string.strncpy(self.DecoratorFuncNames + idx_dc, func_name, DECOR_NAME_LEN - 1)
|
|
self.DecoratorFuncNames[idx_dc + DECOR_NAME_LEN - 1] = '\0'
|
|
string.strncpy(self.DecoratorWrapNames + idx_dc, wrapper_name, DECOR_NAME_LEN - 1)
|
|
self.DecoratorWrapNames[idx_dc + DECOR_NAME_LEN - 1] = '\0'
|
|
self.DecoratorArgs[self.DecoratorFuncCount] = arg0
|
|
self.DecoratorFuncCount += 1
|
|
return 0
|
|
def lookup_decor_wrapper(self, func_name: str) -> str:
|
|
"""Look up the wrapper name by original function name; None when not registered."""
|
|
if self.DecoratorFuncNames is None or func_name is None: return None
|
|
for i in range(int(self.DecoratorFuncCount)):
|
|
if string.strcmp(self.DecoratorFuncNames + i * DECOR_NAME_LEN, func_name) == 0: return self.DecoratorWrapNames + i * DECOR_NAME_LEN
|
|
return None
|
|
def lookup_decor_arg0(self, func_name: str) -> int64:
|
|
"""Look up a decorator's first constant arg by original function name (0 when none)."""
|
|
if self.DecoratorFuncNames is None or func_name is None: return 0
|
|
for i in range(int(self.DecoratorFuncCount)):
|
|
if string.strcmp(self.DecoratorFuncNames + i * DECOR_NAME_LEN, func_name) == 0: return self.DecoratorArgs[i]
|
|
return 0
|
|
# ============================================================
|
|
# Exception class registry for try/except (mirrors TPC HandlesClassDef).
|
|
def _ensure_exception_tables(self) -> int:
|
|
"""Lazily initialize the exception tables (perm_pool); 0=success."""
|
|
if self.ExceptionNames is not None: return 0
|
|
pp_e: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
|
if pp_e is None: return -1
|
|
self.ExceptionNames = pp_e.alloc(EXCEPTION_MAX * EXCEPTION_NAME_LEN)
|
|
self.ExceptionParents = pp_e.alloc(EXCEPTION_MAX * EXCEPTION_NAME_LEN)
|
|
self.ExceptionCodes = pp_e.alloc(EXCEPTION_MAX * 4)
|
|
if self.ExceptionNames is None or self.ExceptionParents is None or self.ExceptionCodes is None: return -1
|
|
string.memset(self.ExceptionNames, 0, EXCEPTION_MAX * EXCEPTION_NAME_LEN)
|
|
string.memset(self.ExceptionParents, 0, EXCEPTION_MAX * EXCEPTION_NAME_LEN)
|
|
string.memset(self.ExceptionCodes, 0, EXCEPTION_MAX * 4)
|
|
self.ExceptionCount = 0
|
|
self.NextExceptionCode = EH_USER_CODE_START
|
|
return 0
|
|
def register_exception(self, class_name: str, parent_name: str) -> int:
|
|
"""Register a user exception class and assign its code (idempotent by name); returns the code or -1."""
|
|
if class_name is None: return -1
|
|
if self._ensure_exception_tables() != 0: return -1
|
|
# Idempotent: return the existing code if already registered (multi-pass).
|
|
for i in range(int(self.ExceptionCount)):
|
|
if string.strcmp(self.ExceptionNames + i * EXCEPTION_NAME_LEN, class_name) == 0: return self.ExceptionCodes[i]
|
|
if self.ExceptionCount >= EXCEPTION_MAX: return -1
|
|
idx_e: sizet = sizet(self.ExceptionCount) * EXCEPTION_NAME_LEN
|
|
string.strncpy(self.ExceptionNames + idx_e, class_name, EXCEPTION_NAME_LEN - 1)
|
|
self.ExceptionNames[idx_e + EXCEPTION_NAME_LEN - 1] = '\0'
|
|
if parent_name is not None:
|
|
string.strncpy(self.ExceptionParents + idx_e, parent_name, EXCEPTION_NAME_LEN - 1)
|
|
self.ExceptionParents[idx_e + EXCEPTION_NAME_LEN - 1] = '\0'
|
|
code_e: int = self.NextExceptionCode
|
|
self.NextExceptionCode += 1
|
|
self.ExceptionCodes[self.ExceptionCount] = code_e
|
|
self.ExceptionCount += 1
|
|
return code_e
|
|
def _builtin_exception_code(self, name: str) -> int:
|
|
"""Return the builtin exception code (per TPC EXCEPTION_CODE_MAP); 0 when not builtin."""
|
|
if name is None: return 0
|
|
if string.strcmp(name, "ValueError") == 0: return 1
|
|
if string.strcmp(name, "TypeError") == 0: return 2
|
|
if string.strcmp(name, "RuntimeError") == 0: return 3
|
|
if string.strcmp(name, "ZeroDivisionError") == 0: return 4
|
|
if string.strcmp(name, "IndexError") == 0: return 5
|
|
if string.strcmp(name, "KeyError") == 0: return 6
|
|
if string.strcmp(name, "IOError") == 0: return 7
|
|
if string.strcmp(name, "OSError") == 0: return 8
|
|
if string.strcmp(name, "AssertionError") == 0: return 9
|
|
if string.strcmp(name, "Exception") == 0: return 99
|
|
return 0
|
|
def lookup_exception_code(self, name: str) -> int:
|
|
"""Look up an exception code: builtin table first, then user table; 0 when neither matches."""
|
|
if name is None: return 0
|
|
bi: int = self._builtin_exception_code(name)
|
|
if bi != 0: return bi
|
|
if self.ExceptionNames is None: return 0
|
|
for i in range(int(self.ExceptionCount)):
|
|
if string.strcmp(self.ExceptionNames + i * EXCEPTION_NAME_LEN, name) == 0: return self.ExceptionCodes[i]
|
|
return 0
|
|
def lookup_exception_parent(self, name: str) -> str:
|
|
"""Look up a user exception's parent name; None when it is not a user exception."""
|
|
if name is None or self.ExceptionNames is None: return None
|
|
for i in range(int(self.ExceptionCount)):
|
|
if string.strcmp(self.ExceptionNames + i * EXCEPTION_NAME_LEN, name) == 0:
|
|
p_e: str = self.ExceptionParents + i * EXCEPTION_NAME_LEN
|
|
if p_e is not None and p_e[0] != '\0': return p_e
|
|
return None
|
|
return None
|
|
def is_exception_name(self, name: str) -> int:
|
|
"""Return 1 if name is a builtin or user exception class name."""
|
|
if name is None: return 0
|
|
if self._builtin_exception_code(name) != 0: return 1
|
|
if self.ExceptionNames is None: return 0
|
|
for i in range(int(self.ExceptionCount)):
|
|
if string.strcmp(self.ExceptionNames + i * EXCEPTION_NAME_LEN, name) == 0: return 1
|
|
return 0
|
|
def collect_descendant_codes(self, name: str, out_codes: int*, max_codes: int) -> int:
|
|
"""Collect the codes of name and all its descendant exceptions into out_codes; returns the count."""
|
|
if name is None or out_codes is None: return 0
|
|
self._exception_codes_tmp_count = 0
|
|
self._collect_desc_codes_rec(name, out_codes, max_codes, 0)
|
|
return self._exception_codes_tmp_count
|
|
def _collect_desc_codes_rec(self, name: str, out_codes: int*, max_codes: int, depth: int) -> None:
|
|
"""Recursively collect name's code plus all descendant codes."""
|
|
if depth > 32: return
|
|
code_e: int = self.lookup_exception_code(name)
|
|
if code_e != 0 and self._exception_codes_tmp_count < max_codes:
|
|
# Deduplicate (guards against inheritance cycles).
|
|
dup: int = 0
|
|
for i in range(int(self._exception_codes_tmp_count)):
|
|
if out_codes[i] == code_e:
|
|
dup = 1
|
|
break
|
|
if dup == 0:
|
|
out_codes[self._exception_codes_tmp_count] = code_e
|
|
self._exception_codes_tmp_count += 1
|
|
# Recurse over user exceptions whose parent is name.
|
|
if self.ExceptionNames is None: return
|
|
for i in range(int(self.ExceptionCount)):
|
|
cname_e: str = self.ExceptionNames + i * EXCEPTION_NAME_LEN
|
|
pname_e: str = self.ExceptionParents + i * EXCEPTION_NAME_LEN
|
|
if pname_e is not None and pname_e[0] != '\0':
|
|
if string.strcmp(pname_e, name) == 0: self._collect_desc_codes_rec(cname_e, out_codes, max_codes, depth + 1)
|
|
def _is_exception_class_def(self, cd: ast.ClassDef*) -> int:
|
|
"""Return 1 if the class def is an exception class (base is Exception or a registered exception)."""
|
|
if cd is None or cd.bases is None: return 0
|
|
bases_e: list[ast.AST*]* = cd.bases
|
|
bn_e: sizet = len(bases_e)
|
|
for bi_e in range(int(bn_e)):
|
|
base_e: ast.AST* = bases_e.get(bi_e)
|
|
if base_e is None: continue
|
|
base_name_e: str = None
|
|
if base_e.kind() == ast.ASTKind.Name: base_name_e = (ast.Name*(base_e)).id
|
|
elif base_e.kind() == ast.ASTKind.Attribute:
|
|
base_at_e: ast.Attribute* = ast.Attribute*(base_e)
|
|
if base_at_e.attr is not None: base_name_e = base_at_e.attr
|
|
if base_name_e is None: continue
|
|
if string.strcmp(base_name_e, "Exception") == 0: return 1
|
|
if self.is_exception_name(base_name_e) != 0: return 1
|
|
return 0
|
|
def _get_exception_parent_name(self, cd: ast.ClassDef*) -> str:
|
|
"""Return the real parent name of an exception class (first registered non-Exception base); None otherwise."""
|
|
if cd is None or cd.bases is None: return None
|
|
bases_e2: list[ast.AST*]* = cd.bases
|
|
bn_e2: sizet = len(bases_e2)
|
|
for bi_e2 in range(int(bn_e2)):
|
|
base_e2: ast.AST* = bases_e2.get(bi_e2)
|
|
if base_e2 is None: continue
|
|
base_name_e2: str = None
|
|
if base_e2.kind() == ast.ASTKind.Name: base_name_e2 = (ast.Name*(base_e2)).id
|
|
elif base_e2.kind() == ast.ASTKind.Attribute:
|
|
base_at_e2: ast.Attribute* = ast.Attribute*(base_e2)
|
|
if base_at_e2.attr is not None: base_name_e2 = base_at_e2.attr
|
|
if base_name_e2 is None: continue
|
|
if string.strcmp(base_name_e2, "Exception") != 0 and self.is_exception_name(base_name_e2) != 0: return base_name_e2
|
|
return None
|
|
def _register_exception_class_def(self, trans: HT.Translator*, cd: ast.ClassDef*) -> int:
|
|
"""Register an exception class (assign code + record parent) without emitting a struct; returns 0."""
|
|
if cd is None or cd.name is None: return 0
|
|
parent_e: str = self._get_exception_parent_name(cd)
|
|
self.register_exception(cd.name, parent_e)
|
|
return 0
|
|
def _register_class_meta_list(self, pool: memhub.MemBuddy*, kind: int, cls: str, value_node: ast.AST*) -> None:
|
|
"""Parse a class-body __provides__/__requires__ list[str] literal and register its entries."""
|
|
if value_node is None or cls is None: return
|
|
if value_node.kind() != ast.ASTKind.List: return
|
|
lst_rcm: ast.List* = ast.List*(value_node)
|
|
if lst_rcm.elts is None: return
|
|
elts_rcm: list[ast.AST*]* = lst_rcm.elts
|
|
en_rcm: sizet = len(elts_rcm)
|
|
for ei_rcm in range(int(en_rcm)):
|
|
el_rcm: ast.AST* = elts_rcm.get(ei_rcm)
|
|
if el_rcm is None or el_rcm.kind() != ast.ASTKind.Constant: continue
|
|
ec_rcm: ast.Constant* = ast.Constant*(el_rcm)
|
|
if ec_rcm.str_val is not None: self.register_class_meta(pool, kind, cls, ec_rcm.str_val)
|
|
def register_method_meta(self, class_name: str, method_name: str, meta: int) -> None:
|
|
"""Register method metadata (idempotent)."""
|
|
if class_name is None or method_name is None or meta == 0: return
|
|
# Lazy initialization.
|
|
if self.MethodMetaKeys is None:
|
|
self.MethodMetaKeys = CompilerFactory.factory.PermPool.alloc(META_MAX * META_KEY_LEN)
|
|
self.MethodMetaFlags = CompilerFactory.factory.PermPool.alloc(META_MAX * 4)
|
|
if self.MethodMetaKeys is None or self.MethodMetaFlags is None: return
|
|
string.memset(self.MethodMetaKeys, 0, META_MAX * META_KEY_LEN)
|
|
string.memset(self.MethodMetaFlags, 0, META_MAX * 4)
|
|
# Build key "ClassName.method".
|
|
cl_len: sizet = string.strlen(class_name)
|
|
mn_len: sizet = string.strlen(method_name)
|
|
key_buf: bytes = stdlib.malloc(cl_len + mn_len + 2)
|
|
if key_buf is None: return
|
|
viperlib.snprintf(key_buf, cl_len + mn_len + 2, "%s.%s", class_name, method_name)
|
|
# Update duplicate entry.
|
|
i: int
|
|
for i in range(int(self.MethodMetaCount)):
|
|
if string.strcmp(self.MethodMetaKeys + i * META_KEY_LEN, key_buf) == 0:
|
|
flags_ptr: int* = (int*)(uint64(self.MethodMetaFlags) + i * 4)
|
|
# OR-combine flags: a getter then setter/deleter registers repeatedly,
|
|
# so whole-value replacement would drop GETTER and crash downstream mlirlite.
|
|
flags_ptr[0] = flags_ptr[0] | meta
|
|
stdlib.free(key_buf)
|
|
return
|
|
if self.MethodMetaCount < META_MAX:
|
|
# Slot is META_KEY_LEN=128 bytes; clamp strncpy to avoid overflowing the next slot.
|
|
string.strncpy(self.MethodMetaKeys + self.MethodMetaCount * META_KEY_LEN, key_buf, META_KEY_LEN - 1)
|
|
flags_ptr2: int* = (int*)(uint64(self.MethodMetaFlags) + self.MethodMetaCount * 4)
|
|
flags_ptr2[0] = meta
|
|
self.MethodMetaCount += 1
|
|
stdlib.free(key_buf)
|
|
def lookup_method_meta(self, class_name: str, method_name: str) -> int:
|
|
"""Look up the method-metadata bitmask; 0=not registered or not a decorator method."""
|
|
if class_name is None or method_name is None: return 0
|
|
if self.MethodMetaKeys is None or self.MethodMetaCount <= 0: return 0
|
|
cl_len: sizet = string.strlen(class_name)
|
|
mn_len: sizet = string.strlen(method_name)
|
|
key_buf: bytes = stdlib.malloc(cl_len + mn_len + 2)
|
|
if key_buf is None: return 0
|
|
viperlib.snprintf(key_buf, cl_len + mn_len + 2, "%s.%s", class_name, method_name)
|
|
found: int = 0
|
|
i: int
|
|
for i in range(int(self.MethodMetaCount)):
|
|
if string.strcmp(self.MethodMetaKeys + i * META_KEY_LEN, key_buf) == 0:
|
|
flags_ptr: int* = (int*)(uint64(self.MethodMetaFlags) + i * 4)
|
|
found = flags_ptr[0]
|
|
break
|
|
stdlib.free(key_buf)
|
|
return found
|
|
def _is_marker_base(self, base_name: str) -> int:
|
|
"""Return 1 if the base name is a marker base class (Object/CVTable/Exception/CEnum/Enum/CStruct/Struct/CUnion/REnum)."""
|
|
if base_name is None: return 0
|
|
if string.strcmp(base_name, "Object") == 0: return 1
|
|
if string.strcmp(base_name, "CVTable") == 0: return 1
|
|
if string.strcmp(base_name, "Exception") == 0: return 1
|
|
if string.strcmp(base_name, "CEnum") == 0: return 1
|
|
if string.strcmp(base_name, "Enum") == 0: return 1
|
|
if string.strcmp(base_name, "CStruct") == 0: return 1
|
|
if string.strcmp(base_name, "Struct") == 0: return 1
|
|
if string.strcmp(base_name, "CUnion") == 0: return 1
|
|
if string.strcmp(base_name, "REnum") == 0: return 1
|
|
return 0
|
|
def _get_base_name(self, base_node: ast.AST*, pool: memhub.MemBuddy* = None, from_imports: str = None) -> str:
|
|
"""Extract the base class name from a base AST node; Subscript nodes also build the specialized name; None on failure."""
|
|
if base_node is None: return None
|
|
k: int = base_node.kind()
|
|
# Name(id) form.
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(base_node)
|
|
return nm.id
|
|
# Attribute(attr) form.
|
|
if k == ast.ASTKind.Attribute:
|
|
at: ast.Attribute* = ast.Attribute*(base_node)
|
|
return at.attr
|
|
# Subscript(value, slice) form, e.g. GSListNode[Value].
|
|
if k == ast.ASTKind.Subscript:
|
|
sub: ast.Subscript* = ast.Subscript*(base_node)
|
|
if sub is None or sub.value is None or sub.slice is None: return None
|
|
# Extract the base class name (e.g. "GSListNode").
|
|
base_nm: str = self._get_base_name(sub.value, pool, from_imports)
|
|
if base_nm is None: return None
|
|
# Extract the type arg name (e.g. "Value") and normalize it like the generator.
|
|
slice_k: int = sub.slice.kind()
|
|
arg_nm: str = None
|
|
if slice_k == ast.ASTKind.Name:
|
|
sl_nm: ast.Name* = ast.Name*(sub.slice)
|
|
arg_nm = sl_nm.id
|
|
# Fall back to the base name when the type arg cannot be extracted.
|
|
if arg_nm is None: return base_nm
|
|
# Without a pool, fall back to the base name (cannot build the specialized name).
|
|
if pool is None: return base_nm
|
|
arg_norm: str = self.Gen.TypeGenResolve._norm_type_arg_name(pool, arg_nm, from_imports)
|
|
if arg_norm is not None: arg_nm = arg_norm
|
|
# Build the specialized name "GSListNode[Value]".
|
|
base_len: sizet = string.strlen(base_nm)
|
|
arg_len: sizet = string.strlen(arg_nm)
|
|
total_len: sizet = base_len + arg_len + 3 # "[" + arg + "]" + NUL
|
|
mangled: str = pool.alloc(total_len)
|
|
if mangled is None:
|
|
return base_nm # fallback
|
|
mangled[0] = '\0'
|
|
string.strcat(mangled, base_nm)
|
|
string.strcat(mangled, "[")
|
|
string.strcat(mangled, arg_nm)
|
|
string.strcat(mangled, "]")
|
|
return mangled
|
|
return None
|
|
def _get_parent_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> str:
|
|
"""Return the ClassDef's real parent name; None when there is no real parent."""
|
|
if cd is None or cd.bases is None: return None
|
|
pool: memhub.MemBuddy* = None
|
|
if trans is not None: pool = trans.Pool
|
|
bases: list[ast.AST*]* = cd.bases
|
|
bn: sizet = len(bases)
|
|
if bn == 0: return None
|
|
for bi in range(int(bn)):
|
|
base_node: ast.AST* = bases.get(bi)
|
|
if base_node is None: continue
|
|
_fi_pc: str = None
|
|
if trans is not None: _fi_pc = trans._from_imports
|
|
bname: str = self._get_base_name(base_node, pool, _fi_pc)
|
|
if bname is not None:
|
|
# Resolve base aliases (from t import CStruct as cs -> cs is CStruct).
|
|
# Otherwise find_struct("cs") fails and the class is deferred forever.
|
|
bname = self._resolve_base_alias(bname, trans)
|
|
if self._is_marker_base(bname) == 0: return bname
|
|
return None
|
|
def _resolve_base_alias(self, base_name: str, trans: HT.Translator* = None) -> str:
|
|
"""Resolve a base alias (from t import CStruct as cs -> base cs is actually CStruct)."""
|
|
if base_name is None or trans is None or trans._from_imports is None: return base_name
|
|
orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, base_name)
|
|
if orig is not None:
|
|
# lookup_from_import_original returns a non-NUL-terminated interior pointer;
|
|
# copy and cut at the space so strcmp gets a clean original name.
|
|
og_len: sizet = 0
|
|
while orig[og_len] != ' ' and orig[og_len] != '\0': og_len += 1
|
|
if og_len > 0:
|
|
og_buf: str = CompilerFactory.factory.PermPool.alloc(og_len + 1)
|
|
if og_buf is not None:
|
|
string.strncpy(og_buf, orig, og_len)
|
|
og_buf[og_len] = '\0'
|
|
return og_buf
|
|
return base_name
|
|
def _collect_parents(self, pool: memhub.MemBuddy*, cd: ast.ClassDef*, parents_buf: sizet*, max_parents: int, trans: HT.Translator* = None) -> int:
|
|
"""Collect all non-marker parent names into parents_buf and return the count."""
|
|
if pool is None or cd is None or cd.bases is None or parents_buf is None: return 0
|
|
bases: list[ast.AST*]* = cd.bases
|
|
bn: sizet = len(bases)
|
|
count: int = 0
|
|
for bi in range(int(bn)):
|
|
if count >= max_parents: break
|
|
base_node: ast.AST* = bases.get(bi)
|
|
if base_node is None: continue
|
|
_fi_cp: str = None
|
|
if trans is not None: _fi_cp = trans._from_imports
|
|
bname: str = self._get_base_name(base_node, pool, _fi_cp)
|
|
if bname is not None:
|
|
# Resolve base alias (same as _get_parent_class, see _resolve_base_alias).
|
|
bname = self._resolve_base_alias(bname, trans)
|
|
if self._is_marker_base(bname) == 0:
|
|
parents_buf[count] = sizet(bname)
|
|
count += 1
|
|
return count
|
|
def _field_name_exists(self, names_buf: sizet*, count: int, name: str) -> int:
|
|
"""Return 1 if the field name already exists in names_buf (first-declared parent wins; duplicates skipped)."""
|
|
if names_buf is None or name is None: return 0
|
|
for fi in range(int(count)):
|
|
addr: sizet = names_buf[fi]
|
|
if addr != 0:
|
|
existing: str = rawptr(addr)
|
|
if existing is not None and string.strcmp(existing, name) == 0: return 1
|
|
return 0
|
|
def _class_body_has_field(self, cd: ast.ClassDef*) -> int:
|
|
"""Return 1 if the class body declares fields (AnnAssign); 0=methods-only/no-field class."""
|
|
if cd is None or cd.children is None: return 0
|
|
cn: sizet = len(cd.children)
|
|
for ci in range(int(cn)):
|
|
stmt: ast.AST* = cd.children.get(ci)
|
|
if stmt is None: continue
|
|
if stmt.kind() == ast.ASTKind.AnnAssign: return 1
|
|
return 0
|
|
def _trigger_base_specialization(self, trans: HT.Translator*, cd: ast.ClassDef*):
|
|
"""Trigger generic specialization for Subscript bases (e.g. GSListNode[Value])."""
|
|
if trans is None or cd is None or cd.bases is None: return
|
|
pool: memhub.MemBuddy* = trans.Pool
|
|
bases: list[ast.AST*]* = cd.bases
|
|
bn: sizet = len(bases)
|
|
if bn == 0: return
|
|
for bi in range(int(bn)):
|
|
base_node: ast.AST* = bases.get(bi)
|
|
if base_node is None: continue
|
|
bk: int = base_node.kind()
|
|
if bk != ast.ASTKind.Subscript: continue
|
|
sub: ast.Subscript* = ast.Subscript*(base_node)
|
|
if sub is None or sub.value is None or sub.slice is None: continue
|
|
# Extract the generic class name (e.g. "GSListNode").
|
|
gen_class_name: str = self._get_base_name(sub.value, pool)
|
|
if gen_class_name is None: continue
|
|
# Skip marker base classes.
|
|
if self._is_marker_base(gen_class_name) == 1: continue
|
|
# Extract the type arg name (e.g. "Value").
|
|
slice_k: int = sub.slice.kind()
|
|
arg_nm: str = None
|
|
if slice_k == ast.ASTKind.Name:
|
|
sl_nm: ast.Name* = ast.Name*(sub.slice)
|
|
arg_nm = sl_nm.id
|
|
if arg_nm is None: continue
|
|
# Build type_args (elem_size=8: str is a pointer; 1 would overlap elements).
|
|
type_args: list[str]* = list[str](pool, 8)
|
|
if type_args is None: continue
|
|
type_args.append(arg_nm)
|
|
# Trigger specialization (cached, so repeated calls are safe).
|
|
self.Gen.ClassGenGeneric._specialize_generic_class(trans, gen_class_name, type_args)
|
|
def _has_any_func_cvtable(self, cd: ast.ClassDef*) -> int:
|
|
"""Return 1 if any non-constructor function in the class is marked @t.CVTable."""
|
|
if cd is None or cd.children is None: return 0
|
|
children: list[ast.AST*]* = cd.children
|
|
cn: sizet = len(children)
|
|
for ci in range(int(cn)):
|
|
stmt: ast.AST* = children.get(ci)
|
|
if stmt is None: continue
|
|
if stmt.kind() != ast.ASTKind.FunctionDef: continue
|
|
fd: ast.FunctionDef* = ast.FunctionDef*(stmt)
|
|
if fd is None or fd.name is None: continue
|
|
# Skip constructors.
|
|
if string.strcmp(fd.name, "__init__") == 0: continue
|
|
if string.strcmp(fd.name, "__before_init__") == 0: continue
|
|
if string.strcmp(fd.name, "__new__") == 0: continue
|
|
if fd.decorator_list is not None:
|
|
if self._has_decorator(fd.decorator_list, "CVTable") == 1: return 1
|
|
return 0
|
|
def _should_method_be_virtual(self, cd: ast.ClassDef*, fd: ast.FunctionDef*, trans: HT.Translator* = None) -> int:
|
|
"""Decide whether a method belongs in the vtable; 1=include, 0=exclude."""
|
|
if fd is None: return 0
|
|
# 1. Function-level @t.NoVTable has top priority (exclude).
|
|
if fd.decorator_list is not None:
|
|
if self._has_decorator(fd.decorator_list, "NoVTable") == 1: return 0
|
|
# 2. Function-level @t.CVTable (explicitly include).
|
|
if self._has_decorator(fd.decorator_list, "CVTable") == 1: return 1
|
|
|
|
# 3. Class-level @t.CVTable.
|
|
if cd is not None and cd.decorator_list is not None:
|
|
if self._has_decorator(cd.decorator_list, "CVTable") == 1: return 1
|
|
|
|
# 4. Check inheritance.
|
|
parent_name: str = self._get_parent_class(cd, trans)
|
|
if parent_name is not None:
|
|
# SHA1-aware parent lookup to avoid cross-module same-name collisions.
|
|
is_nvt: int = 0
|
|
if trans is not None:
|
|
p_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(parent_name, trans.ModuleSha1)
|
|
if p_entry is None:
|
|
# Fall back to a global lookup for cross-module parents; consistent with
|
|
# field flattening, otherwise a NoVTable parent is misread as having a vptr.
|
|
p_entry = self.Gen.StructGen.find_struct(parent_name)
|
|
if p_entry is not None: is_nvt = p_entry.IsNoVTable
|
|
else: is_nvt = self.Gen.StructGen.is_novtable_by_name(parent_name)
|
|
if is_nvt != 1:
|
|
# vtable follows the parent: parent virtual -> method is virtual, else static.
|
|
# Matches _detect_vtable_status to keep parent/child layouts aligned.
|
|
if p_entry is None:
|
|
return 1 # unknown parent: conservatively make it virtual
|
|
if p_entry.HasVTable == 1: return 1
|
|
return 0
|
|
|
|
# 5. Default: exclude.
|
|
return 0
|
|
def _detect_vtable_status(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
|
|
"""Detect whether the class should enable a vtable."""
|
|
if cd is None: return 0
|
|
|
|
# Check class decorators.
|
|
if cd.decorator_list is not None:
|
|
# @t.CVTable has top priority: enable directly.
|
|
if self._has_decorator(cd.decorator_list, "CVTable") == 1: return 1
|
|
# @t.NoVTable disables at class level, but still check function-level @t.CVTable.
|
|
if self._has_decorator(cd.decorator_list, "NoVTable") == 1: return self._has_any_func_cvtable(cd)
|
|
|
|
# Check for a real parent (inheritance enables CVTable automatically).
|
|
parent_name: str = self._get_parent_class(cd, trans)
|
|
if parent_name is not None:
|
|
# SHA1-aware parent lookup to avoid cross-module same-name collisions.
|
|
is_nvt: int = 0
|
|
if trans is not None:
|
|
p_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(parent_name, trans.ModuleSha1)
|
|
if p_entry is None:
|
|
# Fall back to a global lookup for cross-module parents.
|
|
p_entry = self.Gen.StructGen.find_struct(parent_name)
|
|
if p_entry is not None: is_nvt = p_entry.IsNoVTable
|
|
else: is_nvt = self.Gen.StructGen.is_novtable_by_name(parent_name)
|
|
if is_nvt == 1:
|
|
# Parent is NoVTable; check function-level @t.CVTable.
|
|
return self._has_any_func_cvtable(cd)
|
|
# vtable follows the parent: parent virtual -> child virtual, else static.
|
|
# A vptr mismatch would shift child fields and corrupt runtime access.
|
|
if p_entry is None: return 1 # unknown/missing parent: conservatively enable vtable
|
|
if p_entry.HasVTable == 1: return 1
|
|
return 0
|
|
|
|
# Default (no decorator, no parent): check function-level @t.CVTable.
|
|
return self._has_any_func_cvtable(cd)
|
|
def InitClassGen(self) -> int:
|
|
"""Initialize the ClassGen family fields (called by Generator during construction)."""
|
|
if self is None: return 1
|
|
self.MethodMetaKeys = None
|
|
self.MethodMetaFlags = None
|
|
self.MethodMetaCount = 0
|
|
# REnum variant registry lives on the compile-time factory (perm_pool), so it is
|
|
# restored here, not cleared: clearing loses variants registered by earlier work.
|
|
self.RenumClassNames = CompilerFactory.factory.RenumClassNames
|
|
self.RenumClassVariantCounts = CompilerFactory.factory.RenumClassVariantCounts
|
|
self.RenumVariantNames = CompilerFactory.factory.RenumVariantNames
|
|
self.RenumVariantTags = CompilerFactory.factory.RenumVariantTags
|
|
self.RenumClassCount = CompilerFactory.factory.RenumClassCount
|
|
self.RenumAliasClass = CompilerFactory.factory.RenumAliasClass
|
|
self.RenumAliasField = CompilerFactory.factory.RenumAliasField
|
|
self.RenumAliasIndex = CompilerFactory.factory.RenumAliasIndex
|
|
self.RenumAliasCount = CompilerFactory.factory.RenumAliasCount
|
|
self.SpecKeys = None
|
|
self.SpecNames = None
|
|
self.SpecCount = 0
|
|
self.GenericClassNames = None
|
|
self.GenericClassNodes = None
|
|
self.GenericClassSha1s = None
|
|
self.GenericClassFiles = None
|
|
self.GenericClassTpParams = None
|
|
self.GenericFuncKeys = None
|
|
self.GenericFuncNodes = None
|
|
self.GenericFuncSha1s = None
|
|
self.GenericFuncFiles = None
|
|
self.GenericFuncTpParams = None
|
|
self.VtFullNames = None
|
|
self.VtMethodNames = None
|
|
self.VtParentSha1s = None
|
|
self.VtMethodCount = 0
|
|
self.RenumAliasTy = CompilerFactory.factory.RenumAliasTy
|
|
self.RenumAliasVariant = CompilerFactory.factory.RenumAliasVariant
|
|
self.ClassMetaKinds = None
|
|
self.ClassMetaClss = None
|
|
self.ClassMetaFields = None
|
|
self.ClassMetaCount = 0
|
|
self.DecoratorFuncCount = 0
|
|
self.DecoratorFuncNames = None
|
|
self.DecoratorWrapNames = None
|
|
self.DecoratorArgs = None
|
|
self.ExceptionCount = 0
|
|
self.ExceptionNames = None
|
|
self.ExceptionParents = None
|
|
self.ExceptionCodes = None
|
|
self.NextExceptionCode = 0
|
|
self._exception_codes_tmp_count = 0
|
|
self.GenericSpecDepth = 0
|
|
return 0
|