feat: pattern bindings, empty closures, match-as-expr, string interp
Sessions 10–12 from QUALITY_PLAN:
- Pattern payload bindings (Some(value) => value) in bootstrap and selfhost
- Empty-param closures via || (tkPipePipe) with loop/return bodies
- Expression-form match: let x = match …; newline before arms
- f"…" string interpolation desugared to String_Concat + conversions
- Lexer preserves \{ \} for literal braces in f-strings
- Bootstrap fix: f"plain" strips the f prefix after escape processing
- Examples: pattern_matching, closure_control, match_let, string_interp
Selfhost-loop remains binary-identical; all examples and error goldens pass.
This commit is contained in:
+111
@@ -384,6 +384,97 @@ func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
|
||||
// Expression type checking
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Bind identifiers from a match pattern into the current scope.
|
||||
// Enum payloads: Option::Some(value) → value:int (from variant field type).
|
||||
func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
||||
if pat == null as *Pattern { return; }
|
||||
if pat.kind == pkIdent {
|
||||
var sym: Symbol;
|
||||
Sema_ZeroInitSymbol(&sym);
|
||||
sym.kind = skVar;
|
||||
sym.name = pat.patIdent;
|
||||
sym.typeKind = tyInt;
|
||||
sym.typeName = "int";
|
||||
if subject != null as *Expr && subject.refType != null as *TypeExpr {
|
||||
sym.refType = subject.refType;
|
||||
sym.typeName = subject.refType.typeName;
|
||||
sym.typeKind = Sema_ResolveType(sema, subject.refType);
|
||||
}
|
||||
sym.isMutable = false;
|
||||
sym.isPublic = false;
|
||||
sym.decl = null as *Decl;
|
||||
discard Scope_Define(sema.scope, sym);
|
||||
return;
|
||||
}
|
||||
if pat.kind == pkEnum {
|
||||
// Resolve enum + variant field types for payload bindings
|
||||
var enumName: String = "";
|
||||
var variantName: String = pat.patEnumPath;
|
||||
if String_Contains(pat.patEnumPath, "::") {
|
||||
enumName = String_SplitPart(pat.patEnumPath, "::", 0);
|
||||
variantName = String_SplitPart(pat.patEnumPath, "::", 1);
|
||||
} else if subject != null as *Expr && subject.refType != null as *TypeExpr {
|
||||
enumName = subject.refType.typeName;
|
||||
}
|
||||
var fieldType0: String = "int";
|
||||
var fieldType1: String = "int";
|
||||
var fieldCount: int = 0;
|
||||
if !String_Eq(enumName, "") {
|
||||
let enumSym: Symbol = Scope_Lookup(sema.scope, enumName);
|
||||
if enumSym.decl != null as *Decl && enumSym.decl.kind == dkEnum {
|
||||
var vi: int = 0;
|
||||
while vi < enumSym.decl.variantCount {
|
||||
var v: *EnumVariant = null as *EnumVariant;
|
||||
if vi == 0 { v = &enumSym.decl.variant0; }
|
||||
else if vi == 1 { v = &enumSym.decl.variant1; }
|
||||
else if vi == 2 { v = &enumSym.decl.variant2; }
|
||||
else if vi == 3 { v = &enumSym.decl.variant3; }
|
||||
else if vi == 4 { v = &enumSym.decl.variant4; }
|
||||
else if vi == 5 { v = &enumSym.decl.variant5; }
|
||||
else if vi == 6 { v = &enumSym.decl.variant6; }
|
||||
else if vi == 7 { v = &enumSym.decl.variant7; }
|
||||
else if vi == 8 { v = &enumSym.decl.variant8; }
|
||||
if v != null as *EnumVariant && String_Eq(v.name, variantName) {
|
||||
fieldCount = v.fieldCount;
|
||||
if !String_Eq(v.fieldTypeName0, "") { fieldType0 = v.fieldTypeName0; }
|
||||
if !String_Eq(v.fieldTypeName1, "") { fieldType1 = v.fieldTypeName1; }
|
||||
}
|
||||
vi = vi + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
var arg: *Pattern = pat.patArgs;
|
||||
var ai: int = 0;
|
||||
while arg != null as *Pattern {
|
||||
if arg.kind == pkIdent {
|
||||
var ftype: String = "int";
|
||||
if ai == 0 { ftype = fieldType0; }
|
||||
else if ai == 1 { ftype = fieldType1; }
|
||||
var bsym: Symbol;
|
||||
Sema_ZeroInitSymbol(&bsym);
|
||||
bsym.kind = skVar;
|
||||
bsym.name = arg.patIdent;
|
||||
bsym.typeName = ftype;
|
||||
bsym.typeKind = tyInt;
|
||||
if String_Eq(ftype, "String") || String_Eq(ftype, "str") { bsym.typeKind = tyStr; }
|
||||
else if String_Eq(ftype, "bool") { bsym.typeKind = tyBool; }
|
||||
else if String_Eq(ftype, "float64") || String_Eq(ftype, "float") { bsym.typeKind = tyFloat64; }
|
||||
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||
te.kind = tekNamed;
|
||||
te.typeName = ftype;
|
||||
bsym.refType = te;
|
||||
bsym.isMutable = false;
|
||||
bsym.isPublic = false;
|
||||
bsym.decl = null as *Decl;
|
||||
discard Scope_Define(sema.scope, bsym);
|
||||
}
|
||||
arg = arg.patNext;
|
||||
ai = ai + 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
func Sema_IsMutRefDeref(target: *Expr) -> bool {
|
||||
if target == null as *Expr { return false; }
|
||||
if target.kind != ekUnary { return false; }
|
||||
@@ -790,7 +881,13 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
||||
var first: bool = true;
|
||||
var arm: *MatchArm = expr.matchArms;
|
||||
while arm != null as *MatchArm {
|
||||
// Pattern bindings: Option::Some(value) → define value in arm scope
|
||||
let armScope: Scope = Scope_NewChild(sema.scope);
|
||||
let savedScope: *Scope = sema.scope;
|
||||
sema.scope = &armScope;
|
||||
Sema_BindPattern(sema, arm.pattern, expr.child1);
|
||||
let bt: int = Sema_CheckExpr(sema, arm.body);
|
||||
sema.scope = savedScope;
|
||||
if first {
|
||||
armType = bt;
|
||||
first = false;
|
||||
@@ -823,6 +920,20 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
||||
return armType;
|
||||
}
|
||||
|
||||
// String interpolation f"...{expr}..." → String
|
||||
if kind == ekStringInterp {
|
||||
var part: *ExprList = expr.callArgs;
|
||||
while part != null as *ExprList {
|
||||
discard Sema_CheckExpr(sema, part.expr);
|
||||
part = part.next;
|
||||
}
|
||||
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||
te.kind = tekNamed;
|
||||
te.typeName = "String";
|
||||
expr.refType = te;
|
||||
return tyStr;
|
||||
}
|
||||
|
||||
// Closure: |params| -> Ret { body }
|
||||
if kind == ekClosure {
|
||||
let savedRetType: int = sema.currentRetType;
|
||||
|
||||
Reference in New Issue
Block a user