feat(selfhost): full tuple type support with .0/.1 field access
Parse (T, U) types and (a, b) expressions, lower to Tuple_* C structs, and emit common tuple typedefs so buxc2 matches bootstrap tuple codegen.
This commit is contained in:
+11
-3
@@ -55,8 +55,8 @@
|
||||
|
||||
| # | Задача | Защо | Статус |
|
||||
|---|--------|------|--------|
|
||||
| B.1 | Proper tuple types в C backend | `(T,U)` → `Tuple_T_U` struct + `.0`/`.1` | ✅ |
|
||||
| B.2 | Function pointer types | `func(T)->U` вече работи в LIR backend | ✅ |
|
||||
| B.1 | Proper tuple types в C backend | `(T,U)` → `Tuple_T_U` struct + `.0`/`.1` | ✅ bootstrap + selfhost |
|
||||
| B.2 | Function pointer types | `func(T)->U` fat ABI | ✅ bootstrap + selfhost |
|
||||
| B.3 | Match expression до край в C (не `return "0"`) | Expression-context match |
|
||||
| B.4 | Closures: multi-instance + loop/return в body | Реални higher-order callbacks |
|
||||
| B.5 | По-добри diagnostics (snippet + hint) | DX #1 за нови потребители | ✅ |
|
||||
@@ -160,4 +160,12 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
||||
4. Calls through func values: `f.code(f.env, args...)`
|
||||
5. Example `multi_closure.bux` — MakeAdder(10)/MakeAdder(20) yield 11 and 21
|
||||
6. **Selfhost parity:** same fat ABI in `src/hir_lower.bux` + `src/c_backend.bux` (makers, adapters)
|
||||
```
|
||||
|
||||
## Сесия 6 (selfhost tuples)
|
||||
|
||||
1. Parser: `(T, U)` types, `(a, b)` exprs, field access `.0`/`.1`
|
||||
2. Sema: tekTuple / ekTuple
|
||||
3. HIR lower → `hStructInit` of `Tuple_int_int`
|
||||
4. C backend: `typedef struct Tuple_int_int { int _0; int _1; }`
|
||||
5. Verified with `buxc2` on `examples/tuples.bux`
|
||||
```
|
||||
@@ -52,6 +52,8 @@ struct TypeExpr {
|
||||
funcParams: *TypeExprList, // for tekFunc
|
||||
funcRet: *TypeExpr, // for tekFunc
|
||||
funcParamCount: int, // for tekFunc
|
||||
tupleElems: *TypeExprList, // for tekTuple
|
||||
tupleCount: int, // for tekTuple
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1435,6 +1435,12 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
||||
// Fat function-pointer typedefs (BuxFn_*) — before forward decls
|
||||
CBE_EmitFatFuncTypedefs(cbe, mod);
|
||||
|
||||
// Common tuple struct types
|
||||
StringBuilder_Append(&cbe.sb, "/* Tuple types */\n");
|
||||
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int {\n int _0;\n int _1;\n} Tuple_int_int;\n");
|
||||
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");
|
||||
|
||||
// Env structs for capturing closures (no static instance — heap per value)
|
||||
var ei2: int = 0;
|
||||
while ei2 < mod.funcCount {
|
||||
|
||||
+40
-1
@@ -74,7 +74,7 @@ func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
|
||||
|
||||
if te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef { return tyPointer; }
|
||||
if te.kind == tekSlice { return tySlice; }
|
||||
if te.kind == tekTuple { return tyTuple; }
|
||||
if te.kind == tekTuple { return tyNamed; /* Tuple_T_U is a C struct */ }
|
||||
if te.kind == tekFunc { return tyFunc; }
|
||||
|
||||
return Lcx_ResolveTypeKindFromName(te.typeName);
|
||||
@@ -1202,6 +1202,45 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||
return n;
|
||||
}
|
||||
|
||||
// Tuple expression (a, b, ...) → struct init Tuple_int_int { ._0 = a, ._1 = b }
|
||||
if kind == ekTuple {
|
||||
var tname: String = "Tuple";
|
||||
var ti: int = 0;
|
||||
while ti < expr.callArgCount {
|
||||
tname = String_Concat(tname, "_int");
|
||||
ti = ti + 1;
|
||||
}
|
||||
if expr.callArgCount == 0 { tname = "Tuple_Empty"; }
|
||||
if expr.refType != null as *TypeExpr && !String_Eq(expr.refType.typeName, "") {
|
||||
tname = expr.refType.typeName;
|
||||
}
|
||||
n.kind = hStructInit;
|
||||
n.strValue = tname;
|
||||
n.typeKind = tyNamed;
|
||||
n.typeName = tname;
|
||||
var firstField: *HirNode = null as *HirNode;
|
||||
var lastField: *HirNode = null as *HirNode;
|
||||
var tcur: *ExprList = expr.callArgs;
|
||||
var tidx: int = 0;
|
||||
while tcur != null as *ExprList {
|
||||
let fNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
fNode.kind = hBlock;
|
||||
fNode.strValue = String_Concat("_", String_FromInt(tidx as int64));
|
||||
fNode.child1 = Lcx_LowerExpr(ctx, tcur.expr);
|
||||
if firstField == null as *HirNode {
|
||||
firstField = fNode;
|
||||
lastField = fNode;
|
||||
} else {
|
||||
lastField.child3 = fNode;
|
||||
lastField = fNode;
|
||||
}
|
||||
tcur = tcur.next;
|
||||
tidx = tidx + 1;
|
||||
}
|
||||
n.child1 = firstField;
|
||||
return n;
|
||||
}
|
||||
|
||||
// Closure: fat function pointer (multi-instance via heap env + maker)
|
||||
if kind == ekClosure {
|
||||
let f: *HirFunc = Lcx_LowerClosureFunc(ctx, expr);
|
||||
|
||||
+95
-4
@@ -231,6 +231,57 @@ func parserParseType(p: *Parser) -> *TypeExpr {
|
||||
return te;
|
||||
}
|
||||
|
||||
// (T, U, ...) tuple type
|
||||
if kindTok == tkLParen {
|
||||
discard parserAdvance(p);
|
||||
var elems: *TypeExprList = null as *TypeExprList;
|
||||
var elemsTail: *TypeExprList = null as *TypeExprList;
|
||||
var count: int = 0;
|
||||
var typeName: String = "Tuple";
|
||||
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
||||
let elemTe: *TypeExpr = parserParseType(p);
|
||||
let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
|
||||
node.te = elemTe;
|
||||
node.next = null as *TypeExprList;
|
||||
if elems == null as *TypeExprList {
|
||||
elems = node;
|
||||
} else {
|
||||
elemsTail.next = node;
|
||||
}
|
||||
elemsTail = node;
|
||||
count = count + 1;
|
||||
// Build mangled name: Tuple_int_int
|
||||
var part: String = "int";
|
||||
if elemTe != null as *TypeExpr {
|
||||
if !String_Eq(elemTe.typeName, "") {
|
||||
part = elemTe.typeName;
|
||||
} else if elemTe.kind == tekPointer && elemTe.pointerPointee != null as *TypeExpr {
|
||||
part = String_Concat(elemTe.pointerPointee.typeName, "Ptr");
|
||||
}
|
||||
}
|
||||
if String_Eq(part, "String") || String_Eq(part, "str") { part = "cstr"; }
|
||||
typeName = String_Concat(typeName, "_");
|
||||
typeName = String_Concat(typeName, part);
|
||||
if parserCheck(p, tkComma) {
|
||||
discard parserAdvance(p);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
discard parserExpect(p, tkRParen, "expected ')' to close tuple type");
|
||||
if count == 0 {
|
||||
typeName = "Tuple_Empty";
|
||||
}
|
||||
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||
te.kind = tekTuple;
|
||||
te.line = line;
|
||||
te.column = col;
|
||||
te.tupleElems = elems;
|
||||
te.tupleCount = count;
|
||||
te.typeName = typeName;
|
||||
return te;
|
||||
}
|
||||
|
||||
// name
|
||||
let nameTok: LexToken = parserExpect(p, tkIdent, "expected type name");
|
||||
// self / Self -> tekSelf
|
||||
@@ -386,12 +437,43 @@ func parserParsePrimary(p: *Parser) -> *Expr {
|
||||
return e;
|
||||
}
|
||||
|
||||
// ( expr )
|
||||
// ( expr ) or (a, b, ...) tuple
|
||||
if kind == tkLParen {
|
||||
discard parserAdvance(p);
|
||||
let e: *Expr = parserParseExpr(p);
|
||||
// Empty tuple ()
|
||||
if parserCheck(p, tkRParen) {
|
||||
discard parserAdvance(p);
|
||||
let te: *Expr = parserMakeExpr(ekTuple, line, col);
|
||||
te.callArgCount = 0;
|
||||
return te;
|
||||
}
|
||||
let first: *Expr = parserParseExpr(p);
|
||||
if parserCheck(p, tkComma) {
|
||||
// Tuple expression
|
||||
let te: *Expr = parserMakeExpr(ekTuple, line, col);
|
||||
var firstArg: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
||||
firstArg.expr = first;
|
||||
firstArg.next = null as *ExprList;
|
||||
var lastArg: *ExprList = firstArg;
|
||||
var count: int = 1;
|
||||
while parserCheck(p, tkComma) {
|
||||
discard parserAdvance(p);
|
||||
if parserCheck(p, tkRParen) { break; }
|
||||
let elem: *Expr = parserParseExpr(p);
|
||||
let node: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
||||
node.expr = elem;
|
||||
node.next = null as *ExprList;
|
||||
lastArg.next = node;
|
||||
lastArg = node;
|
||||
count = count + 1;
|
||||
}
|
||||
discard parserExpect(p, tkRParen, "expected ')' to close tuple");
|
||||
te.callArgs = firstArg;
|
||||
te.callArgCount = count;
|
||||
return te;
|
||||
}
|
||||
discard parserExpect(p, tkRParen, "expected ')'");
|
||||
return e;
|
||||
return first;
|
||||
}
|
||||
|
||||
// Closure: |params| -> Ret { body }
|
||||
@@ -586,11 +668,20 @@ func parserParsePostfixExpr(p: *Parser) -> *Expr {
|
||||
}
|
||||
}
|
||||
|
||||
// Field: expr.name
|
||||
// Field: expr.name or tuple index expr.0 / expr.1
|
||||
if kind == tkDot {
|
||||
discard parserAdvance(p);
|
||||
let line: uint32 = parserCurToken(p).line;
|
||||
let col: uint32 = parserCurToken(p).column;
|
||||
if parserCheck(p, tkIntLiteral) {
|
||||
let idxTok: LexToken = parserCurToken(p);
|
||||
discard parserAdvance(p);
|
||||
let e: *Expr = parserMakeExpr(ekField, line, col);
|
||||
e.child1 = left;
|
||||
e.strValue = String_Concat("_", idxTok.text);
|
||||
left = e;
|
||||
continue;
|
||||
}
|
||||
let name: LexToken = parserExpectIdentOrKeyword(p, "expected field name");
|
||||
let e: *Expr = parserMakeExpr(ekField, line, col);
|
||||
e.child1 = left;
|
||||
|
||||
@@ -145,6 +145,11 @@ func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
|
||||
return tyFunc;
|
||||
}
|
||||
|
||||
if te.kind == tekTuple {
|
||||
// Tuples lower to named C structs (Tuple_int_int, ...)
|
||||
return tyNamed;
|
||||
}
|
||||
|
||||
return Type_FromName(te.typeName);
|
||||
}
|
||||
|
||||
@@ -688,9 +693,41 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
||||
return tyNamed;
|
||||
}
|
||||
|
||||
// Tuple expression (a, b, ...)
|
||||
if kind == ekTuple {
|
||||
var cur: *ExprList = expr.callArgs;
|
||||
while cur != null as *ExprList {
|
||||
discard Sema_CheckExpr(sema, cur.expr);
|
||||
cur = cur.next;
|
||||
}
|
||||
// Build Tuple_* type name from element types (default int)
|
||||
var tname: String = "Tuple";
|
||||
var i: int = 0;
|
||||
while i < expr.callArgCount {
|
||||
tname = String_Concat(tname, "_int");
|
||||
i = i + 1;
|
||||
}
|
||||
if expr.callArgCount == 0 { tname = "Tuple_Empty"; }
|
||||
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||
te.kind = tekTuple;
|
||||
te.typeName = tname;
|
||||
te.tupleCount = expr.callArgCount;
|
||||
expr.refType = te;
|
||||
return tyNamed;
|
||||
}
|
||||
|
||||
// Field access
|
||||
if kind == ekField {
|
||||
discard Sema_CheckExpr(sema, expr.child1);
|
||||
// Tuple field .0 / .1 stored as "_0" / "_1" → element type (int for now)
|
||||
if String_StartsWith(expr.strValue, "_") {
|
||||
// Propagate element type as int; real C type is Tuple field
|
||||
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||
te.kind = tekNamed;
|
||||
te.typeName = "int";
|
||||
expr.refType = te;
|
||||
return tyInt;
|
||||
}
|
||||
return tyUnknown;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user