Files
ViperC/App/lib/core/Gen/Class/ClassGenBase.vp
T

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