fix: C backend, parser, sema, HIR lowering for all examples
- C backend: strip c8/c16/c32 string prefixes in emitted C - C backend: resolve imports to fully-qualified names (Std_Io_PrintLine) - Parser: fix infinite loop on < in comparisons vs generic calls - Parser: fix enum patterns without parens (Option::None) - Parser: fix match expressions inside blocks - Sema: extract pattern bindings for match arms - HIR: lower match expressions to if-else chains with enum tag checks - HIR/C backend: support block expressions as function return values - Makefile: add integration tests for all 9 examples - Add pattern_matching.bux example
This commit is contained in:
+10
@@ -1,13 +1,23 @@
|
|||||||
# Compiled binary
|
# Compiled binary
|
||||||
buxc
|
buxc
|
||||||
|
src/main
|
||||||
|
|
||||||
# Nim cache
|
# Nim cache
|
||||||
nimcache/
|
nimcache/
|
||||||
|
|
||||||
# Test binaries
|
# Test binaries
|
||||||
tests/lexer_test
|
tests/lexer_test
|
||||||
|
tests/parser_test
|
||||||
|
tests/sema_test
|
||||||
|
tests/hir_test
|
||||||
|
tests/test_generics_parse
|
||||||
tests/*.exe
|
tests/*.exe
|
||||||
|
|
||||||
# Build artifacts
|
# Build artifacts
|
||||||
build/
|
build/
|
||||||
*.o
|
*.o
|
||||||
|
|
||||||
|
# Temporary directories
|
||||||
|
_test_tmp_pkg/
|
||||||
|
test_pkg/
|
||||||
|
examples_pkg/
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ SRC := src/main.nim
|
|||||||
OUT := buxc
|
OUT := buxc
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
|
|
||||||
.PHONY: all build test clean
|
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics pattern_matching
|
||||||
|
|
||||||
|
.PHONY: all build dev test clean test-examples
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -13,14 +15,40 @@ build:
|
|||||||
dev:
|
dev:
|
||||||
$(NIM) c -o:$(OUT) $(SRC)
|
$(NIM) c -o:$(OUT) $(SRC)
|
||||||
|
|
||||||
test: build
|
test: build test-examples
|
||||||
@echo "Running lexer tests..."
|
@echo "Running lexer tests..."
|
||||||
$(NIM) c -r tests/lexer_test.nim
|
$(NIM) c -r tests/lexer_test.nim
|
||||||
|
@echo "Running parser tests..."
|
||||||
|
$(NIM) c -r tests/parser_test.nim
|
||||||
|
@echo "Running sema tests..."
|
||||||
|
$(NIM) c -r tests/sema_test.nim
|
||||||
|
@echo "Running HIR tests..."
|
||||||
|
$(NIM) c -r tests/hir_test.nim
|
||||||
@echo "Running integration tests..."
|
@echo "Running integration tests..."
|
||||||
./$(OUT) new _test_tmp_pkg
|
./$(OUT) new _test_tmp_pkg
|
||||||
./$(OUT) --version
|
./$(OUT) --version
|
||||||
|
|
||||||
|
test-examples: build
|
||||||
|
@for ex in $(EXAMPLES); do \
|
||||||
|
echo "=== Testing example: $$ex ==="; \
|
||||||
|
mkdir -p examples_pkg/$$ex/src; \
|
||||||
|
cp examples/$$ex.bux examples_pkg/$$ex/src/Main.bux; \
|
||||||
|
if [ ! -f examples_pkg/$$ex/bux.toml ]; then \
|
||||||
|
echo '[Package]' > examples_pkg/$$ex/bux.toml; \
|
||||||
|
echo 'Name = "'$$ex'"' >> examples_pkg/$$ex/bux.toml; \
|
||||||
|
echo 'Version = "0.1.0"' >> examples_pkg/$$ex/bux.toml; \
|
||||||
|
echo 'Type = "bin"' >> examples_pkg/$$ex/bux.toml; \
|
||||||
|
echo '' >> examples_pkg/$$ex/bux.toml; \
|
||||||
|
echo '[Build]' >> examples_pkg/$$ex/bux.toml; \
|
||||||
|
echo 'Output = "Bin"' >> examples_pkg/$$ex/bux.toml; \
|
||||||
|
fi; \
|
||||||
|
(cd examples_pkg/$$ex && timeout 10 ../../$(OUT) run) || exit 1; \
|
||||||
|
done
|
||||||
|
@echo "All examples passed!"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OUT)
|
rm -f $(OUT)
|
||||||
rm -rf $(BUILD_DIR)
|
rm -rf $(BUILD_DIR)
|
||||||
rm -rf nimcache
|
rm -rf nimcache
|
||||||
|
rm -rf examples_pkg
|
||||||
|
rm -rf _test_tmp_pkg
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Pattern Matching - Match expressions with algebraic enums
|
||||||
|
extern func Std_Io_PrintLine(s: String);
|
||||||
|
extern func Std_Io_PrintInt(n: int);
|
||||||
|
|
||||||
|
enum Option {
|
||||||
|
Some(int),
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetValue(opt: Option) -> int {
|
||||||
|
match opt {
|
||||||
|
Option::Some(value) => opt.data.Some_0,
|
||||||
|
Option::None => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let opt1: Option = Option { tag: Option_Some };
|
||||||
|
opt1.data.Some_0 = 42;
|
||||||
|
|
||||||
|
let opt2: Option = Option { tag: Option_None };
|
||||||
|
|
||||||
|
Std_Io_PrintLine("opt1 value: ");
|
||||||
|
Std_Io_PrintInt(GetValue(opt1));
|
||||||
|
Std_Io_PrintLine("");
|
||||||
|
|
||||||
|
Std_Io_PrintLine("opt2 value: ");
|
||||||
|
Std_Io_PrintInt(GetValue(opt2));
|
||||||
|
Std_Io_PrintLine("");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+17
-2
@@ -136,7 +136,15 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
|||||||
if node.litToken.text == "true": return "true"
|
if node.litToken.text == "true": return "true"
|
||||||
else: return "false"
|
else: return "false"
|
||||||
of tkStringLiteral:
|
of tkStringLiteral:
|
||||||
return node.litToken.text
|
var text = node.litToken.text
|
||||||
|
# Strip c8" c16" c32" prefixes — in C they are just regular string literals
|
||||||
|
if text.startsWith("c32\""):
|
||||||
|
text = text[3..^1]
|
||||||
|
elif text.startsWith("c16\""):
|
||||||
|
text = text[3..^1]
|
||||||
|
elif text.startsWith("c8\""):
|
||||||
|
text = text[2..^1]
|
||||||
|
return text
|
||||||
of tkNull:
|
of tkNull:
|
||||||
return "NULL"
|
return "NULL"
|
||||||
else:
|
else:
|
||||||
@@ -340,7 +348,14 @@ proc emitFunc*(be: var CBackend, hfunc: HirFunc) =
|
|||||||
be.emitLine(&"{retType} {hfunc.name}({paramsStr}) {{")
|
be.emitLine(&"{retType} {hfunc.name}({paramsStr}) {{")
|
||||||
inc be.indent
|
inc be.indent
|
||||||
if hfunc.body != nil:
|
if hfunc.body != nil:
|
||||||
be.emitStmt(hfunc.body)
|
if hfunc.body.kind == hBlock and hfunc.body.blockExpr != nil and hfunc.retType.kind != tkVoid:
|
||||||
|
# Function returns a value via block expression — emit statements and add return
|
||||||
|
for stmt in hfunc.body.blockStmts:
|
||||||
|
be.emitStmt(stmt)
|
||||||
|
let val = be.emitExpr(hfunc.body.blockExpr)
|
||||||
|
be.emitLine(&"return {val};")
|
||||||
|
else:
|
||||||
|
be.emitStmt(hfunc.body)
|
||||||
dec be.indent
|
dec be.indent
|
||||||
be.emitLine("}")
|
be.emitLine("}")
|
||||||
be.emitLine("")
|
be.emitLine("")
|
||||||
|
|||||||
+123
-6
@@ -9,17 +9,98 @@ type
|
|||||||
currentFuncRetType*: Type
|
currentFuncRetType*: Type
|
||||||
varCounter*: int
|
varCounter*: int
|
||||||
typeSubst*: Table[string, Type] # Type parameter substitution for generics
|
typeSubst*: Table[string, Type] # Type parameter substitution for generics
|
||||||
|
importTable*: Table[string, string] # Local name → fully qualified name for imports
|
||||||
|
|
||||||
proc freshName(ctx: var LowerCtx): string =
|
proc freshName(ctx: var LowerCtx): string =
|
||||||
inc ctx.varCounter
|
inc ctx.varCounter
|
||||||
result = "__tmp_" & $ctx.varCounter
|
result = "__tmp_" & $ctx.varCounter
|
||||||
|
|
||||||
|
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.
|
||||||
|
# For now, supports enum tag matching and wildcard/ident fallbacks.
|
||||||
|
let resultName = ctx.freshName()
|
||||||
|
var stmts: seq[HirNode] = @[]
|
||||||
|
|
||||||
|
# Allocate result variable
|
||||||
|
stmts.add(hirAlloca(resultName, typ, loc))
|
||||||
|
|
||||||
|
# Build if-else chain from arms (last arm is the outermost else)
|
||||||
|
var ifChain: HirNode = nil
|
||||||
|
|
||||||
|
for i in countdown(arms.len - 1, 0):
|
||||||
|
let arm = arms[i]
|
||||||
|
let body = arm.body
|
||||||
|
|
||||||
|
case arm.pattern.kind
|
||||||
|
of pkEnum:
|
||||||
|
let path = arm.pattern.patEnumPath
|
||||||
|
if path.len >= 2:
|
||||||
|
let enumName = path[0]
|
||||||
|
let variantName = path[^1]
|
||||||
|
let tagName = enumName & "_" & variantName
|
||||||
|
|
||||||
|
# condition: subject.tag == EnumName_VariantName
|
||||||
|
let tagField = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: "tag",
|
||||||
|
typ: makePointer(makeNamed(enumName & "_Tag")), loc: loc)
|
||||||
|
let tagLoad = HirNode(kind: hLoad, loadPtr: tagField, typ: makeNamed(enumName & "_Tag"), loc: loc)
|
||||||
|
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName & "_Tag"), loc)
|
||||||
|
let cond = hirBinary(tkEq, tagLoad, tagConst, makeBool(), loc)
|
||||||
|
|
||||||
|
# body: result = arm_body
|
||||||
|
var armStmts: seq[HirNode] = @[]
|
||||||
|
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
||||||
|
let armBlock = hirBlock(armStmts, nil, makeVoid(), loc)
|
||||||
|
|
||||||
|
if ifChain == nil:
|
||||||
|
ifChain = HirNode(kind: hIf, ifCond: cond, ifThen: armBlock, ifElse: nil,
|
||||||
|
typ: makeVoid(), loc: loc)
|
||||||
|
else:
|
||||||
|
ifChain = HirNode(kind: hIf, ifCond: cond, ifThen: armBlock, ifElse: ifChain,
|
||||||
|
typ: makeVoid(), loc: loc)
|
||||||
|
else:
|
||||||
|
var armStmts: seq[HirNode] = @[]
|
||||||
|
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
||||||
|
let armBlock = hirBlock(armStmts, nil, makeVoid(), loc)
|
||||||
|
if ifChain == nil:
|
||||||
|
ifChain = armBlock
|
||||||
|
else:
|
||||||
|
ifChain = HirNode(kind: hIf,
|
||||||
|
ifCond: hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc),
|
||||||
|
ifThen: armBlock, ifElse: ifChain, typ: makeVoid(), loc: loc)
|
||||||
|
of pkWildcard, pkIdent:
|
||||||
|
# Default arm — always matches
|
||||||
|
var armStmts: seq[HirNode] = @[]
|
||||||
|
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
||||||
|
let armBlock = hirBlock(armStmts, nil, makeVoid(), loc)
|
||||||
|
if ifChain == nil:
|
||||||
|
ifChain = armBlock
|
||||||
|
else:
|
||||||
|
ifChain = HirNode(kind: hIf,
|
||||||
|
ifCond: hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc),
|
||||||
|
ifThen: armBlock, ifElse: ifChain, typ: makeVoid(), loc: loc)
|
||||||
|
else:
|
||||||
|
var armStmts: seq[HirNode] = @[]
|
||||||
|
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
||||||
|
let armBlock = hirBlock(armStmts, nil, makeVoid(), loc)
|
||||||
|
if ifChain == nil:
|
||||||
|
ifChain = armBlock
|
||||||
|
else:
|
||||||
|
ifChain = HirNode(kind: hIf,
|
||||||
|
ifCond: hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc),
|
||||||
|
ifThen: armBlock, ifElse: ifChain, typ: makeVoid(), loc: loc)
|
||||||
|
|
||||||
|
stmts.add(ifChain)
|
||||||
|
|
||||||
|
# Return the result variable as the block expression
|
||||||
|
return hirBlock(stmts, hirVar(resultName, typ, loc), typ, loc)
|
||||||
|
|
||||||
proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
||||||
result.module = module
|
result.module = module
|
||||||
result.globalScope = sema.globalScope
|
result.globalScope = sema.globalScope
|
||||||
result.methodTable = sema.methodTable
|
result.methodTable = sema.methodTable
|
||||||
result.varCounter = 0
|
result.varCounter = 0
|
||||||
result.typeSubst = initTable[string, Type]()
|
result.typeSubst = initTable[string, Type]()
|
||||||
|
result.importTable = initTable[string, string]()
|
||||||
|
|
||||||
# Forward declarations
|
# Forward declarations
|
||||||
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
||||||
@@ -127,7 +208,10 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
return hirLit(expr.exprLit, typ, loc)
|
return hirLit(expr.exprLit, typ, loc)
|
||||||
|
|
||||||
of ekIdent:
|
of ekIdent:
|
||||||
return hirVar(expr.exprIdent, typ, loc)
|
let name = expr.exprIdent
|
||||||
|
if ctx.importTable.hasKey(name):
|
||||||
|
return hirVar(ctx.importTable[name], typ, loc)
|
||||||
|
return hirVar(name, typ, loc)
|
||||||
|
|
||||||
of ekPath:
|
of ekPath:
|
||||||
# Handle enum variants: Color::Red → Color_Red
|
# Handle enum variants: Color::Red → Color_Red
|
||||||
@@ -193,8 +277,10 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
var calleeName = ""
|
var calleeName = ""
|
||||||
if expr.exprCallCallee.kind == ekIdent:
|
if expr.exprCallCallee.kind == ekIdent:
|
||||||
calleeName = expr.exprCallCallee.exprIdent
|
calleeName = expr.exprCallCallee.exprIdent
|
||||||
|
if ctx.importTable.hasKey(calleeName):
|
||||||
|
calleeName = ctx.importTable[calleeName]
|
||||||
elif expr.exprCallCallee.kind == ekPath:
|
elif expr.exprCallCallee.kind == ekPath:
|
||||||
calleeName = expr.exprCallCallee.exprPath.join("::")
|
calleeName = expr.exprCallCallee.exprPath.join("_")
|
||||||
var args: seq[HirNode] = @[]
|
var args: seq[HirNode] = @[]
|
||||||
for arg in expr.exprCallArgs:
|
for arg in expr.exprCallArgs:
|
||||||
args.add(ctx.lowerExpr(arg))
|
args.add(ctx.lowerExpr(arg))
|
||||||
@@ -284,8 +370,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
var arms: seq[HirMatchArm] = @[]
|
var arms: seq[HirMatchArm] = @[]
|
||||||
for arm in expr.exprMatchArms:
|
for arm in expr.exprMatchArms:
|
||||||
arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body)))
|
arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body)))
|
||||||
return HirNode(kind: hMatch, matchSubject: subject, matchArms: arms,
|
return lowerMatch(ctx, subject, arms, typ, loc)
|
||||||
typ: typ, loc: loc)
|
|
||||||
|
|
||||||
of ekSizeOf:
|
of ekSizeOf:
|
||||||
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
||||||
@@ -405,8 +490,20 @@ proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode =
|
|||||||
let hir = ctx.lowerStmt(s)
|
let hir = ctx.lowerStmt(s)
|
||||||
if hir != nil:
|
if hir != nil:
|
||||||
stmts.add(hir)
|
stmts.add(hir)
|
||||||
return HirNode(kind: hBlock, blockStmts: stmts, blockExpr: nil,
|
# If the last statement is an expression, make it the block's result expression
|
||||||
typ: makeVoid(), loc: blk.loc)
|
var expr: HirNode = nil
|
||||||
|
if stmts.len > 0 and stmts[^1].kind == hBlock and stmts[^1].blockExpr != nil:
|
||||||
|
# Nested block expression (e.g., from match lowering) — lift it
|
||||||
|
let last = stmts[^1]
|
||||||
|
stmts[^1] = hirBlock(last.blockStmts, nil, makeVoid(), last.loc)
|
||||||
|
expr = last.blockExpr
|
||||||
|
elif stmts.len > 0 and stmts[^1].kind != hBlock:
|
||||||
|
# Last stmt is a simple expression-like node — we can't easily extract it,
|
||||||
|
# but for hVar/hLit/hCall etc. we could treat them as block expr.
|
||||||
|
# For now, leave as-is to avoid breaking control-flow statements.
|
||||||
|
discard
|
||||||
|
return HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr,
|
||||||
|
typ: if expr != nil: expr.typ else: makeVoid(), loc: blk.loc)
|
||||||
|
|
||||||
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||||
# Set up type substitution for generic functions
|
# Set up type substitution for generic functions
|
||||||
@@ -469,6 +566,26 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
|||||||
var enums: seq[tuple[name: string, variants: seq[HirEnumVariant]]] = @[]
|
var enums: seq[tuple[name: string, variants: seq[HirEnumVariant]]] = @[]
|
||||||
var consts: seq[tuple[name: string, typ: Type, value: HirNode]] = @[]
|
var consts: seq[tuple[name: string, typ: Type, value: HirNode]] = @[]
|
||||||
|
|
||||||
|
# Collect imports for name resolution
|
||||||
|
for decl in module.items:
|
||||||
|
if decl.kind == dkUse:
|
||||||
|
case decl.declUseKind
|
||||||
|
of ukSingle:
|
||||||
|
if decl.declUsePath.len > 0:
|
||||||
|
let localName = decl.declUsePath[^1]
|
||||||
|
let fullName = decl.declUsePath.join("_")
|
||||||
|
ctx.importTable[localName] = fullName
|
||||||
|
of ukMulti:
|
||||||
|
if decl.declUsePath.len > 0:
|
||||||
|
let basePath = decl.declUsePath.join("_")
|
||||||
|
for name in decl.declUseNames:
|
||||||
|
ctx.importTable[name] = basePath & "_" & name
|
||||||
|
of ukGlob:
|
||||||
|
# For glob imports, we can't statically resolve all names here.
|
||||||
|
# Store the base path for potential future use.
|
||||||
|
discard
|
||||||
|
|
||||||
|
|
||||||
# First pass: collect generic functions
|
# First pass: collect generic functions
|
||||||
var genericFuncs = initTable[string, Decl]()
|
var genericFuncs = initTable[string, Decl]()
|
||||||
for decl in module.items:
|
for decl in module.items:
|
||||||
|
|||||||
+34
-3
@@ -62,6 +62,26 @@ proc match(p: var Parser, kind: TokenKind): bool =
|
|||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
proc isTypeArgListAhead(p: Parser): bool =
|
||||||
|
## Lookahead to determine if '<' starts a type argument list.
|
||||||
|
## Returns true if we can find a matching '>' before EOF, '{', or ';'.
|
||||||
|
if not p.check(tkLt): return false
|
||||||
|
var depth = 0
|
||||||
|
var ahead = 0
|
||||||
|
while true:
|
||||||
|
let kind = p.peek(ahead)
|
||||||
|
if kind == tkEndOfFile or kind == tkLBrace or kind == tkSemicolon:
|
||||||
|
return false
|
||||||
|
if kind == tkLt:
|
||||||
|
inc depth
|
||||||
|
elif kind == tkGt:
|
||||||
|
dec depth
|
||||||
|
if depth == 0:
|
||||||
|
return true
|
||||||
|
if depth < 0:
|
||||||
|
return false
|
||||||
|
inc ahead
|
||||||
|
|
||||||
proc expect(p: var Parser, kind: TokenKind, message: string): Token =
|
proc expect(p: var Parser, kind: TokenKind, message: string): Token =
|
||||||
if p.check(kind):
|
if p.check(kind):
|
||||||
return p.advance()
|
return p.advance()
|
||||||
@@ -240,7 +260,7 @@ proc parsePrimaryPattern(p: var Parser): Pattern =
|
|||||||
discard p.advance()
|
discard p.advance()
|
||||||
discard p.expect(tkRParen, "expected ')' to close enum pattern")
|
discard p.expect(tkRParen, "expected ')' to close enum 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: pkIdent, loc: loc, patIdent: name)
|
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: 0, y: 0 }
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
@@ -443,7 +463,8 @@ proc parsePostfix(p: var Parser): Expr =
|
|||||||
left = Expr(kind: ekCall, loc: loc, exprCallCallee: left, exprCallArgs: args)
|
left = Expr(kind: ekCall, loc: loc, exprCallCallee: left, exprCallArgs: args)
|
||||||
of tkLt:
|
of tkLt:
|
||||||
# Generic type arguments: Max<int>(10, 20)
|
# Generic type arguments: Max<int>(10, 20)
|
||||||
if left.kind == ekIdent:
|
# Only treat '<' as generic args if lookahead confirms a matching '>'
|
||||||
|
if left.kind == ekIdent and p.isTypeArgListAhead():
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
var typeArgs: seq[TypeExpr] = @[]
|
var typeArgs: seq[TypeExpr] = @[]
|
||||||
while not p.check(tkGt) and not p.isAtEnd:
|
while not p.check(tkGt) and not p.isAtEnd:
|
||||||
@@ -727,10 +748,20 @@ proc parseStmt(p: var Parser): Stmt =
|
|||||||
return Stmt(kind: skFor, loc: loc, stmtForVar: varName, stmtForIter: iter, stmtForBody: body)
|
return Stmt(kind: skFor, loc: loc, stmtForVar: varName, stmtForIter: iter, stmtForBody: body)
|
||||||
of tkMatch:
|
of tkMatch:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
|
p.structInitAllowed = false
|
||||||
let subject = p.parseExpr()
|
let subject = p.parseExpr()
|
||||||
|
p.structInitAllowed = true
|
||||||
|
# Skip newlines before opening brace
|
||||||
|
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:
|
||||||
|
# Skip newlines
|
||||||
|
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")
|
||||||
@@ -739,7 +770,7 @@ proc parseStmt(p: var Parser): Stmt =
|
|||||||
if p.check(tkComma):
|
if p.check(tkComma):
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
discard p.expect(tkRBrace, "expected '}' to close match")
|
discard p.expect(tkRBrace, "expected '}' to close match")
|
||||||
return Stmt(kind: skMatch, loc: loc, stmtMatchSubject: subject, stmtMatchArms: arms)
|
return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekMatch, loc: loc, exprMatchSubject: subject, exprMatchArms: arms))
|
||||||
of tkReturn:
|
of tkReturn:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
var val: Expr = nil
|
var val: Expr = nil
|
||||||
|
|||||||
+26
-1
@@ -211,6 +211,29 @@ proc collectGlobals*(sema: var Sema) =
|
|||||||
proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type
|
proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type
|
||||||
proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type
|
proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type
|
||||||
|
|
||||||
|
proc extractPatternBindings(sema: var Sema, pat: Pattern, scope: Scope) =
|
||||||
|
## Add pattern-bound identifiers to scope with unknown type (best-effort)
|
||||||
|
if pat == nil: return
|
||||||
|
case pat.kind
|
||||||
|
of pkIdent:
|
||||||
|
let sym = Symbol(kind: skVar, name: pat.patIdent, typ: makeUnknown(), isMutable: false)
|
||||||
|
discard scope.define(sym)
|
||||||
|
of pkEnum:
|
||||||
|
for arg in pat.patEnumArgs:
|
||||||
|
sema.extractPatternBindings(arg, scope)
|
||||||
|
for nf in pat.patEnumNamed:
|
||||||
|
sema.extractPatternBindings(nf.pattern, scope)
|
||||||
|
of pkTuple:
|
||||||
|
for elem in pat.patTupleElements:
|
||||||
|
sema.extractPatternBindings(elem, scope)
|
||||||
|
of pkStruct:
|
||||||
|
for f in pat.patStructFields:
|
||||||
|
sema.extractPatternBindings(f.pattern, scope)
|
||||||
|
of pkGuarded:
|
||||||
|
sema.extractPatternBindings(pat.patGuardedInner, scope)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
proc checkExprList(sema: var Sema, exprs: seq[Expr], scope: Scope): seq[Type] =
|
proc checkExprList(sema: var Sema, exprs: seq[Expr], scope: Scope): seq[Type] =
|
||||||
for e in exprs:
|
for e in exprs:
|
||||||
result.add(sema.checkExpr(e, scope))
|
result.add(sema.checkExpr(e, scope))
|
||||||
@@ -522,7 +545,9 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
let subjectType = 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:
|
||||||
let armType = sema.checkExpr(arm.body, scope)
|
var armScope = newScope(scope)
|
||||||
|
sema.extractPatternBindings(arm.pattern, armScope)
|
||||||
|
let armType = sema.checkExpr(arm.body, armScope)
|
||||||
if resultType.isUnknown:
|
if resultType.isUnknown:
|
||||||
resultType = armType
|
resultType = armType
|
||||||
elif armType != resultType and not armType.isUnknown:
|
elif armType != resultType and not armType.isUnknown:
|
||||||
|
|||||||
Reference in New Issue
Block a user