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,7 +3,7 @@ SRC := bootstrap/main.nim
|
|||||||
OUT := buxc
|
OUT := buxc
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
|
|
||||||
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic
|
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic struct_tuple_pat
|
||||||
|
|
||||||
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp
|
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,53 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
|||||||
result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc))
|
result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc))
|
||||||
of pkGuarded:
|
of pkGuarded:
|
||||||
result.add(ctx.matchPatternBindings(subject, pattern.patGuardedInner, subjectEnumName, subjectHasData, loc))
|
result.add(ctx.matchPatternBindings(subject, pattern.patGuardedInner, subjectEnumName, subjectHasData, loc))
|
||||||
|
of pkTuple:
|
||||||
|
# (a, b) => bind a = subject._0, b = subject._1
|
||||||
|
for i, elem in pattern.patTupleElements:
|
||||||
|
if elem == nil:
|
||||||
|
continue
|
||||||
|
let fieldName = "_" & $i
|
||||||
|
let fieldTy = if subject.typ != nil and subject.typ.kind == tkTuple and i < subject.typ.inner.len:
|
||||||
|
subject.typ.inner[i]
|
||||||
|
else: makeInt()
|
||||||
|
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: fieldName,
|
||||||
|
typ: makePointer(fieldTy), loc: loc)
|
||||||
|
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
||||||
|
if elem.kind == pkIdent:
|
||||||
|
if elem.patIdent notin ctx.patternBoundNames:
|
||||||
|
result.add(hirAlloca(elem.patIdent, fieldTy, loc))
|
||||||
|
ctx.patternBoundNames.incl(elem.patIdent)
|
||||||
|
result.add(hirStore(hirVar(elem.patIdent, fieldTy, loc), fieldLoad, loc))
|
||||||
|
else:
|
||||||
|
# Nested patterns: recurse with field as subject
|
||||||
|
result.add(ctx.matchPatternBindings(fieldLoad, elem, subjectEnumName, subjectHasData, loc))
|
||||||
|
of pkStruct:
|
||||||
|
# Point { x: px, y: py } => px = subject.x, py = subject.y
|
||||||
|
var structName = pattern.patStructName
|
||||||
|
if structName.len == 0 and subject.typ != nil and subject.typ.kind == tkNamed:
|
||||||
|
structName = subject.typ.name
|
||||||
|
var fieldTypes = initTable[string, Type]()
|
||||||
|
if structName.len > 0:
|
||||||
|
let ssym = ctx.globalScope.lookup(structName)
|
||||||
|
if ssym != nil and ssym.decl != nil and ssym.decl.kind == dkStruct:
|
||||||
|
for f in ssym.decl.declStructFields:
|
||||||
|
fieldTypes[f.name] = ctx.resolveTypeExpr(f.ftype)
|
||||||
|
for entry in pattern.patStructFields:
|
||||||
|
let fname = entry.name
|
||||||
|
let fpat = entry.pattern
|
||||||
|
if fpat == nil:
|
||||||
|
continue
|
||||||
|
let fieldTy = if fieldTypes.hasKey(fname): fieldTypes[fname] else: makeInt()
|
||||||
|
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: fname,
|
||||||
|
typ: makePointer(fieldTy), loc: loc)
|
||||||
|
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
||||||
|
if fpat.kind == pkIdent:
|
||||||
|
if fpat.patIdent notin ctx.patternBoundNames:
|
||||||
|
result.add(hirAlloca(fpat.patIdent, fieldTy, loc))
|
||||||
|
ctx.patternBoundNames.incl(fpat.patIdent)
|
||||||
|
result.add(hirStore(hirVar(fpat.patIdent, fieldTy, loc), fieldLoad, loc))
|
||||||
|
else:
|
||||||
|
result.add(ctx.matchPatternBindings(fieldLoad, fpat, subjectEnumName, subjectHasData, loc))
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|||||||
@@ -767,10 +767,60 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
|
|||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
proc walkHirForTuples(n: HirNode) =
|
||||||
|
if n == nil: return
|
||||||
|
registerTuple(n.typ)
|
||||||
|
case n.kind
|
||||||
|
of hAlloca:
|
||||||
|
registerTuple(n.allocaType)
|
||||||
|
of hBlock:
|
||||||
|
for s in n.blockStmts: walkHirForTuples(s)
|
||||||
|
walkHirForTuples(n.blockExpr)
|
||||||
|
of hIf:
|
||||||
|
walkHirForTuples(n.ifCond)
|
||||||
|
walkHirForTuples(n.ifThen)
|
||||||
|
walkHirForTuples(n.ifElse)
|
||||||
|
of hWhile:
|
||||||
|
walkHirForTuples(n.whileCond)
|
||||||
|
walkHirForTuples(n.whileBody)
|
||||||
|
of hLoop:
|
||||||
|
walkHirForTuples(n.loopBody)
|
||||||
|
of hReturn:
|
||||||
|
walkHirForTuples(n.returnValue)
|
||||||
|
of hStore:
|
||||||
|
walkHirForTuples(n.storePtr)
|
||||||
|
walkHirForTuples(n.storeValue)
|
||||||
|
of hAssign:
|
||||||
|
walkHirForTuples(n.assignTarget)
|
||||||
|
walkHirForTuples(n.assignValue)
|
||||||
|
of hBinary:
|
||||||
|
walkHirForTuples(n.binaryLeft)
|
||||||
|
walkHirForTuples(n.binaryRight)
|
||||||
|
of hUnary:
|
||||||
|
walkHirForTuples(n.unaryOperand)
|
||||||
|
of hCall:
|
||||||
|
for a in n.callArgs: walkHirForTuples(a)
|
||||||
|
of hCallIndirect:
|
||||||
|
walkHirForTuples(n.callIndirectCallee)
|
||||||
|
for a in n.callIndirectArgs: walkHirForTuples(a)
|
||||||
|
of hLoad:
|
||||||
|
walkHirForTuples(n.loadPtr)
|
||||||
|
of hFieldPtr:
|
||||||
|
walkHirForTuples(n.fieldPtrBase)
|
||||||
|
of hFieldAccess:
|
||||||
|
walkHirForTuples(n.fieldAccessBase)
|
||||||
|
of hStructInit:
|
||||||
|
for f in n.structInitFields: walkHirForTuples(f.value)
|
||||||
|
of hTupleInit:
|
||||||
|
for e in n.tupleInitElements: walkHirForTuples(e)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
for f in module.funcs:
|
for f in module.funcs:
|
||||||
registerTuple(f.retType)
|
registerTuple(f.retType)
|
||||||
for p in f.params:
|
for p in f.params:
|
||||||
registerTuple(p.typ)
|
registerTuple(p.typ)
|
||||||
|
walkHirForTuples(f.body)
|
||||||
for ef in module.externFuncs:
|
for ef in module.externFuncs:
|
||||||
registerTuple(ef.retType)
|
registerTuple(ef.retType)
|
||||||
for p in ef.params:
|
for p in ef.params:
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ proc parsePrimaryPattern(p: var Parser): Pattern =
|
|||||||
return Pattern(kind: pkEnum, loc: loc, patEnumPath: path, patEnumArgs: args, patEnumNamed: named)
|
return Pattern(kind: pkEnum, loc: loc, patEnumPath: path, patEnumArgs: args, patEnumNamed: named)
|
||||||
return Pattern(kind: pkEnum, loc: loc, patEnumPath: path, patEnumArgs: @[], patEnumNamed: @[])
|
return Pattern(kind: pkEnum, loc: loc, patEnumPath: path, patEnumArgs: @[], patEnumNamed: @[])
|
||||||
elif p.check(tkLBrace):
|
elif p.check(tkLBrace):
|
||||||
# Struct pattern: Point { x: 0, y: 0 }
|
# Struct pattern: Point { x: px, y: py } or shorthand Point { x, y }
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
var fields: seq[tuple[name: string, pattern: Pattern]] = @[]
|
var fields: seq[tuple[name: string, pattern: Pattern]] = @[]
|
||||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||||
@@ -333,8 +333,12 @@ proc parsePrimaryPattern(p: var Parser): Pattern =
|
|||||||
if p.check(tkRBrace) or p.isAtEnd:
|
if p.check(tkRBrace) or p.isAtEnd:
|
||||||
break
|
break
|
||||||
let fieldName = p.expect(tkIdent, "expected field name in struct pattern").text
|
let fieldName = p.expect(tkIdent, "expected field name in struct pattern").text
|
||||||
discard p.expect(tkColon, "expected ':' after field name in pattern")
|
if p.check(tkColon):
|
||||||
fields.add((fieldName, p.parsePattern()))
|
discard p.advance()
|
||||||
|
fields.add((fieldName, p.parsePattern()))
|
||||||
|
else:
|
||||||
|
# Shorthand: { x } means { x: x }
|
||||||
|
fields.add((fieldName, Pattern(kind: pkIdent, loc: loc, patIdent: fieldName)))
|
||||||
if p.check(tkComma):
|
if p.check(tkComma):
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
discard p.expect(tkRBrace, "expected '}' to close struct pattern")
|
discard p.expect(tkRBrace, "expected '}' to close struct pattern")
|
||||||
|
|||||||
+17
-2
@@ -814,8 +814,23 @@ proc extractPatternBindings(sema: var Sema, pat: Pattern, scope: Scope, subjectT
|
|||||||
else:
|
else:
|
||||||
sema.extractPatternBindings(elem, scope, elemTy)
|
sema.extractPatternBindings(elem, scope, elemTy)
|
||||||
of pkStruct:
|
of pkStruct:
|
||||||
for f in pat.patStructFields:
|
# Resolve field types from struct declaration when possible
|
||||||
sema.extractPatternBindings(f.pattern, scope)
|
var fieldTypes = initTable[string, Type]()
|
||||||
|
var structName = pat.patStructName
|
||||||
|
if structName.len == 0 and subjectType != nil and subjectType.kind == tkNamed:
|
||||||
|
structName = subjectType.name
|
||||||
|
if structName.len > 0:
|
||||||
|
let ssym = sema.globalScope.lookup(structName)
|
||||||
|
if ssym != nil and ssym.decl != nil and ssym.decl.kind == dkStruct:
|
||||||
|
for f in ssym.decl.declStructFields:
|
||||||
|
fieldTypes[f.name] = sema.resolveType(f.ftype)
|
||||||
|
for entry in pat.patStructFields:
|
||||||
|
let fty = if fieldTypes.hasKey(entry.name): fieldTypes[entry.name] else: makeUnknown()
|
||||||
|
if entry.pattern != nil and entry.pattern.kind == pkIdent:
|
||||||
|
let sym = Symbol(kind: skVar, name: entry.pattern.patIdent, typ: fty, isMutable: false)
|
||||||
|
discard scope.define(sym)
|
||||||
|
else:
|
||||||
|
sema.extractPatternBindings(entry.pattern, scope, fty)
|
||||||
of pkGuarded:
|
of pkGuarded:
|
||||||
sema.extractPatternBindings(pat.patGuardedInner, scope, subjectType)
|
sema.extractPatternBindings(pat.patGuardedInner, scope, subjectType)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+14
-1
@@ -383,7 +383,20 @@ Supported patterns:
|
|||||||
- Identifier catch-all: `name` (binds whole subject)
|
- Identifier catch-all: `name` (binds whole subject)
|
||||||
- Range: `1..9`, `1..=9`
|
- Range: `1..9`, `1..=9`
|
||||||
- Enum tags + **payload bindings**: `Option::Some(value)`, `Pair::Two(a, b)`
|
- Enum tags + **payload bindings**: `Option::Some(value)`, `Pair::Two(a, b)`
|
||||||
- Struct / tuple / guard patterns: parsed; full lowering still evolving
|
- **Tuple patterns**: `(a, b)` → binds `subject._0`, `subject._1`
|
||||||
|
- **Struct patterns**: `Point { x: px, y: py }` or shorthand `Point { x, y }`
|
||||||
|
- Guard patterns: parsed; full lowering still evolving
|
||||||
|
|
||||||
|
```bux
|
||||||
|
match pair {
|
||||||
|
(a, b) => a + b,
|
||||||
|
_ => 0
|
||||||
|
}
|
||||||
|
match p {
|
||||||
|
Point { x, y } => x * 10 + y,
|
||||||
|
_ => -1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+15
-3
@@ -261,9 +261,21 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Сесия 15 (struct/tuple patterns)
|
||||||
|
|
||||||
|
1. **Tuple patterns:** `match t { (a, b) => a + b }` — bind `subject._0` / `._1`
|
||||||
|
2. **Struct patterns:** `Point { x: px, y: py }` + shorthand `Point { x, y }`
|
||||||
|
3. Bootstrap: `matchPatternBindings` for pkTuple/pkStruct; field types from struct decl; range registration of local tuple typedefs
|
||||||
|
4. Selfhost: parse `()` / `Name { … }` patterns; `Sema_BindPattern` + `Lcx_PatternBindings` with Scope_Define
|
||||||
|
5. Fix: operator-overload path treated `String_Eq(null, "")` as non-empty → crash on `a + b` after pattern bind
|
||||||
|
6. Example: `examples/struct_tuple_pat.bux`
|
||||||
|
7. Verified: bootstrap + **buxc2** + selfhost-loop IDENTICAL ✓
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Следващи стъпки
|
## Следващи стъпки
|
||||||
|
|
||||||
1. Struct/tuple patterns (`Point { x, y }`, `(a, b)`) + nested bindings
|
1. Match arm multi-stmt bodies (beyond single expr)
|
||||||
2. Match arm multi-stmt bodies (beyond single expr)
|
2. Nested patterns deeper (`Some((a, b))`, `Point { x: (a, b) }`)
|
||||||
3. LSP: wire hover types from real sema (replace lightweight index where possible)
|
3. LSP: wire hover types from real sema
|
||||||
4. Generic type inference for `Iter_Map` without explicit `<T,U>`
|
4. Generic type inference for `Iter_Map` without explicit `<T,U>`
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Struct and tuple patterns in match: (a, b), Point { x, y }
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
x: int,
|
||||||
|
y: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
func SumPair(t: (int, int)) -> int {
|
||||||
|
match t {
|
||||||
|
(a, b) => a + b,
|
||||||
|
_ => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PointCode(p: Point) -> int {
|
||||||
|
match p {
|
||||||
|
Point { x: px, y: py } => px * 10 + py,
|
||||||
|
_ => -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PointShorthand(p: Point) -> int {
|
||||||
|
// { x, y } means { x: x, y: y }
|
||||||
|
match p {
|
||||||
|
Point { x, y } => x + y,
|
||||||
|
_ => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let t: (int, int) = (10, 20);
|
||||||
|
Test_AssertEqInt(SumPair(t), 30);
|
||||||
|
|
||||||
|
let lit: (int, int) = (7, 8);
|
||||||
|
let s: int = match lit {
|
||||||
|
(a, b) => a * b,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(s, 56);
|
||||||
|
|
||||||
|
let p: Point = Point { x: 3, y: 4 };
|
||||||
|
Test_AssertEqInt(PointCode(p), 34);
|
||||||
|
Test_AssertEqInt(PointShorthand(p), 7);
|
||||||
|
|
||||||
|
PrintInt(SumPair(t));
|
||||||
|
PrintLine("");
|
||||||
|
PrintInt(PointCode(p));
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("struct_tuple_pat");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+3
-2
@@ -76,10 +76,11 @@ struct Pattern {
|
|||||||
patLitText: String, // for pkLiteral (token text)
|
patLitText: String, // for pkLiteral (token text)
|
||||||
patRangeInclusive: bool, // for pkRange
|
patRangeInclusive: bool, // for pkRange
|
||||||
patEnumPath: String, // for pkEnum: "Enum::Variant"
|
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
|
patChild1: *Pattern, // range lo / nested
|
||||||
patChild2: *Pattern, // range hi / 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
|
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 {
|
line: uint32, col: uint32) -> *HirNode {
|
||||||
if pat == null as *Pattern { return null as *HirNode; }
|
if pat == null as *Pattern { return null as *HirNode; }
|
||||||
if pat.kind == pkIdent {
|
if pat.kind == pkIdent {
|
||||||
|
// `_` is wildcard, not a binding
|
||||||
|
if String_Eq(pat.patIdent, "_") { return null as *HirNode; }
|
||||||
let ty: String = "int";
|
let ty: String = "int";
|
||||||
if subject != null as *HirNode && !String_Eq(subject.typeName, "") {
|
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;
|
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
alloca.kind = hAlloca;
|
alloca.kind = hAlloca;
|
||||||
@@ -589,8 +591,144 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
store.child1 = v;
|
store.child1 = v;
|
||||||
store.child2 = subject;
|
store.child2 = subject;
|
||||||
alloca.child3 = store;
|
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;
|
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; }
|
if pat.kind != pkEnum || !subjectHasData { return null as *HirNode; }
|
||||||
|
|
||||||
var enumName: String = "";
|
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 {
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
||||||
let refTe: *TypeExpr = expr.child1.refType;
|
let refTe: *TypeExpr = expr.child1.refType;
|
||||||
if refTe.kind == tekNamed {
|
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 {
|
} 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 funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), opMethodName);
|
||||||
let sym: Symbol = Scope_Lookup(ctx.scope, funcName);
|
let sym: Symbol = Scope_Lookup(ctx.scope, funcName);
|
||||||
if sym.kind == skFunc && sym.decl != null as *Decl {
|
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.patRangeInclusive = false;
|
||||||
pat.patEnumPath = "";
|
pat.patEnumPath = "";
|
||||||
pat.patStructName = "";
|
pat.patStructName = "";
|
||||||
|
pat.patFieldName = "";
|
||||||
pat.patChild1 = null as *Pattern;
|
pat.patChild1 = null as *Pattern;
|
||||||
pat.patChild2 = null as *Pattern;
|
pat.patChild2 = null as *Pattern;
|
||||||
pat.patArgs = null as *Pattern;
|
pat.patArgs = null as *Pattern;
|
||||||
@@ -767,6 +768,42 @@ func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
|
|||||||
pat.patArgs = enumArgs;
|
pat.patArgs = enumArgs;
|
||||||
return pat;
|
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
|
// Bare name with (args): Variant(...) treated as single-segment enum
|
||||||
if parserCheck(p, tkLParen) {
|
if parserCheck(p, tkLParen) {
|
||||||
discard parserAdvance(p);
|
discard parserAdvance(p);
|
||||||
@@ -796,6 +833,29 @@ func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
|
|||||||
return pat;
|
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");
|
parserEmitDiag(p, line, col, "expected pattern");
|
||||||
return parserMakePattern(pkWildcard, line, col);
|
return parserMakePattern(pkWildcard, line, col);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -473,6 +473,88 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
|||||||
}
|
}
|
||||||
return;
|
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 {
|
func Sema_IsMutRefDeref(target: *Expr) -> bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user