diff --git a/docs/QUALITY_PLAN.md b/docs/QUALITY_PLAN.md index 668052a..7f7465c 100644 --- a/docs/QUALITY_PLAN.md +++ b/docs/QUALITY_PLAN.md @@ -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` +``` \ No newline at end of file diff --git a/src/ast.bux b/src/ast.bux index d5dd8f4..9f44590 100644 --- a/src/ast.bux +++ b/src/ast.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 } // --------------------------------------------------------------------------- diff --git a/src/c_backend.bux b/src/c_backend.bux index 1ea1e84..ba151f5 100644 --- a/src/c_backend.bux +++ b/src/c_backend.bux @@ -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 { diff --git a/src/hir_lower.bux b/src/hir_lower.bux index c6e4db3..55f5bfc 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -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); diff --git a/src/parser.bux b/src/parser.bux index cd8b524..c1f162e 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -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; diff --git a/src/sema.bux b/src/sema.bux index 98abfd2..9755658 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -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; }