feat: field-move Drop (partial/nested/ptr), stmt/pat macros, Windows runtime
ci / build (ubuntu) (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled

Sessions 70–74: partialMovedPaths + ptrAliases in bootstrap/selfhost CBE,
remaining drops after field moves, macro stmt/pat fragments, runtime_win.c
and MinGW hello CI, examples + drop-move smoke coverage, QUALITY_PLAN update.
This commit is contained in:
2026-07-21 12:29:53 +03:00
parent fe3b1e8b6a
commit a939f74b1b
22 changed files with 2672 additions and 117 deletions
+9 -1
View File
@@ -128,6 +128,8 @@ module Ast {
const ekStringInterp: int = 26;
const ekClosure: int = 27;
const ekMacroCall: int = 28; // name!(args) — expanded before sema
const ekMacroStmt: int = 29; // `$s:stmt` arg wrapper (expand only)
const ekMacroPat: int = 30; // `$p:pat` arg wrapper (expand only)
struct ExprList {
expr: *Expr,
@@ -188,6 +190,9 @@ module Ast {
// Match arms (for ekMatch)
matchArms: *MatchArm,
matchArmCount: int,
// Macro fragment wrappers (session 72)
macroStmt: *Stmt, // ekMacroStmt
macroPat: *Pattern, // ekMacroPat
}
// ---------------------------------------------------------------------------
@@ -387,7 +392,8 @@ module Ast {
refType: null as *TypeExpr, refBlock: null as *Block,
genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0,
structName: "", structFieldCount: 0,
callArgs: null as *ExprList, callArgCount: 0 };
callArgs: null as *ExprList, callArgCount: 0,
macroStmt: null as *Stmt, macroPat: null as *Pattern };
}
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
@@ -706,6 +712,8 @@ module Ast {
n.callArgCount = e.callArgCount;
n.matchArms = Ast_CloneMatchArm(e.matchArms);
n.matchArmCount = e.matchArmCount;
n.macroStmt = Ast_CloneStmt(e.macroStmt);
n.macroPat = Ast_ClonePattern(e.macroPat);
return n;
}
+495 -35
View File
@@ -76,6 +76,60 @@ module CBackend {
movedName5: String,
movedName6: String,
movedName7: String,
// Partial field moves: (var, field) pairs (session 70)
partialCount: int,
partialVar0: String,
partialField0: String,
partialVar1: String,
partialField1: String,
partialVar2: String,
partialField2: String,
partialVar3: String,
partialField3: String,
partialVar4: String,
partialField4: String,
partialVar5: String,
partialField5: String,
partialVar6: String,
partialField6: String,
partialVar7: String,
partialField7: String,
// Local name → typeName for remaining-field Drop lookup
localCount: int,
localName0: String,
localType0: String,
localName1: String,
localType1: String,
localName2: String,
localType2: String,
localName3: String,
localType3: String,
localName4: String,
localType4: String,
localName5: String,
localType5: String,
localName6: String,
localType6: String,
localName7: String,
localType7: String,
// Pointer aliases: p → bag when `p = &bag` (session 74)
aliasCount: int,
aliasPtr0: String,
aliasOwner0: String,
aliasPtr1: String,
aliasOwner1: String,
aliasPtr2: String,
aliasOwner2: String,
aliasPtr3: String,
aliasOwner3: String,
aliasPtr4: String,
aliasOwner4: String,
aliasPtr5: String,
aliasOwner5: String,
aliasPtr6: String,
aliasOwner6: String,
aliasPtr7: String,
aliasOwner7: String,
tmpCounter: int,
currentRetType: String,
// #line debug maps (E.4 selfhost parity)
@@ -130,6 +184,329 @@ module CBackend {
return true;
}
func CBE_AddPtrAlias(cbe: *CEmitter, ptrName: String, ownerName: String) {
if ptrName == null as String || String_Eq(ptrName, "") { return; }
if ownerName == null as String || String_Eq(ownerName, "") { return; }
// Update existing alias for same pointer
var i: int = 0;
while i < cbe.aliasCount {
var pn: String = "";
if i == 0 { pn = cbe.aliasPtr0; }
else if i == 1 { pn = cbe.aliasPtr1; }
else if i == 2 { pn = cbe.aliasPtr2; }
else if i == 3 { pn = cbe.aliasPtr3; }
else if i == 4 { pn = cbe.aliasPtr4; }
else if i == 5 { pn = cbe.aliasPtr5; }
else if i == 6 { pn = cbe.aliasPtr6; }
else if i == 7 { pn = cbe.aliasPtr7; }
if String_Eq(pn, ptrName) {
if i == 0 { cbe.aliasOwner0 = ownerName; }
else if i == 1 { cbe.aliasOwner1 = ownerName; }
else if i == 2 { cbe.aliasOwner2 = ownerName; }
else if i == 3 { cbe.aliasOwner3 = ownerName; }
else if i == 4 { cbe.aliasOwner4 = ownerName; }
else if i == 5 { cbe.aliasOwner5 = ownerName; }
else if i == 6 { cbe.aliasOwner6 = ownerName; }
else if i == 7 { cbe.aliasOwner7 = ownerName; }
return;
}
i = i + 1;
}
if cbe.aliasCount >= 8 { return; }
if cbe.aliasCount == 0 { cbe.aliasPtr0 = ptrName; cbe.aliasOwner0 = ownerName; }
else if cbe.aliasCount == 1 { cbe.aliasPtr1 = ptrName; cbe.aliasOwner1 = ownerName; }
else if cbe.aliasCount == 2 { cbe.aliasPtr2 = ptrName; cbe.aliasOwner2 = ownerName; }
else if cbe.aliasCount == 3 { cbe.aliasPtr3 = ptrName; cbe.aliasOwner3 = ownerName; }
else if cbe.aliasCount == 4 { cbe.aliasPtr4 = ptrName; cbe.aliasOwner4 = ownerName; }
else if cbe.aliasCount == 5 { cbe.aliasPtr5 = ptrName; cbe.aliasOwner5 = ownerName; }
else if cbe.aliasCount == 6 { cbe.aliasPtr6 = ptrName; cbe.aliasOwner6 = ownerName; }
else if cbe.aliasCount == 7 { cbe.aliasPtr7 = ptrName; cbe.aliasOwner7 = ownerName; }
cbe.aliasCount = cbe.aliasCount + 1;
}
func CBE_ResolvePtrAlias(cbe: *CEmitter, name: String) -> String {
var cur: String = name;
var guard: int = 0;
while guard < 8 {
var found: bool = false;
var owner: String = "";
var i: int = 0;
while i < cbe.aliasCount {
var pn: String = "";
var on: String = "";
if i == 0 { pn = cbe.aliasPtr0; on = cbe.aliasOwner0; }
else if i == 1 { pn = cbe.aliasPtr1; on = cbe.aliasOwner1; }
else if i == 2 { pn = cbe.aliasPtr2; on = cbe.aliasOwner2; }
else if i == 3 { pn = cbe.aliasPtr3; on = cbe.aliasOwner3; }
else if i == 4 { pn = cbe.aliasPtr4; on = cbe.aliasOwner4; }
else if i == 5 { pn = cbe.aliasPtr5; on = cbe.aliasOwner5; }
else if i == 6 { pn = cbe.aliasPtr6; on = cbe.aliasOwner6; }
else if i == 7 { pn = cbe.aliasPtr7; on = cbe.aliasOwner7; }
if String_Eq(pn, cur) {
owner = on;
found = true;
break;
}
i = i + 1;
}
if !found { break; }
cur = owner;
guard = guard + 1;
}
return cur;
}
/// Record alias if init is `&local` (hUnary Amp of hVar).
func CBE_TryRecordPtrAlias(cbe: *CEmitter, ptrName: String, init: *HirNode) {
if init == null as *HirNode { return; }
var n: *HirNode = init;
if n.kind == hLoad { n = n.child1; }
if n == null as *HirNode { return; }
if n.kind == hUnary && n.intValue == tkAmp {
let op: *HirNode = n.child1;
if op != null as *HirNode && op.kind == hVar {
CBE_AddPtrAlias(cbe, ptrName, op.strValue);
}
}
}
/// Base local name under load/field/deref chains (for partial field moves).
/// Resolves pointer aliases: p → bag when `p = &bag`.
func CBE_BaseVarName(cbe: *CEmitter, node: *HirNode) -> String {
if node == null as *HirNode { return ""; }
if node.kind == hVar {
return CBE_ResolvePtrAlias(cbe, node.strValue);
}
// Deref: *p → follow p's alias
if node.kind == hUnary && node.intValue == tkStar {
return CBE_BaseVarName(cbe, node.child1);
}
if node.kind == hLoad || node.kind == hFieldAccess || node.kind == hFieldPtr {
return CBE_BaseVarName(cbe, node.child1);
}
return "";
}
/// Back-compat: BaseVarName without emitter (no alias resolve).
func CBE_BaseVarNameRaw(node: *HirNode) -> String {
if node == null as *HirNode { return ""; }
if node.kind == hVar { return node.strValue; }
if node.kind == hUnary && node.intValue == tkStar {
return CBE_BaseVarNameRaw(node.child1);
}
if node.kind == hLoad || node.kind == hFieldAccess || node.kind == hFieldPtr {
return CBE_BaseVarNameRaw(node.child1);
}
return "";
}
/// Dotted field path from nested hFieldAccess/hFieldPtr chain: "inner.items".
/// Peels explicit derefs (`*p`) so path is field-only.
func CBE_FieldPathFromNode(node: *HirNode) -> String {
if node == null as *HirNode { return ""; }
if node.kind == hLoad {
return CBE_FieldPathFromNode(node.child1);
}
if node.kind == hUnary && node.intValue == tkStar {
return CBE_FieldPathFromNode(node.child1);
}
if node.kind == hFieldAccess || node.kind == hFieldPtr {
let rest: String = CBE_FieldPathFromNode(node.child1);
let seg: String = node.strValue;
if String_Eq(seg, "") { return rest; }
if String_Eq(rest, "") { return seg; }
return String_Concat(rest, String_Concat(".", seg));
}
return "";
}
/// True if dotted path `key` is exactly moved, or is a prefix of a moved path.
func CBE_PathIsFullyMoved(cbe: *CEmitter, varName: String, key: String) -> bool {
return CBE_IsPartialMovedField(cbe, varName, key);
}
func CBE_PathHasNestedMove(cbe: *CEmitter, varName: String, key: String) -> bool {
let prefix: String = String_Concat(key, ".");
var i: int = 0;
while i < cbe.partialCount {
var pvar: String = "";
var pfield: String = "";
if i == 0 { pvar = cbe.partialVar0; pfield = cbe.partialField0; }
else if i == 1 { pvar = cbe.partialVar1; pfield = cbe.partialField1; }
else if i == 2 { pvar = cbe.partialVar2; pfield = cbe.partialField2; }
else if i == 3 { pvar = cbe.partialVar3; pfield = cbe.partialField3; }
else if i == 4 { pvar = cbe.partialVar4; pfield = cbe.partialField4; }
else if i == 5 { pvar = cbe.partialVar5; pfield = cbe.partialField5; }
else if i == 6 { pvar = cbe.partialVar6; pfield = cbe.partialField6; }
else if i == 7 { pvar = cbe.partialVar7; pfield = cbe.partialField7; }
if String_Eq(pvar, varName) && String_StartsWith(pfield, prefix) {
return true;
}
i = i + 1;
}
return false;
}
func CBE_RegisterLocalType(cbe: *CEmitter, name: String, typeName: String) {
if name == null as String || String_Eq(name, "") { return; }
if typeName == null as String || String_Eq(typeName, "") { return; }
if cbe.localCount >= 8 { return; }
let tn: String = CBE_NormalizeTypeName(typeName);
if cbe.localCount == 0 { cbe.localName0 = name; cbe.localType0 = tn; }
else if cbe.localCount == 1 { cbe.localName1 = name; cbe.localType1 = tn; }
else if cbe.localCount == 2 { cbe.localName2 = name; cbe.localType2 = tn; }
else if cbe.localCount == 3 { cbe.localName3 = name; cbe.localType3 = tn; }
else if cbe.localCount == 4 { cbe.localName4 = name; cbe.localType4 = tn; }
else if cbe.localCount == 5 { cbe.localName5 = name; cbe.localType5 = tn; }
else if cbe.localCount == 6 { cbe.localName6 = name; cbe.localType6 = tn; }
else if cbe.localCount == 7 { cbe.localName7 = name; cbe.localType7 = tn; }
cbe.localCount = cbe.localCount + 1;
}
func CBE_LookupLocalType(cbe: *CEmitter, name: String) -> String {
if cbe.localCount > 0 && String_Eq(cbe.localName0, name) { return cbe.localType0; }
if cbe.localCount > 1 && String_Eq(cbe.localName1, name) { return cbe.localType1; }
if cbe.localCount > 2 && String_Eq(cbe.localName2, name) { return cbe.localType2; }
if cbe.localCount > 3 && String_Eq(cbe.localName3, name) { return cbe.localType3; }
if cbe.localCount > 4 && String_Eq(cbe.localName4, name) { return cbe.localType4; }
if cbe.localCount > 5 && String_Eq(cbe.localName5, name) { return cbe.localType5; }
if cbe.localCount > 6 && String_Eq(cbe.localName6, name) { return cbe.localType6; }
if cbe.localCount > 7 && String_Eq(cbe.localName7, name) { return cbe.localType7; }
return "";
}
func CBE_AddPartialMoved(cbe: *CEmitter, varName: String, fieldName: String) {
if varName == null as String || String_Eq(varName, "") { return; }
if fieldName == null as String || String_Eq(fieldName, "") { return; }
if cbe.partialCount >= 8 { return; }
if cbe.partialCount == 0 { cbe.partialVar0 = varName; cbe.partialField0 = fieldName; }
else if cbe.partialCount == 1 { cbe.partialVar1 = varName; cbe.partialField1 = fieldName; }
else if cbe.partialCount == 2 { cbe.partialVar2 = varName; cbe.partialField2 = fieldName; }
else if cbe.partialCount == 3 { cbe.partialVar3 = varName; cbe.partialField3 = fieldName; }
else if cbe.partialCount == 4 { cbe.partialVar4 = varName; cbe.partialField4 = fieldName; }
else if cbe.partialCount == 5 { cbe.partialVar5 = varName; cbe.partialField5 = fieldName; }
else if cbe.partialCount == 6 { cbe.partialVar6 = varName; cbe.partialField6 = fieldName; }
else if cbe.partialCount == 7 { cbe.partialVar7 = varName; cbe.partialField7 = fieldName; }
cbe.partialCount = cbe.partialCount + 1;
}
func CBE_IsPartialMovedField(cbe: *CEmitter, varName: String, fieldName: String) -> bool {
if cbe.partialCount > 0 && String_Eq(cbe.partialVar0, varName) && String_Eq(cbe.partialField0, fieldName) { return true; }
if cbe.partialCount > 1 && String_Eq(cbe.partialVar1, varName) && String_Eq(cbe.partialField1, fieldName) { return true; }
if cbe.partialCount > 2 && String_Eq(cbe.partialVar2, varName) && String_Eq(cbe.partialField2, fieldName) { return true; }
if cbe.partialCount > 3 && String_Eq(cbe.partialVar3, varName) && String_Eq(cbe.partialField3, fieldName) { return true; }
if cbe.partialCount > 4 && String_Eq(cbe.partialVar4, varName) && String_Eq(cbe.partialField4, fieldName) { return true; }
if cbe.partialCount > 5 && String_Eq(cbe.partialVar5, varName) && String_Eq(cbe.partialField5, fieldName) { return true; }
if cbe.partialCount > 6 && String_Eq(cbe.partialVar6, varName) && String_Eq(cbe.partialField6, fieldName) { return true; }
if cbe.partialCount > 7 && String_Eq(cbe.partialVar7, varName) && String_Eq(cbe.partialField7, fieldName) { return true; }
return false;
}
func CBE_HasPartialMoved(cbe: *CEmitter, varName: String) -> bool {
if cbe.partialCount > 0 && String_Eq(cbe.partialVar0, varName) { return true; }
if cbe.partialCount > 1 && String_Eq(cbe.partialVar1, varName) { return true; }
if cbe.partialCount > 2 && String_Eq(cbe.partialVar2, varName) { return true; }
if cbe.partialCount > 3 && String_Eq(cbe.partialVar3, varName) { return true; }
if cbe.partialCount > 4 && String_Eq(cbe.partialVar4, varName) { return true; }
if cbe.partialCount > 5 && String_Eq(cbe.partialVar5, varName) { return true; }
if cbe.partialCount > 6 && String_Eq(cbe.partialVar6, varName) { return true; }
if cbe.partialCount > 7 && String_Eq(cbe.partialVar7, varName) { return true; }
return false;
}
/// Drop function name for a C type (`Array_int` → `Array_Drop_int`, `Bag` → `Bag_Drop`).
func CBE_DropFuncName(tn: String) -> String {
if tn == null as String || String_Eq(tn, "") { return ""; }
if !CBE_IsDroppableTypeName(tn) { return ""; }
if String_StartsWith(tn, "Array_") {
let n: uint = String_Len(tn);
let elem: String = String_Slice(tn, 6, n - 6);
return String_Concat("Array_Drop_", elem);
}
if String_StartsWith(tn, "Map_") {
let n: uint = String_Len(tn);
let rest: String = String_Slice(tn, 4, n - 4);
return String_Concat("Map_Drop_", rest);
}
if String_StartsWith(tn, "Set_") {
let n: uint = String_Len(tn);
let elem: String = String_Slice(tn, 4, n - 4);
return String_Concat("Set_Drop_", elem);
}
if String_StartsWith(tn, "Channel_") {
let n: uint = String_Len(tn);
let elem: String = String_Slice(tn, 8, n - 8);
return String_Concat("Channel_Drop_", elem);
}
return String_Concat(tn, "_Drop");
}
/// Lookup struct index by name; -1 if missing.
func CBE_FindStruct(cbe: *CEmitter, typeName: String) -> int {
if cbe.mod == null as *HirModule { return -1; }
var si: int = 0;
while si < cbe.mod.structCount {
if String_Eq(cbe.mod.structs[si].name, typeName) { return si; }
si = si + 1;
}
return -1;
}
/// Emit one Drop call: `Type_Drop(&(accessPath));`
func CBE_EmitOneFieldDrop(cbe: *CEmitter, dropFn: String, accessPath: String) {
if String_Eq(dropFn, "") { return; }
StringBuilder_Append(&cbe.sb, "\n");
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, dropFn);
StringBuilder_Append(&cbe.sb, "(&(");
StringBuilder_Append(&cbe.sb, accessPath);
StringBuilder_Append(&cbe.sb, "));");
}
/// Recursive remaining-field drops for nested paths (session 73).
/// `prefix` is dotted path under varName ("" at root); `typeName` is type at that prefix.
func CBE_EmitRemainingAt(cbe: *CEmitter, varName: String, typeName: String, prefix: String) {
let si: int = CBE_FindStruct(cbe, typeName);
if si < 0 { return; }
var fi: int = 0;
while fi < cbe.mod.structs[si].fieldCount {
let fname: String = cbe.mod.structs[si].fields[fi].name;
let fty: String = CBE_NormalizeTypeName(cbe.mod.structs[si].fields[fi].typeName);
var key: String = fname;
if !String_Eq(prefix, "") {
key = String_Concat(prefix, String_Concat(".", fname));
}
var access: String = String_Concat(varName, String_Concat(".", key));
// path may use dots in key already: var.inner.note
if String_Eq(prefix, "") {
access = String_Concat(varName, String_Concat(".", fname));
} else {
access = String_Concat(varName, String_Concat(".", String_Concat(prefix, String_Concat(".", fname))));
}
if CBE_PathIsFullyMoved(cbe, varName, key) {
// fully moved — skip
} else if CBE_PathHasNestedMove(cbe, varName, key) {
// recurse into nested type
CBE_EmitRemainingAt(cbe, varName, fty, key);
} else {
let dropFn: String = CBE_DropFuncName(fty);
CBE_EmitOneFieldDrop(cbe, dropFn, access);
}
fi = fi + 1;
}
}
/// After partial field move, Drop remaining droppable fields (incl. nested).
func CBE_EmitRemainingFieldDrops(cbe: *CEmitter, varName: String) {
let typeName: String = CBE_LookupLocalType(cbe, varName);
if String_Eq(typeName, "") { return; }
CBE_EmitRemainingAt(cbe, varName, typeName, "");
}
/// Mark droppable locals moved by-value (struct fields / nested / partial field).
/// `valueTypeHint`: when non-empty (e.g. function return type), used to decide
/// whether a field access is an ownership move (`return bag.items` vs `return bag.tag`).
@@ -145,14 +522,43 @@ module CBackend {
}
// Partial field move: only when the *value* type is droppable
if node.kind == hFieldPtr || node.kind == hFieldAccess {
// Prefer return-type hint, then resolved field type, then node.typeName
// (selfhost stores *base* struct on field HIR, not the field type).
var vty: String = valueTypeHint;
if String_Eq(vty, "") && cbe.mod != null as *HirModule {
vty = CBE_GetExprTypeName(cbe.mod, node);
}
if String_Eq(vty, "") {
vty = node.typeName;
}
// Field HIR often stores the *base* struct typeName — prefer hint
if CBE_IsDroppableTypeName(vty) {
// Walk to base local (hVar / load / nested)
CBE_MarkMovedFromNodeHint(cbe, node.child1, "");
vty = CBE_NormalizeTypeName(vty);
let baseName: String = CBE_BaseVarName(cbe, node);
var path: String = CBE_FieldPathFromNode(node);
// Fallback path: just the leaf field name if chain walk failed
if String_Eq(path, "") && !String_Eq(node.strValue, "") {
path = node.strValue;
}
// Single-level: mark when value type is droppable.
// Nested path (`a.b.c`): always mark — leaf is ownership transfer.
if !String_Eq(baseName, "") && !String_Eq(path, "") {
var shouldMark: bool = CBE_IsDroppableTypeName(vty);
if !shouldMark && cbe.mod != null as *HirModule {
let ft: String = CBE_NormalizeTypeName(CBE_GetExprTypeName(cbe.mod, node));
shouldMark = CBE_IsDroppableTypeName(ft);
}
var pi: int = 0;
let plen: int = bux_strlen(path) as int;
while pi < plen {
if path[pi] == 46 as char8 { // '.'
shouldMark = true;
break;
}
pi = pi + 1;
}
if shouldMark {
CBE_AddPartialMoved(cbe, baseName, path);
CBE_AddMoved(cbe, baseName);
}
}
return;
}
@@ -292,10 +698,16 @@ module CBackend {
if i == 5 { dn = cbe.defer5; }
if i == 6 { dn = cbe.defer6; }
if i == 7 { dn = cbe.defer7; }
// Skip auto-drop for moved variables
// Skip auto-drop for moved variables; after partial field move still
// Drop remaining droppable fields (session 70/73).
let deferVarName: String = CBE_GetAutoDropVarName(dn);
if !String_Eq(deferVarName, "") && CBE_IsMoved(cbe, deferVarName) {
return;
if !String_Eq(deferVarName, "") {
if CBE_IsMoved(cbe, deferVarName) || CBE_HasPartialMoved(cbe, deferVarName) {
if CBE_HasPartialMoved(cbe, deferVarName) {
CBE_EmitRemainingFieldDrops(cbe, deferVarName);
}
return;
}
}
StringBuilder_Append(&cbe.sb, "\n");
var sp: int = 0;
@@ -427,12 +839,14 @@ module CBackend {
return;
}
// Unary — always parenthesize operand so `!(a && b)` is not `!a && b`
// Unary — wrap whole op so `(*p).field` is not parsed as `*(p.field)`
// and `!(a && b)` is not `!a && b`.
if kind == hUnary {
StringBuilder_Append(&cbe.sb, "(");
StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue));
StringBuilder_Append(&cbe.sb, "(");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
StringBuilder_Append(&cbe.sb, "))");
return;
}
@@ -596,6 +1010,7 @@ module CBackend {
if !String_Eq(node.typeName, "") {
ct = node.typeName;
}
CBE_RegisterLocalType(cbe, node.strValue, ct);
StringBuilder_Append(&cbe.sb, CBE_CParamDecl(ct, node.strValue));
return;
}
@@ -610,6 +1025,15 @@ module CBackend {
CBE_MarkMovedFromNode(cbe, node.child2);
}
}
// Pointer alias: `p = &bag` / `let p = &bag`
if node.child1 != null as *HirNode && node.child2 != null as *HirNode {
var ptrN: String = "";
if node.child1.kind == hAlloca { ptrN = node.child1.strValue; }
if node.child1.kind == hVar { ptrN = node.child1.strValue; }
if !String_Eq(ptrN, "") {
CBE_TryRecordPtrAlias(cbe, ptrN, node.child2);
}
}
// Reinitialization removes moved status
if node.child1 != null as *HirNode && node.child1.kind == hVar {
CBE_RemoveMoved(cbe, node.child1.strValue);
@@ -620,6 +1044,8 @@ module CBackend {
if !String_Eq(node.child1.typeName, "") {
ct = node.child1.typeName;
}
// Register for remaining-field Drop after partial move (session 70)
CBE_RegisterLocalType(cbe, node.child1.strValue, ct);
StringBuilder_Append(&cbe.sb, CBE_CParamDecl(ct, node.child1.strValue));
if node.child2 != null as *HirNode {
StringBuilder_Append(&cbe.sb, " = ");
@@ -880,6 +1306,10 @@ module CBackend {
// Assign: target = value
if kind == hAssign {
// Pointer alias: `p = &bag`
if node.child1 != null as *HirNode && node.child1.kind == hVar && node.child2 != null as *HirNode {
CBE_TryRecordPtrAlias(cbe, node.child1.strValue, node.child2);
}
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
@@ -1478,6 +1908,9 @@ module CBackend {
cbe.mod = mod;
cbe.deferCount = 0;
cbe.movedCount = 0;
cbe.partialCount = 0;
cbe.aliasCount = 0;
cbe.localCount = 0;
cbe.tmpCounter = 0;
cbe.lastDebugLine = 0;
cbe.lastDebugFile = "";
@@ -1542,34 +1975,59 @@ module CBackend {
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int_int {\n int _0;\n int _1;\n int _2;\n} Tuple_int_int_int;\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_Empty {\n char _pad;\n} Tuple_Empty;\n\n");
// Struct definitions before enums (enums may embed structs by value, e.g. Shape::Dot(Point))
// Pass 1: emit structs with no value-typed struct fields (leaf structs)
si = 0;
while si < mod.structCount {
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
// Struct definitions before enums (enums may embed structs by value).
// Multi-pass topo: emit only when every value-typed field type is already
// emitted (handles Outer { inner: Inner } nesting — session 73).
var emittedNames: String = ";";
var progress: bool = true;
var pass: int = 0;
while progress && pass < 64 {
progress = false;
pass = pass + 1;
si = 0;
while si < mod.structCount {
let sn: String = mod.structs[si].name;
if String_Eq(sn, "") || CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
let mark: String = String_Concat(";", String_Concat(sn, ";"));
if String_Contains(emittedNames, mark) {
si = si + 1;
continue;
}
var ready: bool = true;
var fi2: int = 0;
while fi2 < mod.structs[si].fieldCount {
let ft: String = CBE_NormalizeTypeName(mod.structs[si].fields[fi2].typeName);
if !CBE_IsPrimitiveTypeName(ft) && !String_EndsWith(ft, "*") {
let fmark: String = String_Concat(";", String_Concat(ft, ";"));
if !String_Contains(emittedNames, fmark) {
// Only block if ft is a pending user struct we define
var isOur: bool = false;
var oj: int = 0;
while oj < mod.structCount {
if String_Eq(mod.structs[oj].name, ft) {
isOur = true;
break;
}
oj = oj + 1;
}
if isOur {
ready = false;
break;
}
}
}
fi2 = fi2 + 1;
}
if ready {
CBE_EmitStructDef(cbe, &mod.structs[si]);
emittedNames = String_Concat(emittedNames, String_Concat(sn, ";"));
progress = true;
}
si = si + 1;
continue;
}
if CBE_StructHasValueStructField(&mod.structs[si]) {
si = si + 1;
continue;
}
CBE_EmitStructDef(cbe, &mod.structs[si]);
si = si + 1;
}
// Pass 2: emit structs that contain value-typed struct fields
si = 0;
while si < mod.structCount {
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
if !CBE_StructHasValueStructField(&mod.structs[si]) {
si = si + 1;
continue;
}
CBE_EmitStructDef(cbe, &mod.structs[si]);
si = si + 1;
}
// Enum definitions
@@ -1810,6 +2268,8 @@ module CBackend {
cbe.checkedFunc = mod.funcs[i].checkedFunc;
cbe.deferCount = 0;
cbe.movedCount = 0;
cbe.partialCount = 0;
cbe.localCount = 0;
cbe.tmpCounter = 0;
cbe.currentRetType = mod.funcs[i].retTypeName;
var hasReturn: bool = false;
+179 -10
View File
@@ -329,20 +329,134 @@ module MacroExpand {
}
}
// Fragment kind check: "ident" | "literal" | "block" | expr|tt (any)
func Macro_FragMatches(kindStr: String, aexp: *Expr) -> bool {
if aexp == null as *Expr { return false; }
// Flatten ekIdent / ekField(::) chain to "A::B::C" (selfhost path style)
func Macro_PathFromExpr(e: *Expr) -> String {
if e == null as *Expr { return ""; }
if e.kind == ekIdent { return e.strValue; }
if e.kind == ekPath { return e.strValue; }
if e.kind == ekField {
let base: String = Macro_PathFromExpr(e.child1);
if String_Eq(base, "") { return e.strValue; }
return String_Concat(base, String_Concat("::", e.strValue));
}
return "";
}
// Convert call-site expr → pattern for `$p:pat` (ident/lit/path/call/field)
func Macro_ExprToPattern(aexp: *Expr) -> *Pattern {
if aexp == null as *Expr { return null as *Pattern; }
if aexp.kind == ekMacroPat { return Ast_ClonePattern(aexp.macroPat); }
if aexp.kind == ekIdent {
if String_Eq(aexp.strValue, "_") {
let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
p.kind = pkWildcard;
p.line = aexp.line;
p.column = aexp.column;
return p;
}
let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
p.kind = pkIdent;
p.line = aexp.line;
p.column = aexp.column;
p.patIdent = aexp.strValue;
return p;
}
if aexp.kind == ekLiteral {
let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
p.kind = pkLiteral;
p.line = aexp.line;
p.column = aexp.column;
p.patLitKind = aexp.tokKind;
p.patLitText = aexp.tokText;
return p;
}
if aexp.kind == ekPath || aexp.kind == ekField {
let path: String = Macro_PathFromExpr(aexp);
if String_Eq(path, "") { return null as *Pattern; }
let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
p.kind = pkEnum;
p.line = aexp.line;
p.column = aexp.column;
p.patEnumPath = path;
return p;
}
if aexp.kind == ekCall {
// Opt::Some(v) — callee is ekField chain
var path: String = Macro_PathFromExpr(aexp.child1);
if String_Eq(path, "") { return null as *Pattern; }
let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
p.kind = pkEnum;
p.line = aexp.line;
p.column = aexp.column;
p.patEnumPath = path;
var last: *Pattern = null as *Pattern;
var arg: *ExprList = aexp.callArgs;
while arg != null as *ExprList {
let ap: *Pattern = Macro_ExprToPattern(arg.expr);
if ap == null as *Pattern { return null as *Pattern; }
if last == null as *Pattern {
p.patArgs = ap;
last = ap;
} else {
last.patNext = ap;
last = ap;
}
arg = arg.next;
}
return p;
}
return null as *Pattern;
}
// Coerce arg for kind; returns normalized expr or null on mismatch
func Macro_CoerceArg(kindStr: String, aexp: *Expr) -> *Expr {
if aexp == null as *Expr { return null as *Expr; }
if String_Eq(kindStr, "ident") {
return aexp.kind == ekIdent;
if aexp.kind != ekIdent { return null as *Expr; }
return aexp;
}
if String_Eq(kindStr, "literal") {
return aexp.kind == ekLiteral;
if aexp.kind != ekLiteral { return null as *Expr; }
return aexp;
}
if String_Eq(kindStr, "block") {
return aexp.kind == ekBlock;
if aexp.kind != ekBlock { return null as *Expr; }
return aexp;
}
// expr / tt / unknown → accept
return true;
if String_Eq(kindStr, "stmt") {
if aexp.kind == ekMacroStmt { return aexp; }
if aexp.kind == ekMacroPat { return null as *Expr; }
// Wrap expression as expression-statement
let st: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
st.kind = skExpr;
st.line = aexp.line;
st.column = aexp.column;
st.child1 = aexp;
let e: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
e.kind = ekMacroStmt;
e.line = aexp.line;
e.column = aexp.column;
e.macroStmt = st;
return e;
}
if String_Eq(kindStr, "pat") {
let pat: *Pattern = Macro_ExprToPattern(aexp);
if pat == null as *Pattern { return null as *Expr; }
let e: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
e.kind = ekMacroPat;
e.line = aexp.line;
e.column = aexp.column;
e.macroPat = pat;
return e;
}
// expr / tt
if aexp.kind == ekMacroStmt || aexp.kind == ekMacroPat { return null as *Expr; }
return aexp;
}
// Fragment kind check: "ident" | "literal" | "block" | "stmt" | "pat" | expr|tt
func Macro_FragMatches(kindStr: String, aexp: *Expr) -> bool {
return Macro_CoerceArg(kindStr, aexp) != null as *Expr;
}
// kinds encoded as "expr;ident;rep:expr," — return fi-th segment
@@ -479,6 +593,36 @@ module MacroExpand {
li = li + 1;
}
}
} else if s.kind == skExpr && s.child1 != null as *Expr && s.child1.kind == ekIdent {
// Splice `$s:stmt` bound to ekMacroStmt as a real statement
let bound: *Expr = Env_Lookup(env, s.child1.strValue);
if bound != null as *Expr && bound.kind == ekMacroStmt && bound.macroStmt != null as *Stmt {
let one: *Stmt = Subst_Stmt(bound.macroStmt, env, file, line, col);
if one != null as *Stmt {
one.nextStmt = null as *Stmt;
if n.firstStmt == null as *Stmt {
n.firstStmt = one;
n.lastStmt = one;
} else {
n.lastStmt.nextStmt = one;
n.lastStmt = one;
}
n.stmtCount = n.stmtCount + 1;
}
} else {
let one2: *Stmt = Subst_Stmt(s, env, file, line, col);
if one2 != null as *Stmt {
one2.nextStmt = null as *Stmt;
if n.firstStmt == null as *Stmt {
n.firstStmt = one2;
n.lastStmt = one2;
} else {
n.lastStmt.nextStmt = one2;
n.lastStmt = one2;
}
n.stmtCount = n.stmtCount + 1;
}
}
} else {
let one: *Stmt = Subst_Stmt(s, env, file, line, col);
if one != null as *Stmt {
@@ -647,6 +791,7 @@ module MacroExpand {
}
var arm: *MatchArm = c.matchArms;
while arm != null as *MatchArm {
arm.pattern = Subst_Pattern(arm.pattern, env, file, line, col);
arm.body = Subst_Expr(arm.body, env, file, line, col);
arm = arm.next;
}
@@ -658,6 +803,29 @@ module MacroExpand {
return c;
}
func Subst_Pattern(p: *Pattern, env: *MacroEnv, file: String, line: uint32, col: uint32) -> *Pattern {
if p == null as *Pattern { return null as *Pattern; }
// `$p:pat` as whole pattern (pkIdent name `$p`)
if p.kind == pkIdent && !String_Eq(p.patIdent, "") {
let bound: *Expr = Env_Lookup(env, p.patIdent);
if bound != null as *Expr && bound.kind == ekMacroPat && bound.macroPat != null as *Pattern {
let np: *Pattern = Ast_ClonePattern(bound.macroPat);
if np != null as *Pattern {
np.line = line;
np.column = col;
}
return np;
}
}
// MVP: other patterns kept as cloned (no nested `$p` rewrite)
let c: *Pattern = Ast_ClonePattern(p);
if c != null as *Pattern {
c.line = line;
c.column = col;
}
return c;
}
func Subst_Stmt(s: *Stmt, env: *MacroEnv, file: String, line: uint32, col: uint32) -> *Stmt {
if s == null as *Stmt { return null as *Stmt; }
let c: *Stmt = Ast_CloneStmt(s);
@@ -944,8 +1112,9 @@ module MacroExpand {
if argList == null as *ExprList { ok = false; break; }
}
let aexp: *Expr = argList.expr;
if !Macro_FragMatches(kindStr, aexp) { ok = false; break; }
Env_Set(&env, paramIdx, Rule_FragName(rule, paramIdx), aexp);
let coerced: *Expr = Macro_CoerceArg(kindStr, aexp);
if coerced == null as *Expr { ok = false; break; }
Env_Set(&env, paramIdx, Rule_FragName(rule, paramIdx), coerced);
argList = argList.next;
flatLeft = flatLeft - 1;
paramIdx = paramIdx + 1;
+36 -3
View File
@@ -395,9 +395,38 @@ module Parser {
e.callArgCount = 0;
e.matchArms = null as *MatchArm;
e.matchArmCount = 0;
e.macroStmt = null as *Stmt;
e.macroPat = null as *Pattern;
return e;
}
func parserIsMacroStmtStart(p: *Parser) -> bool {
let k: int = parserPeek(p, 0);
if k == tkLet || k == tkVar { return true; }
if k == tkIf || k == tkWhile || k == tkFor || k == tkLoop { return true; }
if k == tkMatch || k == tkReturn || k == tkBreak || k == tkContinue { return true; }
if k == tkDefer || k == tkSwitch || k == tkDo { return true; }
return false;
}
func parserParseMacroArg(p: *Parser) -> *Expr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
if parserIsMacroStmtStart(p) {
let st: *Stmt = parserParseStmt(p);
let e: *Expr = parserMakeExpr(ekMacroStmt, line, col);
e.macroStmt = st;
return e;
}
if parserCheck(p, tkUnderscore) {
let pat: *Pattern = parserParsePattern(p);
let e: *Expr = parserMakeExpr(ekMacroPat, line, col);
e.macroPat = pat;
return e;
}
return parserParseExpr(p);
}
func parserMakeStringLitExpr(text: String, line: uint32, col: uint32) -> *Expr {
let quoted: String = String_Concat(String_Concat("\"", text), "\"");
@@ -1284,7 +1313,7 @@ module Parser {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
continue;
}
let argExpr: *Expr = parserParseExpr(p);
let argExpr: *Expr = parserParseMacroArg(p);
let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
argNode.expr = argExpr;
argNode.next = null as *ExprList;
@@ -2387,8 +2416,10 @@ module Parser {
let kindTok: LexToken = parserExpect(p, tkIdent, "expected fragment kind");
var kname: String = kindTok.text;
if String_Eq(kname, "lit") { kname = "literal"; }
if String_Eq(kname, "pattern") { kname = "pat"; }
if !(String_Eq(kname, "expr") || String_Eq(kname, "ident") || String_Eq(kname, "tt")
|| String_Eq(kname, "literal") || String_Eq(kname, "block")) {
|| String_Eq(kname, "literal") || String_Eq(kname, "block")
|| String_Eq(kname, "stmt") || String_Eq(kname, "pat")) {
kname = "expr";
}
if nIn == 0 {
@@ -2432,8 +2463,10 @@ module Parser {
let kindTok: LexToken = parserExpect(p, tkIdent, "expected fragment kind");
var kname: String = kindTok.text;
if String_Eq(kname, "lit") { kname = "literal"; }
if String_Eq(kname, "pattern") { kname = "pat"; }
if !(String_Eq(kname, "expr") || String_Eq(kname, "ident") || String_Eq(kname, "tt")
|| String_Eq(kname, "literal") || String_Eq(kname, "block")) {
|| String_Eq(kname, "literal") || String_Eq(kname, "block")
|| String_Eq(kname, "stmt") || String_Eq(kname, "pat")) {
kname = "expr";
}
if rule.paramCount < 9 {