feat(define): 支持字符串 list 宏常量与 *list 解包

- AssignGenMixin:_register_cdefine_list 校验元素为字符串常量或 *other_list,\n 连接,不建全局

- TypeGenBase/TypeGenCDef:新增宏 list 常量表(register/lookup/clear),与 DefineT 同池
This commit is contained in:
TermiNexus committed 2026-10-24 00:14:08 +08:00
1 parent 8461a3da47
commit 52f3ff7f56
3 files changed
+146 -2

No files matched your search

+91 -1
View File
@@ -720,7 +720,93 @@ novirtual class AssignGenMixin:
return 0
return 0
# _register_cdefine_from_value - register a CDefine constant (float or int)
# _register_cdefine_from_value - register a DefineT constant (float or int)
def _register_cdefine_list(self, pool: memhub.MemBuddy*, name: str, list_node: ast.AST*) -> int:
"""Register `define NAME = [ ... ]` as a compile-time list[str] macro constant.
Elements are string literals, or `*OTHER` where OTHER is a macro list defined earlier
(the parser renders a display-level `*X` as UnaryOp(Deref, X); both shapes are accepted).
The value is stored '\n'-joined and no LLVM global is created.
Returns 1=ok / 0=failed (failed paths already fatal-errored).
"""
if pool is None or name is None or list_node is None: return 0
ln: ast.List* = ast.List*(list_node)
if ln is None or ln.elts is None: return 0
elts: list[ast.AST*]* = ln.elts
n: sizet = len(elts)
tp: memhub.MemBuddy* = pool
# Pass 1: total byte length (elements + one '\n' separator each).
total: sizet = 0
for i in range(int(n)):
e: ast.AST* = elts.get(i)
if e is None: return 0
if e.kind() == ast.ASTKind.Constant:
c: ast.Constant* = ast.Constant*(e)
if c.const_kind != ast.CONST_STR:
self.Gen.TypeGenErr.fatal_error(e, f"define list '{name}' elements must be string constants", "TypeError")
return 0
total += string.strlen(c.str_val) + 1
elif self._cdefine_list_unpack_operand(e) is not None:
prev: str = self._cdefine_list_lookup_unpack(e, name)
if prev is None: return 0
total += string.strlen(prev) + 1
else:
self.Gen.TypeGenErr.fatal_error(e, f"define list '{name}' elements must be string constants or `*other_list` unpacking", "TypeError")
return 0
buf: str = tp.alloc(total + 1)
if buf is None:
self.Gen.TypeGenErr.fatal_error(None, f"define list '{name}' value buffer allocation failed", "MemoryError")
return 0
# Pass 2: copy, '\n'-joined.
pos: sizet = 0
for i in range(int(n)):
e = elts.get(i)
src: str = None
if e.kind() == ast.ASTKind.Constant:
c2: ast.Constant* = ast.Constant*(e)
src = c2.str_val
else:
src = self._cdefine_list_lookup_unpack(e, name)
if src is None: return 0
sl: sizet = string.strlen(src)
if pos > 0:
buf[pos] = char(10)
pos += 1
string.memcpy(buf + pos, src, sl)
pos += sl
buf[pos] = '\0'
self.Gen.TypeGenCDef.register_cdefine_string_list(pool, name, buf)
return 1
def _cdefine_list_unpack_operand(self, node: ast.AST*) -> ast.AST*:
"""Operand of a define-list unpacking element (`*X` -> X); None if not an unpacking form."""
if node is None: return None
if node.kind() == ast.ASTKind.Starred:
st: ast.Starred* = ast.Starred*(node)
if st is None: return None
return st.value
if node.kind() == ast.ASTKind.UnaryOp:
uo: ast.UnaryOp* = ast.UnaryOp*(node)
if uo is not None and uo.op == ast.OpKind.Deref: return uo.operand
return None
def _cdefine_list_lookup_unpack(self, node: ast.AST*, owner: str) -> str:
"""Resolve `*OTHER` to the '\n'-joined value of an earlier macro list; None on failure."""
op: ast.AST* = self._cdefine_list_unpack_operand(node)
if op is None or op.kind() != ast.ASTKind.Name:
self.Gen.TypeGenErr.fatal_error(node, f"define list '{owner}': `*` unpacking needs a macro-list name", "TypeError")
return None
nm: ast.Name* = ast.Name*(op)
if nm is None or nm.id is None: return None
prev: str = self.Gen.TypeGenCDef.lookup_cdefine_string_list(nm.id)
if prev is None:
self.Gen.TypeGenErr.fatal_error(op, f"define list '{owner}': '{nm.id}' is not a macro list defined earlier in this file", "NameError")
return None
return prev
def _register_cdefine_from_value(self, pool: memhub.MemBuddy*, name: str, val_node: ast.AST*, annot: ast.AST* = None) -> None:
"""Register a CDefine constant (double for floats, else integer)."""
if pool is None or name is None: return
@@ -757,6 +843,10 @@ novirtual class AssignGenMixin:
if uu_cd is not None and uu_cd.op == ast.OpKind.USub and uu_cd.operand is not None and uu_cd.operand.kind() == ast.ASTKind.Constant:
ic_cd: ast.Constant* = ast.Constant*(uu_cd.operand)
if ic_cd is not None and ic_cd.const_kind == ast.CONST_FLOAT: val_node = uu_cd.operand
# Macro list: `define NAME = [ ... ]` -> compile-time list[str] constant, no global.
if val_node is not None and val_node.kind() == ast.ASTKind.List:
self._register_cdefine_list(pool, name, val_node)
return
if val_node is not None and val_node.kind() == ast.ASTKind.Constant:
cn_fp: ast.Constant* = ast.Constant*(val_node)
if cn_fp.const_kind == ast.CONST_STR:
+5
View File
@@ -57,6 +57,11 @@ novirtual class TypeGenBase:
CDefineNameIdx: sizet*
CDefineNameIdxCap: int
CDefineNameIdxBuilt: int
# --- `define NAME = [ ... ]` 宏 list(list[str],编译期常量,不建全局) ---
# 值与 CDefineValues 分开存:名字不变,值是 '\n' 连接的元素串(元素内不含换行),
# 供 `*OTHER_LIST` 解包与后续编译期消费者读取。与 CDefineNames 同池同生命周期。
CDefineListNames: list[str]*
CDefineListValues: list[str]*
# --- Cross-module CDefine lookup cache (PermPool-allocated, reused across files) ---
# Not cleared by reset_to(mark2)/clear_cdefine_constants, so CONST_INT lookup happens once.
+50 -1
View File
@@ -248,7 +248,52 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
# Cross-module string CDefine reads call this + set_cdefine_found; missing the flag degrades to int 0 -> NULL crash.
self.CDefineIsStr = 1
self.CDefineFound = 1
# Cross-module CDefine lookup cache (PermPool-allocated, reused across files)
def register_cdefine_string_list(self, pool: memhub.MemBuddy*, name: str, joined: str) -> None:
"""Register a macro-list DefineT constant (`define NAME = [...]`, '\n'-joined value).
The list is compile-time only: no LLVM global is created (an unannotated module-level
string list has no representable initializer). Consumers are compile-time readers, e.g.
`*OTHER_NAME` unpacking inside another define list.
"""
if pool is None or name is None or joined is None: return
tp: memhub.MemBuddy* = self._cdefine_table_pool(pool)
if tp is None:
self.Gen.TypeGenErr.fatal_error(None, "DefineT list table pool unavailable", "MemoryError")
return
if self.CDefineListNames is None or self.CDefineListValues is None:
ln_l: list[str]* = list[str](tp, 16)
lv_l: list[str]* = list[str](tp, 16)
if ln_l is None or lv_l is None:
self.Gen.TypeGenErr.fatal_error(None, "DefineT list table allocation failed", "MemoryError")
return
self.CDefineListNames = ln_l
self.CDefineListValues = lv_l
nlen_l: sizet = string.strlen(name)
ncopy_l: str = tp.alloc(nlen_l + 1)
if ncopy_l is None:
self.Gen.TypeGenErr.fatal_error(None, f"DefineT list '{name}' name buffer allocation failed", "MemoryError")
return
string.strcpy(ncopy_l, name)
vlen_l: sizet = string.strlen(joined)
vcopy_l: str = tp.alloc(vlen_l + 1)
if vcopy_l is None:
self.Gen.TypeGenErr.fatal_error(None, f"DefineT list '{name}' value buffer allocation failed", "MemoryError")
return
string.strcpy(vcopy_l, joined)
self.CDefineListNames.append(ncopy_l)
self.CDefineListValues.append(vcopy_l)
def lookup_cdefine_string_list(self, name: str) -> str:
"""Return the '\n'-joined value of a macro-list DefineT constant; None if absent."""
if name is None or self.CDefineListNames is None or self.CDefineListValues is None: return None
n_l: sizet = len(self.CDefineListNames)
for i in range(int(n_l)):
nm_l: str = self.CDefineListNames.get(sizet(i))
if nm_l is not None and string.strcmp(nm_l, name) == 0:
return self.CDefineListValues.get(sizet(i))
return None
# Cross-module DefineT lookup cache (PermPool-allocated, reused across files)
def register_xmod_cdefine(self, name: str, value_str: str, is_double: int, is_str: int) -> None:
"""Cache a cross-module CDefine lookup result in the perm_pool global cache.
@@ -450,6 +495,10 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
if self.CDefineDoubleFlags is not None: self.CDefineDoubleFlags.clear()
if self.CDefineStrFlags is not None: self.CDefineStrFlags.clear()
if self.CDefineBits is not None: self.CDefineBits.clear()
# Macro-list table is per-file too: a stale entry would let `*NAME` resolve a list that
# belongs to another file's compilation unit.
if self.CDefineListNames is not None: self.CDefineListNames.clear()
if self.CDefineListValues is not None: self.CDefineListValues.clear()
# Clear index slots (store index+1; 0=empty) and reset Built to 0 (empty table and index
# in sync, matching a freshly built table before its first register).
if self.CDefineNameIdx is not None and self.CDefineNameIdxCap > 0: