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:
@@ -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
|
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
|
||||||
|
|
||||||
.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
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -595,11 +595,19 @@ proc emitEnum*(be: var CBackend, name: string, variants: seq[HirEnumVariant]) =
|
|||||||
be.emitLine(&"typedef union {{")
|
be.emitLine(&"typedef union {{")
|
||||||
inc be.indent
|
inc be.indent
|
||||||
for v in variants:
|
for v in variants:
|
||||||
if v.fields.len > 0:
|
if v.fields.len == 1:
|
||||||
# Positional fields
|
# Single positional field — flat union member (compat: data.Variant_0)
|
||||||
|
let typ = typeToC(be, v.fields[0])
|
||||||
|
be.emitLine(&"{typ} {v.name}_0;")
|
||||||
|
elif v.fields.len > 1:
|
||||||
|
# Multi positional fields — nested struct so fields don't overlay
|
||||||
|
be.emitLine(&"struct {{")
|
||||||
|
inc be.indent
|
||||||
for i, f in v.fields:
|
for i, f in v.fields:
|
||||||
let typ = typeToC(be, f)
|
let typ = typeToC(be, f)
|
||||||
be.emitLine(&"{typ} {v.name}_{i};")
|
be.emitLine(&"{typ} {v.name}_{i};")
|
||||||
|
dec be.indent
|
||||||
|
be.emitLine(&"}} {v.name};")
|
||||||
elif v.namedFields.len > 0:
|
elif v.namedFields.len > 0:
|
||||||
# Named fields - generate as struct
|
# Named fields - generate as struct
|
||||||
be.emitLine(&"struct {{")
|
be.emitLine(&"struct {{")
|
||||||
|
|||||||
+146
-7
@@ -30,6 +30,9 @@ type
|
|||||||
funcAdapterSigs*: Table[string, Type]
|
funcAdapterSigs*: Table[string, Type]
|
||||||
## All func types that need BuxFn_* typedefs (including locals)
|
## All func types that need BuxFn_* typedefs (including locals)
|
||||||
seenFatTypes*: seq[Type]
|
seenFatTypes*: seq[Type]
|
||||||
|
## Pattern-binding names already alloca'd in the current function
|
||||||
|
## (avoids `int v;` twice when two matches bind the same name)
|
||||||
|
patternBoundNames*: HashSet[string]
|
||||||
|
|
||||||
proc freshName(ctx: var LowerCtx): string =
|
proc freshName(ctx: var LowerCtx): string =
|
||||||
inc ctx.varCounter
|
inc ctx.varCounter
|
||||||
@@ -73,6 +76,8 @@ proc patternLiteralNode(pat: Pattern, loc: SourceLocation): HirNode =
|
|||||||
proc matchAlwaysTrue(loc: SourceLocation): HirNode =
|
proc matchAlwaysTrue(loc: SourceLocation): HirNode =
|
||||||
hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc)
|
hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc)
|
||||||
|
|
||||||
|
proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type
|
||||||
|
|
||||||
proc matchPatternCond(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
proc matchPatternCond(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
||||||
subjectEnumName: string, subjectHasData: bool,
|
subjectEnumName: string, subjectHasData: bool,
|
||||||
loc: SourceLocation): HirNode =
|
loc: SourceLocation): HirNode =
|
||||||
@@ -126,9 +131,99 @@ proc matchPatternCond(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
|||||||
# Struct/tuple patterns: not yet fully lowered — always-true
|
# Struct/tuple patterns: not yet fully lowered — always-true
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
||||||
|
subjectEnumName: string, subjectHasData: bool,
|
||||||
|
loc: SourceLocation): seq[HirNode] =
|
||||||
|
## Emit alloca+store for identifiers bound by a match pattern.
|
||||||
|
## Enum payload: `Option::Some(value)` → `value = subject.data.Some_0`
|
||||||
|
## Ident catch-all: `x` → `x = subject`
|
||||||
|
result = @[]
|
||||||
|
if pattern == nil: return
|
||||||
|
case pattern.kind
|
||||||
|
of pkIdent:
|
||||||
|
let ty = if subject.typ != nil: subject.typ else: makeUnknown()
|
||||||
|
if pattern.patIdent notin ctx.patternBoundNames:
|
||||||
|
result.add(hirAlloca(pattern.patIdent, ty, loc))
|
||||||
|
ctx.patternBoundNames.incl(pattern.patIdent)
|
||||||
|
result.add(hirStore(hirVar(pattern.patIdent, ty, loc), subject, loc))
|
||||||
|
of pkEnum:
|
||||||
|
if not subjectHasData:
|
||||||
|
return
|
||||||
|
var enumName = ""
|
||||||
|
var variantName = ""
|
||||||
|
if pattern.patEnumPath.len >= 2:
|
||||||
|
enumName = pattern.patEnumPath[0]
|
||||||
|
variantName = pattern.patEnumPath[^1]
|
||||||
|
elif pattern.patEnumPath.len == 1:
|
||||||
|
variantName = pattern.patEnumPath[0]
|
||||||
|
enumName = subjectEnumName
|
||||||
|
if enumName == "" or variantName == "":
|
||||||
|
return
|
||||||
|
# Look up field types from enum declaration
|
||||||
|
var fieldTypes: seq[Type] = @[]
|
||||||
|
var namedFields: seq[tuple[name: string, typ: Type]] = @[]
|
||||||
|
let enumSym = ctx.globalScope.lookup(enumName)
|
||||||
|
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
|
||||||
|
for v in enumSym.decl.declEnumVariants:
|
||||||
|
if v.name == variantName:
|
||||||
|
for f in v.fields:
|
||||||
|
fieldTypes.add(ctx.resolveTypeExpr(f))
|
||||||
|
for nf in v.namedFields:
|
||||||
|
namedFields.add((nf.name, ctx.resolveTypeExpr(nf.ftype)))
|
||||||
|
break
|
||||||
|
let dataType = makeNamed(enumName & "_Data")
|
||||||
|
let dataPtr = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: "data",
|
||||||
|
typ: makePointer(dataType), loc: loc)
|
||||||
|
let dataLoad = HirNode(kind: hLoad, loadPtr: dataPtr, typ: dataType, loc: loc)
|
||||||
|
# Multi-field positional variants live in a nested struct data.Variant.{Variant_i}
|
||||||
|
# Single-field stay flat as data.Variant_0 for ABI compat.
|
||||||
|
let multiField = fieldTypes.len > 1
|
||||||
|
var payloadBase = dataLoad
|
||||||
|
if multiField:
|
||||||
|
let variantStructTy = makeNamed(variantName)
|
||||||
|
let variantPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: variantName,
|
||||||
|
typ: makePointer(variantStructTy), loc: loc)
|
||||||
|
payloadBase = HirNode(kind: hLoad, loadPtr: variantPtr, typ: variantStructTy, loc: loc)
|
||||||
|
for i, arg in pattern.patEnumArgs:
|
||||||
|
if arg == nil or arg.kind != pkIdent:
|
||||||
|
continue
|
||||||
|
let fieldName = variantName & "_" & $i
|
||||||
|
let fieldTy = if i < fieldTypes.len: fieldTypes[i] else: makeInt()
|
||||||
|
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: payloadBase, fieldName: fieldName,
|
||||||
|
typ: makePointer(fieldTy), loc: loc)
|
||||||
|
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
||||||
|
if arg.patIdent notin ctx.patternBoundNames:
|
||||||
|
result.add(hirAlloca(arg.patIdent, fieldTy, loc))
|
||||||
|
ctx.patternBoundNames.incl(arg.patIdent)
|
||||||
|
result.add(hirStore(hirVar(arg.patIdent, fieldTy, loc), fieldLoad, loc))
|
||||||
|
for nf in pattern.patEnumNamed:
|
||||||
|
if nf.pattern == nil or nf.pattern.kind != pkIdent:
|
||||||
|
continue
|
||||||
|
var fieldTy = makeInt()
|
||||||
|
for entry in namedFields:
|
||||||
|
if entry.name == nf.name:
|
||||||
|
fieldTy = entry.typ
|
||||||
|
break
|
||||||
|
# Named payload fields live under data.VariantName.name
|
||||||
|
let variantPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: variantName,
|
||||||
|
typ: makePointer(makeNamed(variantName)), loc: loc)
|
||||||
|
let variantLoad = HirNode(kind: hLoad, loadPtr: variantPtr, typ: makeNamed(variantName), loc: loc)
|
||||||
|
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: variantLoad, fieldName: nf.name,
|
||||||
|
typ: makePointer(fieldTy), loc: loc)
|
||||||
|
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
||||||
|
if nf.pattern.patIdent notin ctx.patternBoundNames:
|
||||||
|
result.add(hirAlloca(nf.pattern.patIdent, fieldTy, loc))
|
||||||
|
ctx.patternBoundNames.incl(nf.pattern.patIdent)
|
||||||
|
result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc))
|
||||||
|
of pkGuarded:
|
||||||
|
result.add(ctx.matchPatternBindings(subject, pattern.patGuardedInner, subjectEnumName, subjectHasData, loc))
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ: Type, loc: SourceLocation): HirNode =
|
proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ: Type, loc: SourceLocation): HirNode =
|
||||||
## Lower match expression to a block with if-else chain.
|
## Lower match expression to a block with if-else chain.
|
||||||
## Supports: enum tags, integer/bool/char/string literals, ranges, wildcard/ident.
|
## Supports: enum tags + payload bindings, integer/bool/char/string literals,
|
||||||
|
## ranges, wildcard/ident catch-all.
|
||||||
let hasResult = typ != nil and typ.kind != tkVoid and typ.kind != tkUnknown
|
let hasResult = typ != nil and typ.kind != tkVoid and typ.kind != tkUnknown
|
||||||
let resultName = ctx.freshName()
|
let resultName = ctx.freshName()
|
||||||
var stmts: seq[HirNode] = @[]
|
var stmts: seq[HirNode] = @[]
|
||||||
@@ -143,8 +238,8 @@ proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ
|
|||||||
subjectEnumName = subject.typ.name
|
subjectEnumName = subject.typ.name
|
||||||
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
|
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
|
||||||
|
|
||||||
proc makeArmBlock(body: HirNode): HirNode =
|
proc makeArmBlock(body: HirNode, bindStmts: seq[HirNode]): HirNode =
|
||||||
var armStmts: seq[HirNode] = @[]
|
var armStmts: seq[HirNode] = bindStmts
|
||||||
if hasResult:
|
if hasResult:
|
||||||
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
||||||
elif body != nil:
|
elif body != nil:
|
||||||
@@ -157,7 +252,8 @@ proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ
|
|||||||
|
|
||||||
for i in countdown(arms.len - 1, 0):
|
for i in countdown(arms.len - 1, 0):
|
||||||
let arm = arms[i]
|
let arm = arms[i]
|
||||||
let armBlock = makeArmBlock(arm.body)
|
let binds = matchPatternBindings(ctx, subject, arm.pattern, subjectEnumName, subjectHasData, loc)
|
||||||
|
let armBlock = makeArmBlock(arm.body, binds)
|
||||||
let cond = matchPatternCond(ctx, subject, arm.pattern, subjectEnumName, subjectHasData, loc)
|
let cond = matchPatternCond(ctx, subject, arm.pattern, subjectEnumName, subjectHasData, loc)
|
||||||
|
|
||||||
if cond == nil:
|
if cond == nil:
|
||||||
@@ -202,6 +298,7 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
|||||||
result.funcAdapters = initHashSet[string]()
|
result.funcAdapters = initHashSet[string]()
|
||||||
result.funcAdapterSigs = initTable[string, Type]()
|
result.funcAdapterSigs = initTable[string, Type]()
|
||||||
result.seenFatTypes = @[]
|
result.seenFatTypes = @[]
|
||||||
|
result.patternBoundNames = initHashSet[string]()
|
||||||
|
|
||||||
proc sanitizeFatPart(s: string): string =
|
proc sanitizeFatPart(s: string): string =
|
||||||
result = s.replace("const char*", "cstr").replace("unsigned int", "uint")
|
result = s.replace("const char*", "cstr").replace("unsigned int", "uint")
|
||||||
@@ -244,8 +341,6 @@ proc hirFuncFatTypeName*(typ: Type): string =
|
|||||||
parts.add("void")
|
parts.add("void")
|
||||||
return "BuxFn_" & parts.join("_")
|
return "BuxFn_" & parts.join("_")
|
||||||
|
|
||||||
proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type
|
|
||||||
|
|
||||||
proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type]): Type =
|
proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type]): Type =
|
||||||
if te == nil: return makeUnknown()
|
if te == nil: return makeUnknown()
|
||||||
case te.kind
|
case te.kind
|
||||||
@@ -1314,10 +1409,51 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
|
|
||||||
of ekMatch:
|
of ekMatch:
|
||||||
let subject = ctx.lowerExpr(expr.exprMatchSubject)
|
let subject = ctx.lowerExpr(expr.exprMatchSubject)
|
||||||
|
var subjectEnumName = ""
|
||||||
|
var subjectHasData = false
|
||||||
|
if subject.typ != nil and subject.typ.kind == tkNamed:
|
||||||
|
subjectEnumName = subject.typ.name
|
||||||
|
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
|
||||||
|
# Prefer resolved match type; fall back to function return type when arms
|
||||||
|
# only reference pattern bindings (not yet in varTypeExprs during resolve).
|
||||||
|
var matchTyp = typ
|
||||||
|
if matchTyp == nil or matchTyp.kind == tkUnknown:
|
||||||
|
if ctx.currentFuncRetType != nil and ctx.currentFuncRetType.kind notin {tkVoid, tkUnknown}:
|
||||||
|
matchTyp = ctx.currentFuncRetType
|
||||||
var arms: seq[HirMatchArm] = @[]
|
var arms: seq[HirMatchArm] = @[]
|
||||||
for arm in expr.exprMatchArms:
|
for arm in expr.exprMatchArms:
|
||||||
|
# Register bind types only (do NOT call matchPatternBindings here — that
|
||||||
|
# would mark names as already alloca'd and lowerMatch would skip them).
|
||||||
|
if arm.pattern != nil and arm.pattern.kind == pkEnum and subjectHasData:
|
||||||
|
var enumName = ""
|
||||||
|
var variantName = ""
|
||||||
|
if arm.pattern.patEnumPath.len >= 2:
|
||||||
|
enumName = arm.pattern.patEnumPath[0]
|
||||||
|
variantName = arm.pattern.patEnumPath[^1]
|
||||||
|
elif arm.pattern.patEnumPath.len == 1:
|
||||||
|
variantName = arm.pattern.patEnumPath[0]
|
||||||
|
enumName = subjectEnumName
|
||||||
|
var fieldTypes: seq[Type] = @[]
|
||||||
|
let enumSym = ctx.globalScope.lookup(enumName)
|
||||||
|
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
|
||||||
|
for v in enumSym.decl.declEnumVariants:
|
||||||
|
if v.name == variantName:
|
||||||
|
for f in v.fields:
|
||||||
|
fieldTypes.add(ctx.resolveTypeExpr(f))
|
||||||
|
break
|
||||||
|
for i, arg in arm.pattern.patEnumArgs:
|
||||||
|
if arg != nil and arg.kind == pkIdent:
|
||||||
|
let ft = if i < fieldTypes.len: fieldTypes[i] else: makeInt()
|
||||||
|
ctx.varTypeExprs[arg.patIdent] = typeToTypeExpr(ft)
|
||||||
|
elif arm.pattern != nil and arm.pattern.kind == pkIdent:
|
||||||
|
let ty = if subject.typ != nil: subject.typ else: makeUnknown()
|
||||||
|
ctx.varTypeExprs[arm.pattern.patIdent] = typeToTypeExpr(ty)
|
||||||
arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body)))
|
arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body)))
|
||||||
return lowerMatch(ctx, subject, arms, typ, loc)
|
# Re-resolve match type now that bindings are registered
|
||||||
|
if matchTyp == nil or matchTyp.kind == tkUnknown:
|
||||||
|
if arms.len > 0 and arms[0].body != nil and arms[0].body.typ != nil:
|
||||||
|
matchTyp = arms[0].body.typ
|
||||||
|
return lowerMatch(ctx, subject, arms, matchTyp, loc)
|
||||||
|
|
||||||
of ekSizeOf:
|
of ekSizeOf:
|
||||||
let ty = ctx.resolveTypeExpr(expr.exprSizeOfType)
|
let ty = ctx.resolveTypeExpr(expr.exprSizeOfType)
|
||||||
@@ -1796,9 +1932,11 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
|||||||
let oldFuncDecl = ctx.currentFuncDecl
|
let oldFuncDecl = ctx.currentFuncDecl
|
||||||
let oldFuncRetType = ctx.currentFuncRetType
|
let oldFuncRetType = ctx.currentFuncRetType
|
||||||
let oldVarTypeExprs = ctx.varTypeExprs
|
let oldVarTypeExprs = ctx.varTypeExprs
|
||||||
|
let oldPatternBound = ctx.patternBoundNames
|
||||||
ctx.currentFuncRetType = retType
|
ctx.currentFuncRetType = retType
|
||||||
ctx.currentFuncDecl = decl
|
ctx.currentFuncDecl = decl
|
||||||
ctx.varTypeExprs = initTable[string, TypeExpr]() # Clear local vars for new function
|
ctx.varTypeExprs = initTable[string, TypeExpr]() # Clear local vars for new function
|
||||||
|
ctx.patternBoundNames = initHashSet[string]()
|
||||||
# Add parameters to varTypeExprs after clearing so they are visible in the body.
|
# Add parameters to varTypeExprs after clearing so they are visible in the body.
|
||||||
for p in funcParams:
|
for p in funcParams:
|
||||||
if p.ptype != nil:
|
if p.ptype != nil:
|
||||||
@@ -1824,6 +1962,7 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
|||||||
ctx.currentFuncDecl = oldFuncDecl
|
ctx.currentFuncDecl = oldFuncDecl
|
||||||
ctx.currentFuncRetType = oldFuncRetType
|
ctx.currentFuncRetType = oldFuncRetType
|
||||||
ctx.varTypeExprs = oldVarTypeExprs
|
ctx.varTypeExprs = oldVarTypeExprs
|
||||||
|
ctx.patternBoundNames = oldPatternBound
|
||||||
|
|
||||||
result = HirFunc(name: funcName, params: params, retType: retType,
|
result = HirFunc(name: funcName, params: params, retType: retType,
|
||||||
body: body, isPublic: decl.isPublic)
|
body: body, isPublic: decl.isPublic)
|
||||||
|
|||||||
@@ -228,6 +228,9 @@ proc scanEscapeSequence(lex: var Lexer): string =
|
|||||||
of 'r': result = "\r"
|
of 'r': result = "\r"
|
||||||
of 't': result = "\t"
|
of 't': result = "\t"
|
||||||
of '0': result = "\0"
|
of '0': result = "\0"
|
||||||
|
of '{', '}':
|
||||||
|
# Preserve \{ and \} so f"..." interpolation can treat them as literal braces
|
||||||
|
result = "\\" & $c
|
||||||
of 'x':
|
of 'x':
|
||||||
var hexVal = ""
|
var hexVal = ""
|
||||||
for _ in 0..<2:
|
for _ in 0..<2:
|
||||||
|
|||||||
@@ -496,9 +496,17 @@ proc emitEnumDef(be: var LirCBackend, name: string, variants: seq[HirEnumVariant
|
|||||||
be.emitLine(&"typedef union {{")
|
be.emitLine(&"typedef union {{")
|
||||||
be.indent += 1
|
be.indent += 1
|
||||||
for v in variants:
|
for v in variants:
|
||||||
if v.fields.len > 0:
|
if v.fields.len == 1:
|
||||||
|
# Single positional field — flat (compat: data.Variant_0)
|
||||||
|
be.emitLine(&"{typeToCStr(v.fields[0])} {v.name}_0;")
|
||||||
|
elif v.fields.len > 1:
|
||||||
|
# Multi positional — nested struct so fields don't share union storage
|
||||||
|
be.emitLine(&"struct {{")
|
||||||
|
be.indent += 1
|
||||||
for i, f in v.fields:
|
for i, f in v.fields:
|
||||||
be.emitLine(&"{typeToCStr(f)} {v.name}_{i};")
|
be.emitLine(&"{typeToCStr(f)} {v.name}_{i};")
|
||||||
|
be.indent -= 1
|
||||||
|
be.emitLine(&"}} {v.name};")
|
||||||
elif v.namedFields.len > 0:
|
elif v.namedFields.len > 0:
|
||||||
be.emitLine(&"struct {{")
|
be.emitLine(&"struct {{")
|
||||||
be.indent += 1
|
be.indent += 1
|
||||||
|
|||||||
+22
-1
@@ -463,7 +463,10 @@ proc parseStringInterpolation(p: var Parser, tok: Token): Expr =
|
|||||||
i += 1
|
i += 1
|
||||||
texts.add(currentText)
|
texts.add(currentText)
|
||||||
if exprs.len == 0:
|
if exprs.len == 0:
|
||||||
return newLiteralExpr(tok)
|
# f"plain" / f"use \{x\}" with no real {expr} — use processed text (escapes applied)
|
||||||
|
var litTok = tok
|
||||||
|
litTok.text = "\"" & texts[0] & "\""
|
||||||
|
return newLiteralExpr(litTok)
|
||||||
return newStringInterpExpr(texts, exprs, tok.loc)
|
return newStringInterpExpr(texts, exprs, tok.loc)
|
||||||
|
|
||||||
proc parsePrimary(p: var Parser): Expr =
|
proc parsePrimary(p: var Parser): Expr =
|
||||||
@@ -524,9 +527,16 @@ proc parsePrimary(p: var Parser): Expr =
|
|||||||
p.structInitAllowed = false
|
p.structInitAllowed = false
|
||||||
let subject = p.parseExpr()
|
let subject = p.parseExpr()
|
||||||
p.structInitAllowed = true
|
p.structInitAllowed = true
|
||||||
|
# Same newline rules as statement-form match (needed for `let x = match ...`)
|
||||||
|
while p.check(tkNewLine):
|
||||||
|
discard p.advance()
|
||||||
discard p.expect(tkLBrace, "expected '{' to start match")
|
discard p.expect(tkLBrace, "expected '{' to start match")
|
||||||
var arms: seq[MatchArm] = @[]
|
var arms: seq[MatchArm] = @[]
|
||||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||||
|
while p.check(tkNewLine):
|
||||||
|
discard p.advance()
|
||||||
|
if p.check(tkRBrace) or p.isAtEnd:
|
||||||
|
break
|
||||||
let armLoc = p.currentLoc
|
let armLoc = p.currentLoc
|
||||||
let pat = p.parsePattern()
|
let pat = p.parsePattern()
|
||||||
discard p.expect(tkFatArrow, "expected '=>' in match arm")
|
discard p.expect(tkFatArrow, "expected '=>' in match arm")
|
||||||
@@ -581,6 +591,17 @@ proc parsePrimary(p: var Parser): Expr =
|
|||||||
of tkNull:
|
of tkNull:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
return newLiteralExpr(Token(kind: tkNull, text: "null", loc: loc))
|
return newLiteralExpr(Token(kind: tkNull, text: "null", loc: loc))
|
||||||
|
of tkPipePipe:
|
||||||
|
# Empty-param closure written as `||` — lexer merges two '|' into tkPipePipe.
|
||||||
|
# Disambiguate from logical-or (which only appears mid-expression): as a primary,
|
||||||
|
# `|| -> T { ... }` / `|| { ... }` is always a zero-param closure.
|
||||||
|
discard p.advance() # ||
|
||||||
|
var retTypePP: TypeExpr = nil
|
||||||
|
if p.check(tkArrow):
|
||||||
|
discard p.advance() # ->
|
||||||
|
retTypePP = p.parseType()
|
||||||
|
let bodyPP = p.parseBlock()
|
||||||
|
return Expr(kind: ekClosure, loc: loc, exprClosureParams: @[], exprClosureBody: bodyPP, exprClosureReturnType: retTypePP)
|
||||||
of tkPipe:
|
of tkPipe:
|
||||||
# Closure: |params| -> Ret { body }
|
# Closure: |params| -> Ret { body }
|
||||||
discard p.advance() # |
|
discard p.advance() # |
|
||||||
|
|||||||
+56
-13
@@ -757,26 +757,67 @@ proc checkTraitBounds(sema: var Sema, funcDecl: Decl, inferredTypes: seq[Type],
|
|||||||
if not sema.typeImplements(inferredTypes[i], bound):
|
if not sema.typeImplements(inferredTypes[i], bound):
|
||||||
sema.emitError(loc, &"type '{inferredTypes[i].toString}' does not implement trait '{bound}'")
|
sema.emitError(loc, &"type '{inferredTypes[i].toString}' does not implement trait '{bound}'")
|
||||||
|
|
||||||
proc extractPatternBindings(sema: var Sema, pat: Pattern, scope: Scope) =
|
proc extractPatternBindings(sema: var Sema, pat: Pattern, scope: Scope, subjectType: Type = nil) =
|
||||||
## Add pattern-bound identifiers to scope with unknown type (best-effort)
|
## Add pattern-bound identifiers to scope. For enum payloads, resolve field types
|
||||||
|
## from the matched enum variant so arm bodies type-check correctly.
|
||||||
if pat == nil: return
|
if pat == nil: return
|
||||||
case pat.kind
|
case pat.kind
|
||||||
of pkIdent:
|
of pkIdent:
|
||||||
let sym = Symbol(kind: skVar, name: pat.patIdent, typ: makeUnknown(), isMutable: false)
|
let bindTy = if subjectType != nil and not subjectType.isUnknown: subjectType else: makeUnknown()
|
||||||
|
let sym = Symbol(kind: skVar, name: pat.patIdent, typ: bindTy, isMutable: false)
|
||||||
discard scope.define(sym)
|
discard scope.define(sym)
|
||||||
of pkEnum:
|
of pkEnum:
|
||||||
for arg in pat.patEnumArgs:
|
# Resolve variant field types from enum declaration
|
||||||
sema.extractPatternBindings(arg, scope)
|
var enumName = ""
|
||||||
|
var variantName = ""
|
||||||
|
if pat.patEnumPath.len >= 2:
|
||||||
|
enumName = pat.patEnumPath[0]
|
||||||
|
variantName = pat.patEnumPath[^1]
|
||||||
|
elif pat.patEnumPath.len == 1:
|
||||||
|
variantName = pat.patEnumPath[0]
|
||||||
|
if subjectType != nil and subjectType.kind == tkNamed:
|
||||||
|
enumName = subjectType.name
|
||||||
|
var fieldTypes: seq[Type] = @[]
|
||||||
|
var namedFieldTypes: Table[string, Type]
|
||||||
|
if enumName != "":
|
||||||
|
let enumSym = sema.globalScope.lookup(enumName)
|
||||||
|
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
|
||||||
|
for v in enumSym.decl.declEnumVariants:
|
||||||
|
if v.name == variantName:
|
||||||
|
for f in v.fields:
|
||||||
|
fieldTypes.add(sema.resolveType(f))
|
||||||
|
for nf in v.namedFields:
|
||||||
|
namedFieldTypes[nf.name] = sema.resolveType(nf.ftype)
|
||||||
|
break
|
||||||
|
for i, arg in pat.patEnumArgs:
|
||||||
|
let argTy = if i < fieldTypes.len: fieldTypes[i] else: makeUnknown()
|
||||||
|
if arg.kind == pkIdent:
|
||||||
|
let sym = Symbol(kind: skVar, name: arg.patIdent, typ: argTy, isMutable: false)
|
||||||
|
discard scope.define(sym)
|
||||||
|
else:
|
||||||
|
sema.extractPatternBindings(arg, scope, argTy)
|
||||||
for nf in pat.patEnumNamed:
|
for nf in pat.patEnumNamed:
|
||||||
sema.extractPatternBindings(nf.pattern, scope)
|
let argTy = if namedFieldTypes.hasKey(nf.name): namedFieldTypes[nf.name] else: makeUnknown()
|
||||||
|
if nf.pattern.kind == pkIdent:
|
||||||
|
let sym = Symbol(kind: skVar, name: nf.pattern.patIdent, typ: argTy, isMutable: false)
|
||||||
|
discard scope.define(sym)
|
||||||
|
else:
|
||||||
|
sema.extractPatternBindings(nf.pattern, scope, argTy)
|
||||||
of pkTuple:
|
of pkTuple:
|
||||||
for elem in pat.patTupleElements:
|
for i, elem in pat.patTupleElements:
|
||||||
sema.extractPatternBindings(elem, scope)
|
let elemTy = if subjectType != nil and subjectType.kind == tkTuple and i < subjectType.inner.len:
|
||||||
|
subjectType.inner[i]
|
||||||
|
else: makeUnknown()
|
||||||
|
if elem.kind == pkIdent:
|
||||||
|
let sym = Symbol(kind: skVar, name: elem.patIdent, typ: elemTy, isMutable: false)
|
||||||
|
discard scope.define(sym)
|
||||||
|
else:
|
||||||
|
sema.extractPatternBindings(elem, scope, elemTy)
|
||||||
of pkStruct:
|
of pkStruct:
|
||||||
for f in pat.patStructFields:
|
for f in pat.patStructFields:
|
||||||
sema.extractPatternBindings(f.pattern, scope)
|
sema.extractPatternBindings(f.pattern, scope)
|
||||||
of pkGuarded:
|
of pkGuarded:
|
||||||
sema.extractPatternBindings(pat.patGuardedInner, scope)
|
sema.extractPatternBindings(pat.patGuardedInner, scope, subjectType)
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
@@ -1381,11 +1422,11 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
lastType = sema.checkStmt(stmt, blockScope)
|
lastType = sema.checkStmt(stmt, blockScope)
|
||||||
return lastType
|
return lastType
|
||||||
of ekMatch:
|
of ekMatch:
|
||||||
discard sema.checkExpr(expr.exprMatchSubject, scope)
|
let subjectType = sema.checkExpr(expr.exprMatchSubject, scope)
|
||||||
var resultType = makeUnknown()
|
var resultType = makeUnknown()
|
||||||
for arm in expr.exprMatchArms:
|
for arm in expr.exprMatchArms:
|
||||||
var armScope = newScope(scope)
|
var armScope = newScope(scope)
|
||||||
sema.extractPatternBindings(arm.pattern, armScope)
|
sema.extractPatternBindings(arm.pattern, armScope, subjectType)
|
||||||
let armType = sema.checkExpr(arm.body, armScope)
|
let armType = sema.checkExpr(arm.body, armScope)
|
||||||
if resultType.isUnknown:
|
if resultType.isUnknown:
|
||||||
resultType = armType
|
resultType = armType
|
||||||
@@ -1541,9 +1582,11 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
|||||||
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtForBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtForBody.loc, exprBlock: stmt.stmtForBody)), forScope)
|
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtForBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtForBody.loc, exprBlock: stmt.stmtForBody)), forScope)
|
||||||
return makeVoid()
|
return makeVoid()
|
||||||
of skMatch:
|
of skMatch:
|
||||||
discard sema.checkExpr(stmt.stmtMatchSubject, scope)
|
let subjectType = sema.checkExpr(stmt.stmtMatchSubject, scope)
|
||||||
for arm in stmt.stmtMatchArms:
|
for arm in stmt.stmtMatchArms:
|
||||||
discard sema.checkExpr(arm.body, scope)
|
var armScope = newScope(scope)
|
||||||
|
sema.extractPatternBindings(arm.pattern, armScope, subjectType)
|
||||||
|
discard sema.checkExpr(arm.body, armScope)
|
||||||
return makeVoid()
|
return makeVoid()
|
||||||
of skReturn:
|
of skReturn:
|
||||||
if stmt.stmtReturnValue != nil:
|
if stmt.stmtReturnValue != nil:
|
||||||
|
|||||||
+15
-7
@@ -361,21 +361,29 @@ func Main() -> int {
|
|||||||
## Pattern Matching
|
## Pattern Matching
|
||||||
|
|
||||||
```bux
|
```bux
|
||||||
|
// Payload bindings: names in Variant(args) are bound in the arm body
|
||||||
|
func GetValue(opt: Option) -> int {
|
||||||
match opt {
|
match opt {
|
||||||
Option::Some(value) => PrintInt(value),
|
Option::Some(value) => value,
|
||||||
Option::None => PrintLine("none")
|
Option::None => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match n {
|
||||||
|
0 => 100,
|
||||||
|
1..5 => 200,
|
||||||
|
6..=10 => 300,
|
||||||
|
_ => -1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported patterns:
|
Supported patterns:
|
||||||
- Wildcard: `_`
|
- Wildcard: `_`
|
||||||
- Literal: `42`, `"hello"`, `true`
|
- Literal: `42`, `"hello"`, `true`
|
||||||
- Identifier: `name`
|
- Identifier catch-all: `name` (binds whole subject)
|
||||||
- Range: `1..9`, `1..=9`
|
- Range: `1..9`, `1..=9`
|
||||||
- Enum destructuring: `Shape::Circle(r)`
|
- Enum tags + **payload bindings**: `Option::Some(value)`, `Pair::Two(a, b)`
|
||||||
- Struct destructuring: `Point { x: 0, y: 0 }`
|
- Struct / tuple / guard patterns: parsed; full lowering still evolving
|
||||||
- Tuple: `(a, b, c)`
|
|
||||||
- Guard: `t if t < 0`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+39
-8
@@ -1,7 +1,7 @@
|
|||||||
# Bux — План към „добър“ език (v0.5 → v1.0)
|
# Bux — План към „добър“ език (v0.5 → v1.0)
|
||||||
|
|
||||||
> **Дата:** 2026-07-16 (вечерта)
|
> **Дата:** 2026-07-18
|
||||||
> **Текущо:** v0.5.x — selfhost loop, gradual ownership, green threads, **40+ examples**, match expr **bootstrap+selfhost** ✅
|
> **Текущо:** v0.5.x — selfhost loop, gradual ownership, green threads, **41+ examples**, match + pattern bindings + **`f"..."` interp** bootstrap+selfhost ✅
|
||||||
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -58,10 +58,11 @@
|
|||||||
| B.1 | Proper tuple types в C backend | `(T,U)` → `Tuple_T_U` struct + `.0`/`.1` | ✅ bootstrap + selfhost |
|
| 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.2 | Function pointer types | `func(T)->U` fat ABI | ✅ bootstrap + selfhost |
|
||||||
| B.3 | Match expression lowering (literals, ranges, enums) | Expression-context match → if-else | ✅ bootstrap + selfhost |
|
| B.3 | Match expression lowering (literals, ranges, enums) | Expression-context match → if-else | ✅ bootstrap + selfhost |
|
||||||
|
| B.3b | Pattern bindings (`Some(value) => value`) | Payload idents bound in arm body | ✅ bootstrap + selfhost |
|
||||||
| B.4 | Closures multi-instance | Fat `BuxFn` + heap env | ✅ bootstrap + selfhost |
|
| B.4 | Closures multi-instance | Fat `BuxFn` + heap env | ✅ bootstrap + selfhost |
|
||||||
| B.4b | Closures: loop/return edge cases in body | По-сложни body control-flow | ⏳ |
|
| B.4b | Closures: `\|\|` empty params + loop/return body | Lexer `\|\|` vs empty closure; while/break/return | ✅ bootstrap + selfhost |
|
||||||
| B.5 | По-добри diagnostics (snippet + hint) | DX #1 за нови потребители | ✅ |
|
| B.5 | По-добри diagnostics (snippet + hint) | DX #1 за нови потребители | ✅ |
|
||||||
| B.6 | Bootstrap ↔ selfhost feature parity | Tuples/closures/**match** done; string interp / some ops still bootstrap-heavy | 🔄 |
|
| B.6 | Bootstrap ↔ selfhost feature parity | empty `\|\|`, match-as-expr, **string interp `f"..."`** bootstrap+selfhost | ✅ |
|
||||||
|
|
||||||
### C — Gradual Ownership 2.0 (P1)
|
### C — Gradual Ownership 2.0 (P1)
|
||||||
|
|
||||||
@@ -200,14 +201,44 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
|||||||
5. **Last-expr return:** `Lcx_LowerBlock` converts final `skExpr` into `return` (needed for `func F() -> T { match ... }`)
|
5. **Last-expr return:** `Lcx_LowerBlock` converts final `skExpr` into `return` (needed for `func F() -> T { match ... }`)
|
||||||
6. Verified: `pattern_matching` via **buxc2**; simple enum + ranges; **selfhost-loop IDENTICAL ✓**
|
6. Verified: `pattern_matching` via **buxc2**; simple enum + ranges; **selfhost-loop IDENTICAL ✓**
|
||||||
|
|
||||||
|
## Сесия 10 (pattern bindings — B.3b)
|
||||||
|
|
||||||
|
1. **Bootstrap sema:** `extractPatternBindings` resolves enum payload field types from variant decl
|
||||||
|
2. **Bootstrap HIR:** `matchPatternBindings` → `alloca name; name = subject.data.Variant_i` before arm body
|
||||||
|
3. **Match result type:** fall back to `currentFuncRetType` when arm bodies only use bindings
|
||||||
|
4. **Multi-field enum layout:** positional `fields.len > 1` → nested struct in `_Data` union (no overlay)
|
||||||
|
5. **Selfhost:** parse `patArgs` linked list; `Sema_BindPattern`; `Lcx_PatternBindings` in match arms
|
||||||
|
6. **Example:** `pattern_matching.bux` uses `Option::Some(value) => value` (real binding, not `opt.data.Some_0`)
|
||||||
|
7. Verified: bootstrap + **buxc2** + all examples + error goldens + **selfhost-loop IDENTICAL ✓**
|
||||||
|
|
||||||
|
## Сесия 11 (empty `||` closures + match-as-expr — B.4b)
|
||||||
|
|
||||||
|
1. **Bug:** `|| -> int { ... }` lexed as `tkPipePipe` (logical-or), not two `tkPipe` → empty-param closures failed to parse
|
||||||
|
2. **Fix bootstrap:** primary `of tkPipePipe:` → zero-param `ekClosure`
|
||||||
|
3. **Fix selfhost:** `parserParseEmptyClosure` for `tkPipePipe`
|
||||||
|
4. **Match-as-expr:** expression-form match now skips newlines (same as statement form) → `let x = match n { ... }` works
|
||||||
|
5. **Verified control-flow in closure body:** while/break, early return from loop, multi-return if-chain
|
||||||
|
6. **Pattern bind reuse:** one alloca per binding name per function (`patternBoundNames`) so two matches can both use `v`
|
||||||
|
7. **Selfhost match-as-expr:** let-init + binary operands expand yield blocks (`Lcx_IsMatchYield` / `__binop_N`)
|
||||||
|
8. Examples: `closure_control.bux`, `match_let.bux`
|
||||||
|
9. Verified: bootstrap + **buxc2** + all examples + error goldens + **selfhost-loop IDENTICAL ✓**
|
||||||
|
|
||||||
|
## Сесия 12 (string interpolation selfhost — B.6)
|
||||||
|
|
||||||
|
1. **Selfhost parser:** `parserParseStringInterp` — interleaved lit/expr parts in `callArgs`, nested fragment parse via `Lexer_Tokenize` + sub-parser
|
||||||
|
2. **Selfhost sema/HIR:** `ekStringInterp` → `String_Concat` + `String_FromInt`/`FromBool`/`FromFloat`
|
||||||
|
3. **Bootstrap fix:** `f"plain"` no longer keeps the `f` prefix in the literal; `\{`/`\}` preserved by lexer and unescaped by interp parser
|
||||||
|
4. **Lexer:** `\{` / `\}` allowed (bootstrap + selfhost) so LanguageRef escape rules work
|
||||||
|
5. **Compiler hygiene:** original dense `&&`/`||` in the large interp loop caused bootstrap OOM (~27 GB) when compiling `parser.bux` — rewrite with simpler control flow
|
||||||
|
6. Example: `examples/string_interp.bux` (name/int/bool/plain/escaped braces)
|
||||||
|
7. Verified: bootstrap + **buxc2** + all 41 examples + error goldens + **selfhost-loop IDENTICAL ✓**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Следващи стъпки
|
## Следващи стъпки
|
||||||
|
|
||||||
1. **LSP hover / go-to-def** (над текущите diagnostics)
|
1. **LSP hover / go-to-def** (над текущите diagnostics)
|
||||||
2. **Generic Iter map** (не само int), ако monomorphization с `func` params е стабилна
|
2. **Generic Iter map** (не само int), ако monomorphization с `func` params е стабилна
|
||||||
3. **Closures B.4b** — loop/return edge cases in closure body
|
3. Struct/tuple patterns (`Point { x, y }`, `(a, b)`) + nested bindings
|
||||||
4. Struct/tuple patterns + pattern bindings (`Some(value)` binds `value`)
|
4. Match arm multi-stmt bodies (beyond single expr)
|
||||||
5. `let x = match ...` multi-stmt yield (beyond tail-position return)
|
|
||||||
6. String interpolation / remaining bootstrap-only features (B.6)
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Closures with control flow: empty `||` params, while/break, early return
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
// Zero-param capturing closure written as `||` (not `| |`)
|
||||||
|
func MakeCounter(limit: int) -> func() -> int {
|
||||||
|
return || -> int {
|
||||||
|
var sum: int = 0;
|
||||||
|
var i: int = 0;
|
||||||
|
while i < limit {
|
||||||
|
sum = sum + i;
|
||||||
|
i = i + 1;
|
||||||
|
if i == 3 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeClamp(n: int) -> func(int) -> int {
|
||||||
|
return |x: int| -> int {
|
||||||
|
if x < 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if x > n {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// return from inside a loop inside a closure
|
||||||
|
func MakeLoopReturn(n: int) -> func() -> int {
|
||||||
|
return || -> int {
|
||||||
|
var i: int = 0;
|
||||||
|
while i < n {
|
||||||
|
if i == 2 {
|
||||||
|
return 99;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let c: func() -> int = MakeCounter(10);
|
||||||
|
Test_AssertEqInt(c(), 3); // 0+1+2
|
||||||
|
|
||||||
|
let f: func(int) -> int = MakeClamp(5);
|
||||||
|
Test_AssertEqInt(f(-1), 0);
|
||||||
|
Test_AssertEqInt(f(3), 3);
|
||||||
|
Test_AssertEqInt(f(100), 5);
|
||||||
|
|
||||||
|
let g: func() -> int = MakeLoopReturn(10);
|
||||||
|
Test_AssertEqInt(g(), 99);
|
||||||
|
|
||||||
|
PrintInt(c());
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("closure_control");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Match as expression: let-init, inline arms, combined with operators
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
enum Option {
|
||||||
|
Some(int),
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let opt: Option = Option { tag: Option_Some };
|
||||||
|
opt.data.Some_0 = 21;
|
||||||
|
|
||||||
|
let x: int = match opt {
|
||||||
|
Option::Some(v) => v + v,
|
||||||
|
Option::None => 0
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(x, 42);
|
||||||
|
|
||||||
|
let n: int = 3;
|
||||||
|
let y: int = match n {
|
||||||
|
0 => 100,
|
||||||
|
1..5 => 200,
|
||||||
|
_ => -1
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(y, 200);
|
||||||
|
|
||||||
|
let z: int = match n { 3 => 1, _ => 0 } + match n { 3 => 2, _ => 0 };
|
||||||
|
Test_AssertEqInt(z, 3);
|
||||||
|
|
||||||
|
let none: Option = Option { tag: Option_None };
|
||||||
|
// Same binding name as above — one alloca, reassigned per arm
|
||||||
|
let w: int = match none {
|
||||||
|
Option::Some(v) => v,
|
||||||
|
Option::None => -7
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(w, -7);
|
||||||
|
|
||||||
|
PrintInt(x);
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("match_let");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,19 +1,41 @@
|
|||||||
// Pattern Matching — enum tags, literals, ranges, wildcard
|
// Pattern Matching — enum tags, payload bindings, literals, ranges, wildcard
|
||||||
import Std::Io::{PrintLine, PrintInt};
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
enum Option {
|
enum Option {
|
||||||
Some(int),
|
Some(int),
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Msg {
|
||||||
|
Quit,
|
||||||
|
Move(int),
|
||||||
|
Write(String)
|
||||||
|
}
|
||||||
|
|
||||||
func GetValue(opt: Option) -> int {
|
func GetValue(opt: Option) -> int {
|
||||||
|
// Pattern binding: value is bound from Option::Some payload
|
||||||
match opt {
|
match opt {
|
||||||
Option::Some(value) => opt.data.Some_0,
|
Option::Some(value) => value,
|
||||||
Option::None => 0
|
Option::None => 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DoubleSome(opt: Option) -> int {
|
||||||
|
match opt {
|
||||||
|
Option::Some(n) => n + n,
|
||||||
|
Option::None => -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MsgCode(m: Msg) -> int {
|
||||||
|
match m {
|
||||||
|
Msg::Quit => 0,
|
||||||
|
Msg::Move(x) => x,
|
||||||
|
Msg::Write(s) => 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Classify(n: int) -> int {
|
func Classify(n: int) -> int {
|
||||||
// literal arms + exclusive range + inclusive range + wildcard
|
// literal arms + exclusive range + inclusive range + wildcard
|
||||||
match n {
|
match n {
|
||||||
@@ -40,6 +62,15 @@ func Main() -> int {
|
|||||||
|
|
||||||
let opt2: Option = Option { tag: Option_None };
|
let opt2: Option = Option { tag: Option_None };
|
||||||
|
|
||||||
|
Test_AssertEqInt(GetValue(opt1), 42);
|
||||||
|
Test_AssertEqInt(GetValue(opt2), 0);
|
||||||
|
Test_AssertEqInt(DoubleSome(opt1), 84);
|
||||||
|
Test_AssertEqInt(DoubleSome(opt2), -1);
|
||||||
|
|
||||||
|
let m: Msg = Msg { tag: Msg_Move };
|
||||||
|
m.data.Move_0 = 7;
|
||||||
|
Test_AssertEqInt(MsgCode(m), 7);
|
||||||
|
|
||||||
PrintLine("opt1 value: ");
|
PrintLine("opt1 value: ");
|
||||||
PrintInt(GetValue(opt1));
|
PrintInt(GetValue(opt1));
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
@@ -61,5 +92,6 @@ func Main() -> int {
|
|||||||
PrintLine("color:");
|
PrintLine("color:");
|
||||||
PrintLine(ColorName(2));
|
PrintLine(ColorName(2));
|
||||||
|
|
||||||
|
Test_Pass("pattern_matching");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import Std::Io::{PrintLine};
|
||||||
|
import Std::Test::{Test_AssertEqString, Test_Pass};
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let name: String = "Bux";
|
||||||
|
let n: int = 42;
|
||||||
|
let msg: String = f"Hello, {name}! count={n}";
|
||||||
|
PrintLine(msg);
|
||||||
|
Test_AssertEqString(msg, "Hello, Bux! count=42");
|
||||||
|
|
||||||
|
let flag: bool = true;
|
||||||
|
let bmsg: String = f"flag={flag}";
|
||||||
|
Test_AssertEqString(bmsg, "flag=true");
|
||||||
|
|
||||||
|
let empty: String = f"plain";
|
||||||
|
Test_AssertEqString(empty, "plain");
|
||||||
|
|
||||||
|
let raw: String = f"use \{braces\} literally";
|
||||||
|
Test_AssertEqString(raw, "use {braces} literally");
|
||||||
|
|
||||||
|
Test_Pass("string_interp");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -79,6 +79,8 @@ struct Pattern {
|
|||||||
patStructName: String, // for pkStruct
|
patStructName: String, // for pkStruct
|
||||||
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)
|
||||||
|
patNext: *Pattern, // next sibling in patArgs list
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match arm: pattern => body
|
// Match arm: pattern => body
|
||||||
|
|||||||
+361
-4
@@ -533,6 +533,202 @@ func Lcx_PatternCond(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
return null as *HirNode;
|
return null as *HirNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit binding stmts for pattern payload: Option::Some(value) → alloca value; value = subject.data.Some_0
|
||||||
|
// Returns head of child3-linked list of HirNodes (may be null).
|
||||||
|
func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
||||||
|
subjectEnumName: String, subjectHasData: bool,
|
||||||
|
line: uint32, col: uint32) -> *HirNode {
|
||||||
|
if pat == null as *Pattern { return null as *HirNode; }
|
||||||
|
if pat.kind == pkIdent {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
alloca.kind = hAlloca;
|
||||||
|
alloca.line = line;
|
||||||
|
alloca.column = col;
|
||||||
|
alloca.strValue = pat.patIdent;
|
||||||
|
alloca.typeName = ty;
|
||||||
|
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 = pat.patIdent;
|
||||||
|
store.child1 = v;
|
||||||
|
store.child2 = subject;
|
||||||
|
alloca.child3 = store;
|
||||||
|
return alloca;
|
||||||
|
}
|
||||||
|
if pat.kind != pkEnum || !subjectHasData { return null as *HirNode; }
|
||||||
|
|
||||||
|
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 {
|
||||||
|
enumName = subjectEnumName;
|
||||||
|
}
|
||||||
|
if String_Eq(enumName, "") || String_Eq(variantName, "") { return null as *HirNode; }
|
||||||
|
|
||||||
|
// Look up field type names from enum decl
|
||||||
|
var fieldType0: String = "int";
|
||||||
|
var fieldType1: String = "int";
|
||||||
|
var fieldCount: int = 0;
|
||||||
|
let enumSym: Symbol = Scope_Lookup(ctx.scope, enumName);
|
||||||
|
if enumSym.decl != null as *Decl && enumSym.decl.kind == dkEnum {
|
||||||
|
var vi: int = 0;
|
||||||
|
while vi < enumSym.decl.variantCount {
|
||||||
|
var vv: *EnumVariant = null as *EnumVariant;
|
||||||
|
if vi == 0 { vv = &enumSym.decl.variant0; }
|
||||||
|
else if vi == 1 { vv = &enumSym.decl.variant1; }
|
||||||
|
else if vi == 2 { vv = &enumSym.decl.variant2; }
|
||||||
|
else if vi == 3 { vv = &enumSym.decl.variant3; }
|
||||||
|
else if vi == 4 { vv = &enumSym.decl.variant4; }
|
||||||
|
else if vi == 5 { vv = &enumSym.decl.variant5; }
|
||||||
|
else if vi == 6 { vv = &enumSym.decl.variant6; }
|
||||||
|
else if vi == 7 { vv = &enumSym.decl.variant7; }
|
||||||
|
else if vi == 8 { vv = &enumSym.decl.variant8; }
|
||||||
|
if vv != null as *EnumVariant && String_Eq(vv.name, variantName) {
|
||||||
|
fieldCount = vv.fieldCount;
|
||||||
|
if !String_Eq(vv.fieldTypeName0, "") { fieldType0 = vv.fieldTypeName0; }
|
||||||
|
if !String_Eq(vv.fieldTypeName1, "") { fieldType1 = vv.fieldTypeName1; }
|
||||||
|
}
|
||||||
|
vi = vi + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataLoad = subject.data
|
||||||
|
let dataPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
dataPtr.kind = hFieldPtr;
|
||||||
|
dataPtr.line = line;
|
||||||
|
dataPtr.column = col;
|
||||||
|
dataPtr.strValue = "data";
|
||||||
|
dataPtr.child1 = subject;
|
||||||
|
let dataLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
dataLoad.kind = hLoad;
|
||||||
|
dataLoad.line = line;
|
||||||
|
dataLoad.column = col;
|
||||||
|
dataLoad.child1 = dataPtr;
|
||||||
|
dataLoad.typeName = String_Concat(enumName, "_Data");
|
||||||
|
|
||||||
|
// Multi-field: nested struct data.Variant; single-field: flat data.Variant_0
|
||||||
|
var payloadBase: *HirNode = dataLoad;
|
||||||
|
if fieldCount > 1 {
|
||||||
|
let vPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
vPtr.kind = hFieldPtr;
|
||||||
|
vPtr.line = line;
|
||||||
|
vPtr.column = col;
|
||||||
|
vPtr.strValue = variantName;
|
||||||
|
vPtr.child1 = dataLoad;
|
||||||
|
let vLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
vLoad.kind = hLoad;
|
||||||
|
vLoad.line = line;
|
||||||
|
vLoad.column = col;
|
||||||
|
vLoad.child1 = vPtr;
|
||||||
|
vLoad.typeName = variantName;
|
||||||
|
payloadBase = vLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
var head: *HirNode = null as *HirNode;
|
||||||
|
var tail: *HirNode = null as *HirNode;
|
||||||
|
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; }
|
||||||
|
let fieldName: String = String_Concat(String_Concat(variantName, "_"), String_FromInt(ai 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 = payloadBase;
|
||||||
|
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
fLoad.kind = hLoad;
|
||||||
|
fLoad.line = line;
|
||||||
|
fLoad.column = col;
|
||||||
|
fLoad.child1 = fPtr;
|
||||||
|
fLoad.typeName = ftype;
|
||||||
|
|
||||||
|
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
alloca.kind = hAlloca;
|
||||||
|
alloca.line = line;
|
||||||
|
alloca.column = col;
|
||||||
|
alloca.strValue = arg.patIdent;
|
||||||
|
alloca.typeName = ftype;
|
||||||
|
|
||||||
|
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
store.kind = hStore;
|
||||||
|
store.line = line;
|
||||||
|
store.column = col;
|
||||||
|
let bv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
bv.kind = hVar;
|
||||||
|
bv.strValue = arg.patIdent;
|
||||||
|
store.child1 = bv;
|
||||||
|
store.child2 = fLoad;
|
||||||
|
alloca.child3 = store;
|
||||||
|
|
||||||
|
// Define in scope so body idents resolve
|
||||||
|
var bsym: Symbol;
|
||||||
|
bsym.kind = skVar;
|
||||||
|
bsym.name = arg.patIdent;
|
||||||
|
bsym.typeKind = tyInt;
|
||||||
|
bsym.typeName = ftype;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arg = arg.patNext;
|
||||||
|
ai = ai + 1;
|
||||||
|
}
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
// True when n is a multi-stmt yield block (match result, etc.)
|
||||||
|
func Lcx_IsMatchYield(n: *HirNode) -> bool {
|
||||||
|
if n == null as *HirNode { return false; }
|
||||||
|
if n.kind != hBlock { return false; }
|
||||||
|
return !String_Eq(n.strValue, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lcx_YieldVarOf(n: *HirNode) -> *HirNode {
|
||||||
|
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
v.kind = hVar;
|
||||||
|
v.strValue = n.strValue;
|
||||||
|
v.typeName = n.typeName;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append `node` at the end of a child3-linked chain starting at `head` (or its child1 if head is hBlock).
|
||||||
|
func Lcx_AppendToChain(head: *HirNode, node: *HirNode) {
|
||||||
|
if head == null as *HirNode || node == null as *HirNode { return; }
|
||||||
|
var cur: *HirNode = head;
|
||||||
|
if head.kind == hBlock && head.child1 != null as *HirNode {
|
||||||
|
cur = head.child1;
|
||||||
|
}
|
||||||
|
while cur.child3 != null as *HirNode {
|
||||||
|
cur = cur.child3;
|
||||||
|
}
|
||||||
|
cur.child3 = node;
|
||||||
|
}
|
||||||
|
|
||||||
// Lower match expr → hBlock: alloca result; if-else stores; strValue = result name
|
// Lower match expr → hBlock: alloca result; if-else stores; strValue = result name
|
||||||
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||||
let line: uint32 = expr.line;
|
let line: uint32 = expr.line;
|
||||||
@@ -591,6 +787,8 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pattern bindings before body (so body idents resolve)
|
||||||
|
let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
|
||||||
let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
|
let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
|
||||||
// result = body
|
// result = body
|
||||||
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
@@ -603,11 +801,22 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
storeNode.child1 = resVar;
|
storeNode.child1 = resVar;
|
||||||
storeNode.child2 = bodyHir;
|
storeNode.child2 = bodyHir;
|
||||||
|
|
||||||
|
// armBlock = bindings... → storeNode
|
||||||
let armBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let armBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
armBlock.kind = hBlock;
|
armBlock.kind = hBlock;
|
||||||
armBlock.line = line;
|
armBlock.line = line;
|
||||||
armBlock.column = col;
|
armBlock.column = col;
|
||||||
|
if bindHead != null as *HirNode {
|
||||||
|
armBlock.child1 = bindHead;
|
||||||
|
// find tail of bind chain
|
||||||
|
var bt: *HirNode = bindHead;
|
||||||
|
while bt.child3 != null as *HirNode {
|
||||||
|
bt = bt.child3;
|
||||||
|
}
|
||||||
|
bt.child3 = storeNode;
|
||||||
|
} else {
|
||||||
armBlock.child1 = storeNode;
|
armBlock.child1 = storeNode;
|
||||||
|
}
|
||||||
|
|
||||||
let cond: *HirNode = Lcx_PatternCond(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
|
let cond: *HirNode = Lcx_PatternCond(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
|
||||||
if cond == null as *HirNode {
|
if cond == null as *HirNode {
|
||||||
@@ -672,6 +881,72 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
return Lcx_LowerMatch(ctx, expr);
|
return Lcx_LowerMatch(ctx, expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String interpolation: desugar to String_Concat + String_FromInt/Bool/Float
|
||||||
|
// Parts in callArgs are interleaved text lits and expressions.
|
||||||
|
if kind == ekStringInterp {
|
||||||
|
var result: *HirNode = null as *HirNode;
|
||||||
|
var part: *ExprList = expr.callArgs;
|
||||||
|
while part != null as *ExprList {
|
||||||
|
let pe: *Expr = part.expr;
|
||||||
|
var piece: *HirNode = null as *HirNode;
|
||||||
|
if pe != null as *Expr && pe.kind == ekLiteral && pe.tokKind == tkStringLiteral {
|
||||||
|
piece = Lcx_LowerExpr(ctx, pe);
|
||||||
|
} else {
|
||||||
|
let lowered: *HirNode = Lcx_LowerExpr(ctx, pe);
|
||||||
|
// Convert non-string to String
|
||||||
|
var needConv: bool = true;
|
||||||
|
var convName: String = "String_FromInt";
|
||||||
|
if pe != null as *Expr && pe.refType != null as *TypeExpr {
|
||||||
|
let tn: String = pe.refType.typeName;
|
||||||
|
if String_Eq(tn, "String") || String_Eq(tn, "str") {
|
||||||
|
needConv = false;
|
||||||
|
} else if String_Eq(tn, "bool") {
|
||||||
|
convName = "String_FromBool";
|
||||||
|
} else if String_Eq(tn, "float64") || String_Eq(tn, "float") || String_Eq(tn, "float32") {
|
||||||
|
convName = "String_FromFloat";
|
||||||
|
} else {
|
||||||
|
convName = "String_FromInt";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if needConv {
|
||||||
|
let callN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
callN.kind = hCall;
|
||||||
|
callN.line = line;
|
||||||
|
callN.column = col;
|
||||||
|
callN.strValue = convName;
|
||||||
|
callN.child1 = lowered;
|
||||||
|
piece = callN;
|
||||||
|
} else {
|
||||||
|
piece = lowered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result == null as *HirNode {
|
||||||
|
result = piece;
|
||||||
|
} else {
|
||||||
|
let cat: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
cat.kind = hCall;
|
||||||
|
cat.line = line;
|
||||||
|
cat.column = col;
|
||||||
|
cat.strValue = "String_Concat";
|
||||||
|
cat.child1 = result;
|
||||||
|
cat.child2 = piece;
|
||||||
|
result = cat;
|
||||||
|
}
|
||||||
|
part = part.next;
|
||||||
|
}
|
||||||
|
if result == null as *HirNode {
|
||||||
|
// empty f""
|
||||||
|
let empty: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
empty.kind = hLit;
|
||||||
|
empty.line = line;
|
||||||
|
empty.column = col;
|
||||||
|
empty.intValue = tkStringLiteral;
|
||||||
|
empty.strValue = "\"\"";
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Literal
|
// Literal
|
||||||
if kind == ekLiteral {
|
if kind == ekLiteral {
|
||||||
n.kind = hLit;
|
n.kind = hLit;
|
||||||
@@ -874,8 +1149,69 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
|
|
||||||
n.kind = hBinary;
|
n.kind = hBinary;
|
||||||
n.intValue = expr.intValue; // operator
|
n.intValue = expr.intValue; // operator
|
||||||
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
let leftHir: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
|
||||||
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
let rightHir: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
|
||||||
|
// If either side is a match yield block, expand to:
|
||||||
|
// match stmts...; int __binop_N = leftVal op rightVal; yield __binop_N
|
||||||
|
if Lcx_IsMatchYield(leftHir) || Lcx_IsMatchYield(rightHir) {
|
||||||
|
ctx.varCounter = ctx.varCounter + 1;
|
||||||
|
let tmpName: String = String_Concat("__binop_", String_FromInt(ctx.varCounter as int64));
|
||||||
|
var leftVal: *HirNode = leftHir;
|
||||||
|
var rightVal: *HirNode = rightHir;
|
||||||
|
let outer: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
outer.kind = hBlock;
|
||||||
|
outer.line = line;
|
||||||
|
outer.column = col;
|
||||||
|
outer.strValue = tmpName;
|
||||||
|
outer.typeName = "int";
|
||||||
|
var first: *HirNode = null as *HirNode;
|
||||||
|
if Lcx_IsMatchYield(leftHir) {
|
||||||
|
leftVal = Lcx_YieldVarOf(leftHir);
|
||||||
|
leftHir.strValue = "";
|
||||||
|
first = leftHir;
|
||||||
|
}
|
||||||
|
if Lcx_IsMatchYield(rightHir) {
|
||||||
|
rightVal = Lcx_YieldVarOf(rightHir);
|
||||||
|
rightHir.strValue = "";
|
||||||
|
if first == null as *HirNode {
|
||||||
|
first = rightHir;
|
||||||
|
} else {
|
||||||
|
Lcx_AppendToChain(first, rightHir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let tmpAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
tmpAlloca.kind = hAlloca;
|
||||||
|
tmpAlloca.line = line;
|
||||||
|
tmpAlloca.column = col;
|
||||||
|
tmpAlloca.strValue = tmpName;
|
||||||
|
tmpAlloca.typeName = "int";
|
||||||
|
let binNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
binNode.kind = hBinary;
|
||||||
|
binNode.line = line;
|
||||||
|
binNode.column = col;
|
||||||
|
binNode.intValue = expr.intValue;
|
||||||
|
binNode.child1 = leftVal;
|
||||||
|
binNode.child2 = rightVal;
|
||||||
|
let tmpStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
tmpStore.kind = hStore;
|
||||||
|
tmpStore.line = line;
|
||||||
|
tmpStore.column = col;
|
||||||
|
let tmpVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
tmpVar.kind = hVar;
|
||||||
|
tmpVar.strValue = tmpName;
|
||||||
|
tmpStore.child1 = tmpVar;
|
||||||
|
tmpStore.child2 = binNode;
|
||||||
|
tmpAlloca.child3 = tmpStore;
|
||||||
|
if first == null as *HirNode {
|
||||||
|
outer.child1 = tmpAlloca;
|
||||||
|
} else {
|
||||||
|
outer.child1 = first;
|
||||||
|
Lcx_AppendToChain(first, tmpAlloca);
|
||||||
|
}
|
||||||
|
return outer;
|
||||||
|
}
|
||||||
|
n.child1 = leftHir;
|
||||||
|
n.child2 = rightHir;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1875,6 +2211,26 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
|
|||||||
sym.isPublic = false;
|
sym.isPublic = false;
|
||||||
sym.decl = null as *Decl;
|
sym.decl = null as *Decl;
|
||||||
discard Scope_Define(ctx.scope, sym);
|
discard Scope_Define(ctx.scope, sym);
|
||||||
|
|
||||||
|
// Match (or other multi-stmt yield) as let initializer:
|
||||||
|
// match stmts...; Type x = __match_N;
|
||||||
|
// instead of illegal `Type x = <block>;`
|
||||||
|
if Lcx_IsMatchYield(init) {
|
||||||
|
let yieldName: String = init.strValue;
|
||||||
|
init.strValue = "";
|
||||||
|
let yieldVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
yieldVar.kind = hVar;
|
||||||
|
yieldVar.strValue = yieldName;
|
||||||
|
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
storeNode.kind = hStore;
|
||||||
|
storeNode.line = line;
|
||||||
|
storeNode.column = col;
|
||||||
|
storeNode.child1 = alloca;
|
||||||
|
storeNode.child2 = yieldVar;
|
||||||
|
Lcx_AppendToChain(init, storeNode);
|
||||||
|
return init;
|
||||||
|
}
|
||||||
|
|
||||||
// store the init value
|
// store the init value
|
||||||
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
storeNode.kind = hStore;
|
storeNode.kind = hStore;
|
||||||
@@ -3365,11 +3721,12 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
|
|||||||
hm.enums[ei].variants[vi].name = v.name;
|
hm.enums[ei].variants[vi].name = v.name;
|
||||||
hm.enums[ei].variants[vi].fieldCount = v.fieldCount;
|
hm.enums[ei].variants[vi].fieldCount = v.fieldCount;
|
||||||
if v.fieldCount > 0 {
|
if v.fieldCount > 0 {
|
||||||
hm.enums[ei].variants[vi].fieldName0 = "value";
|
// Positional field names: Variant_0, Variant_1 (matches data.Variant_i / nested struct)
|
||||||
|
hm.enums[ei].variants[vi].fieldName0 = String_Concat(v.name, "_0");
|
||||||
hm.enums[ei].variants[vi].fieldType0 = Lcx_ResolveTypeKindFromName(v.fieldTypeName0);
|
hm.enums[ei].variants[vi].fieldType0 = Lcx_ResolveTypeKindFromName(v.fieldTypeName0);
|
||||||
}
|
}
|
||||||
if v.fieldCount > 1 {
|
if v.fieldCount > 1 {
|
||||||
hm.enums[ei].variants[vi].fieldName1 = "value2";
|
hm.enums[ei].variants[vi].fieldName1 = String_Concat(v.name, "_1");
|
||||||
hm.enums[ei].variants[vi].fieldType1 = Lcx_ResolveTypeKindFromName(v.fieldTypeName1);
|
hm.enums[ei].variants[vi].fieldType1 = Lcx_ResolveTypeKindFromName(v.fieldTypeName1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-5
@@ -381,8 +381,8 @@ func lexScanBacktickString(lex: *Lexer) {
|
|||||||
lexEmitToken(lex, tkStringLiteral);
|
lexEmitToken(lex, tkStringLiteral);
|
||||||
}
|
}
|
||||||
|
|
||||||
func lexScanString(lex: *Lexer) {
|
// Assumes lex.startPos already marked (may include f/c8/… prefix before the quote).
|
||||||
lexMarkStart(lex);
|
func lexScanStringFrom(lex: *Lexer) {
|
||||||
// Collect the prefix (before opening quote) for the token text
|
// Collect the prefix (before opening quote) for the token text
|
||||||
var prefix: String = "";
|
var prefix: String = "";
|
||||||
var prefixLen: int = 0;
|
var prefixLen: int = 0;
|
||||||
@@ -412,6 +412,13 @@ func lexScanString(lex: *Lexer) {
|
|||||||
discard lexAdvance(lex);
|
discard lexAdvance(lex);
|
||||||
if !lexIsAtEnd(lex) {
|
if !lexIsAtEnd(lex) {
|
||||||
let ec: uint32 = lexAdvance(lex);
|
let ec: uint32 = lexAdvance(lex);
|
||||||
|
// Preserve \{ and \} as two chars for f"..." brace escaping
|
||||||
|
if ec == 123 || ec == 125 {
|
||||||
|
resolved[rpos] = 92 as char8;
|
||||||
|
rpos = rpos + 1;
|
||||||
|
resolved[rpos] = ec as char8;
|
||||||
|
rpos = rpos + 1;
|
||||||
|
} else {
|
||||||
var rc: char8 = ec as char8;
|
var rc: char8 = ec as char8;
|
||||||
if ec == 110 { rc = 10 as char8; } // \n
|
if ec == 110 { rc = 10 as char8; } // \n
|
||||||
else if ec == 114 { rc = 13 as char8; } // \r
|
else if ec == 114 { rc = 13 as char8; } // \r
|
||||||
@@ -423,6 +430,7 @@ func lexScanString(lex: *Lexer) {
|
|||||||
resolved[rpos] = rc;
|
resolved[rpos] = rc;
|
||||||
rpos = rpos + 1;
|
rpos = rpos + 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let c: uint32 = lexAdvance(lex);
|
let c: uint32 = lexAdvance(lex);
|
||||||
resolved[rpos] = c as char8;
|
resolved[rpos] = c as char8;
|
||||||
@@ -450,6 +458,11 @@ func lexScanString(lex: *Lexer) {
|
|||||||
lexSetLastTokenText(lex, finalBuf);
|
lexSetLastTokenText(lex, finalBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lexScanString(lex: *Lexer) {
|
||||||
|
lexMarkStart(lex);
|
||||||
|
lexScanStringFrom(lex);
|
||||||
|
}
|
||||||
|
|
||||||
func lexScanChar(lex: *Lexer) {
|
func lexScanChar(lex: *Lexer) {
|
||||||
lexMarkStart(lex);
|
lexMarkStart(lex);
|
||||||
// Collect the prefix for the token text
|
// Collect the prefix for the token text
|
||||||
@@ -678,10 +691,12 @@ func lexNextToken(lex: *Lexer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// String prefixes: f" c8" c16" c32"
|
// String prefixes: f" c8" c16" c32"
|
||||||
|
// Keep `f` in token text so the parser can detect interpolating strings.
|
||||||
if c == 102 && lexPeek(lex, 1) == 34 { // f"
|
if c == 102 && lexPeek(lex, 1) == 34 { // f"
|
||||||
discard lexAdvance(lex); // f
|
lexMarkStart(lex); // start at 'f'
|
||||||
lexMarkStart(lex); // treat as plain string literal in selfhost
|
discard lexAdvance(lex); // consume f; startPos still at f
|
||||||
lexScanString(lex); return;
|
lexScanStringFrom(lex); // does not re-mark — prefix = "f"
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if c == 99 { // 'c'
|
if c == 99 { // 'c'
|
||||||
let d: uint32 = lexPeek(lex, 1);
|
let d: uint32 = lexPeek(lex, 1);
|
||||||
|
|||||||
+221
-3
@@ -4,6 +4,7 @@ module Parser {
|
|||||||
|
|
||||||
extern func bux_strlen(s: String) -> uint;
|
extern func bux_strlen(s: String) -> uint;
|
||||||
extern func bux_str_to_int(s: String) -> int64;
|
extern func bux_str_to_int(s: String) -> int64;
|
||||||
|
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
|
||||||
|
|
||||||
// Forward declarations for mutual recursion
|
// Forward declarations for mutual recursion
|
||||||
func parserParseExpr(p: *Parser) -> *Expr;
|
func parserParseExpr(p: *Parser) -> *Expr;
|
||||||
@@ -353,6 +354,171 @@ func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func parserMakeStringLitExpr(text: String, line: uint32, col: uint32) -> *Expr {
|
||||||
|
let quoted: String = String_Concat(String_Concat("\"", text), "\"");
|
||||||
|
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
|
||||||
|
e.tokKind = tkStringLiteral;
|
||||||
|
e.tokText = quoted;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserParseInterpFragment(exprStr: String) -> *Expr {
|
||||||
|
let lex: *Lexer = Lexer_Tokenize(exprStr);
|
||||||
|
var sub: Parser;
|
||||||
|
sub.tokens = lex.tokens;
|
||||||
|
sub.tokenCount = lex.tokenCount;
|
||||||
|
sub.pos = 0;
|
||||||
|
sub.diagCount = 0;
|
||||||
|
sub.diags = null as *ParserDiag;
|
||||||
|
sub.structInitAllowed = true;
|
||||||
|
return parserParseExpr(&sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserAppendPart(head: *ExprList, tail: *ExprList, e: *Expr) -> *ExprList {
|
||||||
|
// returns new tail; head updated via pointer trick not possible — return pair as side effect on first arg using double pointer?
|
||||||
|
// simpler: just inline in main
|
||||||
|
return tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserParseStringInterp(p: *Parser, tok: LexToken) -> *Expr {
|
||||||
|
let text: String = tok.text;
|
||||||
|
let tlen: uint = bux_strlen(text);
|
||||||
|
if tlen < 3 as uint {
|
||||||
|
let e: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
|
||||||
|
e.tokKind = tkStringLiteral;
|
||||||
|
e.tokText = text;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
if text[0] as int != 102 {
|
||||||
|
let e: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
|
||||||
|
e.tokKind = tkStringLiteral;
|
||||||
|
e.tokText = text;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
if text[1] as int != 34 {
|
||||||
|
let e: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
|
||||||
|
e.tokKind = tkStringLiteral;
|
||||||
|
e.tokText = text;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
let inner: String = bux_str_slice(text, 2, tlen - 3);
|
||||||
|
let innerLen: uint = bux_strlen(inner);
|
||||||
|
var head: *ExprList = null as *ExprList;
|
||||||
|
var tail: *ExprList = null as *ExprList;
|
||||||
|
var currentText: String = "";
|
||||||
|
var i: uint = 0;
|
||||||
|
var partCount: int = 0;
|
||||||
|
while i < innerLen {
|
||||||
|
let ch: int = inner[i] as int;
|
||||||
|
var handled: bool = false;
|
||||||
|
if ch == 92 {
|
||||||
|
if i + 1 < innerLen {
|
||||||
|
let nch: int = inner[i + 1] as int;
|
||||||
|
if nch == 123 {
|
||||||
|
currentText = String_Concat(currentText, "{");
|
||||||
|
i = i + 2;
|
||||||
|
handled = true;
|
||||||
|
} else {
|
||||||
|
if nch == 125 {
|
||||||
|
currentText = String_Concat(currentText, "}");
|
||||||
|
i = i + 2;
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if handled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ch == 123 {
|
||||||
|
let textPart: *Expr = parserMakeStringLitExpr(currentText, tok.line, tok.column);
|
||||||
|
let textNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
||||||
|
textNode.expr = textPart;
|
||||||
|
textNode.next = null as *ExprList;
|
||||||
|
textNode.argName = "";
|
||||||
|
if head == null as *ExprList {
|
||||||
|
head = textNode;
|
||||||
|
tail = textNode;
|
||||||
|
} else {
|
||||||
|
tail.next = textNode;
|
||||||
|
tail = textNode;
|
||||||
|
}
|
||||||
|
partCount = partCount + 1;
|
||||||
|
currentText = "";
|
||||||
|
var j: uint = i + 1;
|
||||||
|
var depth: int = 1;
|
||||||
|
while j < innerLen {
|
||||||
|
if depth <= 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let cj: int = inner[j] as int;
|
||||||
|
if cj == 123 {
|
||||||
|
depth = depth + 1;
|
||||||
|
}
|
||||||
|
if cj == 125 {
|
||||||
|
depth = depth - 1;
|
||||||
|
}
|
||||||
|
j = j + 1;
|
||||||
|
}
|
||||||
|
if depth != 0 {
|
||||||
|
parserEmitDiag(p, tok.line, tok.column, "unmatched brace in string interpolation");
|
||||||
|
let bad: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
|
||||||
|
bad.tokKind = tkStringLiteral;
|
||||||
|
bad.tokText = "\"\"";
|
||||||
|
return bad;
|
||||||
|
}
|
||||||
|
let exprLen: uint = j - i - 2;
|
||||||
|
let exprStr: String = bux_str_slice(inner, i + 1, exprLen);
|
||||||
|
if bux_strlen(exprStr) == 0 {
|
||||||
|
parserEmitDiag(p, tok.line, tok.column, "empty interpolation");
|
||||||
|
let bad: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
|
||||||
|
bad.tokKind = tkStringLiteral;
|
||||||
|
bad.tokText = "\"\"";
|
||||||
|
return bad;
|
||||||
|
}
|
||||||
|
let frag: *Expr = parserParseInterpFragment(exprStr);
|
||||||
|
let fragNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
||||||
|
fragNode.expr = frag;
|
||||||
|
fragNode.next = null as *ExprList;
|
||||||
|
fragNode.argName = "";
|
||||||
|
if head == null as *ExprList {
|
||||||
|
head = fragNode;
|
||||||
|
tail = fragNode;
|
||||||
|
} else {
|
||||||
|
tail.next = fragNode;
|
||||||
|
tail = fragNode;
|
||||||
|
}
|
||||||
|
partCount = partCount + 1;
|
||||||
|
i = j;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let one: String = bux_str_slice(inner, i, 1);
|
||||||
|
currentText = String_Concat(currentText, one);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
let lastPart: *Expr = parserMakeStringLitExpr(currentText, tok.line, tok.column);
|
||||||
|
let lastNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
||||||
|
lastNode.expr = lastPart;
|
||||||
|
lastNode.next = null as *ExprList;
|
||||||
|
lastNode.argName = "";
|
||||||
|
if head == null as *ExprList {
|
||||||
|
head = lastNode;
|
||||||
|
tail = lastNode;
|
||||||
|
} else {
|
||||||
|
tail.next = lastNode;
|
||||||
|
tail = lastNode;
|
||||||
|
}
|
||||||
|
partCount = partCount + 1;
|
||||||
|
if partCount == 1 {
|
||||||
|
return head.expr;
|
||||||
|
}
|
||||||
|
let e: *Expr = parserMakeExpr(ekStringInterp, tok.line, tok.column);
|
||||||
|
e.callArgs = head;
|
||||||
|
e.callArgCount = partCount;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Primary expressions
|
// Primary expressions
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -370,6 +536,10 @@ func parserParsePrimary(p: *Parser) -> *Expr {
|
|||||||
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|
||||||
|| kind == tkCharLiteral || kind == tkBoolLiteral {
|
|| kind == tkCharLiteral || kind == tkBoolLiteral {
|
||||||
discard parserAdvance(p);
|
discard parserAdvance(p);
|
||||||
|
if kind == tkStringLiteral && bux_strlen(tok.text) >= 2 as uint
|
||||||
|
&& tok.text[0] as int == 102 && tok.text[1] as int == 34 {
|
||||||
|
return parserParseStringInterp(p, tok);
|
||||||
|
}
|
||||||
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
|
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
|
||||||
e.tokKind = kind;
|
e.tokKind = kind;
|
||||||
e.tokText = tok.text;
|
e.tokText = tok.text;
|
||||||
@@ -480,6 +650,12 @@ func parserParsePrimary(p: *Parser) -> *Expr {
|
|||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Empty-param closure: `||` is lexed as tkPipePipe (logical-or token).
|
||||||
|
// As a primary it can only mean a zero-param closure: || -> T { ... }
|
||||||
|
if kind == tkPipePipe {
|
||||||
|
return parserParseEmptyClosure(p);
|
||||||
|
}
|
||||||
|
|
||||||
// Closure: |params| -> Ret { body }
|
// Closure: |params| -> Ret { body }
|
||||||
if kind == tkPipe {
|
if kind == tkPipe {
|
||||||
return parserParseClosure(p);
|
return parserParseClosure(p);
|
||||||
@@ -520,6 +696,8 @@ func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
|
|||||||
pat.patStructName = "";
|
pat.patStructName = "";
|
||||||
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.patNext = null as *Pattern;
|
||||||
return pat;
|
return pat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,11 +743,20 @@ func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
|
|||||||
path = String_Concat(path, "::");
|
path = String_Concat(path, "::");
|
||||||
path = String_Concat(path, seg.text);
|
path = String_Concat(path, seg.text);
|
||||||
}
|
}
|
||||||
// Optional (args) for algebraic variants — parse and ignore bindings for now
|
// Optional (args) for algebraic variants — store as patArgs linked list
|
||||||
|
var enumArgs: *Pattern = null as *Pattern;
|
||||||
|
var lastArg: *Pattern = null as *Pattern;
|
||||||
if parserCheck(p, tkLParen) {
|
if parserCheck(p, tkLParen) {
|
||||||
discard parserAdvance(p);
|
discard parserAdvance(p);
|
||||||
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
||||||
discard parserParsePattern(p);
|
let argPat: *Pattern = parserParsePattern(p);
|
||||||
|
if enumArgs == null as *Pattern {
|
||||||
|
enumArgs = argPat;
|
||||||
|
lastArg = argPat;
|
||||||
|
} else {
|
||||||
|
lastArg.patNext = argPat;
|
||||||
|
lastArg = argPat;
|
||||||
|
}
|
||||||
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
||||||
else { break; }
|
else { break; }
|
||||||
}
|
}
|
||||||
@@ -577,19 +764,30 @@ func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
|
|||||||
}
|
}
|
||||||
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
|
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
|
||||||
pat.patEnumPath = path;
|
pat.patEnumPath = path;
|
||||||
|
pat.patArgs = enumArgs;
|
||||||
return pat;
|
return pat;
|
||||||
}
|
}
|
||||||
// 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);
|
||||||
|
var bareArgs: *Pattern = null as *Pattern;
|
||||||
|
var bareLast: *Pattern = null as *Pattern;
|
||||||
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
||||||
discard parserParsePattern(p);
|
let argPat: *Pattern = parserParsePattern(p);
|
||||||
|
if bareArgs == null as *Pattern {
|
||||||
|
bareArgs = argPat;
|
||||||
|
bareLast = argPat;
|
||||||
|
} else {
|
||||||
|
bareLast.patNext = argPat;
|
||||||
|
bareLast = argPat;
|
||||||
|
}
|
||||||
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
||||||
else { break; }
|
else { break; }
|
||||||
}
|
}
|
||||||
discard parserExpect(p, tkRParen, "expected ')' to close pattern");
|
discard parserExpect(p, tkRParen, "expected ')' to close pattern");
|
||||||
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
|
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
|
||||||
pat.patEnumPath = name;
|
pat.patEnumPath = name;
|
||||||
|
pat.patArgs = bareArgs;
|
||||||
return pat;
|
return pat;
|
||||||
}
|
}
|
||||||
// Ident binding / catch-all name
|
// Ident binding / catch-all name
|
||||||
@@ -629,6 +827,8 @@ func parserParseMatchExpr(p: *Parser) -> *Expr {
|
|||||||
p.structInitAllowed = false;
|
p.structInitAllowed = false;
|
||||||
let subject: *Expr = parserParseExpr(p);
|
let subject: *Expr = parserParseExpr(p);
|
||||||
p.structInitAllowed = true;
|
p.structInitAllowed = true;
|
||||||
|
// Allow newline before '{' (needed for `let x = match n \n { ... }`)
|
||||||
|
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
||||||
discard parserExpect(p, tkLBrace, "expected '{' to start match body");
|
discard parserExpect(p, tkLBrace, "expected '{' to start match body");
|
||||||
|
|
||||||
var firstArm: *MatchArm = null as *MatchArm;
|
var firstArm: *MatchArm = null as *MatchArm;
|
||||||
@@ -674,6 +874,24 @@ func parserParseMatchExpr(p: *Parser) -> *Expr {
|
|||||||
// Closure: |params| -> Ret { body }
|
// Closure: |params| -> Ret { body }
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Zero-param closure when written as `||` (single tkPipePipe token from lexer)
|
||||||
|
func parserParseEmptyClosure(p: *Parser) -> *Expr {
|
||||||
|
let line: uint32 = parserCurToken(p).line;
|
||||||
|
let col: uint32 = parserCurToken(p).column;
|
||||||
|
discard parserAdvance(p); // ||
|
||||||
|
let e: *Expr = parserMakeExpr(ekClosure, line, col);
|
||||||
|
let params: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
||||||
|
params.kind = dkFunc;
|
||||||
|
params.paramCount = 0;
|
||||||
|
e.closureParams = params;
|
||||||
|
if parserCheck(p, tkArrow) {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
e.refType = parserParseType(p);
|
||||||
|
}
|
||||||
|
e.refBlock = parserParseBlock(p);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
func parserParseClosure(p: *Parser) -> *Expr {
|
func parserParseClosure(p: *Parser) -> *Expr {
|
||||||
let line: uint32 = parserCurToken(p).line;
|
let line: uint32 = parserCurToken(p).line;
|
||||||
let col: uint32 = parserCurToken(p).column;
|
let col: uint32 = parserCurToken(p).column;
|
||||||
|
|||||||
+111
@@ -384,6 +384,97 @@ func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
|
|||||||
// Expression type checking
|
// 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 {
|
func Sema_IsMutRefDeref(target: *Expr) -> bool {
|
||||||
if target == null as *Expr { return false; }
|
if target == null as *Expr { return false; }
|
||||||
if target.kind != ekUnary { 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 first: bool = true;
|
||||||
var arm: *MatchArm = expr.matchArms;
|
var arm: *MatchArm = expr.matchArms;
|
||||||
while arm != null as *MatchArm {
|
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);
|
let bt: int = Sema_CheckExpr(sema, arm.body);
|
||||||
|
sema.scope = savedScope;
|
||||||
if first {
|
if first {
|
||||||
armType = bt;
|
armType = bt;
|
||||||
first = false;
|
first = false;
|
||||||
@@ -823,6 +920,20 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
return armType;
|
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 }
|
// Closure: |params| -> Ret { body }
|
||||||
if kind == ekClosure {
|
if kind == ekClosure {
|
||||||
let savedRetType: int = sema.currentRetType;
|
let savedRetType: int = sema.currentRetType;
|
||||||
|
|||||||
Reference in New Issue
Block a user