feat: add algebraic enums (tagged unions) support
- Generate tagged unions in C backend for enums with data
- Add HirEnumVariant type with fields and namedFields
- Support _Tag and _Data field access in sema
- Support enum variant constants (Result_Ok, Result_Err)
- Support _Data union field access (Ok_0, Err_0, etc.)
- Add algebraic_enums.bux example
Example:
enum Result {
Ok(int),
Err(String)
}
let r: Result = Result { tag: Result_Ok };
r.data.Ok_0 = 42;
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
// Algebraic Enums - Enums with data (tagged unions)
|
||||||
|
extern func Std_Io_PrintLine(s: String);
|
||||||
|
extern func Std_Io_PrintInt(n: int);
|
||||||
|
|
||||||
|
enum Result {
|
||||||
|
Ok(int),
|
||||||
|
Err(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let r1: Result = Result { tag: Result_Ok };
|
||||||
|
r1.data.Ok_0 = 42;
|
||||||
|
|
||||||
|
let r2: Result = Result { tag: Result_Err };
|
||||||
|
r2.data.Err_0 = "error message";
|
||||||
|
|
||||||
|
if r1.tag == Result_Ok {
|
||||||
|
Std_Io_PrintLine("r1 is Ok:");
|
||||||
|
Std_Io_PrintInt(r1.data.Ok_0);
|
||||||
|
Std_Io_PrintLine("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if r2.tag == Result_Err {
|
||||||
|
Std_Io_PrintLine("r2 is Err:");
|
||||||
|
Std_Io_PrintLine(r2.data.Err_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+56
-3
@@ -355,14 +355,67 @@ proc emitStruct*(be: var CBackend, name: string, fields: seq[tuple[name: string,
|
|||||||
be.emitLine(&"}} {name};")
|
be.emitLine(&"}} {name};")
|
||||||
be.emitLine("")
|
be.emitLine("")
|
||||||
|
|
||||||
proc emitEnum*(be: var CBackend, name: string, variants: seq[string]) =
|
proc emitEnum*(be: var CBackend, name: string, variants: seq[HirEnumVariant]) =
|
||||||
|
# Check if this is a simple enum (no data) or algebraic enum (with data)
|
||||||
|
var hasData = false
|
||||||
|
for v in variants:
|
||||||
|
if v.fields.len > 0 or v.namedFields.len > 0:
|
||||||
|
hasData = true
|
||||||
|
break
|
||||||
|
|
||||||
|
if not hasData:
|
||||||
|
# Simple enum - generate as before
|
||||||
be.emitLine(&"typedef enum {{")
|
be.emitLine(&"typedef enum {{")
|
||||||
inc be.indent
|
inc be.indent
|
||||||
for i, v in variants:
|
for i, v in variants:
|
||||||
if i < variants.len - 1:
|
if i < variants.len - 1:
|
||||||
be.emitLine(&"{name}_{v},")
|
be.emitLine(&"{name}_{v.name},")
|
||||||
else:
|
else:
|
||||||
be.emitLine(&"{name}_{v}")
|
be.emitLine(&"{name}_{v.name}")
|
||||||
|
dec be.indent
|
||||||
|
be.emitLine(&"}} {name};")
|
||||||
|
be.emitLine("")
|
||||||
|
else:
|
||||||
|
# Algebraic enum - generate tagged union
|
||||||
|
# 1. Generate tag enum
|
||||||
|
be.emitLine(&"typedef enum {{")
|
||||||
|
inc be.indent
|
||||||
|
for i, v in variants:
|
||||||
|
if i < variants.len - 1:
|
||||||
|
be.emitLine(&"{name}_{v.name},")
|
||||||
|
else:
|
||||||
|
be.emitLine(&"{name}_{v.name}")
|
||||||
|
dec be.indent
|
||||||
|
be.emitLine(&"}} {name}_Tag;")
|
||||||
|
be.emitLine("")
|
||||||
|
|
||||||
|
# 2. Generate union for data
|
||||||
|
be.emitLine(&"typedef union {{")
|
||||||
|
inc be.indent
|
||||||
|
for v in variants:
|
||||||
|
if v.fields.len > 0:
|
||||||
|
# Positional fields
|
||||||
|
for i, f in v.fields:
|
||||||
|
let typ = typeToC(f)
|
||||||
|
be.emitLine(&"{typ} {v.name}_{i};")
|
||||||
|
elif v.namedFields.len > 0:
|
||||||
|
# Named fields - generate as struct
|
||||||
|
be.emitLine(&"struct {{")
|
||||||
|
inc be.indent
|
||||||
|
for nf in v.namedFields:
|
||||||
|
let typ = typeToC(nf.typ)
|
||||||
|
be.emitLine(&"{typ} {nf.name};")
|
||||||
|
dec be.indent
|
||||||
|
be.emitLine(&"}} {v.name};")
|
||||||
|
dec be.indent
|
||||||
|
be.emitLine(&"}} {name}_Data;")
|
||||||
|
be.emitLine("")
|
||||||
|
|
||||||
|
# 3. Generate main struct with tag + union
|
||||||
|
be.emitLine(&"typedef struct {{")
|
||||||
|
inc be.indent
|
||||||
|
be.emitLine(&"{name}_Tag tag;")
|
||||||
|
be.emitLine(&"{name}_Data data;")
|
||||||
dec be.indent
|
dec be.indent
|
||||||
be.emitLine(&"}} {name};")
|
be.emitLine(&"}} {name};")
|
||||||
be.emitLine("")
|
be.emitLine("")
|
||||||
|
|||||||
+6
-1
@@ -124,11 +124,16 @@ type
|
|||||||
body*: HirNode
|
body*: HirNode
|
||||||
isPublic*: bool
|
isPublic*: bool
|
||||||
|
|
||||||
|
HirEnumVariant* = object
|
||||||
|
name*: string
|
||||||
|
fields*: seq[Type] # Positional fields for algebraic enums
|
||||||
|
namedFields*: seq[tuple[name: string, typ: Type]] # Named fields
|
||||||
|
|
||||||
HirModule* = object
|
HirModule* = object
|
||||||
funcs*: seq[HirFunc]
|
funcs*: seq[HirFunc]
|
||||||
externFuncs*: seq[HirFunc] # Functions declared with extern (no body)
|
externFuncs*: seq[HirFunc] # Functions declared with extern (no body)
|
||||||
structs*: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]]
|
structs*: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]]
|
||||||
enums*: seq[tuple[name: string, variants: seq[string]]]
|
enums*: seq[tuple[name: string, variants: seq[HirEnumVariant]]]
|
||||||
consts*: seq[tuple[name: string, typ: Type, value: HirNode]]
|
consts*: seq[tuple[name: string, typ: Type, value: HirNode]]
|
||||||
|
|
||||||
# Constructor helpers
|
# Constructor helpers
|
||||||
|
|||||||
+31
-3
@@ -433,7 +433,7 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
|||||||
var funcs: seq[HirFunc] = @[]
|
var funcs: seq[HirFunc] = @[]
|
||||||
var externFuncs: seq[HirFunc] = @[]
|
var externFuncs: seq[HirFunc] = @[]
|
||||||
var structs: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]] = @[]
|
var structs: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]] = @[]
|
||||||
var enums: seq[tuple[name: string, variants: seq[string]]] = @[]
|
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]] = @[]
|
||||||
|
|
||||||
for decl in module.items:
|
for decl in module.items:
|
||||||
@@ -463,9 +463,37 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
|||||||
fields.add((f.name, fType))
|
fields.add((f.name, fType))
|
||||||
structs.add((decl.declStructName, fields))
|
structs.add((decl.declStructName, fields))
|
||||||
of dkEnum:
|
of dkEnum:
|
||||||
var variants: seq[string] = @[]
|
var variants: seq[HirEnumVariant] = @[]
|
||||||
for v in decl.declEnumVariants:
|
for v in decl.declEnumVariants:
|
||||||
variants.add(v.name)
|
var fields: seq[Type] = @[]
|
||||||
|
for f in v.fields:
|
||||||
|
var fType = makeUnknown()
|
||||||
|
if f != nil and f.kind == tekNamed:
|
||||||
|
case f.typeName
|
||||||
|
of "int", "int32": fType = makeInt()
|
||||||
|
of "int64": fType = makeInt64()
|
||||||
|
of "float64": fType = makeFloat64()
|
||||||
|
of "float32": fType = makeFloat32()
|
||||||
|
of "bool": fType = makeBool()
|
||||||
|
of "String", "str": fType = makeStr()
|
||||||
|
else: fType = makeNamed(f.typeName)
|
||||||
|
fields.add(fType)
|
||||||
|
|
||||||
|
var namedFields: seq[tuple[name: string, typ: Type]] = @[]
|
||||||
|
for nf in v.namedFields:
|
||||||
|
var fType = makeUnknown()
|
||||||
|
if nf.ftype != nil and nf.ftype.kind == tekNamed:
|
||||||
|
case nf.ftype.typeName
|
||||||
|
of "int", "int32": fType = makeInt()
|
||||||
|
of "int64": fType = makeInt64()
|
||||||
|
of "float64": fType = makeFloat64()
|
||||||
|
of "float32": fType = makeFloat32()
|
||||||
|
of "bool": fType = makeBool()
|
||||||
|
of "String", "str": fType = makeStr()
|
||||||
|
else: fType = makeNamed(nf.ftype.typeName)
|
||||||
|
namedFields.add((nf.name, fType))
|
||||||
|
|
||||||
|
variants.add(HirEnumVariant(name: v.name, fields: fields, namedFields: namedFields))
|
||||||
enums.add((decl.declEnumName, variants))
|
enums.add((decl.declEnumName, variants))
|
||||||
of dkConst:
|
of dkConst:
|
||||||
let value = ctx.lowerExpr(decl.declConstValue)
|
let value = ctx.lowerExpr(decl.declConstValue)
|
||||||
|
|||||||
+45
-1
@@ -132,6 +132,13 @@ proc collectGlobals*(sema: var Sema) =
|
|||||||
if not sema.globalScope.define(sym):
|
if not sema.globalScope.define(sym):
|
||||||
sema.emitError(decl.loc, &"duplicate symbol '{decl.declEnumName}'")
|
sema.emitError(decl.loc, &"duplicate symbol '{decl.declEnumName}'")
|
||||||
sema.typeTable[decl.declEnumName] = t
|
sema.typeTable[decl.declEnumName] = t
|
||||||
|
# For algebraic enums, add variant constants with _Tag type
|
||||||
|
for variant in decl.declEnumVariants:
|
||||||
|
let variantName = decl.declEnumName & "_" & variant.name
|
||||||
|
let variantType = makeNamed(decl.declEnumName & "_Tag")
|
||||||
|
let variantSym = Symbol(kind: skConst, name: variantName, typ: variantType,
|
||||||
|
decl: decl, isPublic: decl.isPublic)
|
||||||
|
discard sema.globalScope.define(variantSym)
|
||||||
of dkUnion:
|
of dkUnion:
|
||||||
let t = makeNamed(decl.declUnionName)
|
let t = makeNamed(decl.declUnionName)
|
||||||
let sym = Symbol(kind: skType, name: decl.declUnionName, typ: t,
|
let sym = Symbol(kind: skType, name: decl.declUnionName, typ: t,
|
||||||
@@ -398,12 +405,49 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
of ekField:
|
of ekField:
|
||||||
let obj = sema.checkExpr(expr.exprFieldObj, scope)
|
let obj = sema.checkExpr(expr.exprFieldObj, scope)
|
||||||
if obj.kind == tkNamed:
|
if obj.kind == tkNamed:
|
||||||
|
# Check if this is a _Data union field access
|
||||||
|
if obj.name.endsWith("_Data"):
|
||||||
|
let enumName = obj.name[0..^6] # Remove "_Data" suffix
|
||||||
|
let enumSym = sema.globalScope.lookup(enumName)
|
||||||
|
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
|
||||||
|
# Look for the field in enum variants
|
||||||
|
for variant in enumSym.decl.declEnumVariants:
|
||||||
|
# Check positional fields: Ok_0, Ok_1, etc.
|
||||||
|
for i, f in variant.fields:
|
||||||
|
let fieldName = variant.name & "_" & $i
|
||||||
|
if fieldName == expr.exprFieldName:
|
||||||
|
return sema.resolveType(f)
|
||||||
|
# Check named fields
|
||||||
|
for nf in variant.namedFields:
|
||||||
|
if nf.name == expr.exprFieldName:
|
||||||
|
return sema.resolveType(nf.ftype)
|
||||||
|
sema.emitError(expr.loc, &"union '{obj.name}' has no field '{expr.exprFieldName}'")
|
||||||
|
else:
|
||||||
|
sema.emitError(expr.loc, &"cannot access field on type {obj.toString}")
|
||||||
|
else:
|
||||||
let sym = sema.globalScope.lookup(obj.name)
|
let sym = sema.globalScope.lookup(obj.name)
|
||||||
if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct:
|
if sym != nil and sym.decl != nil:
|
||||||
|
if sym.decl.kind == dkStruct:
|
||||||
for f in sym.decl.declStructFields:
|
for f in sym.decl.declStructFields:
|
||||||
if f.name == expr.exprFieldName:
|
if f.name == expr.exprFieldName:
|
||||||
return sema.resolveType(f.ftype)
|
return sema.resolveType(f.ftype)
|
||||||
sema.emitError(expr.loc, &"struct '{obj.name}' has no field '{expr.exprFieldName}'")
|
sema.emitError(expr.loc, &"struct '{obj.name}' has no field '{expr.exprFieldName}'")
|
||||||
|
elif sym.decl.kind == dkEnum:
|
||||||
|
# Algebraic enum fields
|
||||||
|
if expr.exprFieldName == "tag":
|
||||||
|
return makeNamed(obj.name & "_Tag")
|
||||||
|
elif expr.exprFieldName == "data":
|
||||||
|
return makeNamed(obj.name & "_Data")
|
||||||
|
else:
|
||||||
|
sema.emitError(expr.loc, &"enum '{obj.name}' has no field '{expr.exprFieldName}'")
|
||||||
|
elif sym.decl.kind == dkUnion:
|
||||||
|
# Union fields
|
||||||
|
for f in sym.decl.declUnionFields:
|
||||||
|
if f.name == expr.exprFieldName:
|
||||||
|
return sema.resolveType(f.ftype)
|
||||||
|
sema.emitError(expr.loc, &"union '{obj.name}' has no field '{expr.exprFieldName}'")
|
||||||
|
else:
|
||||||
|
sema.emitError(expr.loc, &"cannot access field on type {obj.toString}")
|
||||||
else:
|
else:
|
||||||
sema.emitError(expr.loc, &"cannot access field on type {obj.toString}")
|
sema.emitError(expr.loc, &"cannot access field on type {obj.toString}")
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user