feat: struct and tuple patterns in match (bootstrap + selfhost)
Support destructuring in match arms:
- Tuple: (a, b) binds subject._0 / _1
- Struct: Point { x: px, y: py } and shorthand Point { x, y }
Bootstrap: matchPatternBindings for pkTuple/pkStruct; register local
tuple typedefs from function bodies. Selfhost: parse, Sema_BindPattern,
Lcx_PatternBindings with scope defines. Fix operator-overload path that
crashed when typeName was null after pattern binds.
Example: examples/struct_tuple_pat.bux. Selfhost-loop IDENTICAL.
This commit is contained in:
+3
-2
@@ -76,10 +76,11 @@ struct Pattern {
|
||||
patLitText: String, // for pkLiteral (token text)
|
||||
patRangeInclusive: bool, // for pkRange
|
||||
patEnumPath: String, // for pkEnum: "Enum::Variant"
|
||||
patStructName: String, // for pkStruct
|
||||
patStructName: String, // for pkStruct (type name)
|
||||
patFieldName: String, // for struct field entry: field name in Point { x: a }
|
||||
patChild1: *Pattern, // range lo / nested
|
||||
patChild2: *Pattern, // range hi / nested
|
||||
patArgs: *Pattern, // pkEnum payload args (head)
|
||||
patArgs: *Pattern, // pkEnum/pkTuple/pkStruct field list (head)
|
||||
patNext: *Pattern, // next sibling in patArgs list
|
||||
}
|
||||
|
||||
|
||||
+145
-4
@@ -569,9 +569,11 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
||||
line: uint32, col: uint32) -> *HirNode {
|
||||
if pat == null as *Pattern { return null as *HirNode; }
|
||||
if pat.kind == pkIdent {
|
||||
// `_` is wildcard, not a binding
|
||||
if String_Eq(pat.patIdent, "_") { return null as *HirNode; }
|
||||
let ty: String = "int";
|
||||
if subject != null as *HirNode && !String_Eq(subject.typeName, "") {
|
||||
// keep int default for catch-all unless subject has a type name
|
||||
ty = subject.typeName;
|
||||
}
|
||||
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
alloca.kind = hAlloca;
|
||||
@@ -589,8 +591,144 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
||||
store.child1 = v;
|
||||
store.child2 = subject;
|
||||
alloca.child3 = store;
|
||||
var bsym: Symbol;
|
||||
bsym.kind = skVar;
|
||||
bsym.name = pat.patIdent;
|
||||
bsym.typeKind = tyInt;
|
||||
bsym.typeName = ty;
|
||||
bsym.refType = null as *TypeExpr;
|
||||
bsym.isMutable = false;
|
||||
bsym.isPublic = false;
|
||||
bsym.decl = null as *Decl;
|
||||
discard Scope_Define(ctx.scope, bsym);
|
||||
return alloca;
|
||||
}
|
||||
|
||||
// Tuple: (a, b) → a = subject._0; b = subject._1
|
||||
if pat.kind == pkTuple {
|
||||
var head: *HirNode = null as *HirNode;
|
||||
var tail: *HirNode = null as *HirNode;
|
||||
var ei: int = 0;
|
||||
var elem: *Pattern = pat.patArgs;
|
||||
while elem != null as *Pattern {
|
||||
if elem.kind == pkIdent && !String_Eq(elem.patIdent, "_") {
|
||||
var fieldName: String = "_0";
|
||||
if ei == 1 { fieldName = "_1"; }
|
||||
else if ei == 2 { fieldName = "_2"; }
|
||||
else if ei == 3 { fieldName = "_3"; }
|
||||
else if ei > 3 { fieldName = String_Concat("_", String_FromInt(ei as int64)); }
|
||||
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
fPtr.kind = hFieldPtr;
|
||||
fPtr.line = line;
|
||||
fPtr.column = col;
|
||||
fPtr.strValue = fieldName;
|
||||
fPtr.child1 = subject;
|
||||
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
fLoad.kind = hLoad;
|
||||
fLoad.line = line;
|
||||
fLoad.column = col;
|
||||
fLoad.child1 = fPtr;
|
||||
fLoad.typeName = "int";
|
||||
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
alloca.kind = hAlloca;
|
||||
alloca.line = line;
|
||||
alloca.column = col;
|
||||
alloca.strValue = elem.patIdent;
|
||||
alloca.typeName = "int";
|
||||
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
store.kind = hStore;
|
||||
store.line = line;
|
||||
store.column = col;
|
||||
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
v.kind = hVar;
|
||||
v.strValue = elem.patIdent;
|
||||
store.child1 = v;
|
||||
store.child2 = fLoad;
|
||||
alloca.child3 = store;
|
||||
var bsym: Symbol;
|
||||
bsym.kind = skVar;
|
||||
bsym.name = elem.patIdent;
|
||||
bsym.typeKind = tyInt;
|
||||
bsym.typeName = "int";
|
||||
bsym.refType = null as *TypeExpr;
|
||||
bsym.isMutable = false;
|
||||
bsym.isPublic = false;
|
||||
bsym.decl = null as *Decl;
|
||||
discard Scope_Define(ctx.scope, bsym);
|
||||
if head == null as *HirNode {
|
||||
head = alloca;
|
||||
tail = store;
|
||||
} else {
|
||||
tail.child3 = alloca;
|
||||
tail = store;
|
||||
}
|
||||
}
|
||||
elem = elem.patNext;
|
||||
ei = ei + 1;
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
// Struct: Point { x: a } → a = subject.x
|
||||
if pat.kind == pkStruct {
|
||||
var head: *HirNode = null as *HirNode;
|
||||
var tail: *HirNode = null as *HirNode;
|
||||
var field: *Pattern = pat.patArgs;
|
||||
while field != null as *Pattern {
|
||||
if field.kind == pkIdent && !String_Eq(field.patIdent, "_") {
|
||||
var fname: String = field.patFieldName;
|
||||
if String_Eq(fname, "") { fname = field.patIdent; }
|
||||
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
fPtr.kind = hFieldPtr;
|
||||
fPtr.line = line;
|
||||
fPtr.column = col;
|
||||
fPtr.strValue = fname;
|
||||
fPtr.child1 = subject;
|
||||
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
fLoad.kind = hLoad;
|
||||
fLoad.line = line;
|
||||
fLoad.column = col;
|
||||
fLoad.child1 = fPtr;
|
||||
fLoad.typeName = "int";
|
||||
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
alloca.kind = hAlloca;
|
||||
alloca.line = line;
|
||||
alloca.column = col;
|
||||
alloca.strValue = field.patIdent;
|
||||
alloca.typeName = "int";
|
||||
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
store.kind = hStore;
|
||||
store.line = line;
|
||||
store.column = col;
|
||||
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
v.kind = hVar;
|
||||
v.strValue = field.patIdent;
|
||||
store.child1 = v;
|
||||
store.child2 = fLoad;
|
||||
alloca.child3 = store;
|
||||
var bsym: Symbol;
|
||||
bsym.kind = skVar;
|
||||
bsym.name = field.patIdent;
|
||||
bsym.typeKind = tyInt;
|
||||
bsym.typeName = "int";
|
||||
bsym.refType = null as *TypeExpr;
|
||||
bsym.isMutable = false;
|
||||
bsym.isPublic = false;
|
||||
bsym.decl = null as *Decl;
|
||||
discard Scope_Define(ctx.scope, bsym);
|
||||
if head == null as *HirNode {
|
||||
head = alloca;
|
||||
tail = store;
|
||||
} else {
|
||||
tail.child3 = alloca;
|
||||
tail = store;
|
||||
}
|
||||
}
|
||||
field = field.patNext;
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
if pat.kind != pkEnum || !subjectHasData { return null as *HirNode; }
|
||||
|
||||
var enumName: String = "";
|
||||
@@ -1115,12 +1253,15 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
||||
let refTe: *TypeExpr = expr.child1.refType;
|
||||
if refTe.kind == tekNamed {
|
||||
receiverTypeName = refTe.typeName;
|
||||
if refTe.typeName != null as String { receiverTypeName = refTe.typeName; }
|
||||
} else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed {
|
||||
receiverTypeName = refTe.pointerPointee.typeName;
|
||||
if refTe.pointerPointee.typeName != null as String {
|
||||
receiverTypeName = refTe.pointerPointee.typeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !String_Eq(receiverTypeName, "") {
|
||||
// Note: String_Eq(null, "") is false — must also reject null type names
|
||||
if receiverTypeName != null as String && !String_Eq(receiverTypeName, "") {
|
||||
let funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), opMethodName);
|
||||
let sym: Symbol = Scope_Lookup(ctx.scope, funcName);
|
||||
if sym.kind == skFunc && sym.decl != null as *Decl {
|
||||
|
||||
@@ -694,6 +694,7 @@ func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
|
||||
pat.patRangeInclusive = false;
|
||||
pat.patEnumPath = "";
|
||||
pat.patStructName = "";
|
||||
pat.patFieldName = "";
|
||||
pat.patChild1 = null as *Pattern;
|
||||
pat.patChild2 = null as *Pattern;
|
||||
pat.patArgs = null as *Pattern;
|
||||
@@ -767,6 +768,42 @@ func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
|
||||
pat.patArgs = enumArgs;
|
||||
return pat;
|
||||
}
|
||||
// Struct pattern: Point { x: a, y: b } or shorthand Point { x, y }
|
||||
if parserCheck(p, tkLBrace) {
|
||||
discard parserAdvance(p);
|
||||
var fieldHead: *Pattern = null as *Pattern;
|
||||
var fieldTail: *Pattern = null as *Pattern;
|
||||
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
||||
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
||||
if parserCheck(p, tkRBrace) { break; }
|
||||
let ftok: LexToken = parserExpectIdentOrKeyword(p, "expected field name in struct pattern");
|
||||
let fieldName: String = ftok.text;
|
||||
var fieldPat: *Pattern = null as *Pattern;
|
||||
if parserCheck(p, tkColon) {
|
||||
discard parserAdvance(p);
|
||||
fieldPat = parserParsePattern(p);
|
||||
} else {
|
||||
// Shorthand { x } → { x: x }
|
||||
fieldPat = parserMakePattern(pkIdent, line, col);
|
||||
fieldPat.patIdent = fieldName;
|
||||
}
|
||||
fieldPat.patFieldName = fieldName;
|
||||
if fieldHead == null as *Pattern {
|
||||
fieldHead = fieldPat;
|
||||
fieldTail = fieldPat;
|
||||
} else {
|
||||
fieldTail.patNext = fieldPat;
|
||||
fieldTail = fieldPat;
|
||||
}
|
||||
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
||||
else { break; }
|
||||
}
|
||||
discard parserExpect(p, tkRBrace, "expected '}' to close struct pattern");
|
||||
let spat: *Pattern = parserMakePattern(pkStruct, line, col);
|
||||
spat.patStructName = name;
|
||||
spat.patArgs = fieldHead;
|
||||
return spat;
|
||||
}
|
||||
// Bare name with (args): Variant(...) treated as single-segment enum
|
||||
if parserCheck(p, tkLParen) {
|
||||
discard parserAdvance(p);
|
||||
@@ -796,6 +833,29 @@ func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
|
||||
return pat;
|
||||
}
|
||||
|
||||
// Tuple pattern: (a, b)
|
||||
if kind == tkLParen {
|
||||
discard parserAdvance(p);
|
||||
var head: *Pattern = null as *Pattern;
|
||||
var tail: *Pattern = null as *Pattern;
|
||||
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
||||
let elem: *Pattern = parserParsePattern(p);
|
||||
if head == null as *Pattern {
|
||||
head = elem;
|
||||
tail = elem;
|
||||
} else {
|
||||
tail.patNext = elem;
|
||||
tail = elem;
|
||||
}
|
||||
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
||||
else { break; }
|
||||
}
|
||||
discard parserExpect(p, tkRParen, "expected ')' to close tuple pattern");
|
||||
let tpat: *Pattern = parserMakePattern(pkTuple, line, col);
|
||||
tpat.patArgs = head;
|
||||
return tpat;
|
||||
}
|
||||
|
||||
parserEmitDiag(p, line, col, "expected pattern");
|
||||
return parserMakePattern(pkWildcard, line, col);
|
||||
}
|
||||
|
||||
@@ -473,6 +473,88 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Tuple pattern: (a, b) — bind elements from subject tuple types
|
||||
if pat.kind == pkTuple {
|
||||
var ei: int = 0;
|
||||
var elem: *Pattern = pat.patArgs;
|
||||
while elem != null as *Pattern {
|
||||
if elem.kind == pkIdent {
|
||||
var ety: String = "int";
|
||||
// Tuple_int_int → fields are ints by default; prefer subject type args if present
|
||||
if subject != null as *Expr && subject.refType != null as *TypeExpr {
|
||||
if subject.refType.kind == tekTuple {
|
||||
// typeArgName0 / typeArgName1 store element type names when available
|
||||
if ei == 0 && !String_Eq(subject.refType.typeArgName0, "") {
|
||||
ety = subject.refType.typeArgName0;
|
||||
} else if ei == 1 && !String_Eq(subject.refType.typeArgName1, "") {
|
||||
ety = subject.refType.typeArgName1;
|
||||
}
|
||||
}
|
||||
}
|
||||
var bsym: Symbol;
|
||||
Sema_ZeroInitSymbol(&bsym);
|
||||
bsym.kind = skVar;
|
||||
bsym.name = elem.patIdent;
|
||||
bsym.typeName = ety;
|
||||
bsym.typeKind = tyInt;
|
||||
if String_Eq(ety, "String") || String_Eq(ety, "str") { bsym.typeKind = tyStr; }
|
||||
else if String_Eq(ety, "bool") { bsym.typeKind = tyBool; }
|
||||
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||
te.kind = tekNamed;
|
||||
te.typeName = ety;
|
||||
bsym.refType = te;
|
||||
bsym.isMutable = false;
|
||||
discard Scope_Define(sema.scope, bsym);
|
||||
}
|
||||
elem = elem.patNext;
|
||||
ei = ei + 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Struct pattern: Point { x: a, y: b }
|
||||
if pat.kind == pkStruct {
|
||||
var structName: String = pat.patStructName;
|
||||
if String_Eq(structName, "") && subject != null as *Expr && subject.refType != null as *TypeExpr {
|
||||
structName = subject.refType.typeName;
|
||||
}
|
||||
var field: *Pattern = pat.patArgs;
|
||||
while field != null as *Pattern {
|
||||
if field.kind == pkIdent {
|
||||
var ftype: String = "int";
|
||||
if !String_Eq(structName, "") {
|
||||
let ssym: Symbol = Scope_Lookup(sema.scope, structName);
|
||||
if ssym.decl != null as *Decl && ssym.decl.kind == dkStruct && ssym.decl.fields != null as *StructField {
|
||||
var fi: int = 0;
|
||||
while fi < ssym.decl.fieldCount {
|
||||
let sf: StructField = ssym.decl.fields[fi];
|
||||
if String_Eq(sf.name, field.patFieldName) {
|
||||
if sf.refFieldType != null as *TypeExpr && !String_Eq(sf.refFieldType.typeName, "") {
|
||||
ftype = sf.refFieldType.typeName;
|
||||
}
|
||||
}
|
||||
fi = fi + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
var bsym: Symbol;
|
||||
Sema_ZeroInitSymbol(&bsym);
|
||||
bsym.kind = skVar;
|
||||
bsym.name = field.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; }
|
||||
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||
te.kind = tekNamed;
|
||||
te.typeName = ftype;
|
||||
bsym.refType = te;
|
||||
bsym.isMutable = false;
|
||||
discard Scope_Define(sema.scope, bsym);
|
||||
}
|
||||
field = field.patNext;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
func Sema_IsMutRefDeref(target: *Expr) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user