repos / gbc

GBC - Go B Compiler
git clone https://github.com/xplshn/gbc.git

commit
640864f
parent
e6c5972
author
xplshn
date
2025-08-29 04:00:11 +0000 UTC
refactor; bug fixes; undo gofmt and keep our terser-style

Signed-off-by: xplshn <[email protected]>
15 files changed,  +2416, -1766
M cmd/gbc/main.go
+6, -2
 1@@ -202,17 +202,21 @@ func findLibrary(libName string, userPaths []string, cfg *config.Config) string
 2 	// Search for libraries matching the target architecture and OS
 3 	filenames := []string{
 4 		fmt.Sprintf("%s_%s_%s.b", libName, cfg.GOARCH, cfg.GOOS),
 5-		fmt.Sprintf("%s_%s.b", libName, cfg.GOARCH),
 6 		fmt.Sprintf("%s_%s.b", libName, cfg.GOOS),
 7+		fmt.Sprintf("%s_%s.b", libName, cfg.GOARCH),
 8 		fmt.Sprintf("%s.b", libName),
 9 		fmt.Sprintf("%s/%s_%s.b", libName, cfg.GOARCH, cfg.GOOS),
10-		fmt.Sprintf("%s/%s.b", libName, cfg.GOARCH),
11 		fmt.Sprintf("%s/%s.b", libName, cfg.GOOS),
12+		fmt.Sprintf("%s/%s.b", libName, cfg.GOARCH),
13+		fmt.Sprintf("%s/%s.b", libName, libName),
14 	}
15 	searchPaths := append(userPaths, []string{"./lib", "/usr/local/lib/gbc", "/usr/lib/gbc", "/lib/gbc"}...)
16 	for _, path := range searchPaths {
17+		//fmt.Println("path:", path)
18 		for _, fname := range filenames {
19+			//fmt.Println("fname:", fname)
20 			fullPath := filepath.Join(path, fname)
21+			//fmt.Println("fullPath:", fullPath)
22 			if _, err := os.Stat(fullPath); err == nil {
23 				return fullPath
24 			}
M pkg/ast/ast.go
+85, -64
  1@@ -8,10 +8,11 @@ import (
  2 type NodeType int
  3 
  4 const (
  5-	// Expressions
  6 	Number NodeType = iota
  7+	FloatNumber
  8 	String
  9 	Ident
 10+	Nil
 11 	Assign
 12 	BinaryOp
 13 	UnaryOp
 14@@ -24,12 +25,12 @@ const (
 15 	AutoAlloc
 16 	MemberAccess
 17 	TypeCast
 18-
 19-	// Statements
 20+	StructLiteral
 21 	FuncDecl
 22 	VarDecl
 23 	MultiVarDecl
 24 	TypeDecl
 25+	EnumDecl
 26 	ExtrnDecl
 27 	If
 28 	While
 29@@ -46,13 +47,12 @@ const (
 30 	Directive
 31 )
 32 
 33-// Node represents a node in the Abstract Syntax Tree
 34 type Node struct {
 35 	Type   NodeType
 36 	Tok    token.Token
 37 	Parent *Node
 38 	Data   interface{}
 39-	Typ    *BxType // Set by the type checker
 40+	Typ    *BxType
 41 }
 42 
 43 type BxTypeKind int
 44@@ -63,46 +63,54 @@ const (
 45 	TYPE_VOID
 46 	TYPE_ARRAY
 47 	TYPE_STRUCT
 48+	TYPE_ENUM
 49 	TYPE_BOOL
 50 	TYPE_FLOAT
 51 	TYPE_UNTYPED
 52+	TYPE_NIL
 53+	TYPE_UNTYPED_INT
 54+	TYPE_UNTYPED_FLOAT
 55 )
 56 
 57-// BxType represents a type in the Bx type system.
 58 type BxType struct {
 59 	Kind      BxTypeKind
 60-	Base      *BxType // Base type for pointers or arrays.
 61-	Name      string  // Name for primitive types or the typedef name.
 62+	Base      *BxType
 63+	Name      string
 64 	ArraySize *Node
 65 	IsConst   bool
 66-	StructTag string  // The name immediately following the 'struct' keyword.
 67-	Fields    []*Node // List of *VarDecl nodes for struct members.
 68+	StructTag string
 69+	Fields    []*Node
 70+	EnumMembers []*Node
 71 }
 72 
 73-// Pre-defined types.
 74 var (
 75-	TypeInt     = &BxType{Kind: TYPE_PRIMITIVE, Name: "int"}
 76-	TypeUint    = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint"}
 77-	TypeInt8    = &BxType{Kind: TYPE_PRIMITIVE, Name: "int8"}
 78-	TypeUint8   = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint8"}
 79-	TypeInt16   = &BxType{Kind: TYPE_PRIMITIVE, Name: "int16"}
 80-	TypeUint16  = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint16"}
 81-	TypeInt32   = &BxType{Kind: TYPE_PRIMITIVE, Name: "int32"}
 82-	TypeUint32  = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint32"}
 83-	TypeInt64   = &BxType{Kind: TYPE_PRIMITIVE, Name: "int64"}
 84-	TypeUint64  = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint64"}
 85-	TypeFloat   = &BxType{Kind: TYPE_FLOAT, Name: "float"}
 86-	TypeFloat32 = &BxType{Kind: TYPE_FLOAT, Name: "float32"}
 87-	TypeFloat64 = &BxType{Kind: TYPE_FLOAT, Name: "float64"}
 88-	TypeByte    = &BxType{Kind: TYPE_PRIMITIVE, Name: "byte"}
 89-	TypeVoid    = &BxType{Kind: TYPE_VOID, Name: "void"}
 90-	TypeBool    = &BxType{Kind: TYPE_BOOL, Name: "bool"}
 91-	TypeUntyped = &BxType{Kind: TYPE_UNTYPED, Name: "untyped"}
 92-	TypeString  = &BxType{Kind: TYPE_POINTER, Base: TypeByte, Name: "string"}
 93+	TypeInt          = &BxType{Kind: TYPE_PRIMITIVE, Name: "int"}
 94+	TypeUint         = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint"}
 95+	TypeInt8         = &BxType{Kind: TYPE_PRIMITIVE, Name: "int8"}
 96+	TypeUint8        = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint8"}
 97+	TypeInt16        = &BxType{Kind: TYPE_PRIMITIVE, Name: "int16"}
 98+	TypeUint16       = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint16"}
 99+	TypeInt32        = &BxType{Kind: TYPE_PRIMITIVE, Name: "int32"}
100+	TypeUint32       = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint32"}
101+	TypeInt64        = &BxType{Kind: TYPE_PRIMITIVE, Name: "int64"}
102+	TypeUint64       = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint64"}
103+	TypeFloat        = &BxType{Kind: TYPE_FLOAT, Name: "float"}
104+	TypeFloat32      = &BxType{Kind: TYPE_FLOAT, Name: "float32"}
105+	TypeFloat64      = &BxType{Kind: TYPE_FLOAT, Name: "float64"}
106+	TypeByte         = &BxType{Kind: TYPE_PRIMITIVE, Name: "byte"}
107+	TypeVoid         = &BxType{Kind: TYPE_VOID, Name: "void"}
108+	TypeBool         = &BxType{Kind: TYPE_BOOL, Name: "bool"}
109+	TypeUntyped      = &BxType{Kind: TYPE_UNTYPED, Name: "untyped"}
110+	TypeString       = &BxType{Kind: TYPE_POINTER, Base: TypeByte, Name: "string"}
111+	TypeNil          = &BxType{Kind: TYPE_NIL, Name: "nil"}
112+	TypeUntypedInt   = &BxType{Kind: TYPE_UNTYPED_INT, Name: "untyped int"}
113+	TypeUntypedFloat = &BxType{Kind: TYPE_UNTYPED_FLOAT, Name: "untyped float"}
114 )
115 
116 type NumberNode struct{ Value int64 }
117+type FloatNumberNode struct{ Value float64 }
118 type StringNode struct{ Value string }
119+type NilNode struct{}
120 type IdentNode struct{ Name string }
121 type AssignNode struct{ Op token.Type; Lhs, Rhs *Node }
122 type BinaryOpNode struct{ Op token.Type; Left, Right *Node }
123@@ -114,6 +122,7 @@ type TernaryNode struct{ Cond, ThenExpr, ElseExpr *Node }
124 type SubscriptNode struct{ Array, Index *Node }
125 type MemberAccessNode struct{ Expr, Member *Node }
126 type TypeCastNode struct{ Expr *Node; TargetType *BxType }
127+type StructLiteralNode struct{ TypeNode *Node; Values []*Node; Names []*Node }
128 type FuncCallNode struct{ FuncExpr *Node; Args []*Node }
129 type AutoAllocNode struct{ Size *Node }
130 type FuncDeclNode struct {
131@@ -135,16 +144,16 @@ type VarDeclNode struct {
132 }
133 type MultiVarDeclNode struct{ Decls []*Node }
134 type TypeDeclNode struct{ Name string; Type *BxType }
135+type EnumDeclNode struct{ Name string; Members []*Node }
136 type ExtrnDeclNode struct{ Names []*Node }
137 type IfNode struct{ Cond, ThenBody, ElseBody *Node }
138 type WhileNode struct{ Cond, Body *Node }
139 type ReturnNode struct{ Expr *Node }
140 type BlockNode struct{ Stmts []*Node; IsSynthetic bool }
141 type GotoNode struct{ Label string }
142-type CaseLabelNode struct{ Value int64; LabelName string }
143-type SwitchNode struct{ Expr, Body *Node; CaseLabels []CaseLabelNode; DefaultLabelName string }
144-type CaseNode struct{ Value, Body *Node; QbeLabel string }
145-type DefaultNode struct{ Body *Node; QbeLabel string }
146+type SwitchNode struct{ Expr, Body *Node }
147+type CaseNode struct{ Values []*Node; Body *Node }
148+type DefaultNode struct{ Body *Node }
149 type BreakNode struct{}
150 type ContinueNode struct{}
151 type LabelNode struct{ Name string; Stmt *Node }
152@@ -154,22 +163,24 @@ type DirectiveNode struct{ Name string }
153 func newNode(tok token.Token, nodeType NodeType, data interface{}, children ...*Node) *Node {
154 	node := &Node{Type: nodeType, Tok: tok, Data: data}
155 	for _, child := range children {
156-		if child != nil {
157-			child.Parent = node
158-		}
159+		if child != nil { child.Parent = node }
160 	}
161 	return node
162 }
163 
164 func NewNumber(tok token.Token, value int64) *Node {
165-	return newNode(tok, Number, NumberNode{Value: value})
166-}
167-func NewString(tok token.Token, value string) *Node {
168-	return newNode(tok, String, StringNode{Value: value})
169+	node := newNode(tok, Number, NumberNode{Value: value})
170+	node.Typ = TypeUntypedInt
171+	return node
172 }
173-func NewIdent(tok token.Token, name string) *Node {
174-	return newNode(tok, Ident, IdentNode{Name: name})
175+func NewFloatNumber(tok token.Token, value float64) *Node {
176+	node := newNode(tok, FloatNumber, FloatNumberNode{Value: value})
177+	node.Typ = TypeUntypedFloat
178+	return node
179 }
180+func NewString(tok token.Token, value string) *Node { return newNode(tok, String, StringNode{Value: value}) }
181+func NewNil(tok token.Token) *Node                 { return newNode(tok, Nil, NilNode{}) }
182+func NewIdent(tok token.Token, name string) *Node   { return newNode(tok, Ident, IdentNode{Name: name}) }
183 func NewAssign(tok token.Token, op token.Type, lhs, rhs *Node) *Node {
184 	return newNode(tok, Assign, AssignNode{Op: op, Lhs: lhs, Rhs: rhs}, lhs, rhs)
185 }
186@@ -200,6 +211,16 @@ func NewMemberAccess(tok token.Token, expr, member *Node) *Node {
187 func NewTypeCast(tok token.Token, expr *Node, targetType *BxType) *Node {
188 	return newNode(tok, TypeCast, TypeCastNode{Expr: expr, TargetType: targetType}, expr)
189 }
190+func NewStructLiteral(tok token.Token, typeNode *Node, values []*Node, names []*Node) *Node {
191+	node := newNode(tok, StructLiteral, StructLiteralNode{TypeNode: typeNode, Values: values, Names: names}, typeNode)
192+	for _, v := range values {
193+		v.Parent = node
194+	}
195+	for _, n := range names {
196+		if n != nil { n.Parent = node }
197+	}
198+	return node
199+}
200 func NewFuncCall(tok token.Token, funcExpr *Node, args []*Node) *Node {
201 	node := newNode(tok, FuncCall, FuncCallNode{FuncExpr: funcExpr, Args: args}, funcExpr)
202 	for _, arg := range args {
203@@ -238,6 +259,13 @@ func NewMultiVarDecl(tok token.Token, decls []*Node) *Node {
204 func NewTypeDecl(tok token.Token, name string, typ *BxType) *Node {
205 	return newNode(tok, TypeDecl, TypeDeclNode{Name: name, Type: typ})
206 }
207+func NewEnumDecl(tok token.Token, name string, members []*Node) *Node {
208+	node := newNode(tok, EnumDecl, EnumDeclNode{Name: name, Members: members})
209+	for _, m := range members {
210+		m.Parent = node
211+	}
212+	return node
213+}
214 func NewExtrnDecl(tok token.Token, names []*Node) *Node {
215 	node := newNode(tok, ExtrnDecl, ExtrnDeclNode{Names: names})
216 	for _, n := range names {
217@@ -257,9 +285,7 @@ func NewReturn(tok token.Token, expr *Node) *Node {
218 func NewBlock(tok token.Token, stmts []*Node, isSynthetic bool) *Node {
219 	node := newNode(tok, Block, BlockNode{Stmts: stmts, IsSynthetic: isSynthetic})
220 	for _, s := range stmts {
221-		if s != nil {
222-			s.Parent = node
223-		}
224+		if s != nil { s.Parent = node }
225 	}
226 	return node
227 }
228@@ -269,18 +295,18 @@ func NewGoto(tok token.Token, label string) *Node {
229 func NewSwitch(tok token.Token, expr, body *Node) *Node {
230 	return newNode(tok, Switch, SwitchNode{Expr: expr, Body: body}, expr, body)
231 }
232-func NewCase(tok token.Token, value, body *Node) *Node {
233-	return newNode(tok, Case, CaseNode{Value: value, Body: body}, value, body)
234+func NewCase(tok token.Token, values []*Node, body *Node) *Node {
235+	node := newNode(tok, Case, CaseNode{Values: values, Body: body}, body)
236+	for _, v := range values {
237+		v.Parent = node
238+	}
239+	return node
240 }
241 func NewDefault(tok token.Token, body *Node) *Node {
242 	return newNode(tok, Default, DefaultNode{Body: body}, body)
243 }
244-func NewBreak(tok token.Token) *Node {
245-	return newNode(tok, Break, BreakNode{})
246-}
247-func NewContinue(tok token.Token) *Node {
248-	return newNode(tok, Continue, ContinueNode{})
249-}
250+func NewBreak(tok token.Token) *Node    { return newNode(tok, Break, BreakNode{}) }
251+func NewContinue(tok token.Token) *Node { return newNode(tok, Continue, ContinueNode{}) }
252 func NewLabel(tok token.Token, name string, stmt *Node) *Node {
253 	return newNode(tok, Label, LabelNode{Name: name, Stmt: stmt}, stmt)
254 }
255@@ -291,11 +317,8 @@ func NewDirective(tok token.Token, name string) *Node {
256 	return newNode(tok, Directive, DirectiveNode{Name: name})
257 }
258 
259-// FoldConstants performs compile-time constant evaluation on the AST.
260 func FoldConstants(node *Node) *Node {
261-	if node == nil {
262-		return nil
263-	}
264+	if node == nil { return nil }
265 
266 	switch d := node.Data.(type) {
267 	case AssignNode:
268@@ -342,15 +365,14 @@ func FoldConstants(node *Node) *Node {
269 			case token.Lt: if l < r { res = 1 }
270 			case token.Gt: if l > r { res = 1 }
271 			case token.Lte: if l <= r { res = 1 }
272-			case token.Gte: if l >= r {	res = 1	}
273+			case token.Gte: if l >= r { res = 1 }
274 			case token.Slash:
275-				if r == 0 { util.Error(node.Tok, "Compile-time division by zero.") }
276+				if r == 0 { util.Error(node.Tok, "Compile-time division by zero") }
277 				res = l / r
278 			case token.Rem:
279-				if r == 0 { util.Error(node.Tok, "Compile-time modulo by zero.") }
280+				if r == 0 { util.Error(node.Tok, "Compile-time modulo by zero") }
281 				res = l % r
282-			default:
283-				folded = false
284+			default: folded = false
285 			}
286 			if folded { return NewNumber(node.Tok, res) }
287 		}
288@@ -364,8 +386,7 @@ func FoldConstants(node *Node) *Node {
289 			case token.Minus: res = -val
290 			case token.Complement: res = ^val
291 			case token.Not: if val == 0 { res = 1 }
292-			default:
293-				folded = false
294+			default: folded = false
295 			}
296 			if folded { return NewNumber(node.Tok, res) }
297 		}
M pkg/cli/cli.go
+79, -5
  1@@ -210,8 +210,40 @@ func (f *FlagSet) Parse(arguments []string) error {
  2 				return err
  3 			}
  4 		} else {
  5-			if err := f.parseShortFlag(arg, arguments, &i); err != nil {
  6-				return err
  7+			// Check if it's a long option with a single dash, e.g., -std=b or -pedantic
  8+			name := arg[1:]
  9+			if strings.Contains(name, "=") {
 10+				name = strings.SplitN(name, "=", 2)[0]
 11+			}
 12+
 13+			flag, ok := f.flags[name]
 14+			if ok {
 15+				// It's a long flag with a single dash. Parse it.
 16+				parts := strings.SplitN(arg[1:], "=", 2)
 17+				if len(parts) == 2 {
 18+					if err := flag.Value.Set(parts[1]); err != nil {
 19+						return err
 20+					}
 21+				} else {
 22+					if _, isBool := flag.Value.(*boolValue); isBool {
 23+						if err := flag.Value.Set(""); err != nil {
 24+							return err
 25+						}
 26+					} else {
 27+						if i+1 >= len(arguments) {
 28+							return fmt.Errorf("flag needs an argument: -%s", name)
 29+						}
 30+						i++
 31+						if err := flag.Value.Set(arguments[i]); err != nil {
 32+							return err
 33+						}
 34+					}
 35+				}
 36+			} else {
 37+				// Fallback to original short flag parsing
 38+				if err := f.parseShortFlag(arg, arguments, &i); err != nil {
 39+					return err
 40+				}
 41 			}
 42 		}
 43 	}
 44@@ -297,7 +329,7 @@ func (a *App) Run(arguments []string) error {
 45 
 46 	if err := a.FlagSet.Parse(arguments); err != nil {
 47 		fmt.Fprintln(os.Stderr, err)
 48-		a.generateHelpPage(os.Stderr)
 49+		a.generateUsagePage(os.Stderr)
 50 		return err
 51 	}
 52 	if help {
 53@@ -310,6 +342,42 @@ func (a *App) Run(arguments []string) error {
 54 	return nil
 55 }
 56 
 57+func (a *App) generateUsagePage(w *os.File) {
 58+	var sb strings.Builder
 59+	termWidth := getTerminalWidth()
 60+	indent := NewIndentState()
 61+
 62+	// Use [] for mandatory and <> for optional as requested
 63+	fmt.Fprintf(&sb, "Usage: %s <options> [input.b] ...\n", a.Name)
 64+
 65+	optionFlags := a.getOptionFlags()
 66+	if len(optionFlags) > 0 {
 67+		// Calculate max widths for alignment within the options section
 68+		maxFlagWidth := 0
 69+		maxUsageWidth := 0
 70+		for _, flag := range optionFlags {
 71+			flagStrLen := len(a.formatFlagString(flag))
 72+			if flagStrLen > maxFlagWidth {
 73+				maxFlagWidth = flagStrLen
 74+			}
 75+			usageLen := len(flag.Usage)
 76+			if usageLen > maxUsageWidth {
 77+				maxUsageWidth = usageLen
 78+			}
 79+		}
 80+
 81+		sb.WriteString("\n")
 82+		fmt.Fprintf(&sb, "%sOptions\n", indent.AtLevel(1))
 83+		sort.Slice(optionFlags, func(i, j int) bool { return optionFlags[i].Name < optionFlags[j].Name })
 84+		for _, flag := range optionFlags {
 85+			a.formatFlagLine(&sb, flag, indent, termWidth, maxFlagWidth, maxUsageWidth)
 86+		}
 87+	}
 88+
 89+	fmt.Fprintf(&sb, "\nRun '%s --help' for all available options and flags.\n", a.Name)
 90+	fmt.Fprint(w, sb.String())
 91+}
 92+
 93 func (a *App) generateHelpPage(w *os.File) {
 94 	var sb strings.Builder
 95 	termWidth := getTerminalWidth()
 96@@ -344,7 +412,10 @@ func (a *App) generateHelpPage(w *os.File) {
 97 	if a.Synopsis != "" {
 98 		sb.WriteString("\n")
 99 		fmt.Fprintf(&sb, "%sSynopsis\n", indent.AtLevel(1))
100-		fmt.Fprintf(&sb, "%s%s %s\n", indent.AtLevel(2), a.Name, a.Synopsis)
101+		// Use [] for mandatory and <> for optional as requested for the synopsis
102+		synopsis := strings.ReplaceAll(a.Synopsis, "[", "<")
103+		synopsis = strings.ReplaceAll(synopsis, "]", ">")
104+		fmt.Fprintf(&sb, "%s%s %s\n", indent.AtLevel(2), a.Name, synopsis)
105 	}
106 
107 	if a.Description != "" {
108@@ -436,7 +507,10 @@ func (a *App) formatFlagString(flag *Flag) string {
109 	} else {
110 		fmt.Fprintf(&flagStr, "--%s", flag.Name)
111 		if !isBool {
112-			fmt.Fprintf(&flagStr, "<%s>", flag.ExpectedType)
113+			// Use equals for long flags that take a value for clarity
114+			if flag.ExpectedType != "" {
115+				fmt.Fprintf(&flagStr, "=%s", flag.ExpectedType)
116+			}
117 		}
118 	}
119 	return flagStr.String()
M pkg/codegen/backend.go
+1, -3
 1@@ -6,9 +6,7 @@ import (
 2 	"github.com/xplshn/gbc/pkg/ir"
 3 )
 4 
 5-// Backend is the interface that all code generation backends must implement.
 6+// Backend is an interface for code generation backends
 7 type Backend interface {
 8-	// Generate takes an IR program and a configuration, and produces the target
 9-	// assembly or intermediate language as a byte buffer.
10 	Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buffer, error)
11 }
M pkg/codegen/codegen.go
+401, -174
  1@@ -42,55 +42,44 @@ type autoVarInfo struct {
  2 	Size int64
  3 }
  4 
  5-type switchContext struct {
  6-	Node      *ast.SwitchNode
  7-	CaseIndex int
  8-}
  9-
 10-// Context holds the state for the codegen pass
 11 type Context struct {
 12-	prog          *ir.Program
 13-	inlineAsm     string
 14-	tempCount     int
 15-	labelCount    int
 16-	currentScope  *scope
 17-	currentFunc   *ir.Func
 18-	currentBlock  *ir.BasicBlock
 19-	breakLabel    *ir.Label
 20-	continueLabel *ir.Label
 21-	wordSize      int
 22-	stackAlign    int
 23-	isTypedPass   bool
 24-	cfg           *config.Config
 25-	switchStack   []*switchContext
 26+	prog             *ir.Program
 27+	inlineAsm        string
 28+	tempCount        int
 29+	labelCount       int
 30+	currentScope     *scope
 31+	currentFunc      *ir.Func
 32+	currentBlock     *ir.BasicBlock
 33+	breakLabel       *ir.Label
 34+	continueLabel    *ir.Label
 35+	wordSize         int
 36+	stackAlign       int
 37+	isTypedPass      bool
 38+	cfg              *config.Config
 39+	switchCaseLabels map[*ast.Node]*ir.Label
 40 }
 41 
 42-// NewContext creates a new codegen context
 43 func NewContext(cfg *config.Config) *Context {
 44 	return &Context{
 45 		prog: &ir.Program{
 46-			Strings:    make(map[string]string),
 47-			ExtrnFuncs: make([]string, 0),
 48-			ExtrnVars:  make(map[string]bool),
 49-			WordSize:   cfg.WordSize,
 50+			Strings:       make(map[string]string),
 51+			ExtrnFuncs:    make([]string, 0),
 52+			ExtrnVars:     make(map[string]bool),
 53+			WordSize:      cfg.WordSize,
 54+			GlobalSymbols: make(map[string]*ast.Node),
 55 		},
 56-		currentScope: newScope(nil),
 57-		wordSize:     cfg.WordSize,
 58-		stackAlign:   cfg.StackAlignment,
 59-		isTypedPass:  cfg.IsFeatureEnabled(config.FeatTyped),
 60-		cfg:          cfg,
 61-		switchStack:  make([]*switchContext, 0),
 62+		currentScope:     newScope(nil),
 63+		wordSize:         cfg.WordSize,
 64+		stackAlign:       cfg.StackAlignment,
 65+		isTypedPass:      cfg.IsFeatureEnabled(config.FeatTyped),
 66+		cfg:              cfg,
 67+		switchCaseLabels: make(map[*ast.Node]*ir.Label),
 68 	}
 69 }
 70 
 71-func newScope(parent *scope) *scope {
 72-	return &scope{Parent: parent}
 73-}
 74-
 75-func (ctx *Context) enterScope() {
 76-	ctx.currentScope = newScope(ctx.currentScope)
 77-}
 78+func newScope(parent *scope) *scope { return &scope{Parent: parent} }
 79 
 80+func (ctx *Context) enterScope() { ctx.currentScope = newScope(ctx.currentScope) }
 81 func (ctx *Context) exitScope() {
 82 	if ctx.currentScope.Parent != nil {
 83 		ctx.currentScope = ctx.currentScope.Parent
 84@@ -100,7 +89,18 @@ func (ctx *Context) exitScope() {
 85 func (ctx *Context) findSymbol(name string) *symbol {
 86 	for s := ctx.currentScope; s != nil; s = s.Parent {
 87 		for sym := s.Symbols; sym != nil; sym = sym.Next {
 88-			if sym.Name == name {
 89+			if sym.Name == name && sym.Type != symType {
 90+				return sym
 91+			}
 92+		}
 93+	}
 94+	return nil
 95+}
 96+
 97+func (ctx *Context) findTypeSymbol(name string) *symbol {
 98+	for s := ctx.currentScope; s != nil; s = s.Parent {
 99+		for sym := s.Symbols; sym != nil; sym = sym.Next {
100+			if sym.Name == name && sym.Type == symType {
101 				return sym
102 			}
103 		}
104@@ -121,28 +121,23 @@ func (ctx *Context) addSymbol(name string, symType symbolType, bxType *ast.BxTyp
105 	var irVal ir.Value
106 	switch symType {
107 	case symVar:
108-		if ctx.currentScope.Parent == nil { // Global
109+		if ctx.currentScope.Parent == nil {
110 			irVal = &ir.Global{Name: name}
111-		} else { // Local
112-			irVal = &ir.Temporary{Name: name, ID: ctx.tempCount}
113-			ctx.tempCount++
114+		} else {
115+			irVal = ctx.newTemp()
116+			if t, ok := irVal.(*ir.Temporary); ok {
117+				t.Name = name
118+			}
119 		}
120 	case symFunc, symExtrn:
121 		irVal = &ir.Global{Name: name}
122 	case symLabel:
123 		irVal = &ir.Label{Name: name}
124-	case symType:
125-		// Types don't have a direct IR value in this model
126 	}
127 
128 	sym := &symbol{
129-		Name:     name,
130-		Type:     symType,
131-		BxType:   bxType,
132-		IRVal:    irVal,
133-		IsVector: isVector,
134-		Next:     ctx.currentScope.Symbols,
135-		Node:     node,
136+		Name: name, Type: symType, BxType: bxType, IRVal: irVal,
137+		IsVector: isVector, Next: ctx.currentScope.Symbols, Node: node,
138 	}
139 	ctx.currentScope.Symbols = sym
140 	return sym
141@@ -182,7 +177,6 @@ func (ctx *Context) addString(value string) ir.Value {
142 	return &ir.Global{Name: label}
143 }
144 
145-// evalConstExpr evaluates a compile-time constant expression node to an integer
146 func (ctx *Context) evalConstExpr(node *ast.Node) (int64, bool) {
147 	if node == nil {
148 		return 0, false
149@@ -197,7 +191,6 @@ func (ctx *Context) evalConstExpr(node *ast.Node) (int64, bool) {
150 		if sym != nil && sym.Node != nil && sym.Node.Type == ast.VarDecl {
151 			decl := sym.Node.Data.(ast.VarDeclNode)
152 			if len(decl.InitList) == 1 {
153-				// Prevent infinite recursion on `auto x = x;`
154 				if decl.InitList[0] == node {
155 					return 0, false
156 				}
157@@ -224,11 +217,11 @@ func (ctx *Context) getSizeof(typ *ast.BxType) int64 {
158 			if val, ok := ctx.evalConstExpr(typ.ArraySize); ok {
159 				arrayLen = val
160 			} else {
161-				util.Error(typ.ArraySize.Tok, "Array size must be a constant expression.")
162+				util.Error(typ.ArraySize.Tok, "Array size must be a constant expression")
163 			}
164 		}
165 		return elemSize * arrayLen
166-	case ast.TYPE_PRIMITIVE:
167+	case ast.TYPE_PRIMITIVE, ast.TYPE_UNTYPED_INT:
168 		switch typ.Name {
169 		case "int", "uint", "string":
170 			return int64(ctx.wordSize)
171@@ -241,12 +234,14 @@ func (ctx *Context) getSizeof(typ *ast.BxType) int64 {
172 		case "byte", "bool", "int8", "uint8":
173 			return 1
174 		default:
175-			if sym := ctx.findSymbol(typ.Name); sym != nil {
176+			if sym := ctx.findTypeSymbol(typ.Name); sym != nil {
177 				return ctx.getSizeof(sym.BxType)
178 			}
179 			return int64(ctx.wordSize)
180 		}
181-	case ast.TYPE_FLOAT:
182+	case ast.TYPE_ENUM:
183+		return ctx.getSizeof(ast.TypeInt)
184+	case ast.TYPE_FLOAT, ast.TYPE_UNTYPED_FLOAT:
185 		switch typ.Name {
186 		case "float", "float32":
187 			return 4
188@@ -256,16 +251,62 @@ func (ctx *Context) getSizeof(typ *ast.BxType) int64 {
189 			return 4
190 		}
191 	case ast.TYPE_STRUCT:
192-		var totalSize int64
193+		var totalSize, maxAlign int64 = 0, 1
194 		for _, field := range typ.Fields {
195-			totalSize += ctx.getSizeof(field.Data.(ast.VarDeclNode).Type)
196+			fieldData := field.Data.(ast.VarDeclNode)
197+			fieldAlign := ctx.getAlignof(fieldData.Type)
198+			if fieldAlign > maxAlign {
199+				maxAlign = fieldAlign
200+			}
201+			totalSize = util.AlignUp(totalSize, fieldAlign)
202+			totalSize += ctx.getSizeof(fieldData.Type)
203 		}
204-		return totalSize
205+		if maxAlign == 0 {
206+			maxAlign = 1
207+		}
208+		return util.AlignUp(totalSize, maxAlign)
209+	}
210+	return int64(ctx.wordSize)
211+}
212+
213+func (ctx *Context) getAlignof(typ *ast.BxType) int64 {
214+	if typ == nil {
215+		return int64(ctx.wordSize)
216+	}
217+
218+	if (typ.Kind == ast.TYPE_PRIMITIVE || typ.Kind == ast.TYPE_STRUCT) && typ.Name != "" {
219+		if sym := ctx.findTypeSymbol(typ.Name); sym != nil {
220+			if sym.BxType != typ {
221+				return ctx.getAlignof(sym.BxType)
222+			}
223+		}
224+	}
225+
226+	if typ.Kind == ast.TYPE_UNTYPED {
227+		return int64(ctx.wordSize)
228+	}
229+	switch typ.Kind {
230+	case ast.TYPE_VOID:
231+		return 1
232+	case ast.TYPE_POINTER:
233+		return int64(ctx.wordSize)
234+	case ast.TYPE_ARRAY:
235+		return ctx.getAlignof(typ.Base)
236+	case ast.TYPE_PRIMITIVE, ast.TYPE_FLOAT, ast.TYPE_ENUM, ast.TYPE_UNTYPED_INT, ast.TYPE_UNTYPED_FLOAT:
237+		return ctx.getSizeof(typ)
238+	case ast.TYPE_STRUCT:
239+		var maxAlign int64 = 1
240+		for _, field := range typ.Fields {
241+			fieldAlign := ctx.getAlignof(field.Data.(ast.VarDeclNode).Type)
242+			if fieldAlign > maxAlign {
243+				maxAlign = fieldAlign
244+			}
245+		}
246+		return maxAlign
247 	}
248 	return int64(ctx.wordSize)
249 }
250 
251-// GenerateIR translates the entire AST into an IR program
252 func (ctx *Context) GenerateIR(root *ast.Node) (*ir.Program, string) {
253 	ctx.collectGlobals(root)
254 	ctx.collectStrings(root)
255@@ -278,7 +319,6 @@ func (ctx *Context) GenerateIR(root *ast.Node) (*ir.Program, string) {
256 	return ctx.prog, ctx.inlineAsm
257 }
258 
259-// walkAST provides a generic way to traverse the AST
260 func walkAST(node *ast.Node, visitor func(n *ast.Node)) {
261 	if node == nil {
262 		return
263@@ -340,7 +380,9 @@ func walkAST(node *ast.Node, visitor func(n *ast.Node)) {
264 		walkAST(d.Expr, visitor)
265 		walkAST(d.Body, visitor)
266 	case ast.CaseNode:
267-		walkAST(d.Value, visitor)
268+		for _, v := range d.Values {
269+			walkAST(v, visitor)
270+		}
271 		walkAST(d.Body, visitor)
272 	case ast.DefaultNode:
273 		walkAST(d.Body, visitor)
274@@ -350,9 +392,7 @@ func walkAST(node *ast.Node, visitor func(n *ast.Node)) {
275 }
276 
277 func (ctx *Context) collectGlobals(node *ast.Node) {
278-	if node == nil {
279-		return
280-	}
281+	if node == nil { return }
282 
283 	switch node.Type {
284 	case ast.Block:
285@@ -366,16 +406,11 @@ func (ctx *Context) collectGlobals(node *ast.Node) {
286 			if existingSym == nil {
287 				ctx.addSymbol(d.Name, symVar, d.Type, d.IsVector, node)
288 			} else if existingSym.Type == symFunc || existingSym.Type == symExtrn {
289-				util.Warn(ctx.cfg, config.WarnExtra, node.Tok, "Definition of '%s' overrides previous external declaration.", d.Name)
290-				existingSym.Type = symVar
291-				existingSym.IsVector = d.IsVector
292-				existingSym.BxType = d.Type
293-				existingSym.Node = node
294+				util.Warn(ctx.cfg, config.WarnExtra, node.Tok, "Definition of '%s' overrides previous external declaration", d.Name)
295+				existingSym.Type, existingSym.IsVector, existingSym.BxType, existingSym.Node = symVar, d.IsVector, d.Type, node
296 			} else if existingSym.Type == symVar {
297-				util.Warn(ctx.cfg, config.WarnExtra, node.Tok, "Redefinition of variable '%s'.", d.Name)
298-				existingSym.IsVector = d.IsVector
299-				existingSym.BxType = d.Type
300-				existingSym.Node = node
301+				util.Warn(ctx.cfg, config.WarnExtra, node.Tok, "Redefinition of variable '%s'", d.Name)
302+				existingSym.IsVector, existingSym.BxType, existingSym.Node = d.IsVector, d.Type, node
303 			}
304 		}
305 	case ast.MultiVarDecl:
306@@ -386,15 +421,13 @@ func (ctx *Context) collectGlobals(node *ast.Node) {
307 		}
308 	case ast.FuncDecl:
309 		d := node.Data.(ast.FuncDeclNode)
310+		ctx.prog.GlobalSymbols[d.Name] = node
311 		existingSym := ctx.findSymbolInCurrentScope(d.Name)
312 		if existingSym == nil {
313 			ctx.addSymbol(d.Name, symFunc, d.ReturnType, false, node)
314 		} else if existingSym.Type != symFunc {
315-			util.Warn(ctx.cfg, config.WarnExtra, node.Tok, "Redefinition of '%s' as a function.", d.Name)
316-			existingSym.Type = symFunc
317-			existingSym.IsVector = false
318-			existingSym.BxType = d.ReturnType
319-			existingSym.Node = node
320+			util.Warn(ctx.cfg, config.WarnExtra, node.Tok, "Redefinition of '%s' as a function", d.Name)
321+			existingSym.Type, existingSym.IsVector, existingSym.BxType, existingSym.Node = symFunc, false, d.ReturnType, node
322 		}
323 	case ast.ExtrnDecl:
324 		d := node.Data.(ast.ExtrnDeclNode)
325@@ -419,6 +452,15 @@ func (ctx *Context) collectGlobals(node *ast.Node) {
326 		if ctx.findSymbolInCurrentScope(d.Name) == nil {
327 			ctx.addSymbol(d.Name, symType, d.Type, false, node)
328 		}
329+	case ast.EnumDecl:
330+		d := node.Data.(ast.EnumDeclNode)
331+		if ctx.findSymbolInCurrentScope(d.Name) == nil {
332+			enumType := &ast.BxType{Kind: ast.TYPE_ENUM, Name: d.Name, EnumMembers: d.Members, Base: ast.TypeInt}
333+			ctx.addSymbol(d.Name, symType, enumType, false, node)
334+		}
335+		for _, memberNode := range d.Members {
336+			ctx.collectGlobals(memberNode)
337+		}
338 	}
339 }
340 
341@@ -426,28 +468,21 @@ func (ctx *Context) findByteArrays(root *ast.Node) {
342 	for {
343 		changedInPass := false
344 		visitor := func(n *ast.Node) {
345-			if n == nil {
346-				return
347-			}
348+			if n == nil { return }
349 			switch n.Type {
350 			case ast.VarDecl:
351 				d := n.Data.(ast.VarDeclNode)
352 				if d.IsVector && len(d.InitList) == 1 && d.InitList[0].Type == ast.String {
353-					sym := ctx.findSymbol(d.Name)
354-					if sym != nil && !sym.IsByteArray {
355+					if sym := ctx.findSymbol(d.Name); sym != nil && !sym.IsByteArray {
356 						sym.IsByteArray = true
357 						changedInPass = true
358 					}
359 				}
360 			case ast.Assign:
361 				d := n.Data.(ast.AssignNode)
362-				if d.Lhs.Type != ast.Ident {
363-					return
364-				}
365+				if d.Lhs.Type != ast.Ident { return }
366 				lhsSym := ctx.findSymbol(d.Lhs.Data.(ast.IdentNode).Name)
367-				if lhsSym == nil || lhsSym.IsByteArray {
368-					return
369-				}
370+				if lhsSym == nil || lhsSym.IsByteArray { return }
371 				rhsIsByteArray := false
372 				switch d.Rhs.Type {
373 				case ast.String:
374@@ -464,9 +499,7 @@ func (ctx *Context) findByteArrays(root *ast.Node) {
375 			}
376 		}
377 		walkAST(root, visitor)
378-		if !changedInPass {
379-			break
380-		}
381+		if !changedInPass { break }
382 	}
383 }
384 
385@@ -490,6 +523,75 @@ func (ctx *Context) genStore(addr, value ir.Value, typ *ast.BxType) {
386 	ctx.addInstr(&ir.Instruction{Op: ir.OpStore, Typ: storeType, Args: []ir.Value{value, addr}})
387 }
388 
389+func (ctx *Context) codegenMemberAccessAddr(node *ast.Node) ir.Value {
390+	d := node.Data.(ast.MemberAccessNode)
391+	structType := d.Expr.Typ
392+
393+	if structType == nil {
394+		if d.Expr.Type == ast.Ident {
395+			if sym := ctx.findSymbol(d.Expr.Data.(ast.IdentNode).Name); sym != nil {
396+				structType = sym.BxType
397+			}
398+		}
399+	}
400+
401+	if structType == nil {
402+		util.Error(node.Tok, "internal: cannot determine type of struct for member access")
403+		return nil
404+	}
405+
406+	var structAddr ir.Value
407+	if structType.Kind == ast.TYPE_POINTER {
408+		structAddr, _ = ctx.codegenExpr(d.Expr)
409+	} else {
410+		structAddr = ctx.codegenLvalue(d.Expr)
411+	}
412+
413+	baseType := structType
414+	if baseType.Kind == ast.TYPE_POINTER { baseType = baseType.Base }
415+
416+	if baseType.Kind != ast.TYPE_STRUCT && baseType.Name != "" {
417+		if sym := ctx.findTypeSymbol(baseType.Name); sym != nil && sym.BxType.Kind == ast.TYPE_STRUCT {
418+			baseType = sym.BxType
419+		}
420+	}
421+
422+	if baseType.Kind != ast.TYPE_STRUCT {
423+		util.Error(node.Tok, "internal: member access on non-struct type '%s'", baseType.Name)
424+		return nil
425+	}
426+
427+	var offset int64
428+	found := false
429+	memberName := d.Member.Data.(ast.IdentNode).Name
430+	for _, fieldNode := range baseType.Fields {
431+		fieldData := fieldNode.Data.(ast.VarDeclNode)
432+		fieldAlign := ctx.getAlignof(fieldData.Type)
433+		offset = util.AlignUp(offset, fieldAlign)
434+		if fieldData.Name == memberName {
435+			found = true
436+			break
437+		}
438+		offset += ctx.getSizeof(fieldData.Type)
439+	}
440+
441+	if !found {
442+		util.Error(node.Tok, "internal: could not find member '%s' during codegen", memberName)
443+		return nil
444+	}
445+
446+	if offset == 0 { return structAddr }
447+
448+	resultAddr := ctx.newTemp()
449+	ctx.addInstr(&ir.Instruction{
450+		Op:     ir.OpAdd,
451+		Typ:    ir.GetType(nil, ctx.wordSize),
452+		Result: resultAddr,
453+		Args:   []ir.Value{structAddr, &ir.Const{Value: offset}},
454+	})
455+	return resultAddr
456+}
457+
458 func (ctx *Context) codegenLvalue(node *ast.Node) ir.Value {
459 	if node == nil {
460 		util.Error(token.Token{}, "Internal error: null l-value node in codegen")
461@@ -503,34 +605,26 @@ func (ctx *Context) codegenLvalue(node *ast.Node) ir.Value {
462 			util.Warn(ctx.cfg, config.WarnImplicitDecl, node.Tok, "Implicit declaration of variable '%s'", name)
463 			sym = ctx.addSymbol(name, symVar, ast.TypeUntyped, false, node)
464 		}
465-
466 		if sym.Type == symFunc {
467-			util.Error(node.Tok, "Cannot assign to function '%s'.", name)
468+			util.Error(node.Tok, "Cannot assign to function '%s'", name)
469 			return nil
470 		}
471-		if sym.BxType != nil && sym.BxType.Kind == ast.TYPE_ARRAY {
472-			return sym.IRVal
473-		}
474-		if sym.IsVector && sym.Node != nil && sym.Node.Type == ast.VarDecl {
475-			d := sym.Node.Data.(ast.VarDeclNode)
476-			if !d.IsBracketed && len(d.InitList) <= 1 && d.Type == nil {
477-				util.Error(node.Tok, "Cannot assign to '%s', it is a constant.", name)
478-				return nil
479-			}
480-		}
481 		return sym.IRVal
482-
483 	case ast.Indirection:
484 		res, _ := ctx.codegenExpr(node.Data.(ast.IndirectionNode).Expr)
485 		return res
486-
487 	case ast.Subscript:
488 		return ctx.codegenSubscriptAddr(node)
489-
490-	default:
491-		util.Error(node.Tok, "Expression is not a valid l-value.")
492-		return nil
493+	case ast.MemberAccess:
494+		return ctx.codegenMemberAccessAddr(node)
495+	case ast.FuncCall:
496+		if node.Typ != nil && node.Typ.Kind == ast.TYPE_STRUCT {
497+			res, _ := ctx.codegenExpr(node)
498+			return res
499+		}
500 	}
501+	util.Error(node.Tok, "Expression is not a valid l-value")
502+	return nil
503 }
504 
505 func (ctx *Context) codegenLogicalCond(node *ast.Node, trueL, falseL *ir.Label) {
506@@ -554,19 +648,22 @@ func (ctx *Context) codegenLogicalCond(node *ast.Node, trueL, falseL *ir.Label)
507 
508 	condVal, _ := ctx.codegenExpr(node)
509 	ctx.addInstr(&ir.Instruction{Op: ir.OpJnz, Args: []ir.Value{condVal, trueL, falseL}})
510-	ctx.currentBlock = nil // This block is terminated
511+	ctx.currentBlock = nil
512 }
513 
514 func (ctx *Context) codegenExpr(node *ast.Node) (result ir.Value, terminates bool) {
515-	if node == nil {
516-		return &ir.Const{Value: 0}, false
517-	}
518+	if node == nil { return &ir.Const{Value: 0}, false }
519 
520 	switch node.Type {
521 	case ast.Number:
522 		return &ir.Const{Value: node.Data.(ast.NumberNode).Value}, false
523+	case ast.FloatNumber:
524+		typ := ir.GetType(node.Typ, ctx.wordSize)
525+		return &ir.FloatConst{Value: node.Data.(ast.FloatNumberNode).Value, Typ: typ}, false
526 	case ast.String:
527 		return ctx.addString(node.Data.(ast.StringNode).Value), false
528+	case ast.Nil:
529+		return &ir.Const{Value: 0}, false
530 	case ast.Ident:
531 		return ctx.codegenIdent(node)
532 	case ast.Assign:
533@@ -586,31 +683,35 @@ func (ctx *Context) codegenExpr(node *ast.Node) (result ir.Value, terminates boo
534 		return ctx.codegenAddressOf(node)
535 	case ast.FuncCall:
536 		return ctx.codegenFuncCall(node)
537+	case ast.TypeCast:
538+		return ctx.codegenTypeCast(node)
539 	case ast.Ternary:
540 		return ctx.codegenTernary(node)
541 	case ast.AutoAlloc:
542 		return ctx.codegenAutoAlloc(node)
543+	case ast.StructLiteral:
544+		return ctx.codegenStructLiteral(node)
545+	case ast.MemberAccess:
546+		addr := ctx.codegenMemberAccessAddr(node)
547+		if addr == nil { return nil, true }
548+		return ctx.genLoad(addr, node.Typ), false
549 	}
550 	util.Error(node.Tok, "Internal error: unhandled expression type in codegen: %v", node.Type)
551 	return nil, true
552 }
553 
554 func (ctx *Context) codegenStmt(node *ast.Node) (terminates bool) {
555-	if node == nil {
556-		return false
557-	}
558+	if node == nil { return false }
559 	switch node.Type {
560 	case ast.Block:
561 		isRealBlock := !node.Data.(ast.BlockNode).IsSynthetic
562-		if isRealBlock {
563-			ctx.enterScope()
564-		}
565+		if isRealBlock { ctx.enterScope() }
566 		var blockTerminates bool
567 		for _, stmt := range node.Data.(ast.BlockNode).Stmts {
568 			if blockTerminates {
569 				isLabel := stmt.Type == ast.Label || stmt.Type == ast.Case || stmt.Type == ast.Default
570 				if !isLabel {
571-					util.Warn(ctx.cfg, config.WarnUnreachableCode, stmt.Tok, "Unreachable code.")
572+					util.Warn(ctx.cfg, config.WarnUnreachableCode, stmt.Tok, "Unreachable code")
573 					continue
574 				}
575 				blockTerminates = false
576@@ -618,9 +719,7 @@ func (ctx *Context) codegenStmt(node *ast.Node) (terminates bool) {
577 			}
578 			blockTerminates = ctx.codegenStmt(stmt)
579 		}
580-		if isRealBlock {
581-			ctx.exitScope()
582-		}
583+		if isRealBlock { ctx.exitScope() }
584 		return blockTerminates
585 
586 	case ast.FuncDecl:
587@@ -634,7 +733,7 @@ func (ctx *Context) codegenStmt(node *ast.Node) (terminates bool) {
588 			ctx.codegenVarDecl(decl)
589 		}
590 		return false
591-	case ast.TypeDecl, ast.Directive:
592+	case ast.TypeDecl, ast.Directive, ast.EnumDecl:
593 		return false
594 	case ast.ExtrnDecl:
595 		d := node.Data.(ast.ExtrnDeclNode)
596@@ -669,35 +768,140 @@ func (ctx *Context) codegenStmt(node *ast.Node) (terminates bool) {
597 		return true
598 
599 	case ast.Break:
600-		if ctx.breakLabel == nil {
601-			util.Error(node.Tok, "'break' not in a loop or switch.")
602-		}
603+		if ctx.breakLabel == nil { util.Error(node.Tok, "'break' not in a loop or switch") }
604 		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{ctx.breakLabel}})
605 		ctx.currentBlock = nil
606 		return true
607 
608 	case ast.Continue:
609-		if ctx.continueLabel == nil {
610-			util.Error(node.Tok, "'continue' not in a loop.")
611-		}
612+		if ctx.continueLabel == nil { util.Error(node.Tok, "'continue' not in a loop") }
613 		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{ctx.continueLabel}})
614 		ctx.currentBlock = nil
615 		return true
616 
617-	case ast.Case, ast.Default:
618-		return ctx.codegenCaseOrDefault(node)
619+	case ast.Case:
620+		if label, ok := ctx.switchCaseLabels[node]; ok {
621+			if ctx.currentBlock != nil {
622+				ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{label}})
623+			}
624+			ctx.startBlock(label)
625+			return ctx.codegenStmt(node.Data.(ast.CaseNode).Body)
626+		}
627+		util.Error(node.Tok, "'case' statement not properly nested in a switch context")
628+		return false
629+
630+	case ast.Default:
631+		if label, ok := ctx.switchCaseLabels[node]; ok {
632+			if ctx.currentBlock != nil {
633+				ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{label}})
634+			}
635+			ctx.startBlock(label)
636+			return ctx.codegenStmt(node.Data.(ast.DefaultNode).Body)
637+		}
638+		util.Error(node.Tok, "'default' statement not properly nested in a switch context")
639+		return false
640 
641 	default:
642-		// Any other node type is treated as an expression statement
643 		_, terminates := ctx.codegenExpr(node)
644 		return terminates
645 	}
646 }
647 
648-func (ctx *Context) findAllAutosInFunc(node *ast.Node, autoVars *[]autoVarInfo, definedNames map[string]bool) {
649-	if node == nil {
650-		return
651+func (ctx *Context) codegenSwitch(node *ast.Node) bool {
652+	d := node.Data.(ast.SwitchNode)
653+	switchVal, _ := ctx.codegenExpr(d.Expr)
654+	endLabel := ctx.newLabel()
655+	var defaultTarget *ir.Label
656+
657+	oldBreak := ctx.breakLabel
658+	ctx.breakLabel = endLabel
659+	defer func() { ctx.breakLabel = oldBreak }()
660+
661+	caseLabels := make(map[*ast.Node]*ir.Label)
662+	var caseOrder []*ast.Node
663+	var findCasesRecursive func(*ast.Node)
664+	findCasesRecursive = func(n *ast.Node) {
665+		if n == nil || (n.Type == ast.Switch && n != node) { return }
666+		if n.Type == ast.Case || n.Type == ast.Default {
667+			if _, exists := caseLabels[n]; !exists {
668+				label := ctx.newLabel()
669+				caseLabels[n] = label
670+				caseOrder = append(caseOrder, n)
671+				if n.Type == ast.Default {
672+					if defaultTarget != nil { util.Error(n.Tok, "multiple default labels in switch") }
673+					defaultTarget = label
674+				}
675+			}
676+		}
677+		switch data := n.Data.(type) {
678+		case ast.BlockNode:
679+			for _, stmt := range data.Stmts {
680+				findCasesRecursive(stmt)
681+			}
682+		case ast.IfNode:
683+			findCasesRecursive(data.ThenBody)
684+			findCasesRecursive(data.ElseBody)
685+		case ast.WhileNode:
686+			findCasesRecursive(data.Body)
687+		case ast.LabelNode:
688+			findCasesRecursive(data.Stmt)
689+		case ast.CaseNode:
690+			findCasesRecursive(data.Body)
691+		case ast.DefaultNode:
692+			findCasesRecursive(data.Body)
693+		}
694+	}
695+	findCasesRecursive(d.Body)
696+
697+	if defaultTarget == nil { defaultTarget = endLabel }
698+
699+	for _, caseStmt := range caseOrder {
700+		if caseStmt.Type == ast.Case {
701+			caseData := caseStmt.Data.(ast.CaseNode)
702+			bodyLabel := caseLabels[caseStmt]
703+			nextCaseCheck := ctx.newLabel()
704+
705+			var finalCond ir.Value
706+			for i, valueExpr := range caseData.Values {
707+				caseVal, _ := ctx.codegenExpr(valueExpr)
708+				cmpRes := ctx.newTemp()
709+				ctx.addInstr(&ir.Instruction{Op: ir.OpCEq, Typ: ir.GetType(nil, ctx.wordSize), OperandType: ir.GetType(d.Expr.Typ, ctx.wordSize), Result: cmpRes, Args: []ir.Value{switchVal, caseVal}})
710+				if i == 0 {
711+					finalCond = cmpRes
712+				} else {
713+					newFinalCond := ctx.newTemp()
714+					ctx.addInstr(&ir.Instruction{Op: ir.OpOr, Typ: ir.GetType(nil, ctx.wordSize), Result: newFinalCond, Args: []ir.Value{finalCond, cmpRes}})
715+					finalCond = newFinalCond
716+				}
717+			}
718+
719+			if finalCond != nil {
720+				ctx.addInstr(&ir.Instruction{Op: ir.OpJnz, Args: []ir.Value{finalCond, bodyLabel, nextCaseCheck}})
721+			} else {
722+				ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{nextCaseCheck}})
723+			}
724+			ctx.startBlock(nextCaseCheck)
725+		}
726+	}
727+	ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{defaultTarget}})
728+	ctx.currentBlock = nil
729+
730+	oldCaseLabels := ctx.switchCaseLabels
731+	ctx.switchCaseLabels = caseLabels
732+	defer func() { ctx.switchCaseLabels = oldCaseLabels }()
733+
734+	terminates := ctx.codegenStmt(d.Body)
735+
736+	if ctx.currentBlock != nil && !terminates {
737+		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endLabel}})
738 	}
739+
740+	ctx.startBlock(endLabel)
741+	return false
742+}
743+
744+func (ctx *Context) findAllAutosInFunc(node *ast.Node, autoVars *[]autoVarInfo, definedNames map[string]bool) {
745+	if node == nil { return }
746 	if node.Type == ast.VarDecl {
747 		varData := node.Data.(ast.VarDeclNode)
748 		if !definedNames[varData.Name] {
749@@ -711,7 +915,7 @@ func (ctx *Context) findAllAutosInFunc(node *ast.Node, autoVars *[]autoVarInfo,
750 					if varData.SizeExpr != nil {
751 						folded := ast.FoldConstants(varData.SizeExpr)
752 						if folded.Type != ast.Number {
753-							util.Error(node.Tok, "Local vector size must be a constant expression.")
754+							util.Error(node.Tok, "Local vector size must be a constant expression")
755 						}
756 						dataSizeInWords = folded.Data.(ast.NumberNode).Value
757 					} else if len(varData.InitList) == 1 && varData.InitList[0].Type == ast.String {
758@@ -721,7 +925,6 @@ func (ctx *Context) findAllAutosInFunc(node *ast.Node, autoVars *[]autoVarInfo,
759 					} else {
760 						dataSizeInWords = int64(len(varData.InitList))
761 					}
762-					// Dope vector: 1 word for the pointer to data
763 					size = int64(ctx.wordSize) + dataSizeInWords*int64(ctx.wordSize)
764 				} else {
765 					size = int64(ctx.wordSize)
766@@ -763,14 +966,12 @@ func (ctx *Context) codegenFuncDecl(node *ast.Node) {
767 		ctx.inlineAsm += fmt.Sprintf(".globl %s\n%s:\n\t%s\n", d.Name, d.Name, asmCode)
768 		return
769 	}
770-	if d.Body == nil {
771-		return
772-	}
773+	if d.Body == nil { return }
774 
775+	irReturnType := ir.GetType(d.ReturnType, ctx.wordSize)
776 	fn := &ir.Func{
777-		Name:       d.Name,
778-		ReturnType: ir.GetType(d.ReturnType, ctx.wordSize),
779-		HasVarargs: d.HasVarargs,
780+		Name: d.Name, ReturnType: irReturnType, AstReturnType: d.ReturnType,
781+		HasVarargs: d.HasVarargs, AstParams: d.Params, Node: node,
782 	}
783 	ctx.prog.Funcs = append(ctx.prog.Funcs, fn)
784 
785@@ -801,7 +1002,6 @@ func (ctx *Context) codegenFuncDecl(node *ast.Node) {
786 		})
787 	}
788 
789-	// Determine stack layout
790 	var paramInfos []autoVarInfo
791 	for _, p := range d.Params {
792 		paramInfos = append(paramInfos, autoVarInfo{Node: p, Size: int64(ctx.wordSize)})
793@@ -855,7 +1055,7 @@ func (ctx *Context) codegenFuncDecl(node *ast.Node) {
794 		var name string
795 		var typ *ast.BxType
796 		var isVec bool
797-		if local.Node.Type == ast.Ident { // Untyped param
798+		if local.Node.Type == ast.Ident {
799 			name = local.Node.Data.(ast.IdentNode).Name
800 			if d.Name == "main" && isParam {
801 				originalIndex := -1
802@@ -865,11 +1065,9 @@ func (ctx *Context) codegenFuncDecl(node *ast.Node) {
803 						break
804 					}
805 				}
806-				if originalIndex == 1 {
807-					isVec = true
808-				}
809+				if originalIndex == 1 { isVec = true }
810 			}
811-		} else { // Typed param or auto var
812+		} else {
813 			varData := local.Node.Data.(ast.VarDeclNode)
814 			name, typ, isVec = varData.Name, varData.Type, varData.IsVector
815 		}
816@@ -899,7 +1097,7 @@ func (ctx *Context) codegenFuncDecl(node *ast.Node) {
817 				paramVal := fn.Params[origParamIndex].Val
818 				ctx.genStore(sym.IRVal, paramVal, typ)
819 			}
820-		} else { // Is an auto var
821+		} else {
822 			if isVec && (typ == nil || typ.Kind == ast.TYPE_UNTYPED) {
823 				storageAddr := ctx.newTemp()
824 				ctx.addInstr(&ir.Instruction{
825@@ -930,31 +1128,36 @@ func (ctx *Context) codegenGlobalConst(node *ast.Node) ir.Value {
826 	switch folded.Type {
827 	case ast.Number:
828 		return &ir.Const{Value: folded.Data.(ast.NumberNode).Value}
829+	case ast.FloatNumber:
830+		typ := ir.GetType(folded.Typ, ctx.wordSize)
831+		return &ir.FloatConst{Value: folded.Data.(ast.FloatNumberNode).Value, Typ: typ}
832 	case ast.String:
833 		return ctx.addString(folded.Data.(ast.StringNode).Value)
834+	case ast.Nil:
835+		return &ir.Const{Value: 0}
836 	case ast.Ident:
837 		name := folded.Data.(ast.IdentNode).Name
838 		sym := ctx.findSymbol(name)
839 		if sym == nil {
840-			util.Error(node.Tok, "Undefined symbol '%s' in global initializer.", name)
841+			util.Error(node.Tok, "Undefined symbol '%s' in global initializer", name)
842 			return nil
843 		}
844 		return sym.IRVal
845 	case ast.AddressOf:
846 		lval := folded.Data.(ast.AddressOfNode).LValue
847 		if lval.Type != ast.Ident {
848-			util.Error(lval.Tok, "Global initializer must be the address of a global symbol.")
849+			util.Error(lval.Tok, "Global initializer must be the address of a global symbol")
850 			return nil
851 		}
852 		name := lval.Data.(ast.IdentNode).Name
853 		sym := ctx.findSymbol(name)
854 		if sym == nil {
855-			util.Error(lval.Tok, "Undefined symbol '%s' in global initializer.", name)
856+			util.Error(lval.Tok, "Undefined symbol '%s' in global initializer", name)
857 			return nil
858 		}
859 		return sym.IRVal
860 	default:
861-		util.Error(node.Tok, "Global initializer must be a constant expression.")
862+		util.Error(node.Tok, "Global initializer must be a constant expression")
863 		return nil
864 	}
865 }
866@@ -966,7 +1169,7 @@ func (ctx *Context) codegenVarDecl(node *ast.Node) {
867 		if ctx.currentFunc == nil {
868 			sym = ctx.addSymbol(d.Name, symVar, d.Type, d.IsVector, node)
869 		} else {
870-			util.Error(node.Tok, "Internal error: symbol '%s' not found during declaration.", d.Name)
871+			util.Error(node.Tok, "Internal error: symbol '%s' not found during declaration", d.Name)
872 			return
873 		}
874 	}
875@@ -979,9 +1182,7 @@ func (ctx *Context) codegenVarDecl(node *ast.Node) {
876 }
877 
878 func (ctx *Context) codegenLocalVarDecl(d ast.VarDeclNode, sym *symbol) {
879-	if len(d.InitList) == 0 {
880-		return
881-	}
882+	if len(d.InitList) == 0 { return }
883 
884 	if d.IsVector || (d.Type != nil && d.Type.Kind == ast.TYPE_ARRAY) {
885 		vectorPtr, _ := ctx.codegenExpr(&ast.Node{Type: ast.Ident, Data: ast.IdentNode{Name: d.Name}, Tok: sym.Node.Tok})
886@@ -1011,14 +1212,44 @@ func (ctx *Context) codegenLocalVarDecl(d ast.VarDeclNode, sym *symbol) {
887 		return
888 	}
889 
890-	rval, _ := ctx.codegenExpr(d.InitList[0])
891-	ctx.genStore(sym.IRVal, rval, d.Type)
892+	initExpr := d.InitList[0]
893+	varType := d.Type
894+	if d.IsDefine && initExpr.Typ != nil {
895+		varType = initExpr.Typ
896+	} else if (varType == nil || varType.Kind == ast.TYPE_UNTYPED) && initExpr.Typ != nil {
897+		varType = initExpr.Typ
898+	}
899+
900+	if sym.BxType == nil || sym.BxType.Kind == ast.TYPE_UNTYPED { sym.BxType = varType }
901+
902+	if varType != nil && varType.Kind == ast.TYPE_STRUCT {
903+		rvalPtr, _ := ctx.codegenExpr(initExpr)
904+		lvalAddr := sym.IRVal
905+		size := ctx.getSizeof(varType)
906+		ctx.addInstr(&ir.Instruction{
907+			Op:   ir.OpBlit,
908+			Args: []ir.Value{rvalPtr, lvalAddr, &ir.Const{Value: size}},
909+		})
910+	} else {
911+		rval, _ := ctx.codegenExpr(initExpr)
912+		ctx.genStore(sym.IRVal, rval, varType)
913+	}
914 }
915 
916 func (ctx *Context) codegenGlobalVarDecl(d ast.VarDeclNode, sym *symbol) {
917 	globalData := &ir.Data{
918-		Name:  sym.IRVal.(*ir.Global).Name,
919-		Align: ctx.wordSize,
920+		Name:    sym.IRVal.(*ir.Global).Name,
921+		Align:   int(ctx.getAlignof(d.Type)),
922+		AstType: d.Type,
923+	}
924+
925+	if d.Type != nil && d.Type.Kind == ast.TYPE_STRUCT && len(d.InitList) == 0 {
926+		structSize := ctx.getSizeof(d.Type)
927+		if structSize > 0 {
928+			globalData.Items = append(globalData.Items, ir.DataItem{Typ: ir.TypeB, Count: int(structSize)})
929+		}
930+		if len(globalData.Items) > 0 { ctx.prog.Globals = append(ctx.prog.Globals, globalData) }
931+		return
932 	}
933 
934 	isUntypedStringVec := d.IsVector && (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) &&
935@@ -1046,7 +1277,7 @@ func (ctx *Context) codegenGlobalVarDecl(d ast.VarDeclNode, sym *symbol) {
936 		if val, ok := ctx.evalConstExpr(sizeNode); ok {
937 			numElements = val
938 		} else {
939-			util.Error(sizeNode.Tok, "Global array size must be a constant expression.")
940+			util.Error(sizeNode.Tok, "Global array size must be a constant expression")
941 		}
942 	} else {
943 		numElements = int64(len(d.InitList))
944@@ -1059,9 +1290,7 @@ func (ctx *Context) codegenGlobalVarDecl(d ast.VarDeclNode, sym *symbol) {
945 		for _, init := range d.InitList {
946 			val := ctx.codegenGlobalConst(init)
947 			itemType := elemType
948-			if _, ok := val.(*ir.Global); ok {
949-				itemType = ir.TypePtr
950-			}
951+			if _, ok := val.(*ir.Global); ok { itemType = ir.TypePtr }
952 			globalData.Items = append(globalData.Items, ir.DataItem{Typ: itemType, Value: val})
953 		}
954 		initializedElements := int64(len(d.InitList))
955@@ -1072,7 +1301,5 @@ func (ctx *Context) codegenGlobalVarDecl(d ast.VarDeclNode, sym *symbol) {
956 		globalData.Items = append(globalData.Items, ir.DataItem{Typ: elemType, Count: int(numElements)})
957 	}
958 
959-	if len(globalData.Items) > 0 {
960-		ctx.prog.Globals = append(ctx.prog.Globals, globalData)
961-	}
962+	if len(globalData.Items) > 0 { ctx.prog.Globals = append(ctx.prog.Globals, globalData) }
963 }
M pkg/codegen/codegen_helpers.go
+324, -164
  1@@ -1,8 +1,6 @@
  2 package codegen
  3 
  4 import (
  5-	"strings"
  6-
  7 	"github.com/xplshn/gbc/pkg/ast"
  8 	"github.com/xplshn/gbc/pkg/config"
  9 	"github.com/xplshn/gbc/pkg/ir"
 10@@ -10,8 +8,6 @@ import (
 11 	"github.com/xplshn/gbc/pkg/util"
 12 )
 13 
 14-// Helper functions for codegenExpr
 15-
 16 func (ctx *Context) codegenIdent(node *ast.Node) (ir.Value, bool) {
 17 	name := node.Data.(ast.IdentNode).Name
 18 	sym := ctx.findSymbol(name)
 19@@ -27,9 +23,7 @@ func (ctx *Context) codegenIdent(node *ast.Node) (ir.Value, bool) {
 20 		return sym.IRVal, false
 21 	case symExtrn:
 22 		isCall := node.Parent != nil && node.Parent.Type == ast.FuncCall && node.Parent.Data.(ast.FuncCallNode).FuncExpr == node
 23-		if isCall {
 24-			return sym.IRVal, false
 25-		}
 26+		if isCall { return sym.IRVal, false }
 27 		ctx.prog.ExtrnVars[name] = true
 28 		res := ctx.newTemp()
 29 		ctx.addInstr(&ir.Instruction{Op: ir.OpLoad, Typ: ir.TypePtr, Result: res, Args: []ir.Value{sym.IRVal}})
 30@@ -51,23 +45,63 @@ func (ctx *Context) codegenIdent(node *ast.Node) (ir.Value, bool) {
 31 		_, isLocal := sym.IRVal.(*ir.Temporary)
 32 		if isLocal {
 33 			isDopeVector := sym.IsVector && (sym.BxType == nil || sym.BxType.Kind == ast.TYPE_UNTYPED)
 34-			if isParam || isDopeVector {
 35-				return ctx.genLoad(sym.IRVal, sym.BxType), false
 36-			}
 37+			if isParam || isDopeVector { return ctx.genLoad(sym.IRVal, sym.BxType), false }
 38 		}
 39 		return sym.IRVal, false
 40 	}
 41 
 42+	if sym.BxType != nil && sym.BxType.Kind == ast.TYPE_STRUCT { return sym.IRVal, false }
 43+
 44 	return ctx.genLoad(sym.IRVal, sym.BxType), false
 45 }
 46 
 47+func (ctx *Context) isIntegerType(t *ast.BxType) bool {
 48+	return t != nil && (t.Kind == ast.TYPE_PRIMITIVE || t.Kind == ast.TYPE_UNTYPED_INT)
 49+}
 50+
 51+func (ctx *Context) isFloatType(t *ast.BxType) bool {
 52+	return t != nil && (t.Kind == ast.TYPE_FLOAT || t.Kind == ast.TYPE_UNTYPED_FLOAT)
 53+}
 54+
 55 func (ctx *Context) codegenAssign(node *ast.Node) (ir.Value, bool) {
 56 	d := node.Data.(ast.AssignNode)
 57+
 58+	if d.Lhs.Typ != nil && d.Lhs.Typ.Kind == ast.TYPE_STRUCT {
 59+		if d.Op != token.Eq {
 60+			util.Error(node.Tok, "Compound assignment operators are not supported for structs")
 61+			return nil, false
 62+		}
 63+		lvalAddr := ctx.codegenLvalue(d.Lhs)
 64+		rvalPtr, _ := ctx.codegenExpr(d.Rhs)
 65+		size := ctx.getSizeof(d.Lhs.Typ)
 66+		ctx.addInstr(&ir.Instruction{
 67+			Op:   ir.OpBlit,
 68+			Args: []ir.Value{rvalPtr, lvalAddr, &ir.Const{Value: size}},
 69+		})
 70+		return lvalAddr, false
 71+	}
 72+
 73 	lvalAddr := ctx.codegenLvalue(d.Lhs)
 74 	var rval ir.Value
 75 
 76 	if d.Op == token.Eq {
 77 		rval, _ = ctx.codegenExpr(d.Rhs)
 78+		if d.Lhs.Typ != nil && d.Rhs.Typ != nil && d.Lhs.Typ.Kind == ast.TYPE_FLOAT && ctx.isIntegerType(d.Rhs.Typ) {
 79+			castRval := ctx.newTemp()
 80+			var convOp ir.Op
 81+			if ctx.getSizeof(d.Rhs.Typ) == 8 {
 82+				convOp = ir.OpSLToF
 83+			} else {
 84+				convOp = ir.OpSWToF
 85+			}
 86+			ctx.addInstr(&ir.Instruction{
 87+				Op:     convOp,
 88+				Typ:    ir.GetType(d.Lhs.Typ, ctx.wordSize),
 89+				Result: castRval,
 90+				Args:   []ir.Value{rval},
 91+			})
 92+			rval = castRval
 93+		}
 94 	} else {
 95 		currentLvalVal := ctx.genLoad(lvalAddr, d.Lhs.Typ)
 96 		rhsVal, _ := ctx.codegenExpr(d.Rhs)
 97@@ -111,8 +145,72 @@ func (ctx *Context) codegenBinaryOp(node *ast.Node) (ir.Value, bool) {
 98 	l, _ := ctx.codegenExpr(d.Left)
 99 	r, _ := ctx.codegenExpr(d.Right)
100 	res := ctx.newTemp()
101-	op, typ := getBinaryOpAndType(d.Op, d.Left.Typ, ctx.wordSize)
102-	ctx.addInstr(&ir.Instruction{Op: op, Typ: typ, Result: res, Args: []ir.Value{l, r}})
103+	op, resultIrType := getBinaryOpAndType(d.Op, node.Typ, ctx.wordSize)
104+
105+	isComparison := op >= ir.OpCEq && op <= ir.OpCGe
106+	isFloatComparison := false
107+	if isComparison && (ctx.isFloatType(d.Left.Typ) || ctx.isFloatType(d.Right.Typ)) {
108+		isFloatComparison = true
109+	}
110+
111+	if ctx.isFloatType(node.Typ) || isFloatComparison {
112+		floatType := resultIrType
113+		if isFloatComparison {
114+			if ctx.isFloatType(d.Left.Typ) {
115+				floatType = ir.GetType(d.Left.Typ, ctx.wordSize)
116+			} else {
117+				floatType = ir.GetType(d.Right.Typ, ctx.wordSize)
118+			}
119+		}
120+
121+		if !ctx.isFloatType(d.Left.Typ) {
122+			castL := ctx.newTemp()
123+			var convOp ir.Op
124+			if ctx.getSizeof(d.Left.Typ) == 8 {
125+				convOp = ir.OpSLToF
126+			} else {
127+				convOp = ir.OpSWToF
128+			}
129+			ctx.addInstr(&ir.Instruction{Op: convOp, Typ: floatType, Result: castL, Args: []ir.Value{l}})
130+			l = castL
131+		}
132+		if !ctx.isFloatType(d.Right.Typ) {
133+			castR := ctx.newTemp()
134+			var convOp ir.Op
135+			if ctx.getSizeof(d.Right.Typ) == 8 {
136+				convOp = ir.OpSLToF
137+			} else {
138+				convOp = ir.OpSWToF
139+			}
140+			ctx.addInstr(&ir.Instruction{Op: convOp, Typ: floatType, Result: castR, Args: []ir.Value{r}})
141+			r = castR
142+		}
143+		if l_const, ok := l.(*ir.Const); ok { l = &ir.FloatConst{Value: float64(l_const.Value), Typ: floatType} }
144+		if r_const, ok := r.(*ir.Const); ok { r = &ir.FloatConst{Value: float64(r_const.Value), Typ: floatType} }
145+	}
146+
147+	var operandIrType ir.Type
148+	if isComparison {
149+		if ctx.isFloatType(d.Left.Typ) || ctx.isFloatType(d.Right.Typ) {
150+			if ctx.isFloatType(d.Left.Typ) {
151+				operandIrType = ir.GetType(d.Left.Typ, ctx.wordSize)
152+			} else {
153+				operandIrType = ir.GetType(d.Right.Typ, ctx.wordSize)
154+			}
155+		} else {
156+			operandIrType = ir.GetType(d.Left.Typ, ctx.wordSize)
157+		}
158+	} else {
159+		operandIrType = resultIrType
160+	}
161+
162+	ctx.addInstr(&ir.Instruction{
163+		Op:          op,
164+		Typ:         resultIrType,
165+		OperandType: operandIrType,
166+		Result:      res,
167+		Args:        []ir.Value{l, r},
168+	})
169 	return res, false
170 }
171 
172@@ -121,23 +219,33 @@ func (ctx *Context) codegenUnaryOp(node *ast.Node) (ir.Value, bool) {
173 	res := ctx.newTemp()
174 	val, _ := ctx.codegenExpr(d.Expr)
175 	valType := ir.GetType(d.Expr.Typ, ctx.wordSize)
176+	isFloat := ctx.isFloatType(d.Expr.Typ)
177 
178 	switch d.Op {
179 	case token.Minus:
180-		ctx.addInstr(&ir.Instruction{Op: ir.OpSub, Typ: valType, Result: res, Args: []ir.Value{&ir.Const{Value: 0}, val}})
181+		if isFloat {
182+			ctx.addInstr(&ir.Instruction{Op: ir.OpNegF, Typ: valType, Result: res, Args: []ir.Value{val}})
183+		} else {
184+			ctx.addInstr(&ir.Instruction{Op: ir.OpSub, Typ: valType, Result: res, Args: []ir.Value{&ir.Const{Value: 0}, val}})
185+		}
186 	case token.Plus:
187 		return val, false
188 	case token.Not:
189 		wordType := ir.GetType(nil, ctx.wordSize)
190-		ctx.addInstr(&ir.Instruction{Op: ir.OpCEq, Typ: wordType, Result: res, Args: []ir.Value{val, &ir.Const{Value: 0}}})
191+		ctx.addInstr(&ir.Instruction{Op: ir.OpCEq, Typ: wordType, OperandType: valType, Result: res, Args: []ir.Value{val, &ir.Const{Value: 0}}})
192 	case token.Complement:
193 		wordType := ir.GetType(nil, ctx.wordSize)
194 		ctx.addInstr(&ir.Instruction{Op: ir.OpXor, Typ: wordType, Result: res, Args: []ir.Value{val, &ir.Const{Value: -1}}})
195-	case token.Inc, token.Dec: // Prefix
196+	case token.Inc, token.Dec:
197 		lvalAddr := ctx.codegenLvalue(d.Expr)
198 		op := map[token.Type]ir.Op{token.Inc: ir.OpAdd, token.Dec: ir.OpSub}[d.Op]
199+		if isFloat {
200+			op = map[token.Type]ir.Op{token.Inc: ir.OpAddF, token.Dec: ir.OpSubF}[d.Op]
201+		}
202 		currentVal := ctx.genLoad(lvalAddr, d.Expr.Typ)
203-		ctx.addInstr(&ir.Instruction{Op: op, Typ: valType, Result: res, Args: []ir.Value{currentVal, &ir.Const{Value: 1}}})
204+		oneConst := ir.Value(&ir.Const{Value: 1})
205+		if isFloat { oneConst = &ir.FloatConst{Value: 1.0, Typ: valType} }
206+		ctx.addInstr(&ir.Instruction{Op: op, Typ: valType, Result: res, Args: []ir.Value{currentVal, oneConst}})
207 		ctx.genStore(lvalAddr, res, d.Expr.Typ)
208 	default:
209 		util.Error(node.Tok, "Unsupported unary operator")
210@@ -151,9 +259,16 @@ func (ctx *Context) codegenPostfixOp(node *ast.Node) (ir.Value, bool) {
211 	res := ctx.genLoad(lvalAddr, d.Expr.Typ)
212 
213 	newVal := ctx.newTemp()
214-	op := map[token.Type]ir.Op{token.Inc: ir.OpAdd, token.Dec: ir.OpSub}[d.Op]
215 	valType := ir.GetType(d.Expr.Typ, ctx.wordSize)
216-	ctx.addInstr(&ir.Instruction{Op: op, Typ: valType, Result: newVal, Args: []ir.Value{res, &ir.Const{Value: 1}}})
217+	isFloat := ctx.isFloatType(d.Expr.Typ)
218+
219+	op := map[token.Type]ir.Op{token.Inc: ir.OpAdd, token.Dec: ir.OpSub}[d.Op]
220+	if isFloat { op = map[token.Type]ir.Op{token.Inc: ir.OpAddF, token.Dec: ir.OpSubF}[d.Op] }
221+
222+	oneConst := ir.Value(&ir.Const{Value: 1})
223+	if isFloat { oneConst = &ir.FloatConst{Value: 1.0, Typ: valType} }
224+
225+	ctx.addInstr(&ir.Instruction{Op: op, Typ: valType, Result: newVal, Args: []ir.Value{res, oneConst}})
226 	ctx.genStore(lvalAddr, newVal, d.Expr.Typ)
227 	return res, false
228 }
229@@ -161,6 +276,9 @@ func (ctx *Context) codegenPostfixOp(node *ast.Node) (ir.Value, bool) {
230 func (ctx *Context) codegenIndirection(node *ast.Node) (ir.Value, bool) {
231 	exprNode := node.Data.(ast.IndirectionNode).Expr
232 	addr, _ := ctx.codegenExpr(exprNode)
233+
234+	if node.Typ != nil && node.Typ.Kind == ast.TYPE_STRUCT { return addr, false }
235+
236 	loadType := node.Typ
237 	if !ctx.isTypedPass && exprNode.Type == ast.Ident {
238 		if sym := ctx.findSymbol(exprNode.Data.(ast.IdentNode).Name); sym != nil && sym.IsByteArray {
239@@ -178,9 +296,7 @@ func (ctx *Context) codegenSubscriptAddr(node *ast.Node) ir.Value {
240 	var scale int64 = int64(ctx.wordSize)
241 	if d.Array.Typ != nil {
242 		if d.Array.Typ.Kind == ast.TYPE_POINTER || d.Array.Typ.Kind == ast.TYPE_ARRAY {
243-			if d.Array.Typ.Base != nil {
244-				scale = ctx.getSizeof(d.Array.Typ.Base)
245-			}
246+			if d.Array.Typ.Base != nil { scale = ctx.getSizeof(d.Array.Typ.Base) }
247 		}
248 	} else if !ctx.isTypedPass && d.Array.Type == ast.Ident {
249 		if sym := ctx.findSymbol(d.Array.Data.(ast.IdentNode).Name); sym != nil && sym.IsByteArray {
250@@ -215,9 +331,7 @@ func (ctx *Context) codegenAddressOf(node *ast.Node) (ir.Value, bool) {
251 		name := lvalNode.Data.(ast.IdentNode).Name
252 		if sym := ctx.findSymbol(name); sym != nil {
253 			isTypedArray := sym.BxType != nil && sym.BxType.Kind == ast.TYPE_ARRAY
254-			if sym.Type == symFunc || isTypedArray {
255-				return sym.IRVal, false
256-			}
257+			if sym.Type == symFunc || isTypedArray { return sym.IRVal, false }
258 			if sym.IsVector {
259 				res, _ := ctx.codegenExpr(lvalNode)
260 				return res, false
261@@ -236,23 +350,45 @@ func (ctx *Context) codegenFuncCall(node *ast.Node) (ir.Value, bool) {
262 		}
263 	}
264 
265+	funcVal, _ := ctx.codegenExpr(d.FuncExpr)
266+
267+	isVariadic := false
268+	if d.FuncExpr.Type == ast.Ident {
269+		name := d.FuncExpr.Data.(ast.IdentNode).Name
270+		if sym := ctx.findSymbol(name); sym != nil {
271+			if sym.Node != nil {
272+				if fd, ok := sym.Node.Data.(ast.FuncDeclNode); ok { isVariadic = fd.HasVarargs }
273+			}
274+			if !isVariadic && sym.Type == symExtrn { isVariadic = true }
275+		}
276+	}
277+
278 	argVals := make([]ir.Value, len(d.Args))
279 	argTypes := make([]ir.Type, len(d.Args))
280 	for i := len(d.Args) - 1; i >= 0; i-- {
281 		argVals[i], _ = ctx.codegenExpr(d.Args[i])
282 		argTypes[i] = ir.GetType(d.Args[i].Typ, ctx.wordSize)
283+
284+		if isVariadic && argTypes[i] == ir.TypeS {
285+			promotedVal := ctx.newTemp()
286+			ctx.addInstr(&ir.Instruction{
287+				Op:     ir.OpFToF,
288+				Typ:    ir.TypeD,
289+				Result: promotedVal,
290+				Args:   []ir.Value{argVals[i]},
291+			})
292+			argVals[i] = promotedVal
293+			argTypes[i] = ir.TypeD
294+		}
295 	}
296-	funcVal, _ := ctx.codegenExpr(d.FuncExpr)
297 
298 	isStmt := node.Parent != nil && node.Parent.Type == ast.Block
299+	var res ir.Value
300 	returnType := ir.GetType(node.Typ, ctx.wordSize)
301+	callArgs := append([]ir.Value{funcVal}, argVals...)
302 
303-	var res ir.Value
304-	if !isStmt && returnType != ir.TypeNone {
305-		res = ctx.newTemp()
306-	}
307+	if !isStmt && returnType != ir.TypeNone { res = ctx.newTemp() }
308 
309-	callArgs := append([]ir.Value{funcVal}, argVals...)
310 	ctx.addInstr(&ir.Instruction{
311 		Op:       ir.OpCall,
312 		Typ:      returnType,
313@@ -260,6 +396,53 @@ func (ctx *Context) codegenFuncCall(node *ast.Node) (ir.Value, bool) {
314 		Args:     callArgs,
315 		ArgTypes: argTypes,
316 	})
317+
318+	return res, false
319+}
320+
321+func (ctx *Context) codegenTypeCast(node *ast.Node) (ir.Value, bool) {
322+	d := node.Data.(ast.TypeCastNode)
323+	val, _ := ctx.codegenExpr(d.Expr)
324+
325+	sourceType := d.Expr.Typ
326+	targetType := d.TargetType
327+
328+	if ir.GetType(sourceType, ctx.wordSize) == ir.GetType(targetType, ctx.wordSize) { return val, false }
329+
330+	res := ctx.newTemp()
331+	targetIrType := ir.GetType(targetType, ctx.wordSize)
332+
333+	sourceIsInt := ctx.isIntegerType(sourceType)
334+	sourceIsFloat := ctx.isFloatType(sourceType)
335+	targetIsInt := ctx.isIntegerType(targetType)
336+	targetIsFloat := ctx.isFloatType(targetType)
337+
338+	op := ir.OpCast
339+	if sourceIsInt && targetIsFloat {
340+		op = ir.OpSWToF
341+		if ctx.getSizeof(sourceType) == 8 { op = ir.OpSLToF }
342+	} else if sourceIsFloat && targetIsFloat {
343+		op = ir.OpFToF
344+	} else if sourceIsFloat && targetIsInt {
345+		op = ir.OpFToSI
346+	} else if sourceIsInt && targetIsInt {
347+		sourceSize, targetSize := ctx.getSizeof(sourceType), ctx.getSizeof(targetType)
348+		if targetSize > sourceSize {
349+			switch sourceSize {
350+			case 1: op = ir.OpExtSB
351+			case 2: op = ir.OpExtSH
352+			case 4: op = ir.OpExtSW
353+			}
354+		}
355+	}
356+
357+	ctx.addInstr(&ir.Instruction{
358+		Op:     op,
359+		Typ:    targetIrType,
360+		Result: res,
361+		Args:   []ir.Value{val},
362+	})
363+
364 	return res, false
365 }
366 
367@@ -301,12 +484,8 @@ func (ctx *Context) codegenTernary(node *ast.Node) (ir.Value, bool) {
368 	if !terminates {
369 		ctx.startBlock(endL)
370 		phiArgs := []ir.Value{}
371-		if !thenTerminates {
372-			phiArgs = append(phiArgs, thenPred, thenVal)
373-		}
374-		if !elseTerminates {
375-			phiArgs = append(phiArgs, elsePred, elseVal)
376-		}
377+		if !thenTerminates { phiArgs = append(phiArgs, thenPred, thenVal) }
378+		if !elseTerminates { phiArgs = append(phiArgs, elsePred, elseVal) }
379 		ctx.addInstr(&ir.Instruction{Op: ir.OpPhi, Typ: resType, Result: res, Args: phiArgs})
380 	}
381 	return res, terminates
382@@ -336,11 +515,81 @@ func (ctx *Context) codegenAutoAlloc(node *ast.Node) (ir.Value, bool) {
383 	return res, false
384 }
385 
386-// Helper functions for codegenStmt
387+func (ctx *Context) codegenStructLiteral(node *ast.Node) (ir.Value, bool) {
388+	d := node.Data.(ast.StructLiteralNode)
389+	structType := node.Typ
390+	if structType == nil || structType.Kind != ast.TYPE_STRUCT {
391+		util.Error(node.Tok, "internal: struct literal has invalid type")
392+		return nil, false
393+	}
394+
395+	size := ctx.getSizeof(structType)
396+	align := ctx.getAlignof(structType)
397+	structPtr := ctx.newTemp()
398+	ctx.addInstr(&ir.Instruction{
399+		Op:     ir.OpAlloc,
400+		Typ:    ir.GetType(nil, ctx.wordSize),
401+		Result: structPtr,
402+		Args:   []ir.Value{&ir.Const{Value: size}},
403+		Align:  int(align),
404+	})
405+
406+	if d.Names == nil {
407+		var currentOffset int64
408+		for i, valNode := range d.Values {
409+			field := structType.Fields[i].Data.(ast.VarDeclNode)
410+			fieldAlign := ctx.getAlignof(field.Type)
411+			currentOffset = util.AlignUp(currentOffset, fieldAlign)
412+			fieldAddr := ctx.newTemp()
413+			ctx.addInstr(&ir.Instruction{
414+				Op:     ir.OpAdd,
415+				Typ:    ir.GetType(nil, ctx.wordSize),
416+				Result: fieldAddr,
417+				Args:   []ir.Value{structPtr, &ir.Const{Value: currentOffset}},
418+			})
419+			val, _ := ctx.codegenExpr(valNode)
420+			ctx.genStore(fieldAddr, val, field.Type)
421+			currentOffset += ctx.getSizeof(field.Type)
422+		}
423+	} else {
424+		fieldOffsets := make(map[string]int64)
425+		fieldTypes := make(map[string]*ast.BxType)
426+		var currentOffset int64
427+		for _, fieldNode := range structType.Fields {
428+			fieldData := fieldNode.Data.(ast.VarDeclNode)
429+			fieldAlign := ctx.getAlignof(fieldData.Type)
430+			currentOffset = util.AlignUp(currentOffset, fieldAlign)
431+			fieldOffsets[fieldData.Name] = currentOffset
432+			fieldTypes[fieldData.Name] = fieldData.Type
433+			currentOffset += ctx.getSizeof(fieldData.Type)
434+		}
435+
436+		for i, nameNode := range d.Names {
437+			fieldName := nameNode.Data.(ast.IdentNode).Name
438+			offset, ok := fieldOffsets[fieldName]
439+			if !ok {
440+				util.Error(nameNode.Tok, "internal: struct '%s' has no field '%s'", structType.Name, fieldName)
441+				continue
442+			}
443+			fieldAddr := ctx.newTemp()
444+			ctx.addInstr(&ir.Instruction{
445+				Op:     ir.OpAdd,
446+				Typ:    ir.GetType(nil, ctx.wordSize),
447+				Result: fieldAddr,
448+				Args:   []ir.Value{structPtr, &ir.Const{Value: offset}},
449+			})
450+
451+			val, _ := ctx.codegenExpr(d.Values[i])
452+			ctx.genStore(fieldAddr, val, fieldTypes[fieldName])
453+		}
454+	}
455+
456+	return structPtr, false
457+}
458 
459 func (ctx *Context) codegenReturn(node *ast.Node) bool {
460-	var retVal ir.Value
461 	d := node.Data.(ast.ReturnNode)
462+	var retVal ir.Value
463 	if d.Expr != nil {
464 		retVal, _ = ctx.codegenExpr(d.Expr)
465 	} else if ctx.currentFunc != nil && ctx.currentFunc.ReturnType != ir.TypeNone {
466@@ -355,30 +604,22 @@ func (ctx *Context) codegenIf(node *ast.Node) bool {
467 	d := node.Data.(ast.IfNode)
468 	thenL, endL := ctx.newLabel(), ctx.newLabel()
469 	elseL := endL
470-	if d.ElseBody != nil {
471-		elseL = ctx.newLabel()
472-	}
473+	if d.ElseBody != nil { elseL = ctx.newLabel() }
474 
475 	ctx.codegenLogicalCond(d.Cond, thenL, elseL)
476 
477 	ctx.startBlock(thenL)
478 	thenTerminates := ctx.codegenStmt(d.ThenBody)
479-	if !thenTerminates {
480-		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
481-	}
482+	if !thenTerminates { ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}}) }
483 
484 	var elseTerminates bool
485 	if d.ElseBody != nil {
486 		ctx.startBlock(elseL)
487 		elseTerminates = ctx.codegenStmt(d.ElseBody)
488-		if !elseTerminates {
489-			ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
490-		}
491+		if !elseTerminates { ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}}) }
492 	}
493 
494-	if !thenTerminates || !elseTerminates {
495-		ctx.startBlock(endL)
496-	}
497+	if !thenTerminates || !elseTerminates { ctx.startBlock(endL) }
498 	return thenTerminates && (d.ElseBody != nil && elseTerminates)
499 }
500 
501@@ -396,129 +637,48 @@ func (ctx *Context) codegenWhile(node *ast.Node) bool {
502 
503 	ctx.startBlock(bodyL)
504 	bodyTerminates := ctx.codegenStmt(d.Body)
505-	if !bodyTerminates {
506-		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{startL}})
507-	}
508+	if !bodyTerminates { ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{startL}}) }
509 
510 	ctx.startBlock(endL)
511 	return false
512 }
513 
514-func (ctx *Context) codegenSwitch(node *ast.Node) bool {
515-	d := node.Data.(ast.SwitchNode)
516-	switchVal, _ := ctx.codegenExpr(d.Expr)
517-	endLabel := ctx.newLabel()
518-
519-	oldBreak := ctx.breakLabel
520-	ctx.breakLabel = endLabel
521-	defer func() { ctx.breakLabel = oldBreak }()
522-
523-	ctx.switchStack = append(ctx.switchStack, &switchContext{Node: &d, CaseIndex: 0})
524-	defer func() { ctx.switchStack = ctx.switchStack[:len(ctx.switchStack)-1] }()
525-
526-	var defaultTarget *ir.Label
527-	if d.DefaultLabelName != "" {
528-		labelName := strings.TrimLeft(d.DefaultLabelName, "@")
529-		defaultTarget = &ir.Label{Name: labelName}
530-	} else {
531-		defaultTarget = endLabel
532-	}
533-
534-	wordType := ir.GetType(nil, ctx.wordSize)
535-	for _, caseLabelInfo := range d.CaseLabels {
536-		caseValConst := &ir.Const{Value: caseLabelInfo.Value}
537-		cmpRes := ctx.newTemp()
538-		nextCheckLabel := ctx.newLabel()
539-		labelName := strings.TrimLeft(caseLabelInfo.LabelName, "@")
540-		caseTargetLabel := &ir.Label{Name: labelName}
541-
542-		ctx.addInstr(&ir.Instruction{Op: ir.OpCEq, Typ: wordType, Result: cmpRes, Args: []ir.Value{switchVal, caseValConst}})
543-		ctx.addInstr(&ir.Instruction{Op: ir.OpJnz, Args: []ir.Value{cmpRes, caseTargetLabel, nextCheckLabel}})
544-		ctx.startBlock(nextCheckLabel)
545-	}
546-	ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{defaultTarget}})
547-	ctx.currentBlock = nil
548-
549-	bodyTerminates := ctx.codegenStmt(d.Body)
550-
551-	if ctx.currentBlock != nil {
552-		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endLabel}})
553-	}
554-
555-	ctx.startBlock(endLabel)
556-	return bodyTerminates && d.DefaultLabelName != ""
557-}
558-
559-func (ctx *Context) codegenCaseOrDefault(node *ast.Node) bool {
560-	var body *ast.Node
561-	var labelName string
562-
563-	if node.Type == ast.Case {
564-		d := node.Data.(ast.CaseNode)
565-		body = d.Body
566-		if len(ctx.switchStack) > 0 {
567-			info := ctx.switchStack[len(ctx.switchStack)-1]
568-			if info.CaseIndex < len(info.Node.CaseLabels) {
569-				labelName = info.Node.CaseLabels[info.CaseIndex].LabelName
570-				info.CaseIndex++
571-			}
572-		}
573-	} else { // Default
574-		d := node.Data.(ast.DefaultNode)
575-		body = d.Body
576-		if len(ctx.switchStack) > 0 {
577-			info := ctx.switchStack[len(ctx.switchStack)-1]
578-			labelName = info.Node.DefaultLabelName
579-		}
580-	}
581-
582-	if labelName != "" {
583-		label := &ir.Label{Name: strings.TrimLeft(labelName, "@")}
584-		if ctx.currentBlock != nil {
585-			ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{label}})
586+func getBinaryOpAndType(op token.Type, resultAstType *ast.BxType, wordSize int) (ir.Op, ir.Type) {
587+	if resultAstType != nil && (resultAstType.Kind == ast.TYPE_FLOAT || resultAstType.Kind == ast.TYPE_UNTYPED_FLOAT) {
588+		typ := ir.GetType(resultAstType, wordSize)
589+		switch op {
590+		case token.Plus, token.PlusEq, token.EqPlus: return ir.OpAddF, typ
591+		case token.Minus, token.MinusEq, token.EqMinus: return ir.OpSubF, typ
592+		case token.Star, token.StarEq, token.EqStar: return ir.OpMulF, typ
593+		case token.Slash, token.SlashEq, token.EqSlash: return ir.OpDivF, typ
594+		case token.Rem, token.RemEq, token.EqRem: return ir.OpRemF, typ
595+		case token.EqEq: return ir.OpCEq, typ
596+		case token.Neq: return ir.OpCNeq, typ
597+		case token.Lt: return ir.OpCLt, typ
598+		case token.Gt: return ir.OpCGt, typ
599+		case token.Lte: return ir.OpCLe, typ
600+		case token.Gte: return ir.OpCGe, typ
601 		}
602-		ctx.startBlock(label)
603 	}
604 
605-	return ctx.codegenStmt(body)
606-}
607-
608-// Helper for op mapping
609-func getBinaryOpAndType(op token.Type, astType *ast.BxType, wordSize int) (ir.Op, ir.Type) {
610-	typ := ir.GetType(astType, wordSize)
611+	typ := ir.GetType(resultAstType, wordSize)
612 	switch op {
613-	case token.Plus, token.PlusEq, token.EqPlus:
614-		return ir.OpAdd, typ
615-	case token.Minus, token.MinusEq, token.EqMinus:
616-		return ir.OpSub, typ
617-	case token.Star, token.StarEq, token.EqStar:
618-		return ir.OpMul, typ
619-	case token.Slash, token.SlashEq, token.EqSlash:
620-		return ir.OpDiv, typ
621-	case token.Rem, token.RemEq, token.EqRem:
622-		return ir.OpRem, typ
623-	case token.And, token.AndEq, token.EqAnd:
624-		return ir.OpAnd, typ
625-	case token.Or, token.OrEq, token.EqOr:
626-		return ir.OpOr, typ
627-	case token.Xor, token.XorEq, token.EqXor:
628-		return ir.OpXor, typ
629-	case token.Shl, token.ShlEq, token.EqShl:
630-		return ir.OpShl, typ
631-	case token.Shr, token.ShrEq, token.EqShr:
632-		return ir.OpShr, typ
633-	case token.EqEq:
634-		return ir.OpCEq, typ
635-	case token.Neq:
636-		return ir.OpCNeq, typ
637-	case token.Lt:
638-		return ir.OpCLt, typ
639-	case token.Gt:
640-		return ir.OpCGt, typ
641-	case token.Lte:
642-		return ir.OpCLe, typ
643-	case token.Gte:
644-		return ir.OpCGe, typ
645+	case token.Plus, token.PlusEq, token.EqPlus: return ir.OpAdd, typ
646+	case token.Minus, token.MinusEq, token.EqMinus: return ir.OpSub, typ
647+	case token.Star, token.StarEq, token.EqStar: return ir.OpMul, typ
648+	case token.Slash, token.SlashEq, token.EqSlash: return ir.OpDiv, typ
649+	case token.Rem, token.RemEq, token.EqRem: return ir.OpRem, typ
650+	case token.And, token.AndEq, token.EqAnd: return ir.OpAnd, typ
651+	case token.Or, token.OrEq, token.EqOr: return ir.OpOr, typ
652+	case token.Xor, token.XorEq, token.EqXor: return ir.OpXor, typ
653+	case token.Shl, token.ShlEq, token.EqShl: return ir.OpShl, typ
654+	case token.Shr, token.ShrEq, token.EqShr: return ir.OpShr, typ
655+	case token.EqEq: return ir.OpCEq, typ
656+	case token.Neq: return ir.OpCNeq, typ
657+	case token.Lt: return ir.OpCLt, typ
658+	case token.Gt: return ir.OpCGt, typ
659+	case token.Lte: return ir.OpCLe, typ
660+	case token.Gte: return ir.OpCGe, typ
661 	}
662 	return -1, -1
663 }
M pkg/codegen/llvm_backend.go
+235, -236
  1@@ -3,6 +3,7 @@ package codegen
  2 import (
  3 	"bytes"
  4 	"fmt"
  5+	"math"
  6 	"os"
  7 	"os/exec"
  8 	"sort"
  9@@ -13,23 +14,18 @@ import (
 10 	"github.com/xplshn/gbc/pkg/ir"
 11 )
 12 
 13-// llvmBackend implements the Backend interface for LLVM IR.
 14 type llvmBackend struct {
 15 	out       *strings.Builder
 16 	prog      *ir.Program
 17 	cfg       *config.Config
 18 	wordType  string
 19-	tempTypes map[string]string // Maps temporary/global name to its LLVM type string
 20-	funcSigs  map[string]string // Caches function signatures
 21+	tempTypes map[string]string
 22+	funcSigs  map[string]string
 23 	currentFn *ir.Func
 24 }
 25 
 26-// NewLLVMBackend creates a new instance of the LLVM backend.
 27-func NewLLVMBackend() Backend {
 28-	return &llvmBackend{}
 29-}
 30+func NewLLVMBackend() Backend { return &llvmBackend{} }
 31 
 32-// Generate translates a generic IR program into final assembly using the LLVM toolchain.
 33 func (b *llvmBackend) Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buffer, error) {
 34 	var llvmIRBuilder strings.Builder
 35 	b.out = &llvmIRBuilder
 36@@ -43,18 +39,13 @@ func (b *llvmBackend) Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buf
 37 
 38 	llvmIR := llvmIRBuilder.String()
 39 	asm, err := b.compileLLVMIR(llvmIR)
 40-	if err != nil {
 41-		return nil, err
 42-	}
 43+	if err != nil { return nil, err }
 44 	return bytes.NewBufferString(asm), nil
 45 }
 46 
 47-// compileLLVMIR invokes the 'llc' tool to compile LLVM IR into assembly.
 48 func (b *llvmBackend) compileLLVMIR(llvmIR string) (string, error) {
 49 	llFile, err := os.CreateTemp("", "gbc-main-*.ll")
 50-	if err != nil {
 51-		return "", fmt.Errorf("failed to create temp file for LLVM IR: %w", err)
 52-	}
 53+	if err != nil { return "", fmt.Errorf("failed to create temp file for LLVM IR: %w", err) }
 54 	defer os.Remove(llFile.Name())
 55 	if _, err := llFile.WriteString(llvmIR); err != nil {
 56 		return "", fmt.Errorf("failed to write to temp file for LLVM IR: %w", err)
 57@@ -62,9 +53,7 @@ func (b *llvmBackend) compileLLVMIR(llvmIR string) (string, error) {
 58 	llFile.Close()
 59 
 60 	asmFile, err := os.CreateTemp("", "gbc-main-*.s")
 61-	if err != nil {
 62-		return "", fmt.Errorf("failed to create temp file for assembly: %w", err)
 63-	}
 64+	if err != nil { return "", fmt.Errorf("failed to create temp file for assembly: %w", err) }
 65 	asmFile.Close()
 66 	defer os.Remove(asmFile.Name())
 67 
 68@@ -74,9 +63,7 @@ func (b *llvmBackend) compileLLVMIR(llvmIR string) (string, error) {
 69 	}
 70 
 71 	asmBytes, err := os.ReadFile(asmFile.Name())
 72-	if err != nil {
 73-		return "", fmt.Errorf("failed to read temporary assembly file: %w", err)
 74-	}
 75+	if err != nil { return "", fmt.Errorf("failed to read temporary assembly file: %w", err) }
 76 	return string(asmBytes), nil
 77 }
 78 
 79@@ -93,30 +80,17 @@ func (b *llvmBackend) gen() {
 80 	}
 81 }
 82 
 83-func (b *llvmBackend) getFuncSig(name string) (retType string) {
 84-	switch name {
 85-	case "printf", "fprintf", "sprintf", "atoi", "usleep":
 86-		return "i32"
 87-	case "malloc", "realloc", "memset":
 88-		return "i8*"
 89-	case "sin", "cos", "sqrt", "fabs":
 90-		return "double"
 91-	case "free", "exit":
 92-		return "void"
 93-	default:
 94-		return b.wordType
 95-	}
 96-}
 97+func (b *llvmBackend) getFuncSig(name string) (retType string) { return b.wordType }
 98 
 99 func (b *llvmBackend) genDeclarations() {
100 	knownExternals := make(map[string]bool)
101 
102+	b.out.WriteString("declare void @llvm.memcpy.p0i8.p0i8.i64(i8*, i8*, i64, i1)\n")
103+
104 	if len(b.prog.ExtrnVars) > 0 {
105 		b.out.WriteString("; --- External Variables ---\n")
106 		for name := range b.prog.ExtrnVars {
107-			if knownExternals[name] {
108-				continue
109-			}
110+			if knownExternals[name] { continue }
111 			ptrType := "i8*"
112 			fmt.Fprintf(b.out, "@%s = external global %s\n", name, ptrType)
113 			b.tempTypes["@"+name] = ptrType + "*"
114@@ -166,9 +140,7 @@ func (b *llvmBackend) genDeclarations() {
115 }
116 
117 func (b *llvmBackend) genStrings() {
118-	if len(b.prog.Strings) == 0 {
119-		return
120-	}
121+	if len(b.prog.Strings) == 0 { return }
122 	b.out.WriteString("; --- String Literals ---\n")
123 	for s, label := range b.prog.Strings {
124 		strLen := len(s) + 1
125@@ -181,9 +153,7 @@ func (b *llvmBackend) genStrings() {
126 }
127 
128 func (b *llvmBackend) genGlobals() {
129-	if len(b.prog.Globals) == 0 {
130-		return
131-	}
132+	if len(b.prog.Globals) == 0 { return }
133 	b.out.WriteString("; --- Global Variables ---\n")
134 	for _, g := range b.prog.Globals {
135 		hasInitializer := false
136@@ -197,16 +167,12 @@ func (b *llvmBackend) genGlobals() {
137 				totalItemCount++
138 				hasInitializer = true
139 			}
140-			if firstItemType == -1 {
141-				firstItemType = item.Typ
142-			}
143+			if firstItemType == -1 { firstItemType = item.Typ }
144 		}
145 
146 		var globalType string
147 		elemType := b.formatType(firstItemType)
148-		if firstItemType == -1 {
149-			elemType = b.wordType
150-		}
151+		if firstItemType == -1 { elemType = b.wordType }
152 
153 		if totalItemCount > 1 {
154 			globalType = fmt.Sprintf("[%d x %s]", totalItemCount, elemType)
155@@ -232,7 +198,7 @@ func (b *llvmBackend) genGlobals() {
156 					}
157 				}
158 				initializer = fmt.Sprintf("[ %s ]", strings.Join(typedItems, ", "))
159-			} else { // Scalar
160+			} else {
161 				initializer = b.formatGlobalInitializerValue(g.Items[0].Value, globalType)
162 			}
163 		}
164@@ -247,20 +213,19 @@ func (b *llvmBackend) formatGlobalInitializerValue(v ir.Value, targetType string
165 	switch val := v.(type) {
166 	case *ir.Const:
167 		return fmt.Sprintf("%d", val.Value)
168+	case *ir.FloatConst:
169+		if targetType == "float" { return fmt.Sprintf("0x%X", math.Float32bits(float32(val.Value))) }
170+		return fmt.Sprintf("0x%X", math.Float64bits(val.Value))
171 	case *ir.Global:
172 		strContent, isString := b.prog.IsStringLabel(val.Name)
173 		if isString {
174 			strType := fmt.Sprintf("[%d x i8]", len(strContent)+1)
175 			gep := fmt.Sprintf("getelementptr inbounds (%s, %s* @%s, i64 0, i64 0)", strType, strType, val.Name)
176-			if targetType != "i8*" {
177-				return fmt.Sprintf("ptrtoint (i8* %s to %s)", gep, targetType)
178-			}
179+			if targetType != "i8*" { return fmt.Sprintf("ptrtoint (i8* %s to %s)", gep, targetType) }
180 			return gep
181 		}
182 		sourceType := b.getType(val)
183-		if !strings.HasSuffix(sourceType, "*") {
184-			sourceType += "*"
185-		}
186+		if !strings.HasSuffix(sourceType, "*") { sourceType += "*" }
187 		return fmt.Sprintf("bitcast (%s @%s to %s)", sourceType, val.Name, targetType)
188 	default:
189 		return "0"
190@@ -271,9 +236,7 @@ func (b *llvmBackend) genFunc(fn *ir.Func) {
191 	b.currentFn = fn
192 	globalTypes := make(map[string]string)
193 	for k, v := range b.tempTypes {
194-		if strings.HasPrefix(k, "@") {
195-			globalTypes[k] = v
196-		}
197+		if strings.HasPrefix(k, "@") { globalTypes[k] = v }
198 	}
199 	b.tempTypes = globalTypes
200 
201@@ -282,26 +245,20 @@ func (b *llvmBackend) genFunc(fn *ir.Func) {
202 	for _, p := range fn.Params {
203 		pName := b.formatValue(p.Val)
204 		pType := b.formatType(p.Typ)
205-		if fn.Name == "main" && p.Name == "argv" {
206-			pType = "i8**"
207-		}
208+		if fn.Name == "main" && p.Name == "argv" { pType = "i8**" }
209 		params = append(params, fmt.Sprintf("%s %s", pType, pName))
210 		b.tempTypes[pName] = pType
211 	}
212 	paramStr := strings.Join(params, ", ")
213 	if fn.HasVarargs {
214-		if len(params) > 0 {
215-			paramStr += ", "
216-		}
217+		if len(params) > 0 { paramStr += ", " }
218 		paramStr += "..."
219 	}
220 
221 	fmt.Fprintf(b.out, "define %s @%s(%s) {\n", retTypeStr, fn.Name, paramStr)
222 	for i, block := range fn.Blocks {
223 		labelName := block.Label.Name
224-		if i == 0 {
225-			labelName = "entry"
226-		}
227+		if i == 0 { labelName = "entry" }
228 		fmt.Fprintf(b.out, "%s:\n", labelName)
229 		b.genBlock(block)
230 	}
231@@ -325,9 +282,7 @@ func (b *llvmBackend) genBlock(block *ir.BasicBlock) {
232 	for _, instr := range block.Instructions[:phiEndIndex] {
233 		if instr.Op == ir.OpPhi {
234 			cast := b.genPhi(instr)
235-			if cast != "" {
236-				deferredCasts = append(deferredCasts, cast)
237-			}
238+			if cast != "" { deferredCasts = append(deferredCasts, cast) }
239 		}
240 	}
241 
242@@ -341,23 +296,17 @@ func (b *llvmBackend) genBlock(block *ir.BasicBlock) {
243 }
244 
245 func (b *llvmBackend) genInstr(instr *ir.Instruction) {
246-	if instr.Op == ir.OpPhi {
247-		return
248-	}
249+	if instr.Op == ir.OpPhi { return }
250 
251 	resultName := ""
252-	if instr.Result != nil {
253-		resultName = b.formatValue(instr.Result)
254-	}
255+	if instr.Result != nil { resultName = b.formatValue(instr.Result) }
256 
257 	b.out.WriteString("\t")
258 
259 	switch instr.Op {
260 	case ir.OpAlloc:
261 		align := instr.Align
262-		if align == 0 {
263-			align = b.cfg.StackAlignment
264-		}
265+		if align == 0 { align = b.cfg.StackAlignment }
266 		sizeVal := b.prepareArg(instr.Args[0], b.wordType)
267 		fmt.Fprintf(b.out, "%s = alloca i8, %s %s, align %d\n", resultName, b.wordType, sizeVal, align)
268 		b.tempTypes[resultName] = "i8*"
269@@ -417,35 +366,135 @@ func (b *llvmBackend) genInstr(instr *ir.Instruction) {
270 		}
271 
272 	case ir.OpCEq, ir.OpCNeq, ir.OpCLt, ir.OpCGt, ir.OpCLe, ir.OpCGe:
273-		opStr, predicate := b.formatOp(instr.Op)
274-		lhsType := b.getType(instr.Args[0])
275-		rhsType := b.getType(instr.Args[1])
276-		valType := lhsType
277-		if _, ok := instr.Args[0].(*ir.Const); ok {
278-			valType = rhsType
279-		}
280+		lhsType, rhsType := b.getType(instr.Args[0]), b.getType(instr.Args[1])
281 
282-		lhs := b.prepareArg(instr.Args[0], valType)
283-		var rhs string
284+		var valType string
285+		lhsIsPtr := strings.HasSuffix(lhsType, "*") || (lhsType == "unknown" && b.isPointerValue(instr.Args[0]))
286+		rhsIsPtr := strings.HasSuffix(rhsType, "*") || (rhsType == "unknown" && b.isPointerValue(instr.Args[1]))
287 
288-		isPtrComparison := strings.HasSuffix(valType, "*")
289-		if c, ok := instr.Args[1].(*ir.Const); ok && c.Value == 0 && isPtrComparison {
290-			rhs = "null"
291-		} else if c, ok := instr.Args[0].(*ir.Const); ok && c.Value == 0 && strings.HasSuffix(rhsType, "*") {
292+		if lhsIsPtr || rhsIsPtr {
293+			valType = "i8*"
294+		} else if lhsType != "unknown" && lhsType != b.wordType {
295+			valType = lhsType
296+		} else if rhsType != "unknown" && rhsType != b.wordType {
297 			valType = rhsType
298-			lhs = b.prepareArg(instr.Args[0], valType)
299-			rhs = "null"
300 		} else {
301-			rhs = b.prepareArg(instr.Args[1], valType)
302+			valType = b.wordType
303+		}
304+
305+		isFloat := valType == "float" || valType == "double"
306+		var opStr, predicate string
307+		if isFloat {
308+			opStr = "fcmp"
309+			switch instr.Op {
310+			case ir.OpCEq: predicate = "oeq"
311+			case ir.OpCNeq: predicate = "one"
312+			case ir.OpCLt: predicate = "olt"
313+			case ir.OpCGt: predicate = "ogt"
314+			case ir.OpCLe: predicate = "ole"
315+			case ir.OpCGe: predicate = "oge"
316+			}
317+		} else {
318+			opStr = "icmp"
319+			switch instr.Op {
320+			case ir.OpCEq: predicate = "eq"
321+			case ir.OpCNeq: predicate = "ne"
322+			case ir.OpCLt: predicate = "slt"
323+			case ir.OpCGt: predicate = "sgt"
324+			case ir.OpCLe: predicate = "sle"
325+			case ir.OpCGe: predicate = "sge"
326+			}
327 		}
328 
329+		lhs := b.prepareArgForComparison(instr.Args[0], valType)
330+		rhs := b.prepareArgForComparison(instr.Args[1], valType)
331+
332 		i1Temp := b.newBackendTemp()
333 		fmt.Fprintf(b.out, "%s = %s %s %s %s, %s\n", i1Temp, opStr, predicate, valType, lhs, rhs)
334 		b.tempTypes[i1Temp] = "i1"
335 		fmt.Fprintf(b.out, "\t%s = zext i1 %s to %s\n", resultName, i1Temp, b.wordType)
336 		b.tempTypes[resultName] = b.wordType
337 
338-	default: // Other Binary ops
339+	case ir.OpNegF:
340+		opStr, _ := b.formatOp(instr.Op)
341+		valType := b.formatType(instr.Typ)
342+		arg := b.prepareArg(instr.Args[0], valType)
343+		fmt.Fprintf(b.out, "%s = %s %s %s\n", resultName, opStr, valType, arg)
344+		b.tempTypes[resultName] = valType
345+	case ir.OpSub, ir.OpSubF, ir.OpMul, ir.OpMulF, ir.OpDiv, ir.OpDivF, ir.OpRem, ir.OpRemF, ir.OpAnd, ir.OpOr, ir.OpXor, ir.OpShl, ir.OpShr:
346+		opStr, _ := b.formatOp(instr.Op)
347+		valType := b.formatType(instr.Typ)
348+		lhs := b.prepareArg(instr.Args[0], valType)
349+		rhs := b.prepareArg(instr.Args[1], valType)
350+		fmt.Fprintf(b.out, "%s = %s %s %s, %s\n", resultName, opStr, valType, lhs, rhs)
351+		b.tempTypes[resultName] = valType
352+
353+	case ir.OpBlit:
354+		if len(instr.Args) >= 2 {
355+			srcPtr := b.prepareArg(instr.Args[0], "i8*")
356+			dstPtr := b.prepareArg(instr.Args[1], "i8*")
357+			var sizeVal string
358+			if len(instr.Args) >= 3 {
359+				sizeVal = b.prepareArg(instr.Args[2], b.wordType)
360+			} else {
361+				sizeVal = fmt.Sprintf("%d", ir.SizeOfType(instr.Typ, b.cfg.WordSize))
362+			}
363+			fmt.Fprintf(b.out, "call void @llvm.memcpy.p0i8.p0i8.i64(i8* %s, i8* %s, i64 %s, i1 false)\n",
364+				dstPtr, srcPtr, sizeVal)
365+		}
366+
367+	case ir.OpSWToF, ir.OpSLToF:
368+		valType := b.formatType(instr.Typ)
369+		srcType := b.wordType
370+		if instr.Op == ir.OpSWToF { srcType = "i32" }
371+		srcVal := b.prepareArg(instr.Args[0], srcType)
372+		fmt.Fprintf(b.out, "%s = sitofp %s %s to %s\n", resultName, srcType, srcVal, valType)
373+		b.tempTypes[resultName] = valType
374+
375+	case ir.OpFToF:
376+		valType := b.formatType(instr.Typ)
377+		srcType := b.getType(instr.Args[0])
378+		srcVal := b.prepareArg(instr.Args[0], srcType)
379+
380+		var castOp string
381+		if valType == "double" && srcType == "float" {
382+			castOp = "fpext"
383+		} else if valType == "float" && srcType == "double" {
384+			castOp = "fptrunc"
385+		} else {
386+			castOp = "bitcast"
387+		}
388+		fmt.Fprintf(b.out, "%s = %s %s %s to %s\n", resultName, castOp, srcType, srcVal, valType)
389+		b.tempTypes[resultName] = valType
390+
391+	case ir.OpFToSI, ir.OpFToUI:
392+		valType := b.formatType(instr.Typ)
393+		srcType := b.getType(instr.Args[0])
394+		srcVal := b.prepareArg(instr.Args[0], srcType)
395+		castOp := "fptosi"
396+		if instr.Op == ir.OpFToUI { castOp = "fptoui" }
397+		fmt.Fprintf(b.out, "%s = %s %s %s to %s\n", resultName, castOp, srcType, srcVal, valType)
398+		b.tempTypes[resultName] = valType
399+
400+	case ir.OpExtSB, ir.OpExtUB, ir.OpExtSH, ir.OpExtUH, ir.OpExtSW, ir.OpExtUW:
401+		valType := b.formatType(instr.Typ)
402+		var srcType, castOp string
403+		switch instr.Op {
404+		case ir.OpExtSB, ir.OpExtUB:
405+			srcType, castOp = "i8", "sext"
406+			if instr.Op == ir.OpExtUB { castOp = "zext" }
407+		case ir.OpExtSH, ir.OpExtUH:
408+			srcType, castOp = "i16", "sext"
409+			if instr.Op == ir.OpExtUH { castOp = "zext" }
410+		case ir.OpExtSW, ir.OpExtUW:
411+			srcType, castOp = "i32", "sext"
412+			if instr.Op == ir.OpExtUW { castOp = "zext" }
413+		}
414+		srcVal := b.prepareArg(instr.Args[0], srcType)
415+		fmt.Fprintf(b.out, "%s = %s %s %s to %s\n", resultName, castOp, srcType, srcVal, valType)
416+		b.tempTypes[resultName] = valType
417+
418+	default:
419 		opStr, _ := b.formatOp(instr.Op)
420 		valType := b.formatType(instr.Typ)
421 		lhs := b.prepareArg(instr.Args[0], valType)
422@@ -470,24 +519,18 @@ func (b *llvmBackend) genPhi(instr *ir.Instruction) string {
423 		}
424 	}
425 
426-	if hasPtrInput && hasIntInput {
427-		phiType = "i8*"
428-	}
429+	if hasPtrInput && hasIntInput { phiType = "i8*" }
430 
431 	var pairs []string
432 	for i := 0; i < len(instr.Args); i += 2 {
433 		labelName := instr.Args[i].String()
434-		if labelName == "start" {
435-			labelName = "entry"
436-		}
437+		if labelName == "start" { labelName = "entry" }
438 		val := b.prepareArgForPhi(instr.Args[i+1], phiType)
439 		pairs = append(pairs, fmt.Sprintf("[ %s, %%%s ]", val, labelName))
440 	}
441 
442 	phiResultName := resultName
443-	if phiType != originalResultType {
444-		phiResultName = b.newBackendTemp()
445-	}
446+	if phiType != originalResultType { phiResultName = b.newBackendTemp() }
447 
448 	fmt.Fprintf(b.out, "\t%s = phi %s %s\n", phiResultName, phiType, strings.Join(pairs, ", "))
449 	b.tempTypes[phiResultName] = phiType
450@@ -503,22 +546,14 @@ func (b *llvmBackend) prepareArgForPhi(v ir.Value, targetType string) string {
451 	valStr := b.formatValue(v)
452 	currentType := b.getType(v)
453 
454-	if currentType == targetType || currentType == "unknown" {
455-		return valStr
456-	}
457+	if currentType == targetType || currentType == "unknown" { return valStr }
458 
459 	if c, isConst := v.(*ir.Const); isConst {
460-		if strings.HasSuffix(targetType, "*") && c.Value == 0 {
461-			return "null"
462-		}
463-		if strings.HasSuffix(targetType, "*") {
464-			return fmt.Sprintf("inttoptr (%s %s to %s)", currentType, valStr, targetType)
465-		}
466+		if strings.HasSuffix(targetType, "*") && c.Value == 0 { return "null" }
467+		if strings.HasSuffix(targetType, "*") { return fmt.Sprintf("inttoptr (%s %s to %s)", currentType, valStr, targetType) }
468 	}
469 
470-	if _, isGlobal := v.(*ir.Global); isGlobal {
471-		return fmt.Sprintf("bitcast (%s %s to %s)", currentType, valStr, targetType)
472-	}
473+	if _, isGlobal := v.(*ir.Global); isGlobal { return fmt.Sprintf("bitcast (%s %s to %s)", currentType, valStr, targetType) }
474 	return valStr
475 }
476 
477@@ -535,7 +570,6 @@ func (b *llvmBackend) genAdd(instr *ir.Instruction) {
478 	isLhsPtr := strings.HasSuffix(lhsType, "*") || (isLhsGlobal && !isLhsFunc)
479 	isRhsPtr := strings.HasSuffix(rhsType, "*") || (isRhsGlobal && !isRhsFunc)
480 
481-	// Case 1: Pointer + Integer arithmetic
482 	if (isLhsPtr && !isRhsPtr) || (!isLhsPtr && isRhsPtr) {
483 		var ptr ir.Value
484 		var ptrType string
485@@ -546,9 +580,7 @@ func (b *llvmBackend) genAdd(instr *ir.Instruction) {
486 			ptr, ptrType, offset = rhs, rhsType, lhs
487 		}
488 
489-		if ptrType == "unknown" {
490-			ptrType = "i8*"
491-		}
492+		if ptrType == "unknown" { ptrType = "i8*" }
493 
494 		i8PtrVal := b.prepareArg(ptr, "i8*")
495 		offsetVal := b.prepareArg(offset, b.wordType)
496@@ -566,7 +598,6 @@ func (b *llvmBackend) genAdd(instr *ir.Instruction) {
497 		return
498 	}
499 
500-	// Case 2: Pointer + Pointer (B-specific, treat as integer addition)
501 	if isLhsPtr && isRhsPtr {
502 		lhsInt := b.prepareArg(lhs, b.wordType)
503 		rhsInt := b.prepareArg(rhs, b.wordType)
504@@ -580,7 +611,6 @@ func (b *llvmBackend) genAdd(instr *ir.Instruction) {
505 		return
506 	}
507 
508-	// Case 3: Operands are not pointers.
509 	resultType := b.formatType(instr.Typ)
510 	if strings.HasSuffix(resultType, "*") {
511 		lhsInt := b.prepareArg(lhs, b.wordType)
512@@ -602,9 +632,7 @@ func (b *llvmBackend) genAdd(instr *ir.Instruction) {
513 
514 func (b *llvmBackend) genCall(instr *ir.Instruction) {
515 	resultName := ""
516-	if instr.Result != nil {
517-		resultName = b.formatValue(instr.Result)
518-	}
519+	if instr.Result != nil { resultName = b.formatValue(instr.Result) }
520 
521 	callee := instr.Args[0]
522 	calleeStr := b.formatValue(callee)
523@@ -616,9 +644,7 @@ func (b *llvmBackend) genCall(instr *ir.Instruction) {
524 		if instr.ArgTypes != nil && i < len(instr.ArgTypes) {
525 			targetType = b.formatType(instr.ArgTypes[i])
526 		} else if g, ok := arg.(*ir.Global); ok {
527-			if _, isString := b.prog.IsStringLabel(g.Name); isString {
528-				targetType = "i8*"
529-			}
530+			if _, isString := b.prog.IsStringLabel(g.Name); isString { targetType = "i8*" }
531 		}
532 		valStr := b.prepareArg(arg, targetType)
533 		argParts = append(argParts, fmt.Sprintf("%s %s", targetType, valStr))
534@@ -655,14 +681,11 @@ func (b *llvmBackend) prepareArg(v ir.Value, targetType string) string {
535 		}
536 	}
537 
538-	if _, ok := v.(*ir.Const); ok {
539-		return valStr
540-	}
541+	if _, ok := v.(*ir.Const); ok { return valStr }
542+	if _, ok := v.(*ir.FloatConst); ok { return valStr }
543 
544 	currentType := b.getType(v)
545-	if currentType == targetType || currentType == "unknown" {
546-		return valStr
547-	}
548+	if currentType == targetType || currentType == "unknown" { return valStr }
549 
550 	castTemp := b.newBackendTemp()
551 	b.out.WriteString("\t")
552@@ -679,50 +702,33 @@ func (b *llvmBackend) formatCast(sourceName, targetName, sourceType, targetType
553 
554 	var castOp string
555 	switch {
556-	case sourceType == "i1" && isTargetInt:
557-		castOp = "zext"
558-	case isSourceInt && targetType == "i1":
559-		return fmt.Sprintf("%s = icmp ne %s %s, 0", targetName, sourceType, sourceName)
560-	case isSourceInt && isTargetPtr:
561-		castOp = "inttoptr"
562-	case isSourcePtr && isTargetInt:
563-		castOp = "ptrtoint"
564-	case isSourcePtr && isTargetPtr:
565-		castOp = "bitcast"
566+	case sourceType == "i1" && isTargetInt: castOp = "zext"
567+	case isSourceInt && targetType == "i1": return fmt.Sprintf("%s = icmp ne %s %s, 0", targetName, sourceType, sourceName)
568+	case isSourceInt && isTargetPtr: castOp = "inttoptr"
569+	case isSourcePtr && isTargetInt: castOp = "ptrtoint"
570+	case isSourcePtr && isTargetPtr: castOp = "bitcast"
571 	case isSourceInt && isTargetInt:
572 		sourceBits, _ := strconv.Atoi(strings.TrimPrefix(sourceType, "i"))
573 		targetBits, _ := strconv.Atoi(strings.TrimPrefix(targetType, "i"))
574 		castOp = "sext"
575-		if sourceBits > targetBits {
576-			castOp = "trunc"
577-		}
578-	case isSourceInt && isTargetFloat:
579-		castOp = "sitofp"
580-	case isSourceFloat && isTargetInt:
581-		castOp = "fptosi"
582+		if sourceBits > targetBits { castOp = "trunc" }
583+	case isSourceInt && isTargetFloat: castOp = "sitofp"
584+	case isSourceFloat && isTargetInt: castOp = "fptosi"
585 	case isSourceFloat && isTargetFloat:
586 		castOp = "fpext"
587-		if sourceType == "double" {
588-			castOp = "fptrunc"
589-		}
590-	default:
591-		castOp = "bitcast"
592+		if sourceType == "double" { castOp = "fptrunc" }
593+	default: castOp = "bitcast"
594 	}
595 	return fmt.Sprintf("%s = %s %s %s to %s", targetName, castOp, sourceType, sourceName, targetType)
596 }
597 
598 func (b *llvmBackend) getType(v ir.Value) string {
599 	valStr := b.formatValue(v)
600-	if t, ok := b.tempTypes[valStr]; ok {
601-		return t
602-	}
603-	if _, ok := v.(*ir.Const); ok {
604-		return b.wordType
605-	}
606+	if t, ok := b.tempTypes[valStr]; ok { return t }
607+	if _, ok := v.(*ir.Const); ok { return b.wordType }
608+	if fc, ok := v.(*ir.FloatConst); ok { return b.formatType(fc.Typ) }
609 	if g, ok := v.(*ir.Global); ok {
610-		if _, isString := b.prog.IsStringLabel(g.Name); isString {
611-			return "i8*"
612-		}
613+		if _, isString := b.prog.IsStringLabel(g.Name); isString { return "i8*" }
614 	}
615 	return "unknown"
616 }
617@@ -734,88 +740,68 @@ func (b *llvmBackend) newBackendTemp() string {
618 }
619 
620 func (b *llvmBackend) formatValue(v ir.Value) string {
621-	if v == nil {
622-		return "void"
623-	}
624+	if v == nil { return "void" }
625 	switch val := v.(type) {
626-	case *ir.Const:
627-		return fmt.Sprintf("%d", val.Value)
628-	case *ir.Global:
629-		return "@" + val.Name
630+	case *ir.Const: return fmt.Sprintf("%d", val.Value)
631+	case *ir.FloatConst:
632+		if val.Typ == ir.TypeS {
633+			float32Val := float32(val.Value)
634+			float64Val := float64(float32Val)
635+			return fmt.Sprintf("0x%016X", math.Float64bits(float64Val))
636+		} else {
637+			return fmt.Sprintf("0x%016X", math.Float64bits(val.Value))
638+		}
639+	case *ir.Global: return "@" + val.Name
640 	case *ir.Temporary:
641 		safeName := strings.NewReplacer(".", "_", "[", "_", "]", "_").Replace(val.Name)
642-		if safeName != "" {
643-			return fmt.Sprintf("%%.%s_%d", safeName, val.ID)
644-		}
645+		if val.ID == -1 { return "%" + safeName }
646+		if safeName != "" { return fmt.Sprintf("%%.%s_%d", safeName, val.ID) }
647 		return fmt.Sprintf("%%t%d", val.ID)
648-	case *ir.Label:
649-		return "%" + val.Name
650-	case *ir.CastValue:
651-		return b.formatValue(val.Value)
652-	default:
653-		return ""
654+	case *ir.Label: return "%" + val.Name
655+	case *ir.CastValue: return b.formatValue(val.Value)
656+	default: return ""
657 	}
658 }
659 
660 func (b *llvmBackend) formatType(t ir.Type) string {
661 	switch t {
662-	case ir.TypeB:
663-		return "i8"
664-	case ir.TypeH:
665-		return "i16"
666-	case ir.TypeW:
667-		return "i32"
668-	case ir.TypeL:
669-		return "i64"
670-	case ir.TypeS:
671-		return "float"
672-	case ir.TypeD:
673-		return "double"
674-	case ir.TypeNone:
675-		return "void"
676-	case ir.TypePtr:
677-		return "i8*"
678-	default:
679-		return b.wordType
680+	case ir.TypeB: return "i8"
681+	case ir.TypeH: return "i16"
682+	case ir.TypeW: return "i32"
683+	case ir.TypeL: return "i64"
684+	case ir.TypeS: return "float"
685+	case ir.TypeD: return "double"
686+	case ir.TypeNone: return "void"
687+	case ir.TypePtr: return "i8*"
688+	default: return b.wordType
689 	}
690 }
691 
692 func (b *llvmBackend) formatOp(op ir.Op) (string, string) {
693 	switch op {
694-	case ir.OpAdd:
695-		return "add", ""
696-	case ir.OpSub:
697-		return "sub", ""
698-	case ir.OpMul:
699-		return "mul", ""
700-	case ir.OpDiv:
701-		return "sdiv", ""
702-	case ir.OpRem:
703-		return "srem", ""
704-	case ir.OpAnd:
705-		return "and", ""
706-	case ir.OpOr:
707-		return "or", ""
708-	case ir.OpXor:
709-		return "xor", ""
710-	case ir.OpShl:
711-		return "shl", ""
712-	case ir.OpShr:
713-		return "ashr", ""
714-	case ir.OpCEq:
715-		return "icmp", "eq"
716-	case ir.OpCNeq:
717-		return "icmp", "ne"
718-	case ir.OpCLt:
719-		return "icmp", "slt"
720-	case ir.OpCGt:
721-		return "icmp", "sgt"
722-	case ir.OpCLe:
723-		return "icmp", "sle"
724-	case ir.OpCGe:
725-		return "icmp", "sge"
726-	default:
727-		return "unknown_op", ""
728+	case ir.OpAdd: return "add", ""
729+	case ir.OpSub: return "sub", ""
730+	case ir.OpMul: return "mul", ""
731+	case ir.OpDiv: return "sdiv", ""
732+	case ir.OpRem: return "srem", ""
733+	case ir.OpAddF: return "fadd", ""
734+	case ir.OpSubF: return "fsub", ""
735+	case ir.OpMulF: return "fmul", ""
736+	case ir.OpDivF: return "fdiv", ""
737+	case ir.OpRemF: return "frem", ""
738+	case ir.OpNegF: return "fneg", ""
739+	case ir.OpAnd: return "and", ""
740+	case ir.OpOr: return "or", ""
741+	case ir.OpXor: return "xor", ""
742+	case ir.OpShl: return "shl", ""
743+	case ir.OpShr: return "ashr", ""
744+	case ir.OpCEq: return "icmp", "eq"
745+	case ir.OpCNeq: return "icmp", "ne"
746+	case ir.OpCLt: return "icmp", "slt"
747+	case ir.OpCGt: return "icmp", "sgt"
748+	case ir.OpCLe: return "icmp", "sle"
749+	case ir.OpCGe: return "icmp", "sge"
750+	default: return "unknown_op", ""
751 	}
752 }
753 
754@@ -830,3 +816,16 @@ func (b *llvmBackend) escapeString(s string) string {
755 	}
756 	return sb.String()
757 }
758+
759+func (b *llvmBackend) isPointerValue(v ir.Value) bool {
760+	if g, ok := v.(*ir.Global); ok {
761+		if _, isString := b.prog.IsStringLabel(g.Name); isString { return true }
762+		return b.prog.FindFunc(g.Name) == nil && b.funcSigs[g.Name] == ""
763+	}
764+	return false
765+}
766+
767+func (b *llvmBackend) prepareArgForComparison(v ir.Value, targetType string) string {
768+	if c, isConst := v.(*ir.Const); isConst && c.Value == 0 && strings.HasSuffix(targetType, "*") { return "null" }
769+	return b.prepareArg(v, targetType)
770+}
M pkg/codegen/qbe_backend.go
+250, -166
  1@@ -6,23 +6,21 @@ import (
  2 	"strconv"
  3 	"strings"
  4 
  5+	"github.com/xplshn/gbc/pkg/ast"
  6 	"github.com/xplshn/gbc/pkg/config"
  7 	"github.com/xplshn/gbc/pkg/ir"
  8 	"modernc.org/libqbe"
  9 )
 10 
 11-// qbeBackend implements the Backend interface for the QBE intermediate language.
 12 type qbeBackend struct {
 13-	out  *strings.Builder
 14-	prog *ir.Program
 15+	out         *strings.Builder
 16+	prog        *ir.Program
 17+	currentFn   *ir.Func
 18+	structTypes map[string]bool
 19 }
 20 
 21-// NewQBEBackend creates a new instance of the QBE backend.
 22-func NewQBEBackend() Backend {
 23-	return &qbeBackend{}
 24-}
 25+func NewQBEBackend() Backend { return &qbeBackend{structTypes: make(map[string]bool)} }
 26 
 27-// Generate translates a generic IR program into QBE intermediate language text.
 28 func (b *qbeBackend) Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buffer, error) {
 29 	var qbeIRBuilder strings.Builder
 30 	b.out = &qbeIRBuilder
 31@@ -32,19 +30,22 @@ func (b *qbeBackend) Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buff
 32 
 33 	qbeIR := qbeIRBuilder.String()
 34 	var asmBuf bytes.Buffer
 35-	if err := libqbe.Main(cfg.BackendTarget, "input.ssa", strings.NewReader(qbeIR), &asmBuf, nil); err != nil {
 36+	err := libqbe.Main(cfg.BackendTarget, "input.ssa", strings.NewReader(qbeIR), &asmBuf, nil)
 37+	if err != nil {
 38 		return nil, fmt.Errorf("\n--- QBE Compilation Failed ---\nGenerated IR:\n%s\n\nlibqbe error: %w", qbeIR, err)
 39 	}
 40 	return &asmBuf, nil
 41 }
 42 
 43 func (b *qbeBackend) gen() {
 44+	b.genStructTypes()
 45+
 46 	for _, g := range b.prog.Globals {
 47 		b.genGlobal(g)
 48 	}
 49 
 50 	if len(b.prog.Strings) > 0 {
 51-		b.out.WriteString("\n# --- String Literals ---\n")
 52+		b.out.WriteString("\n")
 53 		for s, label := range b.prog.Strings {
 54 			escaped := strconv.Quote(s)
 55 			fmt.Fprintf(b.out, "data $%s = { b %s, b 0 }\n", label, escaped)
 56@@ -56,40 +57,135 @@ func (b *qbeBackend) gen() {
 57 	}
 58 }
 59 
 60+func (b *qbeBackend) formatFieldType(t *ast.BxType) (string, bool) {
 61+	if t == nil { return b.formatType(ir.GetType(nil, b.prog.WordSize)), true }
 62+	switch t.Kind {
 63+	case ast.TYPE_STRUCT:
 64+		if t.Name != "" {
 65+			if _, defined := b.structTypes[t.Name]; defined { return ":" + t.Name, true }
 66+		}
 67+		return "", false
 68+	case ast.TYPE_POINTER, ast.TYPE_ARRAY:
 69+		return b.formatType(ir.GetType(nil, b.prog.WordSize)), true
 70+	default:
 71+		return b.formatType(ir.GetType(t, b.prog.WordSize)), true
 72+	}
 73+}
 74+
 75+func (b *qbeBackend) genStructTypes() {
 76+	allStructs := make(map[string]*ast.BxType)
 77+
 78+	var collect func(t *ast.BxType)
 79+	collect = func(t *ast.BxType) {
 80+		if t == nil { return }
 81+		if t.Kind == ast.TYPE_STRUCT {
 82+			if _, exists := allStructs[t.Name]; !exists && t.Name != "" {
 83+				allStructs[t.Name] = t
 84+				for _, f := range t.Fields {
 85+					collect(f.Data.(ast.VarDeclNode).Type)
 86+				}
 87+			}
 88+		} else if t.Kind == ast.TYPE_POINTER || t.Kind == ast.TYPE_ARRAY {
 89+			collect(t.Base)
 90+		}
 91+	}
 92+
 93+	for _, g := range b.prog.Globals {
 94+		collect(g.AstType)
 95+	}
 96+	for _, f := range b.prog.Funcs {
 97+		collect(f.AstReturnType)
 98+		if f.AstParams != nil {
 99+			for _, pNode := range f.AstParams {
100+				if pNode.Type == ast.VarDecl {
101+					collect(pNode.Data.(ast.VarDeclNode).Type)
102+				}
103+			}
104+		} else if len(f.Params) > 0 && f.Name != "" {
105+			if symNode := b.prog.FindFuncSymbol(f.Name); symNode != nil {
106+				if decl, ok := symNode.Data.(ast.FuncDeclNode); ok {
107+					for _, p := range decl.Params {
108+						if p.Type == ast.VarDecl {
109+							collect(p.Data.(ast.VarDeclNode).Type)
110+						}
111+					}
112+				}
113+			}
114+		}
115+	}
116+
117+	if len(allStructs) == 0 { return }
118+
119+	b.out.WriteString("\n")
120+	definedCount := -1
121+	for len(b.structTypes) < len(allStructs) && len(b.structTypes) != definedCount {
122+		definedCount = len(b.structTypes)
123+		for name, typ := range allStructs {
124+			if b.structTypes[name] { continue }
125+
126+			var fieldTypes []string
127+			canDefine := true
128+			for _, field := range typ.Fields {
129+				fType := field.Data.(ast.VarDeclNode).Type
130+				typeStr, ok := b.formatFieldType(fType)
131+				if !ok {
132+					canDefine = false
133+					break
134+				}
135+				fieldTypes = append(fieldTypes, typeStr)
136+			}
137+
138+			if canDefine {
139+				fmt.Fprintf(b.out, "type :%s = { %s }\n", name, strings.Join(fieldTypes, ", "))
140+				b.structTypes[name] = true
141+			}
142+		}
143+	}
144+}
145+
146 func (b *qbeBackend) genGlobal(g *ir.Data) {
147-	fmt.Fprintf(b.out, "data $%s = align %d { ", g.Name, g.Align)
148+	alignStr := ""
149+	if g.Align > 0 { alignStr = fmt.Sprintf("align %d ", g.Align) }
150+
151+	fmt.Fprintf(b.out, "data $%s = %s{ ", g.Name, alignStr)
152 	for i, item := range g.Items {
153-		if item.Count > 0 { // Zero-initialized
154-			fmt.Fprintf(b.out, "z %d", item.Count*int(ir.SizeOfType(item.Typ, b.prog.WordSize)))
155+		if item.Count > 0 {
156+			size := int64(item.Count)
157+			if item.Typ != ir.TypeB {
158+				size *= ir.SizeOfType(item.Typ, b.prog.WordSize)
159+			}
160+			fmt.Fprintf(b.out, "z %d", size)
161 		} else {
162 			fmt.Fprintf(b.out, "%s %s", b.formatType(item.Typ), b.formatValue(item.Value))
163 		}
164-		if i < len(g.Items)-1 {
165-			b.out.WriteString(", ")
166-		}
167+		if i < len(g.Items)-1 { b.out.WriteString(", ") }
168 	}
169 	b.out.WriteString(" }\n")
170 }
171 
172 func (b *qbeBackend) genFunc(fn *ir.Func) {
173-	retTypeStr := b.formatType(fn.ReturnType)
174-	if retTypeStr != "" {
175-		retTypeStr = " " + retTypeStr
176+	b.currentFn = fn
177+	var retTypeStr string
178+	if fn.AstReturnType != nil && fn.AstReturnType.Kind == ast.TYPE_STRUCT {
179+		retTypeStr = " :" + fn.AstReturnType.Name
180+	} else {
181+		retTypeStr = b.formatType(fn.ReturnType)
182+		if retTypeStr != "" { retTypeStr = " " + retTypeStr }
183 	}
184 
185 	fmt.Fprintf(b.out, "\nexport function%s $%s(", retTypeStr, fn.Name)
186 
187 	for i, p := range fn.Params {
188-		fmt.Fprintf(b.out, "%s %s", b.formatType(p.Typ), b.formatValue(p.Val))
189-		if i < len(fn.Params)-1 {
190-			b.out.WriteString(", ")
191+		paramType := p.Typ
192+		if paramType == ir.TypeB || paramType == ir.TypeH {
193+			paramType = ir.GetType(nil, b.prog.WordSize)
194 		}
195+		fmt.Fprintf(b.out, "%s %s", b.formatType(paramType), b.formatValue(p.Val))
196+		if i < len(fn.Params)-1 { b.out.WriteString(", ") }
197 	}
198 
199 	if fn.HasVarargs {
200-		if len(fn.Params) > 0 {
201-			b.out.WriteString(", ")
202-		}
203+		if len(fn.Params) > 0 { b.out.WriteString(", ") }
204 		b.out.WriteString("...")
205 	}
206 	b.out.WriteString(") {\n")
207@@ -109,199 +205,187 @@ func (b *qbeBackend) genBlock(block *ir.BasicBlock) {
208 }
209 
210 func (b *qbeBackend) genInstr(instr *ir.Instruction) {
211+	if instr.Op == ir.OpCall {
212+		b.out.WriteString("\t")
213+		b.genCall(instr)
214+		return
215+	}
216+
217 	b.out.WriteString("\t")
218 	if instr.Result != nil {
219 		resultType := instr.Typ
220-		isComparison := false
221-		switch instr.Op {
222-		case ir.OpCEq, ir.OpCNeq, ir.OpCLt, ir.OpCGt, ir.OpCLe, ir.OpCGe:
223-			isComparison = true
224-		}
225+		isComparison := instr.Op >= ir.OpCEq && instr.Op <= ir.OpCGe
226 
227-		if isComparison {
228-			resultType = ir.GetType(nil, b.prog.WordSize)
229-		}
230+		if isComparison { resultType = ir.GetType(nil, b.prog.WordSize) }
231 
232 		if instr.Op == ir.OpLoad && (instr.Typ == ir.TypeB || instr.Typ == ir.TypeH) {
233 			resultType = ir.GetType(nil, b.prog.WordSize)
234 		}
235 
236+		if instr.Op == ir.OpCast && (resultType == ir.TypeB || resultType == ir.TypeH) {
237+			resultType = ir.TypeW
238+		}
239+
240 		fmt.Fprintf(b.out, "%s =%s ", b.formatValue(instr.Result), b.formatType(resultType))
241 	}
242 
243-	opStr, isCall := b.formatOp(instr)
244+	opStr, _ := b.formatOp(instr)
245 	b.out.WriteString(opStr)
246 
247-	if isCall {
248-		fmt.Fprintf(b.out, " %s(", b.formatValue(instr.Args[0]))
249-		for i, arg := range instr.Args[1:] {
250-			argType := ir.GetType(nil, b.prog.WordSize)
251-			if instr.ArgTypes != nil && i < len(instr.ArgTypes) {
252-				argType = instr.ArgTypes[i]
253-			}
254-			if argType == ir.TypeB || argType == ir.TypeH {
255-				argType = ir.GetType(nil, b.prog.WordSize)
256-			}
257-
258-			fmt.Fprintf(b.out, "%s %s", b.formatType(argType), b.formatValue(arg))
259-			if i < len(instr.Args)-2 {
260-				b.out.WriteString(", ")
261-			}
262-		}
263-		b.out.WriteString(")\n")
264-		return
265-	}
266-
267 	if instr.Op == ir.OpPhi {
268 		for i := 0; i < len(instr.Args); i += 2 {
269 			fmt.Fprintf(b.out, " @%s %s", instr.Args[i].String(), b.formatValue(instr.Args[i+1]))
270-			if i+2 < len(instr.Args) {
271-				b.out.WriteString(",")
272-			}
273+			if i+2 < len(instr.Args) { b.out.WriteString(",") }
274 		}
275 	} else {
276 		for i, arg := range instr.Args {
277 			b.out.WriteString(" ")
278-			if arg != nil {
279-				b.out.WriteString(b.formatValue(arg))
280-			}
281-			if i < len(instr.Args)-1 {
282-				b.out.WriteString(",")
283-			}
284+			if arg != nil { b.out.WriteString(b.formatValue(arg)) }
285+			if i < len(instr.Args)-1 { b.out.WriteString(",") }
286 		}
287 	}
288 	b.out.WriteString("\n")
289 }
290 
291-func (b *qbeBackend) formatValue(v ir.Value) string {
292-	if v == nil {
293-		return ""
294+func (b *qbeBackend) genCall(instr *ir.Instruction) {
295+	callee := instr.Args[0]
296+	calleeName := ""
297+	if g, ok := callee.(*ir.Global); ok { calleeName = g.Name }
298+
299+	if instr.Result != nil {
300+		var retTypeStr string
301+		calledFunc := b.prog.FindFunc(calleeName)
302+		if calledFunc != nil && calledFunc.AstReturnType != nil && calledFunc.AstReturnType.Kind == ast.TYPE_STRUCT {
303+			retTypeStr = " :" + calledFunc.AstReturnType.Name
304+		} else {
305+			actualReturnType := instr.Typ
306+			if len(instr.ArgTypes) > 0 {
307+				argType := instr.ArgTypes[0]
308+				if argType == ir.TypeS || argType == ir.TypeD {
309+					switch calleeName {
310+					case "sqrt", "sin", "cos", "fabs":
311+						actualReturnType = argType
312+					}
313+				}
314+			}
315+			retTypeStr = b.formatType(actualReturnType)
316+		}
317+		fmt.Fprintf(b.out, "%s =%s ", b.formatValue(instr.Result), retTypeStr)
318 	}
319+
320+	fmt.Fprintf(b.out, "call %s(", b.formatValue(callee))
321+
322+	for i, arg := range instr.Args[1:] {
323+		argType := ir.GetType(nil, b.prog.WordSize)
324+		if instr.ArgTypes != nil && i < len(instr.ArgTypes) {
325+			argType = instr.ArgTypes[i]
326+		}
327+		if argType == ir.TypeB || argType == ir.TypeH {
328+			argType = ir.GetType(nil, b.prog.WordSize)
329+		}
330+
331+		fmt.Fprintf(b.out, "%s %s", b.formatType(argType), b.formatValue(arg))
332+		if i < len(instr.Args)-2 { b.out.WriteString(", ") }
333+	}
334+	b.out.WriteString(")\n")
335+}
336+
337+func (b *qbeBackend) formatValue(v ir.Value) string {
338+	if v == nil { return "" }
339 	switch val := v.(type) {
340-	case *ir.Const:
341-		return fmt.Sprintf("%d", val.Value)
342-	case *ir.FloatConst:
343-		return fmt.Sprintf("%s_%f", b.formatType(val.Typ), val.Value)
344-	case *ir.Global:
345-		return "$" + val.Name
346+	case *ir.Const: return fmt.Sprintf("%d", val.Value)
347+	case *ir.FloatConst: return fmt.Sprintf("%s_%f", b.formatType(val.Typ), val.Value)
348+	case *ir.Global: return "$" + val.Name
349 	case *ir.Temporary:
350 		safeName := strings.NewReplacer(".", "_", "[", "_", "]", "_").Replace(val.Name)
351-		if safeName != "" {
352-			return fmt.Sprintf("%%.%s_%d", safeName, val.ID)
353-		}
354+		if val.ID == -1 { return "%" + safeName }
355+		if safeName != "" { return fmt.Sprintf("%%.%s_%d", safeName, val.ID) }
356 		return fmt.Sprintf("%%t%d", val.ID)
357-	case *ir.Label:
358-		return "@" + val.Name
359-	default:
360-		return ""
361+	case *ir.Label: return "@" + val.Name
362+	default: return ""
363 	}
364 }
365 
366 func (b *qbeBackend) formatType(t ir.Type) string {
367 	switch t {
368-	case ir.TypeB:
369-		return "b"
370-	case ir.TypeH:
371-		return "h"
372-	case ir.TypeW:
373-		return "w"
374-	case ir.TypeL:
375-		return "l"
376-	case ir.TypeS:
377-		return "s"
378-	case ir.TypeD:
379-		return "d"
380-	case ir.TypePtr:
381-		return b.formatType(ir.GetType(nil, b.prog.WordSize))
382-	default:
383-		return ""
384+	case ir.TypeB: return "b"
385+	case ir.TypeH: return "h"
386+	case ir.TypeW: return "w"
387+	case ir.TypeL: return "l"
388+	case ir.TypeS: return "s"
389+	case ir.TypeD: return "d"
390+	case ir.TypePtr: return b.formatType(ir.GetType(nil, b.prog.WordSize))
391+	default: return ""
392 	}
393 }
394 
395-func (b *qbeBackend) getCmpInstType(t ir.Type) string {
396-	if t == ir.TypeB || t == ir.TypeH {
397-		return b.formatType(ir.GetType(nil, b.prog.WordSize))
398-	}
399-	return b.formatType(t)
400+func (b *qbeBackend) getCmpInstType(argType ir.Type) string {
401+	if argType == ir.TypeB || argType == ir.TypeH { return b.formatType(ir.GetType(nil, b.prog.WordSize)) }
402+	return b.formatType(argType)
403 }
404 
405 func (b *qbeBackend) formatOp(instr *ir.Instruction) (opStr string, isCall bool) {
406 	typ := instr.Typ
407+	argType := instr.OperandType
408+	if argType == ir.TypeNone { argType = instr.Typ }
409+
410 	typeStr := b.formatType(typ)
411+	argTypeStr := b.getCmpInstType(argType)
412+
413 	switch instr.Op {
414 	case ir.OpAlloc:
415-		// QBE's stack alloc instruction is based on word size, not arbitrary alignment.
416-		return "alloc" + strconv.Itoa(b.prog.WordSize), false
417+		if instr.Align <= 4 { return "alloc4", false }
418+		if instr.Align <= 8 { return "alloc8", false }
419+		return "alloc16", false
420 	case ir.OpLoad:
421 		switch typ {
422-		case ir.TypeB:
423-			return "loadub", false
424-		case ir.TypeH:
425-			return "loaduh", false
426-		case ir.TypePtr:
427-			return "load" + b.formatType(ir.GetType(nil, b.prog.WordSize)), false
428-		default:
429-			return "load" + typeStr, false
430+		case ir.TypeB: return "loadub", false
431+		case ir.TypeH: return "loaduh", false
432+		case ir.TypePtr: return "load" + b.formatType(ir.GetType(nil, b.prog.WordSize)), false
433+		default: return "load" + typeStr, false
434 		}
435-	case ir.OpStore:
436-		return "store" + typeStr, false
437-	case ir.OpBlit:
438-		return "blit", false
439-	case ir.OpAdd:
440-		return "add", false
441-	case ir.OpSub:
442-		return "sub", false
443-	case ir.OpMul:
444-		return "mul", false
445-	case ir.OpDiv:
446-		return "div", false
447-	case ir.OpRem:
448-		return "rem", false
449-	case ir.OpAnd:
450-		return "and", false
451-	case ir.OpOr:
452-		return "or", false
453-	case ir.OpXor:
454-		return "xor", false
455-	case ir.OpShl:
456-		return "shl", false
457-	case ir.OpShr:
458-		return "shr", false
459-	case ir.OpCEq:
460-		return "ceq" + b.getCmpInstType(typ), false
461-	case ir.OpCNeq:
462-		return "cne" + b.getCmpInstType(typ), false
463+	case ir.OpStore: return "store" + typeStr, false
464+	case ir.OpBlit: return "blit", false
465+	case ir.OpAdd, ir.OpAddF: return "add", false
466+	case ir.OpSub, ir.OpSubF: return "sub", false
467+	case ir.OpMul, ir.OpMulF: return "mul", false
468+	case ir.OpDiv, ir.OpDivF: return "div", false
469+	case ir.OpRem, ir.OpRemF: return "rem", false
470+	case ir.OpAnd: return "and", false
471+	case ir.OpOr: return "or", false
472+	case ir.OpXor: return "xor", false
473+	case ir.OpShl: return "shl", false
474+	case ir.OpShr: return "shr", false
475+	case ir.OpNegF: return "neg", false
476+	case ir.OpCEq: return "ceq" + argTypeStr, false
477+	case ir.OpCNeq: return "cne" + argTypeStr, false
478 	case ir.OpCLt:
479-		if typ == ir.TypeS || typ == ir.TypeD {
480-			return "clt" + typeStr, false
481-		}
482-		return "cslt" + b.getCmpInstType(typ), false
483+		if argType == ir.TypeS || argType == ir.TypeD { return "clt" + argTypeStr, false }
484+		return "cslt" + argTypeStr, false
485 	case ir.OpCGt:
486-		if typ == ir.TypeS || typ == ir.TypeD {
487-			return "cgt" + typeStr, false
488-		}
489-		return "csgt" + b.getCmpInstType(typ), false
490+		if argType == ir.TypeS || argType == ir.TypeD { return "cgt" + argTypeStr, false }
491+		return "csgt" + argTypeStr, false
492 	case ir.OpCLe:
493-		if typ == ir.TypeS || typ == ir.TypeD {
494-			return "cle" + typeStr, false
495-		}
496-		return "csle" + b.getCmpInstType(typ), false
497+		if argType == ir.TypeS || argType == ir.TypeD { return "cle" + argTypeStr, false }
498+		return "csle" + argTypeStr, false
499 	case ir.OpCGe:
500-		if typ == ir.TypeS || typ == ir.TypeD {
501-			return "cge" + typeStr, false
502-		}
503-		return "csge" + b.getCmpInstType(typ), false
504-	case ir.OpJmp:
505-		return "jmp", false
506-	case ir.OpJnz:
507-		return "jnz", false
508-	case ir.OpRet:
509-		return "ret", false
510-	case ir.OpCall:
511-		return "call", true
512-	case ir.OpPhi:
513-		return "phi", false
514-	default:
515-		return "unknown_op", false
516+		if argType == ir.TypeS || argType == ir.TypeD { return "cge" + argTypeStr, false }
517+		return "csge" + argTypeStr, false
518+	case ir.OpJmp: return "jmp", false
519+	case ir.OpJnz: return "jnz", false
520+	case ir.OpRet: return "ret", false
521+	case ir.OpCall: return "call", true
522+	case ir.OpPhi: return "phi", false
523+	case ir.OpSWToF: return "swtof", false
524+	case ir.OpSLToF: return "sltof", false
525+	case ir.OpFToF:
526+		if typ == ir.TypeD { return "exts", false }
527+		return "truncd", false
528+	case ir.OpExtSB, ir.OpExtUB, ir.OpExtSH, ir.OpExtUH, ir.OpExtSW, ir.OpExtUW:
529+		return "exts" + string(b.formatType(argType)[0]), false
530+	case ir.OpFToSI: return "ftosi", false
531+	case ir.OpFToUI: return "ftoui", false
532+	case ir.OpCast: return "copy", false
533+	default: return "unknown_op", false
534 	}
535 }
M pkg/config/config.go
+75, -93
  1@@ -28,6 +28,8 @@ const (
  2 	FeatStrictDecl
  3 	FeatNoDirectives
  4 	FeatContinue
  5+	FeatFloat
  6+	FeatStrictTypes
  7 	FeatCount
  8 )
  9 
 10@@ -48,6 +50,8 @@ const (
 11 	WarnImplicitDecl
 12 	WarnType
 13 	WarnExtra
 14+	WarnFloat
 15+	WarnLocalAddress
 16 	WarnCount
 17 )
 18 
 19@@ -57,14 +61,12 @@ type Info struct {
 20 	Description string
 21 }
 22 
 23-// Backend-specific properties
 24 type Target struct {
 25 	GOOS           string
 26 	GOARCH         string
 27-	BackendName    string // "qbe", "llvm"
 28-	BackendTarget  string // "amd64_sysv", "x86_64-unknown-linux-musl"
 29+	BackendName    string
 30+	BackendTarget  string
 31 	WordSize       int
 32-	WordType       string // QBE type char
 33 	StackAlignment int
 34 }
 35 
 36@@ -82,14 +84,13 @@ var archTranslations = map[string]string{
 37 
 38 var archProperties = map[string]struct {
 39 	WordSize       int
 40-	WordType       string
 41 	StackAlignment int
 42 }{
 43-	"amd64":   {WordSize: 8, WordType: "l", StackAlignment: 16},
 44-	"arm64":   {WordSize: 8, WordType: "l", StackAlignment: 16},
 45-	"386":     {WordSize: 4, WordType: "w", StackAlignment: 8},
 46-	"arm":     {WordSize: 4, WordType: "w", StackAlignment: 8},
 47-	"riscv64": {WordSize: 8, WordType: "l", StackAlignment: 16},
 48+	"amd64":   {WordSize: 8, StackAlignment: 16},
 49+	"arm64":   {WordSize: 8, StackAlignment: 16},
 50+	"386":     {WordSize: 4, StackAlignment: 8},
 51+	"arm":     {WordSize: 4, StackAlignment: 8},
 52+	"riscv64": {WordSize: 8, StackAlignment: 16},
 53 }
 54 
 55 type Config struct {
 56@@ -116,37 +117,41 @@ func NewConfig() *Config {
 57 	}
 58 
 59 	features := map[Feature]Info{
 60-		FeatExtrn:              {"extrn", true, "Allow the 'extrn' keyword."},
 61-		FeatAsm:                {"asm", true, "Allow `__asm__` blocks for inline assembly."},
 62-		FeatBEsc:               {"b-esc", false, "Recognize B-style '*' character escapes."},
 63-		FeatCEsc:               {"c-esc", true, "Recognize C-style '\\' character escapes."},
 64-		FeatBOps:               {"b-ops", false, "Recognize B-style assignment operators like '=+'."},
 65-		FeatCOps:               {"c-ops", true, "Recognize C-style assignment operators like '+='."},
 66-		FeatCComments:          {"c-comments", true, "Recognize C-style '//' line comments."},
 67-		FeatTyped:              {"typed", true, "Enable the Bx opt-in & backwards-compatible type system."},
 68-		FeatShortDecl:          {"short-decl", true, "Enable Bx-style short declaration `:=`."},
 69-		FeatBxDeclarations:     {"bx-decl", true, "Enable Bx-style `auto name = val` declarations."},
 70-		FeatAllowUninitialized: {"allow-uninitialized", true, "Allow declarations without an initializer (`var;` or `auto var;`)."},
 71-		FeatStrictDecl:         {"strict-decl", false, "Require all declarations to be initialized."},
 72-		FeatContinue:           {"continue", true, "Allow the Bx keyword `continue` to be used."},
 73-		FeatNoDirectives:       {"no-directives", false, "Disable `// [b]:` directives."},
 74+		FeatExtrn:              {"extrn", true, "Allow the 'extrn' keyword"},
 75+		FeatAsm:                {"asm", true, "Allow `__asm__` blocks for inline assembly"},
 76+		FeatBEsc:               {"b-esc", false, "Recognize B-style '*' character escapes"},
 77+		FeatCEsc:               {"c-esc", true, "Recognize C-style '\\' character escapes"},
 78+		FeatBOps:               {"b-ops", false, "Recognize B-style assignment operators like '=+'"},
 79+		FeatCOps:               {"c-ops", true, "Recognize C-style assignment operators like '+='"},
 80+		FeatCComments:          {"c-comments", true, "Recognize C-style '//' line comments"},
 81+		FeatTyped:              {"typed", true, "Enable the Bx opt-in & backwards-compatible type system"},
 82+		FeatShortDecl:          {"short-decl", true, "Enable Bx-style short declaration `:=`"},
 83+		FeatBxDeclarations:     {"bx-decl", true, "Enable Bx-style `auto name = val` declarations"},
 84+		FeatAllowUninitialized: {"allow-uninitialized", true, "Allow declarations without an initializer (`var;` or `auto var;`)"},
 85+		FeatStrictDecl:         {"strict-decl", false, "Require all declarations to be initialized"},
 86+		FeatContinue:           {"continue", true, "Allow the Bx keyword `continue` to be used"},
 87+		FeatNoDirectives:       {"no-directives", false, "Disable `// [b]:` directives"},
 88+		FeatFloat:              {"float", true, "Enable support for floating-point numbers"},
 89+		FeatStrictTypes:        {"strict-types", false, "Disallow all incompatible type operations"},
 90 	}
 91 
 92 	warnings := map[Warning]Info{
 93-		WarnCEsc:               {"c-esc", false, "Warn on usage of C-style '\\' escapes."},
 94-		WarnBEsc:               {"b-esc", true, "Warn on usage of B-style '*' escapes."},
 95-		WarnBOps:               {"b-ops", true, "Warn on usage of B-style assignment operators like '=+'."},
 96-		WarnCOps:               {"c-ops", false, "Warn on usage of C-style assignment operators like '+='."},
 97-		WarnUnrecognizedEscape: {"u-esc", true, "Warn on unrecognized character escape sequences."},
 98-		WarnTruncatedChar:      {"truncated-char", true, "Warn when a character escape value is truncated."},
 99-		WarnLongCharConst:      {"long-char-const", true, "Warn when a multi-character constant is too long for a word."},
100-		WarnCComments:          {"c-comments", false, "Warn on usage of non-standard C-style '//' comments."},
101-		WarnOverflow:           {"overflow", true, "Warn when an integer constant is out of range for its type."},
102-		WarnPedantic:           {"pedantic", false, "Issue all warnings demanded by the strict standard."},
103-		WarnUnreachableCode:    {"unreachable-code", true, "Warn about code that will never be executed."},
104-		WarnImplicitDecl:       {"implicit-decl", true, "Warn about implicit function or variable declarations."},
105-		WarnType:               {"type", true, "Warn about type mismatches in expressions and assignments."},
106-		WarnExtra:              {"extra", true, "Enable extra miscellaneous warnings."},
107+		WarnCEsc:               {"c-esc", false, "Warn on usage of C-style '\\' escapes"},
108+		WarnBEsc:               {"b-esc", true, "Warn on usage of B-style '*' escapes"},
109+		WarnBOps:               {"b-ops", true, "Warn on usage of B-style assignment operators like '=+'"},
110+		WarnCOps:               {"c-ops", false, "Warn on usage of C-style assignment operators like '+='"},
111+		WarnUnrecognizedEscape: {"u-esc", true, "Warn on unrecognized character escape sequences"},
112+		WarnTruncatedChar:      {"truncated-char", true, "Warn when a character escape value is truncated"},
113+		WarnLongCharConst:      {"long-char-const", true, "Warn when a multi-character constant is too long for a word"},
114+		WarnCComments:          {"c-comments", false, "Warn on usage of non-standard C-style '//' comments"},
115+		WarnOverflow:           {"overflow", true, "Warn when an integer constant is out of range for its type"},
116+		WarnPedantic:           {"pedantic", false, "Issue all warnings demanded by the strict standard"},
117+		WarnUnreachableCode:    {"unreachable-code", true, "Warn about code that will never be executed"},
118+		WarnImplicitDecl:       {"implicit-decl", true, "Warn about implicit function or variable declarations"},
119+		WarnType:               {"type", true, "Warn about type mismatches in expressions and assignments"},
120+		WarnExtra:              {"extra", true, "Enable extra miscellaneous warnings"},
121+		WarnFloat:              {"float", false, "Warn when floating-point numbers are used"},
122+		WarnLocalAddress:       {"local-address", true, "Warn when the address of a local variable is returned"},
123 	}
124 
125 	cfg.Features, cfg.Warnings = features, warnings
126@@ -160,12 +165,9 @@ func NewConfig() *Config {
127 	return cfg
128 }
129 
130-// SetTarget configures the compiler for a specific architecture and backend target
131 func (c *Config) SetTarget(hostOS, hostArch, targetFlag string) {
132-	// Init with host defaults
133 	c.GOOS, c.GOARCH, c.BackendName = hostOS, hostArch, "qbe"
134 
135-	// Parse target flag: <backend>/<target_string>
136 	if targetFlag != "" {
137 		parts := strings.SplitN(targetFlag, "/", 2)
138 		c.BackendName = parts[0]
139@@ -174,13 +176,9 @@ func (c *Config) SetTarget(hostOS, hostArch, targetFlag string) {
140 		}
141 	}
142 
143-	// Valid QBE targets |https://pkg.go.dev/modernc.org/libqbe#hdr-Supported_targets|
144 	validQBETargets := map[string]string{
145-		"amd64_apple": "amd64",
146-		"amd64_sysv":  "amd64",
147-		"arm64":       "arm64",
148-		"arm64_apple": "arm64",
149-		"rv64":        "riscv64",
150+		"amd64_apple": "amd64", "amd64_sysv": "amd64", "arm64": "arm64",
151+		"arm64_apple": "arm64", "rv64": "riscv64",
152 	}
153 
154 	if c.BackendName == "qbe" {
155@@ -193,13 +191,12 @@ func (c *Config) SetTarget(hostOS, hostArch, targetFlag string) {
156 		} else {
157 			fmt.Fprintf(os.Stderr, "gbc: warning: unsupported QBE target '%s', defaulting to GOARCH '%s'\n", c.BackendTarget, c.GOARCH)
158 		}
159-	} else { // llvm
160+	} else {
161 		if c.BackendTarget == "" {
162 			tradArch := archTranslations[hostArch]
163 			if tradArch == "" {
164 				tradArch = hostArch
165-			} // No target architecture specified
166-			// TODO: ? Infer env ("musl", "gnu", etc..?)
167+			}
168 			c.BackendTarget = fmt.Sprintf("%s-unknown-%s-unknown", tradArch, hostOS)
169 			fmt.Fprintf(os.Stderr, "gbc: info: no target specified, defaulting to host target '%s' for backend '%s'\n", c.BackendTarget, c.BackendName)
170 		}
171@@ -216,13 +213,12 @@ func (c *Config) SetTarget(hostOS, hostArch, targetFlag string) {
172 		}
173 	}
174 
175-	// Set architecture-specific properties
176 	if props, ok := archProperties[c.GOARCH]; ok {
177-		c.WordSize, c.WordType, c.StackAlignment = props.WordSize, props.WordType, props.StackAlignment
178+		c.WordSize, c.StackAlignment = props.WordSize, props.StackAlignment
179 	} else {
180-		fmt.Fprintf(os.Stderr, "gbc: warning: unrecognized architecture '%s'.\n", c.GOARCH)
181-		fmt.Fprintf(os.Stderr, "gbc: warning: defaulting to 64-bit properties. Compilation may fail.\n")
182-		c.WordSize, c.WordType, c.StackAlignment = 8, "l", 16
183+		fmt.Fprintf(os.Stderr, "gbc: warning: unrecognized architecture '%s'\n", c.GOARCH)
184+		fmt.Fprintf(os.Stderr, "gbc: warning: defaulting to 64-bit properties; compilation may fail\n")
185+		c.WordSize, c.StackAlignment = 8, 16
186 	}
187 
188 	fmt.Fprintf(os.Stderr, "gbc: info: using backend '%s' with target '%s' (GOOS=%s, GOARCH=%s)\n", c.BackendName, c.BackendTarget, c.GOOS, c.GOARCH)
189@@ -251,24 +247,18 @@ func (c *Config) ApplyStd(stdName string) error {
190 	isPedantic := c.IsWarningEnabled(WarnPedantic)
191 
192 	type stdSettings struct {
193-		feature Feature
194-		bValue  bool
195-		bxValue bool
196+		feature         Feature
197+		bValue, bxValue bool
198 	}
199 
200 	settings := []stdSettings{
201-		{FeatAllowUninitialized, true, !isPedantic},
202-		{FeatBOps, true, false},
203-		{FeatBEsc, true, false},
204-		{FeatCOps, !isPedantic, true},
205-		{FeatCEsc, !isPedantic, true},
206-		{FeatCComments, !isPedantic, true},
207-		{FeatExtrn, !isPedantic, true},
208-		{FeatAsm, !isPedantic, true},
209-		{FeatTyped, false, true},
210-		{FeatShortDecl, false, true},
211-		{FeatBxDeclarations, false, true},
212-		{FeatStrictDecl, false, isPedantic},
213+		{FeatAllowUninitialized, true, !isPedantic}, {FeatBOps, true, false},
214+		{FeatBEsc, true, false}, {FeatCOps, !isPedantic, true},
215+		{FeatCEsc, !isPedantic, true}, {FeatCComments, !isPedantic, true},
216+		{FeatExtrn, !isPedantic, true}, {FeatAsm, !isPedantic, true},
217+		{FeatTyped, false, true}, {FeatShortDecl, false, true},
218+		{FeatBxDeclarations, false, true}, {FeatStrictDecl, false, isPedantic},
219+		{FeatFloat, false, true},
220 	}
221 
222 	switch stdName {
223@@ -276,11 +266,15 @@ func (c *Config) ApplyStd(stdName string) error {
224 		for _, s := range settings {
225 			c.SetFeature(s.feature, s.bValue)
226 		}
227+		if isPedantic {
228+			c.SetFeature(FeatFloat, false)
229+		}
230 		c.SetWarning(WarnBOps, false)
231 		c.SetWarning(WarnBEsc, false)
232 		c.SetWarning(WarnCOps, true)
233 		c.SetWarning(WarnCEsc, true)
234 		c.SetWarning(WarnCComments, true)
235+		c.SetWarning(WarnFloat, true)
236 	case "Bx":
237 		for _, s := range settings {
238 			c.SetFeature(s.feature, s.bxValue)
239@@ -290,40 +284,31 @@ func (c *Config) ApplyStd(stdName string) error {
240 		c.SetWarning(WarnCOps, false)
241 		c.SetWarning(WarnCEsc, false)
242 		c.SetWarning(WarnCComments, false)
243+		c.SetWarning(WarnFloat, false)
244 	default:
245-		return fmt.Errorf("unsupported standard '%s'. Supported: 'B', 'Bx'", stdName)
246+		return fmt.Errorf("unsupported standard '%s'; supported: 'B', 'Bx'", stdName)
247 	}
248 	return nil
249 }
250 
251-// SetupFlagGroups populates a FlagSet with warning and feature flag groups
252-// and returns the corresponding entry slices for processing results.
253 func (c *Config) SetupFlagGroups(fs *cli.FlagSet) ([]cli.FlagGroupEntry, []cli.FlagGroupEntry) {
254 	var warningFlags, featureFlags []cli.FlagGroupEntry
255 
256 	for i := Warning(0); i < WarnCount; i++ {
257-		pEnable := new(bool)
258+		pEnable, pDisable := new(bool), new(bool)
259 		*pEnable = c.Warnings[i].Enabled
260-		pDisable := new(bool)
261 		warningFlags = append(warningFlags, cli.FlagGroupEntry{
262-			Name:     c.Warnings[i].Name,
263-			Prefix:   "W",
264-			Usage:    c.Warnings[i].Description,
265-			Enabled:  pEnable,
266-			Disabled: pDisable,
267+			Name: c.Warnings[i].Name, Prefix: "W", Usage: c.Warnings[i].Description,
268+			Enabled: pEnable, Disabled: pDisable,
269 		})
270 	}
271 
272 	for i := Feature(0); i < FeatCount; i++ {
273-		pEnable := new(bool)
274+		pEnable, pDisable := new(bool), new(bool)
275 		*pEnable = c.Features[i].Enabled
276-		pDisable := new(bool)
277 		featureFlags = append(featureFlags, cli.FlagGroupEntry{
278-			Name:     c.Features[i].Name,
279-			Prefix:   "F",
280-			Usage:    c.Features[i].Description,
281-			Enabled:  pEnable,
282-			Disabled: pDisable,
283+			Name: c.Features[i].Name, Prefix: "F", Usage: c.Features[i].Description,
284+			Enabled: pEnable, Disabled: pDisable,
285 		})
286 	}
287 
288@@ -333,7 +318,6 @@ func (c *Config) SetupFlagGroups(fs *cli.FlagSet) ([]cli.FlagGroupEntry, []cli.F
289 	return warningFlags, featureFlags
290 }
291 
292-// ParseCLIString splits a string into arguments, respecting single quotes.
293 func ParseCLIString(s string) ([]string, error) {
294 	var args []string
295 	var current strings.Builder
296@@ -360,7 +344,6 @@ func ParseCLIString(s string) ([]string, error) {
297 	return args, nil
298 }
299 
300-// ProcessArgs parses a slice of command-line style arguments and updates the configuration.
301 func (c *Config) ProcessArgs(args []string) error {
302 	for i := 0; i < len(args); i++ {
303 		arg := args[i]
304@@ -369,7 +352,7 @@ func (c *Config) ProcessArgs(args []string) error {
305 			c.LibRequests = append(c.LibRequests, strings.TrimPrefix(arg, "-l"))
306 		case strings.HasPrefix(arg, "-L"):
307 			val := strings.TrimPrefix(arg, "-L")
308-			if val == "" { // Space separated: -L <val>
309+			if val == "" {
310 				if i+1 >= len(args) {
311 					return fmt.Errorf("missing argument for flag: %s", arg)
312 				}
313@@ -379,7 +362,7 @@ func (c *Config) ProcessArgs(args []string) error {
314 			c.LinkerArgs = append(c.LinkerArgs, "-L"+val)
315 		case strings.HasPrefix(arg, "-I"):
316 			val := strings.TrimPrefix(arg, "-I")
317-			if val == "" { // Space separated: -I <val>
318+			if val == "" {
319 				if i+1 >= len(args) {
320 					return fmt.Errorf("missing argument for flag: %s", arg)
321 				}
322@@ -389,7 +372,7 @@ func (c *Config) ProcessArgs(args []string) error {
323 			c.UserIncludePaths = append(c.UserIncludePaths, val)
324 		case strings.HasPrefix(arg, "-C"):
325 			val := strings.TrimPrefix(arg, "-C")
326-			if val == "" { // Space separated: -C <val>
327+			if val == "" {
328 				if i+1 >= len(args) {
329 					return fmt.Errorf("missing argument for flag: %s", arg)
330 				}
331@@ -442,7 +425,6 @@ func (c *Config) ProcessArgs(args []string) error {
332 	return nil
333 }
334 
335-// ProcessDirectiveFlags parses flags from a directive string.
336 func (c *Config) ProcessDirectiveFlags(flagStr string, tok token.Token) error {
337 	args, err := ParseCLIString(flagStr)
338 	if err != nil {
M pkg/ir/ir.go
+97, -164
  1@@ -1,21 +1,16 @@
  2-// package ir defines a lower-level representation of our program, that is independent of any specific backend (QBE, LLVM, ... etc)
  3 package ir
  4 
  5 import (
  6 	"github.com/xplshn/gbc/pkg/ast"
  7 )
  8 
  9-// Op represents an operation code for an instruction.
 10 type Op int
 11 
 12 const (
 13-	// Memory Operations
 14-	OpAlloc Op = iota // Allocate stack memory
 15+	OpAlloc Op = iota
 16 	OpLoad
 17 	OpStore
 18-	OpBlit // Memory copy
 19-
 20-	// Integer Arithmetic/Bitwise Operations
 21+	OpBlit
 22 	OpAdd
 23 	OpSub
 24 	OpMul
 25@@ -26,24 +21,18 @@ const (
 26 	OpXor
 27 	OpShl
 28 	OpShr
 29-
 30-	// Floating-Point Operations
 31 	OpAddF
 32 	OpSubF
 33 	OpMulF
 34 	OpDivF
 35 	OpRemF
 36 	OpNegF
 37-
 38-	// Comparison Operations
 39 	OpCEq
 40 	OpCNeq
 41 	OpCLt
 42 	OpCGt
 43 	OpCLe
 44 	OpCGe
 45-
 46-	// Type Conversion/Extension Operations
 47 	OpExtSB
 48 	OpExtUB
 49 	OpExtSH
 50@@ -52,237 +41,171 @@ const (
 51 	OpExtUW
 52 	OpTrunc
 53 	OpCast
 54-	OpFToI
 55-	OpIToF
 56+	OpFToSI
 57+	OpFToUI
 58+	OpSWToF
 59+	OpUWToF
 60+	OpSLToF
 61+	OpULToF
 62 	OpFToF
 63-
 64-	// Control Flow
 65 	OpJmp
 66 	OpJnz
 67 	OpRet
 68-
 69-	// Function Call
 70 	OpCall
 71-
 72-	// Special
 73 	OpPhi
 74 )
 75 
 76-// Type represents a data type in the IR.
 77 type Type int
 78 
 79 const (
 80 	TypeNone Type = iota
 81-	TypeB         // byte (1)
 82-	TypeH         // half-word (2)
 83-	TypeW         // word (4)
 84-	TypeL         // long (8)
 85-	TypeS         // single-precision float (4)
 86-	TypeD         // double-precision float (8)
 87-	TypePtr       // pointer (word size)
 88+	TypeB         // byte (8-bit)
 89+	TypeH         // half-word (16-bit)
 90+	TypeW         // word (32-bit)
 91+	TypeL         // long (64-bit)
 92+	TypeS         // single float (32-bit)
 93+	TypeD         // double float (64-bit)
 94+	TypePtr
 95 )
 96 
 97-// Value represents an operand for an instruction. It can be a constant,
 98-// a temporary register, a global symbol, or a label
 99 type Value interface {
100 	isValue()
101 	String() string
102 }
103 
104-// Const represents a constant integer value
105-type Const struct {
106-	Value int64
107-	Typ   Type
108-}
109-
110-// FloatConst represents a constant floating-point value
111-type FloatConst struct {
112-	Value float64
113-	Typ   Type
114-}
115-
116-// Global represents a global symbol (function or data)
117-type Global struct {
118-	Name string
119-}
120-
121-// Temporary represents a temporary, virtual register
122-type Temporary struct {
123-	Name string
124-	ID   int
125-}
126-
127-// Label represents a basic block label
128-type Label struct {
129-	Name string
130-}
131-
132-// CastValue is a wrapper to signal an explicit cast in the backend
133+type Const struct{ Value int64 }
134+type FloatConst struct{ Value float64; Typ Type }
135+type Global struct{ Name string }
136+type Temporary struct{ Name string; ID int }
137+type Label struct{ Name string }
138 type CastValue struct {
139 	Value
140 	TargetType string
141 }
142 
143-// Func represents a function in the IR
144+func (c *Const) isValue()      {}
145+func (f *FloatConst) isValue() {}
146+func (g *Global) isValue()     {}
147+func (t *Temporary) isValue()  {}
148+func (l *Label) isValue()      {}
149+func (c *CastValue) isValue()  {}
150+
151+func (c *Const) String() string      { return "" }
152+func (f *FloatConst) String() string { return "" }
153+func (g *Global) String() string     { return g.Name }
154+func (t *Temporary) String() string  { return t.Name }
155+func (l *Label) String() string      { return l.Name }
156+func (c *CastValue) String() string  { return c.Value.String() }
157+
158 type Func struct {
159-	Name       string
160-	Params     []*Param
161-	ReturnType Type
162-	HasVarargs bool
163-	Blocks     []*BasicBlock
164+	Name          string
165+	Params        []*Param
166+	AstParams     []*ast.Node
167+	ReturnType    Type
168+	AstReturnType *ast.BxType
169+	HasVarargs    bool
170+	Blocks        []*BasicBlock
171+	Node          *ast.Node
172 }
173 
174-// Param represents a function parameter
175 type Param struct {
176 	Name string
177 	Typ  Type
178 	Val  Value
179 }
180 
181-// BasicBlock represents a sequence of instructions ending with a terminator
182 type BasicBlock struct {
183 	Label        *Label
184 	Instructions []*Instruction
185 }
186 
187-// Instruction represents a single operation with its operands
188 type Instruction struct {
189-	Op       Op
190-	Typ      Type    // The type of the operation/result
191-	Result   Value
192-	Args     []Value
193-	ArgTypes []Type  // Used for OpCall
194-	Align    int     // Used for OpAlloc
195+	Op          Op
196+	Typ         Type
197+	OperandType Type
198+	Result      Value
199+	Args        []Value
200+	ArgTypes    []Type
201+	Align       int
202 }
203 
204-// Program is the top-level container for the entire IR
205 type Program struct {
206 	Globals          []*Data
207-	Strings          map[string]string // Maps string content to its label
208+	Strings          map[string]string
209 	Funcs            []*Func
210 	ExtrnFuncs       []string
211 	ExtrnVars        map[string]bool
212 	WordSize         int
213 	BackendTempCount int
214+	GlobalSymbols    map[string]*ast.Node
215 }
216 
217-// Data represents a global data variable
218 type Data struct {
219-	Name  string
220-	Align int
221-	Items []DataItem
222+	Name    string
223+	Align   int
224+	AstType *ast.BxType
225+	Items   []DataItem
226 }
227 
228-// DataItem represents an item within a global data definition
229 type DataItem struct {
230 	Typ   Type
231-	Value Value // Can be Const or Global
232-	Count int   // For zero-initialization (z)
233+	Value Value
234+	Count int
235 }
236 
237-// isValue implementations to satisfy the Value interface
238-func (c *Const) isValue()      {}
239-func (f *FloatConst) isValue() {}
240-func (g *Global) isValue()     {}
241-func (t *Temporary) isValue()  {}
242-func (l *Label) isValue()      {}
243-func (c *CastValue) isValue()  {}
244-
245-// String representations for Value types.
246-func (c *Const) String() string      { return "" } // Handled by backend
247-func (f *FloatConst) String() string { return "" } // Handled by backend
248-func (g *Global) String() string     { return g.Name }
249-func (t *Temporary) String() string  { return t.Name }
250-func (l *Label) String() string      { return l.Name }
251-func (c *CastValue) String() string  { return c.Value.String() }
252-
253-// GetType converts an AST type to an IR type
254 func GetType(typ *ast.BxType, wordSize int) Type {
255-	if typ == nil || typ.Kind == ast.TYPE_UNTYPED {
256-		return wordTypeFromSize(wordSize)
257-	}
258+	if typ == nil || typ.Kind == ast.TYPE_UNTYPED { return wordTypeFromSize(wordSize) }
259+
260 	switch typ.Kind {
261-	case ast.TYPE_VOID:
262-		return TypeNone
263-	case ast.TYPE_POINTER, ast.TYPE_ARRAY:
264-		return TypePtr
265+	case ast.TYPE_UNTYPED_INT: return wordTypeFromSize(wordSize)
266+	case ast.TYPE_UNTYPED_FLOAT: return TypeS
267+	case ast.TYPE_VOID: return TypeNone
268+	case ast.TYPE_POINTER, ast.TYPE_ARRAY, ast.TYPE_STRUCT: return TypePtr
269 	case ast.TYPE_FLOAT:
270 		switch typ.Name {
271-		case "float", "float32":
272-			return TypeS
273-		case "float64":
274-			return TypeD
275-		default:
276-			return TypeS
277+		case "float", "float32": return TypeS
278+		case "float64": return TypeD
279+		default: return TypeS
280 		}
281 	case ast.TYPE_PRIMITIVE:
282 		switch typ.Name {
283-		case "int", "uint", "string":
284-			return wordTypeFromSize(wordSize)
285-		case "int64", "uint64":
286-			return TypeL
287-		case "int32", "uint32":
288-			return TypeW
289-		case "int16", "uint16":
290-			return TypeH
291-		case "byte", "bool", "int8", "uint8":
292-			return TypeB
293-		default:
294-			return wordTypeFromSize(wordSize)
295+		case "int", "uint", "string": return wordTypeFromSize(wordSize)
296+		case "int64", "uint64": return TypeL
297+		case "int32", "uint32": return TypeW
298+		case "int16", "uint16": return TypeH
299+		case "byte", "bool", "int8", "uint8": return TypeB
300+		default: return wordTypeFromSize(wordSize)
301 		}
302-	case ast.TYPE_STRUCT:
303-		return wordTypeFromSize(wordSize)
304 	}
305 	return wordTypeFromSize(wordSize)
306 }
307 
308 func wordTypeFromSize(size int) Type {
309 	switch size {
310-	case 8:
311-		return TypeL
312-	case 4:
313-		return TypeW
314-	case 2:
315-		return TypeH
316-	case 1:
317-		return TypeB
318-	default:
319-		// Default to the largest supported integer size if word size is unusual
320-		return TypeL
321+	case 8: return TypeL
322+	case 4: return TypeW
323+	case 2: return TypeH
324+	case 1: return TypeB
325+	default: return TypeL
326 	}
327 }
328 
329 func SizeOfType(t Type, wordSize int) int64 {
330 	switch t {
331-	case TypeB:
332-		return 1
333-	case TypeH:
334-		return 2
335-	case TypeW:
336-		return 4
337-	case TypeL:
338-		return 8
339-	case TypeS:
340-		return 4
341-	case TypeD:
342-		return 8
343-	case TypePtr:
344-		return int64(wordSize)
345-	default:
346-		return int64(wordSize)
347+	case TypeB: return 1
348+	case TypeH: return 2
349+	case TypeW: return 4
350+	case TypeL: return 8
351+	case TypeS: return 4
352+	case TypeD: return 8
353+	case TypePtr: return int64(wordSize)
354+	default: return int64(wordSize)
355 	}
356 }
357 
358-// GetBackendTempCount returns the current backend temporary count
359 func (p *Program) GetBackendTempCount() int { return p.BackendTempCount }
360+func (p *Program) IncBackendTempCount()     { p.BackendTempCount++ }
361 
362-// IncBackendTempCount increments and returns the new backend temporary count
363-func (p *Program) IncBackendTempCount() int {
364-	p.BackendTempCount++
365-	return p.BackendTempCount
366-}
367-
368-// IsStringLabel checks if a global name corresponds to a string literal
369 func (p *Program) IsStringLabel(name string) (string, bool) {
370 	for s, label := range p.Strings {
371 		if label == name { return s, true }
372@@ -290,10 +213,20 @@ func (p *Program) IsStringLabel(name string) (string, bool) {
373 	return "", false
374 }
375 
376-// FindFunc finds a function by name in the program.
377 func (p *Program) FindFunc(name string) *Func {
378 	for _, f := range p.Funcs {
379 		if f.Name == name { return f }
380 	}
381 	return nil
382 }
383+
384+func (p *Program) FindFuncSymbol(name string) *ast.Node {
385+	if p.GlobalSymbols != nil {
386+		if node, ok := p.GlobalSymbols[name]; ok {
387+			if _, isFunc := node.Data.(ast.FuncDeclNode); isFunc {
388+				return node
389+			}
390+		}
391+	}
392+	return nil
393+}
M pkg/lexer/lexer.go
+116, -141
  1@@ -30,91 +30,61 @@ func (l *Lexer) Next() token.Token {
  2 		l.skipWhitespaceAndComments()
  3 		startPos, startCol, startLine := l.pos, l.column, l.line
  4 
  5-		if l.isAtEnd() {
  6-			return l.makeToken(token.EOF, "", startPos, startCol, startLine)
  7-		}
  8+		if l.isAtEnd() { return l.makeToken(token.EOF, "", startPos, startCol, startLine) }
  9 
 10-		// Handle directives and line comments
 11 		if l.peek() == '/' && l.peekNext() == '/' {
 12-			// Try parsing as a directive first. We consume the line
 13-			// if it's a directive, but reset the position if it's not
 14 			if !l.cfg.IsFeatureEnabled(config.FeatNoDirectives) {
 15 				if tok, isDirective := l.lineCommentOrDirective(startPos, startCol, startLine); isDirective {
 16 					return tok
 17 				}
 18 			}
 19-
 20-			// If not a directive, treat as a regular C-style comment
 21 			if l.cfg.IsFeatureEnabled(config.FeatCComments) {
 22 				l.lineComment()
 23-				continue // Loop to find the next actual token
 24+				continue
 25 			}
 26 		}
 27 
 28-		ch := l.advance()
 29+		ch := l.peek()
 30 		if unicode.IsLetter(ch) || ch == '_' {
 31+			l.advance()
 32 			return l.identifierOrKeyword(startPos, startCol, startLine)
 33 		}
 34-		if unicode.IsDigit(ch) {
 35+		if unicode.IsDigit(ch) || (ch == '.' && unicode.IsDigit(l.peekNext())) {
 36 			return l.numberLiteral(startPos, startCol, startLine)
 37 		}
 38 
 39+		l.advance()
 40 		switch ch {
 41-		case '(':
 42-			return l.makeToken(token.LParen, "", startPos, startCol, startLine)
 43-		case ')':
 44-			return l.makeToken(token.RParen, "", startPos, startCol, startLine)
 45-		case '{':
 46-			return l.makeToken(token.LBrace, "", startPos, startCol, startLine)
 47-		case '}':
 48-			return l.makeToken(token.RBrace, "", startPos, startCol, startLine)
 49-		case '[':
 50-			return l.makeToken(token.LBracket, "", startPos, startCol, startLine)
 51-		case ']':
 52-			return l.makeToken(token.RBracket, "", startPos, startCol, startLine)
 53-		case ';':
 54-			return l.makeToken(token.Semi, "", startPos, startCol, startLine)
 55-		case ',':
 56-			return l.makeToken(token.Comma, "", startPos, startCol, startLine)
 57-		case '?':
 58-			return l.makeToken(token.Question, "", startPos, startCol, startLine)
 59-		case '~':
 60-			return l.makeToken(token.Complement, "", startPos, startCol, startLine)
 61-		case ':':
 62-			return l.matchThen('=', token.Define, token.Colon, startPos, startCol, startLine)
 63-		case '!':
 64-			return l.matchThen('=', token.Neq, token.Not, startPos, startCol, startLine)
 65-		case '^':
 66-			return l.matchThen('=', token.XorEq, token.Xor, startPos, startCol, startLine)
 67-		case '%':
 68-			return l.matchThen('=', token.RemEq, token.Rem, startPos, startCol, startLine)
 69-		case '+':
 70-			return l.plus(startPos, startCol, startLine)
 71-		case '-':
 72-			return l.minus(startPos, startCol, startLine)
 73-		case '*':
 74-			return l.star(startPos, startCol, startLine)
 75-		case '/':
 76-			return l.slash(startPos, startCol, startLine)
 77-		case '&':
 78-			return l.ampersand(startPos, startCol, startLine)
 79-		case '|':
 80-			return l.pipe(startPos, startCol, startLine)
 81-		case '<':
 82-			return l.less(startPos, startCol, startLine)
 83-		case '>':
 84-			return l.greater(startPos, startCol, startLine)
 85-		case '=':
 86-			return l.equal(startPos, startCol, startLine)
 87+		case '(': return l.makeToken(token.LParen, "", startPos, startCol, startLine)
 88+		case ')': return l.makeToken(token.RParen, "", startPos, startCol, startLine)
 89+		case '{': return l.makeToken(token.LBrace, "", startPos, startCol, startLine)
 90+		case '}': return l.makeToken(token.RBrace, "", startPos, startCol, startLine)
 91+		case '[': return l.makeToken(token.LBracket, "", startPos, startCol, startLine)
 92+		case ']': return l.makeToken(token.RBracket, "", startPos, startCol, startLine)
 93+		case ';': return l.makeToken(token.Semi, "", startPos, startCol, startLine)
 94+		case ',': return l.makeToken(token.Comma, "", startPos, startCol, startLine)
 95+		case '?': return l.makeToken(token.Question, "", startPos, startCol, startLine)
 96+		case '~': return l.makeToken(token.Complement, "", startPos, startCol, startLine)
 97+		case ':': return l.matchThen('=', token.Define, token.Colon, startPos, startCol, startLine)
 98+		case '!': return l.matchThen('=', token.Neq, token.Not, startPos, startCol, startLine)
 99+		case '^': return l.matchThen('=', token.XorEq, token.Xor, startPos, startCol, startLine)
100+		case '%': return l.matchThen('=', token.RemEq, token.Rem, startPos, startCol, startLine)
101+		case '+': return l.plus(startPos, startCol, startLine)
102+		case '-': return l.minus(startPos, startCol, startLine)
103+		case '*': return l.star(startPos, startCol, startLine)
104+		case '/': return l.slash(startPos, startCol, startLine)
105+		case '&': return l.ampersand(startPos, startCol, startLine)
106+		case '|': return l.pipe(startPos, startCol, startLine)
107+		case '<': return l.less(startPos, startCol, startLine)
108+		case '>': return l.greater(startPos, startCol, startLine)
109+		case '=': return l.equal(startPos, startCol, startLine)
110 		case '.':
111 			if l.match('.') && l.match('.') {
112 				return l.makeToken(token.Dots, "", startPos, startCol, startLine)
113 			}
114 			return l.makeToken(token.Dot, "", startPos, startCol, startLine)
115-		case '"':
116-			return l.stringLiteral(startPos, startCol, startLine)
117-		case '\'':
118-			return l.charLiteral(startPos, startCol, startLine)
119+		case '"': return l.stringLiteral(startPos, startCol, startLine)
120+		case '\'': return l.charLiteral(startPos, startCol, startLine)
121 		}
122 
123 		tok := l.makeToken(token.EOF, "", startPos, startCol, startLine)
124@@ -124,23 +94,17 @@ func (l *Lexer) Next() token.Token {
125 }
126 
127 func (l *Lexer) peek() rune {
128-	if l.isAtEnd() {
129-		return 0
130-	}
131+	if l.isAtEnd() { return 0 }
132 	return l.source[l.pos]
133 }
134 
135 func (l *Lexer) peekNext() rune {
136-	if l.pos+1 >= len(l.source) {
137-		return 0
138-	}
139+	if l.pos+1 >= len(l.source) { return 0 }
140 	return l.source[l.pos+1]
141 }
142 
143 func (l *Lexer) advance() rune {
144-	if l.isAtEnd() {
145-		return 0
146-	}
147+	if l.isAtEnd() { return 0 }
148 	ch := l.source[l.pos]
149 	if ch == '\n' {
150 		l.line++
151@@ -153,16 +117,12 @@ func (l *Lexer) advance() rune {
152 }
153 
154 func (l *Lexer) match(expected rune) bool {
155-	if l.isAtEnd() || l.source[l.pos] != expected {
156-		return false
157-	}
158+	if l.isAtEnd() || l.source[l.pos] != expected { return false }
159 	l.advance()
160 	return true
161 }
162 
163-func (l *Lexer) isAtEnd() bool {
164-	return l.pos >= len(l.source)
165-}
166+func (l *Lexer) isAtEnd() bool { return l.pos >= len(l.source) }
167 
168 func (l *Lexer) makeToken(tokType token.Type, value string, startPos, startCol, startLine int) token.Token {
169 	return token.Token{
170@@ -173,26 +133,23 @@ func (l *Lexer) makeToken(tokType token.Type, value string, startPos, startCol,
171 
172 func (l *Lexer) skipWhitespaceAndComments() {
173 	for {
174-		c := l.peek()
175-		switch c {
176-		case ' ', '\t', '\n', '\r':
177-			l.advance()
178+		switch l.peek() {
179+		case ' ', '\t', '\n', '\r': l.advance()
180 		case '/':
181 			if l.peekNext() == '*' {
182 				l.blockComment()
183 			} else {
184-				return // Next() handles `//` comments
185+				return
186 			}
187-		default:
188-			return
189+		default: return
190 		}
191 	}
192 }
193 
194 func (l *Lexer) blockComment() {
195 	startTok := l.makeToken(token.Comment, "", l.pos, l.column, l.line)
196-	l.advance() // Consume '/'
197-	l.advance() // Consume '*'
198+	l.advance()
199+	l.advance()
200 	for !l.isAtEnd() {
201 		if l.peek() == '*' && l.peekNext() == '/' {
202 			l.advance()
203@@ -205,19 +162,15 @@ func (l *Lexer) blockComment() {
204 }
205 
206 func (l *Lexer) lineComment() {
207-	for !l.isAtEnd() && l.peek() != '\n' {
208-		l.advance()
209-	}
210+	for !l.isAtEnd() && l.peek() != '\n' { l.advance() }
211 }
212 
213 func (l *Lexer) lineCommentOrDirective(startPos, startCol, startLine int) (token.Token, bool) {
214 	preCommentPos, preCommentCol, preCommentLine := l.pos, l.column, l.line
215-	l.advance() // Consume '/'
216-	l.advance() // Consume '/'
217+	l.advance()
218+	l.advance()
219 	commentStartPos := l.pos
220-	for !l.isAtEnd() && l.peek() != '\n' {
221-		l.advance()
222-	}
223+	for !l.isAtEnd() && l.peek() != '\n' { l.advance() }
224 	commentContent := string(l.source[commentStartPos:l.pos])
225 	trimmedContent := strings.TrimSpace(commentContent)
226 
227@@ -226,7 +179,6 @@ func (l *Lexer) lineCommentOrDirective(startPos, startCol, startLine int) (token
228 		return l.makeToken(token.Directive, directiveContent, startPos, startCol, startLine), true
229 	}
230 
231-	// It's not a directive, so reset the lexer's position to before the '//'
232 	l.pos, l.column, l.line = preCommentPos, preCommentCol, preCommentLine
233 	return token.Token{}, false
234 }
235@@ -249,16 +201,69 @@ func (l *Lexer) identifierOrKeyword(startPos, startCol, startLine int) token.Tok
236 }
237 
238 func (l *Lexer) numberLiteral(startPos, startCol, startLine int) token.Token {
239-	for unicode.IsDigit(l.peek()) || (l.peek() == 'x' || l.peek() == 'X') || (l.peek() >= 'a' && l.peek() <= 'f') || (l.peek() >= 'A' && l.peek() <= 'F') {
240+	isFloat := false
241+	if l.peek() == '.' {
242+		isFloat = true
243+		l.advance()
244+	}
245+
246+	if l.peek() == '0' && (l.peekNext() == 'x' || l.peekNext() == 'X') {
247+		l.advance()
248 		l.advance()
249+		for unicode.IsDigit(l.peek()) || (l.peek() >= 'a' && l.peek() <= 'f') || (l.peek() >= 'A' && l.peek() <= 'F') {
250+			l.advance()
251+		}
252+	} else {
253+		for unicode.IsDigit(l.peek()) { l.advance() }
254+	}
255+
256+	if l.peek() == '.' {
257+		if unicode.IsDigit(l.peekNext()) {
258+			isFloat = true
259+			l.advance()
260+			for unicode.IsDigit(l.peek()) { l.advance() }
261+		}
262 	}
263+
264 	valueStr := string(l.source[startPos:l.pos])
265+	if (l.peek() == 'e' || l.peek() == 'E') && !strings.HasPrefix(valueStr, "0x") && !strings.HasPrefix(valueStr, "0X") {
266+		isFloat = true
267+		l.advance()
268+		if l.peek() == '+' || l.peek() == '-' { l.advance() }
269+		if !unicode.IsDigit(l.peek()) {
270+			util.Error(l.makeToken(token.FloatNumber, "", startPos, startCol, startLine), "Malformed floating-point literal: exponent has no digits")
271+		}
272+		for unicode.IsDigit(l.peek()) { l.advance() }
273+	}
274+
275+	valueStr = string(l.source[startPos:l.pos])
276+
277+	if isFloat {
278+		if !l.cfg.IsFeatureEnabled(config.FeatFloat) {
279+			tok := l.makeToken(token.FloatNumber, valueStr, startPos, startCol, startLine)
280+			util.Error(tok, "Floating-point numbers are not enabled (use -Ffloat)")
281+			return tok
282+		}
283+		if l.cfg.IsWarningEnabled(config.WarnFloat) {
284+			tok := l.makeToken(token.FloatNumber, valueStr, startPos, startCol, startLine)
285+			util.Warn(l.cfg, config.WarnFloat, tok, "Use of floating-point constant")
286+		}
287+		return l.makeToken(token.FloatNumber, valueStr, startPos, startCol, startLine)
288+	}
289+
290 	tok := l.makeToken(token.Number, "", startPos, startCol, startLine)
291 	val, err := strconv.ParseUint(valueStr, 0, 64)
292 	if err != nil {
293+		if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
294+			util.Warn(l.cfg, config.WarnOverflow, tok, "Integer constant overflow: %s", valueStr)
295+			tok.Value = valueStr
296+			return tok
297+		}
298 		util.Error(tok, "Invalid number literal: %s", valueStr)
299+		tok.Value = "0"
300+	} else {
301+		tok.Value = strconv.FormatUint(val, 10)
302 	}
303-	tok.Value = strconv.FormatInt(int64(val), 10)
304 	return tok
305 }
306 
307@@ -298,9 +303,7 @@ func (l *Lexer) charLiteral(startPos, startCol, startLine int) token.Token {
308 	}
309 
310 	tok := l.makeToken(token.Number, "", startPos, startCol, startLine)
311-	if !l.match('\'') {
312-		util.Error(tok, "Unterminated character literal")
313-	}
314+	if !l.match('\'') { util.Error(tok, "Unterminated character literal") }
315 	tok.Value = strconv.FormatInt(word, 10)
316 	return tok
317 }
318@@ -312,85 +315,57 @@ func (l *Lexer) decodeEscape(escapeChar rune, startPos, startCol, startLine int)
319 	}
320 	c := l.advance()
321 	escapes := map[rune]int64{'n': '\n', 't': '\t', 'e': 4, 'b': '\b', 'r': '\r', '0': 0, '(': '{', ')': '}', '\\': '\\', '\'': '\'', '"': '"', '*': '*'}
322-	if val, ok := escapes[c]; ok {
323-		return val
324-	}
325+	if val, ok := escapes[c]; ok { return val }
326 	util.Warn(l.cfg, config.WarnUnrecognizedEscape, l.makeToken(token.String, "", startPos, startCol, startLine), "Unrecognized escape sequence '%c%c'", escapeChar, c)
327 	return int64(c)
328 }
329 
330 func (l *Lexer) matchThen(expected rune, thenType, elseType token.Type, sPos, sCol, sLine int) token.Token {
331-	if l.match(expected) {
332-		return l.makeToken(thenType, "", sPos, sCol, sLine)
333-	}
334+	if l.match(expected) { return l.makeToken(thenType, "", sPos, sCol, sLine) }
335 	return l.makeToken(elseType, "", sPos, sCol, sLine)
336 }
337 
338 func (l *Lexer) plus(sPos, sCol, sLine int) token.Token {
339-	if l.match('+') {
340-		return l.makeToken(token.Inc, "", sPos, sCol, sLine)
341-	}
342-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
343-		return l.makeToken(token.PlusEq, "", sPos, sCol, sLine)
344-	}
345+	if l.match('+') { return l.makeToken(token.Inc, "", sPos, sCol, sLine) }
346+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.PlusEq, "", sPos, sCol, sLine) }
347 	return l.makeToken(token.Plus, "", sPos, sCol, sLine)
348 }
349 
350 func (l *Lexer) minus(sPos, sCol, sLine int) token.Token {
351-	if l.match('-') {
352-		return l.makeToken(token.Dec, "", sPos, sCol, sLine)
353-	}
354-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
355-		return l.makeToken(token.MinusEq, "", sPos, sCol, sLine)
356-	}
357+	if l.match('-') { return l.makeToken(token.Dec, "", sPos, sCol, sLine) }
358+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.MinusEq, "", sPos, sCol, sLine) }
359 	return l.makeToken(token.Minus, "", sPos, sCol, sLine)
360 }
361 
362 func (l *Lexer) star(sPos, sCol, sLine int) token.Token {
363-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
364-		return l.makeToken(token.StarEq, "", sPos, sCol, sLine)
365-	}
366+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.StarEq, "", sPos, sCol, sLine) }
367 	return l.makeToken(token.Star, "", sPos, sCol, sLine)
368 }
369 
370 func (l *Lexer) slash(sPos, sCol, sLine int) token.Token {
371-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
372-		return l.makeToken(token.SlashEq, "", sPos, sCol, sLine)
373-	}
374+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.SlashEq, "", sPos, sCol, sLine) }
375 	return l.makeToken(token.Slash, "", sPos, sCol, sLine)
376 }
377 
378 func (l *Lexer) ampersand(sPos, sCol, sLine int) token.Token {
379-	if l.match('&') {
380-		return l.makeToken(token.AndAnd, "", sPos, sCol, sLine)
381-	}
382-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
383-		return l.makeToken(token.AndEq, "", sPos, sCol, sLine)
384-	}
385+	if l.match('&') { return l.makeToken(token.AndAnd, "", sPos, sCol, sLine) }
386+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.AndEq, "", sPos, sCol, sLine) }
387 	return l.makeToken(token.And, "", sPos, sCol, sLine)
388 }
389 
390 func (l *Lexer) pipe(sPos, sCol, sLine int) token.Token {
391-	if l.match('|') {
392-		return l.makeToken(token.OrOr, "", sPos, sCol, sLine)
393-	}
394-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
395-		return l.makeToken(token.OrEq, "", sPos, sCol, sLine)
396-	}
397+	if l.match('|') { return l.makeToken(token.OrOr, "", sPos, sCol, sLine) }
398+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.OrEq, "", sPos, sCol, sLine) }
399 	return l.makeToken(token.Or, "", sPos, sCol, sLine)
400 }
401 
402 func (l *Lexer) less(sPos, sCol, sLine int) token.Token {
403-	if l.match('<') {
404-		return l.matchThen('=', token.ShlEq, token.Shl, sPos, sCol, sLine)
405-	}
406+	if l.match('<') { return l.matchThen('=', token.ShlEq, token.Shl, sPos, sCol, sLine) }
407 	return l.matchThen('=', token.Lte, token.Lt, sPos, sCol, sLine)
408 }
409 
410 func (l *Lexer) greater(sPos, sCol, sLine int) token.Token {
411-	if l.match('>') {
412-		return l.matchThen('=', token.ShrEq, token.Shr, sPos, sCol, sLine)
413-	}
414+	if l.match('>') { return l.matchThen('=', token.ShrEq, token.Shr, sPos, sCol, sLine) }
415 	return l.matchThen('=', token.Gte, token.Gt, sPos, sCol, sLine)
416 }
417 
M pkg/parser/parser.go
+295, -307
   1@@ -28,9 +28,7 @@ func NewParser(tokens []token.Token, cfg *config.Config) *Parser {
   2 		isTypedPass: cfg.IsFeatureEnabled(config.FeatTyped),
   3 		typeNames:   make(map[string]bool),
   4 	}
   5-	if len(tokens) > 0 {
   6-		p.current = p.tokens[0]
   7-	}
   8+	if len(tokens) > 0 { p.current = p.tokens[0] }
   9 
  10 	if p.isTypedPass {
  11 		for keyword, tokType := range token.KeywordMap {
  12@@ -46,22 +44,16 @@ func (p *Parser) advance() {
  13 	if p.pos < len(p.tokens) {
  14 		p.previous = p.current
  15 		p.pos++
  16-		if p.pos < len(p.tokens) {
  17-			p.current = p.tokens[p.pos]
  18-		}
  19+		if p.pos < len(p.tokens) { p.current = p.tokens[p.pos] }
  20 	}
  21 }
  22 
  23 func (p *Parser) peek() token.Token {
  24-	if p.pos+1 < len(p.tokens) {
  25-		return p.tokens[p.pos+1]
  26-	}
  27+	if p.pos+1 < len(p.tokens) { return p.tokens[p.pos+1] }
  28 	return p.tokens[len(p.tokens)-1]
  29 }
  30 
  31-func (p *Parser) check(tokType token.Type) bool {
  32-	return p.current.Type == tokType
  33-}
  34+func (p *Parser) check(tokType token.Type) bool { return p.current.Type == tokType }
  35 
  36 func (p *Parser) match(tokTypes ...token.Type) bool {
  37 	for _, tokType := range tokTypes {
  38@@ -82,17 +74,13 @@ func (p *Parser) expect(tokType token.Type, message string) {
  39 }
  40 
  41 func (p *Parser) isTypeName(name string) bool {
  42-	if !p.isTypedPass {
  43-		return false
  44-	}
  45+	if !p.isTypedPass { return false }
  46 	_, exists := p.typeNames[name]
  47 	return exists
  48 }
  49 
  50 func isLValue(node *ast.Node) bool {
  51-	if node == nil {
  52-		return false
  53-	}
  54+	if node == nil { return false }
  55 	switch node.Type {
  56 	case ast.Ident, ast.Indirection, ast.Subscript, ast.MemberAccess:
  57 		return true
  58@@ -107,9 +95,7 @@ func (p *Parser) Parse() *ast.Node {
  59 	for !p.check(token.EOF) {
  60 		for p.match(token.Semi) {
  61 		}
  62-		if p.check(token.EOF) {
  63-			break
  64-		}
  65+		if p.check(token.EOF) { break }
  66 
  67 		stmt := p.parseTopLevel()
  68 		if stmt != nil {
  69@@ -163,7 +149,7 @@ func (p *Parser) parseTopLevel() *ast.Node {
  70 		} else if peekTok.Type == token.Asm {
  71 			p.advance()
  72 			stmt = p.parseAsmFuncDef(identTok)
  73-		} else if p.isTypedPass && p.isTypeName(identTok.Value) {
  74+		} else if p.isTypedPass && p.isTypeName(identTok.Value) && peekTok.Type != token.Define {
  75 			stmt = p.parseTypedVarOrFuncDecl(true)
  76 		} else if p.isBxDeclarationAhead() {
  77 			stmt = p.parseDeclaration(false)
  78@@ -171,21 +157,14 @@ func (p *Parser) parseTopLevel() *ast.Node {
  79 			stmt = p.parseUntypedGlobalDefinition(identTok)
  80 		}
  81 	default:
  82-		if p.isTypedPass && (p.isBuiltinType(currentTok) || p.check(token.Const)) {
  83+		if p.isTypedPass && (p.isBuiltinType(p.current) || p.check(token.Const)) {
  84 			stmt = p.parseTypedVarOrFuncDecl(true)
  85 		} else {
  86 			stmt = p.parseExpr()
  87 			if stmt != nil {
  88-				if stmt.Type == ast.FuncCall {
  89-					funcCallData := stmt.Data.(ast.FuncCallNode)
  90-					if funcCallData.FuncExpr.Type == ast.Ident {
  91-						funcName := funcCallData.FuncExpr.Data.(ast.IdentNode).Name
  92-						stmt = ast.NewFuncDecl(stmt.Tok, funcName, nil, ast.NewBlock(stmt.Tok, nil, true), false, false, ast.TypeUntyped)
  93-					}
  94-				}
  95-				p.expect(token.Semi, "Expected ';' after top-level expression statement.")
  96+				p.expect(token.Semi, "Expected ';' after top-level expression statement")
  97 			} else {
  98-				util.Error(p.current, "Expected a top-level definition or expression.")
  99+				util.Error(p.current, "Expected a top-level definition or expression")
 100 				p.advance()
 101 			}
 102 		}
 103@@ -197,20 +176,18 @@ func (p *Parser) isBxDeclarationAhead() bool {
 104 	originalPos, originalCurrent := p.pos, p.current
 105 	defer func() { p.pos, p.current = originalPos, originalCurrent }()
 106 
 107-	if p.check(token.Auto) {
 108-		p.advance()
 109-	}
 110-	if !p.check(token.Ident) {
 111-		return false
 112-	}
 113+	hasAuto := p.match(token.Auto)
 114+	if !p.check(token.Ident) { return false }
 115 	p.advance()
 116+
 117 	for p.match(token.Comma) {
 118-		if !p.check(token.Ident) {
 119-			return false
 120-		}
 121+		if !p.check(token.Ident) { return false }
 122 		p.advance()
 123 	}
 124-	return p.check(token.Eq) || p.check(token.Define)
 125+
 126+	if p.check(token.Define) { return true }
 127+	if p.check(token.Eq) { return hasAuto }
 128+	return false
 129 }
 130 
 131 func (p *Parser) isBuiltinType(tok token.Token) bool {
 132@@ -220,19 +197,7 @@ func (p *Parser) isBuiltinType(tok token.Token) bool {
 133 func (p *Parser) parseStmt() *ast.Node {
 134 	tok := p.current
 135 
 136-	isLabelAhead := false
 137-	if p.peek().Type == token.Colon {
 138-		if p.check(token.Ident) {
 139-			isLabelAhead = true
 140-		} else {
 141-			for _, kwType := range token.KeywordMap {
 142-				if p.check(kwType) {
 143-					isLabelAhead = true
 144-					break
 145-				}
 146-			}
 147-		}
 148-	}
 149+	isLabelAhead := (p.check(token.Ident) || p.current.Type >= token.Auto) && p.peek().Type == token.Colon
 150 
 151 	if isLabelAhead {
 152 		var labelName string
 153@@ -246,62 +211,56 @@ func (p *Parser) parseStmt() *ast.Node {
 154 				}
 155 			}
 156 		}
 157-		p.advance() // consume label name
 158-		p.advance() // consume ':'
 159-		if p.check(token.RBrace) {
 160-			return ast.NewLabel(tok, labelName, ast.NewBlock(p.current, nil, true))
 161-		}
 162+		p.advance()
 163+		p.advance()
 164+		if p.check(token.RBrace) { return ast.NewLabel(tok, labelName, ast.NewBlock(p.current, nil, true)) }
 165 		return ast.NewLabel(tok, labelName, p.parseStmt())
 166 	}
 167 
 168-	if p.isTypedPass && (p.isBuiltinType(p.current) || p.isTypeName(p.current.Value) || p.check(token.Const)) {
 169+	if p.isTypedPass && (p.isBuiltinType(p.current) || (p.isTypeName(p.current.Value) && p.peek().Type != token.Define) || p.check(token.Const)) {
 170 		return p.parseTypedVarOrFuncDecl(false)
 171 	}
 172 
 173 	switch {
 174 	case p.match(token.If):
 175-		p.expect(token.LParen, "Expected '(' after 'if'.")
 176+		p.expect(token.LParen, "Expected '(' after 'if'")
 177 		cond := p.parseExpr()
 178-		p.expect(token.RParen, "Expected ')' after if condition.")
 179+		p.expect(token.RParen, "Expected ')' after if condition")
 180 		thenBody := p.parseStmt()
 181 		var elseBody *ast.Node
 182-		if p.match(token.Else) {
 183-			elseBody = p.parseStmt()
 184-		}
 185+		if p.match(token.Else) { elseBody = p.parseStmt() }
 186 		return ast.NewIf(tok, cond, thenBody, elseBody)
 187 	case p.match(token.While):
 188-		p.expect(token.LParen, "Expected '(' after 'while'.")
 189+		p.expect(token.LParen, "Expected '(' after 'while'")
 190 		cond := p.parseExpr()
 191-		p.expect(token.RParen, "Expected ')' after while condition.")
 192+		p.expect(token.RParen, "Expected ')' after while condition")
 193 		body := p.parseStmt()
 194 		return ast.NewWhile(tok, cond, body)
 195 	case p.match(token.Switch):
 196 		hasParen := p.match(token.LParen)
 197 		expr := p.parseExpr()
 198-		if hasParen {
 199-			p.expect(token.RParen, "Expected ')' after switch expression.")
 200-		}
 201+		if hasParen { p.expect(token.RParen, "Expected ')' after switch expression") }
 202 		body := p.parseStmt()
 203-		switchNode := ast.NewSwitch(tok, expr, body)
 204-		p.buildSwitchJumpTable(switchNode)
 205-		return switchNode
 206+		return ast.NewSwitch(tok, expr, body)
 207 	case p.check(token.LBrace):
 208 		return p.parseBlockStmt()
 209 	case p.check(token.Auto):
 210-		if p.isBxDeclarationAhead() {
 211-			return p.parseDeclaration(true)
 212-		}
 213+		if p.isBxDeclarationAhead() { return p.parseDeclaration(true) }
 214 		p.advance()
 215 		return p.parseUntypedDeclarationList(token.Auto, p.previous)
 216 	case p.match(token.Extrn):
 217 		return p.parseUntypedDeclarationList(token.Extrn, p.previous)
 218 	case p.match(token.Case):
 219-		value := p.parseExpr()
 220-		p.expect(token.Colon, "Expected ':' after case value.")
 221+		var values []*ast.Node
 222+		for {
 223+			values = append(values, p.parseExpr())
 224+			if !p.match(token.Comma) { break }
 225+		}
 226+		p.expect(token.Colon, "Expected ':' after case value")
 227 		body := p.parseStmt()
 228-		return ast.NewCase(tok, value, body)
 229+		return ast.NewCase(tok, values, body)
 230 	case p.match(token.Default):
 231-		p.expect(token.Colon, "Expected ':' after 'default'.")
 232+		p.expect(token.Colon, "Expected ':' after 'default'")
 233 		body := p.parseStmt()
 234 		return ast.NewDefault(tok, body)
 235 	case p.match(token.Goto):
 236@@ -313,45 +272,40 @@ func (p *Parser) parseStmt() *ast.Node {
 237 			isKeyword := false
 238 			for kw, typ := range token.KeywordMap {
 239 				if p.current.Type == typ {
 240-					labelName = kw
 241-					isKeyword = true
 242+					labelName, isKeyword = kw, true
 243 					break
 244 				}
 245 			}
 246 			if !isKeyword {
 247-				util.Error(p.current, "Expected label name after 'goto'.")
 248-				for !p.check(token.Semi) && !p.check(token.EOF) {
 249-					p.advance()
 250-				}
 251+				util.Error(p.current, "Expected label name after 'goto'")
 252+				for !p.check(token.Semi) && !p.check(token.EOF) { p.advance() }
 253 			} else {
 254 				if labelName == "continue" {
 255-					util.Warn(p.cfg, config.WarnExtra, p.current, "'goto continue' is a workaround for a limitation of -std=B. Please avoid this construct.")
 256+					util.Warn(p.cfg, config.WarnExtra, p.current, "'goto continue' is a workaround for a limitation of -std=B; please avoid this construct")
 257 				}
 258 				p.advance()
 259 			}
 260 		}
 261 		node := ast.NewGoto(tok, labelName)
 262-		p.expect(token.Semi, "Expected ';' after goto statement.")
 263+		p.expect(token.Semi, "Expected ';' after goto statement")
 264 		return node
 265 	case p.match(token.Return):
 266 		var expr *ast.Node
 267 		if !p.check(token.Semi) {
 268-			p.expect(token.LParen, "Expected '(' after 'return' with value.")
 269-			if !p.check(token.RParen) {
 270-				expr = p.parseExpr()
 271-			}
 272-			p.expect(token.RParen, "Expected ')' after return value.")
 273+			p.expect(token.LParen, "Expected '(' after 'return' with value")
 274+			if !p.check(token.RParen) { expr = p.parseExpr() }
 275+			p.expect(token.RParen, "Expected ')' after return value")
 276 		}
 277-		p.expect(token.Semi, "Expected ';' after return statement.")
 278+		p.expect(token.Semi, "Expected ';' after return statement")
 279 		return ast.NewReturn(tok, expr)
 280 	case p.match(token.Break):
 281-		p.expect(token.Semi, "Expected ';' after 'break'.")
 282+		p.expect(token.Semi, "Expected ';' after 'break'")
 283 		return ast.NewBreak(tok)
 284 	case p.match(token.Continue):
 285 		if !p.cfg.IsFeatureEnabled(config.FeatContinue) {
 286-			util.Error(p.previous, "'continue' is a Bx extension, not available in -std=B.")
 287+			util.Error(p.previous, "'continue' is a Bx extension, not available in -std=B")
 288 		}
 289-		p.expect(token.Semi, "Expected ';' after 'continue'.")
 290+		p.expect(token.Semi, "Expected ';' after 'continue'")
 291 		return ast.NewContinue(tok)
 292 	case p.match(token.Semi):
 293 		return ast.NewBlock(tok, nil, true)
 294@@ -361,31 +315,23 @@ func (p *Parser) parseStmt() *ast.Node {
 295 			originalPos, originalCurrent := p.pos, p.current
 296 			p.advance()
 297 			for p.match(token.Comma) {
 298-				if !p.check(token.Ident) {
 299-					break
 300-				}
 301+				if !p.check(token.Ident) { break }
 302 				p.advance()
 303 			}
 304-			if p.check(token.Define) {
 305-				isShortDecl = true
 306-			}
 307+			if p.check(token.Define) { isShortDecl = true }
 308 			p.pos, p.current = originalPos, originalCurrent
 309-			if isShortDecl {
 310-				return p.parseDeclaration(false)
 311-			}
 312+			if isShortDecl { return p.parseDeclaration(false) }
 313 		}
 314 
 315 		expr := p.parseExpr()
 316-		if expr != nil {
 317-			p.expect(token.Semi, "Expected ';' after expression statement.")
 318-		}
 319+		if expr != nil { p.expect(token.Semi, "Expected ';' after expression statement") }
 320 		return expr
 321 	}
 322 }
 323 
 324 func (p *Parser) parseBlockStmt() *ast.Node {
 325 	tok := p.current
 326-	p.expect(token.LBrace, "Expected '{' to start a block.")
 327+	p.expect(token.LBrace, "Expected '{' to start a block")
 328 	var stmts []*ast.Node
 329 	for !p.check(token.RBrace) && !p.check(token.EOF) {
 330 		stmt := p.parseStmt()
 331@@ -397,24 +343,22 @@ func (p *Parser) parseBlockStmt() *ast.Node {
 332 			}
 333 		}
 334 	}
 335-	p.expect(token.RBrace, "Expected '}' after block.")
 336+	p.expect(token.RBrace, "Expected '}' after block")
 337 	return ast.NewBlock(tok, stmts, false)
 338 }
 339 
 340 func (p *Parser) parseDeclaration(hasAuto bool) *ast.Node {
 341 	declTok := p.current
 342 	if hasAuto {
 343-		p.expect(token.Auto, "Expected 'auto' keyword.")
 344+		p.expect(token.Auto, "Expected 'auto' keyword")
 345 		declTok = p.previous
 346 	}
 347 
 348 	var names []*ast.Node
 349 	for {
 350-		p.expect(token.Ident, "Expected identifier in declaration.")
 351+		p.expect(token.Ident, "Expected identifier in declaration")
 352 		names = append(names, ast.NewIdent(p.previous, p.previous.Value))
 353-		if !p.match(token.Comma) {
 354-			break
 355-		}
 356+		if !p.match(token.Comma) { break }
 357 	}
 358 
 359 	var op token.Type
 360@@ -430,34 +374,26 @@ func (p *Parser) parseDeclaration(hasAuto bool) *ast.Node {
 361 	if op != 0 {
 362 		for {
 363 			inits = append(inits, p.parseAssignmentExpr())
 364-			if !p.match(token.Comma) {
 365-				break
 366-			}
 367+			if !p.match(token.Comma) { break }
 368 		}
 369 		if len(names) != len(inits) {
 370 			util.Error(declTok, "Mismatched number of variables and initializers (%d vs %d)", len(names), len(inits))
 371 		}
 372-	} else {
 373-		if !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
 374-			util.Error(declTok, "Uninitialized declaration is not allowed in this mode")
 375-		}
 376+	} else if !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
 377+		util.Error(declTok, "Uninitialized declaration is not allowed in this mode")
 378 	}
 379 
 380 	var decls []*ast.Node
 381 	for i, nameNode := range names {
 382 		var initList []*ast.Node
 383-		if i < len(inits) {
 384-			initList = append(initList, inits[i])
 385-		}
 386+		if i < len(inits) { initList = append(initList, inits[i]) }
 387 		name := nameNode.Data.(ast.IdentNode).Name
 388-		decls = append(decls, ast.NewVarDecl(nameNode.Tok, name, ast.TypeUntyped, initList, nil, false, false, isDefine || op == token.Eq))
 389+		decls = append(decls, ast.NewVarDecl(nameNode.Tok, name, ast.TypeUntyped, initList, nil, false, false, isDefine))
 390 	}
 391 
 392-	p.expect(token.Semi, "Expected ';' after declaration.")
 393+	p.expect(token.Semi, "Expected ';' after declaration")
 394 
 395-	if len(decls) == 1 {
 396-		return decls[0]
 397-	}
 398+	if len(decls) == 1 { return decls[0] }
 399 	return ast.NewMultiVarDecl(declTok, decls)
 400 }
 401 
 402@@ -465,13 +401,11 @@ func (p *Parser) parseUntypedDeclarationList(declType token.Type, declTok token.
 403 	if declType == token.Extrn {
 404 		var names []*ast.Node
 405 		for {
 406-			p.expect(token.Ident, "Expected identifier in 'extrn' list.")
 407+			p.expect(token.Ident, "Expected identifier in 'extrn' list")
 408 			names = append(names, ast.NewIdent(p.previous, p.previous.Value))
 409-			if !p.match(token.Comma) {
 410-				break
 411-			}
 412+			if !p.match(token.Comma) { break }
 413 		}
 414-		p.expect(token.Semi, "Expected ';' after 'extrn' declaration.")
 415+		p.expect(token.Semi, "Expected ';' after 'extrn' declaration")
 416 		return ast.NewExtrnDecl(declTok, names)
 417 	}
 418 
 419@@ -481,16 +415,14 @@ func (p *Parser) parseUntypedDeclarationList(declType token.Type, declTok token.
 420 		var itemToken token.Token
 421 
 422 		if p.check(token.Ident) {
 423-			itemToken = p.current
 424-			name = p.current.Value
 425+			itemToken, name = p.current, p.current.Value
 426 			p.advance()
 427 		} else if p.check(token.TypeKeyword) {
 428-			itemToken = p.current
 429-			name = "type"
 430-			util.Warn(p.cfg, config.WarnExtra, itemToken, "Using keyword 'type' as an identifier.")
 431+			itemToken, name = p.current, "type"
 432+			util.Warn(p.cfg, config.WarnExtra, itemToken, "Using keyword 'type' as an identifier")
 433 			p.advance()
 434 		} else {
 435-			p.expect(token.Ident, "Expected identifier in declaration.")
 436+			p.expect(token.Ident, "Expected identifier in declaration")
 437 			if p.check(token.Comma) || p.check(token.Semi) {
 438 				continue
 439 			}
 440@@ -502,42 +434,32 @@ func (p *Parser) parseUntypedDeclarationList(declType token.Type, declTok token.
 441 
 442 		if p.match(token.LBracket) {
 443 			if declType == token.Auto {
 444-				util.Error(p.previous, "Classic B 'auto' vectors use 'auto name size', not 'auto name[size]'.")
 445+				util.Error(p.previous, "Classic B 'auto' vectors use 'auto name size', not 'auto name[size]'")
 446 			}
 447 			isVector, isBracketed = true, true
 448-			if !p.check(token.RBracket) {
 449-				sizeExpr = p.parseExpr()
 450-			}
 451-			p.expect(token.RBracket, "Expected ']' after array size.")
 452+			if !p.check(token.RBracket) { sizeExpr = p.parseExpr() }
 453+			p.expect(token.RBracket, "Expected ']' after array size")
 454 		} else if p.check(token.Number) {
 455 			isVector = true
 456 			sizeExpr = p.parsePrimaryExpr()
 457 		}
 458 
 459-		if sizeExpr == nil && !isBracketed {
 460-			if !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
 461-				util.Error(itemToken, "Uninitialized declaration of '%s' is not allowed in this mode", name)
 462-			}
 463+		if sizeExpr == nil && !isBracketed && !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
 464+			util.Error(itemToken, "Uninitialized declaration of '%s' is not allowed in this mode", name)
 465 		}
 466 
 467 		decls = append(decls, ast.NewVarDecl(itemToken, name, nil, nil, sizeExpr, isVector, isBracketed, false))
 468-		if !p.match(token.Comma) {
 469-			break
 470-		}
 471+		if !p.match(token.Comma) { break }
 472 	}
 473-	p.expect(token.Semi, "Expected ';' after declaration list.")
 474+	p.expect(token.Semi, "Expected ';' after declaration list")
 475 
 476-	if len(decls) == 1 {
 477-		return decls[0]
 478-	}
 479+	if len(decls) == 1 { return decls[0] }
 480 	return ast.NewMultiVarDecl(declTok, decls)
 481 }
 482 
 483 func (p *Parser) parseUntypedGlobalDefinition(nameToken token.Token) *ast.Node {
 484 	name := nameToken.Value
 485-	if p.isTypeName(name) {
 486-		util.Error(nameToken, "Variable name '%s' shadows a type.", name)
 487-	}
 488+	if p.isTypeName(name) { util.Error(nameToken, "Variable name '%s' shadows a type", name) }
 489 	p.advance()
 490 
 491 	var sizeExpr *ast.Node
 492@@ -545,10 +467,8 @@ func (p *Parser) parseUntypedGlobalDefinition(nameToken token.Token) *ast.Node {
 493 
 494 	if p.match(token.LBracket) {
 495 		isVector, isBracketed = true, true
 496-		if !p.check(token.RBracket) {
 497-			sizeExpr = p.parseExpr()
 498-		}
 499-		p.expect(token.RBracket, "Expected ']' for vector definition.")
 500+		if !p.check(token.RBracket) { sizeExpr = p.parseExpr() }
 501+		p.expect(token.RBracket, "Expected ']' for vector definition")
 502 	}
 503 
 504 	var initList []*ast.Node
 505@@ -556,35 +476,27 @@ func (p *Parser) parseUntypedGlobalDefinition(nameToken token.Token) *ast.Node {
 506 		initList = append(initList, p.parseUnaryExpr())
 507 		if isBracketed || p.match(token.Comma) || (!p.check(token.Semi) && !p.check(token.EOF)) {
 508 			isVector = true
 509-			if p.previous.Type != token.Comma {
 510-				p.match(token.Comma)
 511-			}
 512+			if p.previous.Type != token.Comma { p.match(token.Comma) }
 513 			for !p.check(token.Semi) && !p.check(token.EOF) {
 514 				initList = append(initList, p.parseUnaryExpr())
 515-				if p.check(token.Semi) || p.check(token.EOF) {
 516-					break
 517-				}
 518+				if p.check(token.Semi) || p.check(token.EOF) { break }
 519 				p.match(token.Comma)
 520 			}
 521 		}
 522 	}
 523 
 524-	if len(initList) == 0 && sizeExpr == nil && !isBracketed {
 525-		if !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
 526-			util.Error(nameToken, "Uninitialized declaration of '%s' is not allowed in this mode", name)
 527-		}
 528+	if len(initList) == 0 && sizeExpr == nil && !isBracketed && !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
 529+		util.Error(nameToken, "Uninitialized declaration of '%s' is not allowed in this mode", name)
 530 	}
 531 
 532-	p.expect(token.Semi, "Expected ';' after global definition.")
 533+	p.expect(token.Semi, "Expected ';' after global definition")
 534 	return ast.NewVarDecl(nameToken, name, nil, initList, sizeExpr, isVector, isBracketed, false)
 535 }
 536 
 537 func (p *Parser) parseFuncDecl(returnType *ast.BxType, nameToken token.Token) *ast.Node {
 538 	name := nameToken.Value
 539-	if p.isTypeName(name) {
 540-		util.Error(nameToken, "Function name '%s' shadows a type.", name)
 541-	}
 542-	p.expect(token.LParen, "Expected '(' after function name.")
 543+	if p.isTypeName(name) { util.Error(nameToken, "Function name '%s' shadows a type", name) }
 544+	p.expect(token.LParen, "Expected '(' after function name")
 545 
 546 	var params []*ast.Node
 547 	var hasVarargs bool
 548@@ -595,7 +507,7 @@ func (p *Parser) parseFuncDecl(returnType *ast.BxType, nameToken token.Token) *a
 549 	} else {
 550 		params, hasVarargs = p.parseUntypedParameters()
 551 	}
 552-	p.expect(token.RParen, "Expected ')' after parameters.")
 553+	p.expect(token.RParen, "Expected ')' after parameters")
 554 
 555 	var decls []*ast.Node
 556 	for p.check(token.Auto) || p.check(token.Extrn) {
 557@@ -639,26 +551,24 @@ func (p *Parser) parseFuncDecl(returnType *ast.BxType, nameToken token.Token) *a
 558 
 559 func (p *Parser) parseAsmFuncDef(nameToken token.Token) *ast.Node {
 560 	name := nameToken.Value
 561-	if p.isTypeName(name) {
 562-		util.Error(nameToken, "Function name '%s' shadows a type.", name)
 563-	}
 564+	if p.isTypeName(name) { util.Error(nameToken, "Function name '%s' shadows a type", name) }
 565 
 566-	p.expect(token.Asm, "Expected '__asm__' keyword.")
 567+	p.expect(token.Asm, "Expected '__asm__' keyword")
 568 	asmTok := p.previous
 569 
 570-	p.expect(token.LParen, "Expected '(' after '__asm__'.")
 571+	p.expect(token.LParen, "Expected '(' after '__asm__'")
 572 	var codeParts []string
 573 	for !p.check(token.RParen) && !p.check(token.EOF) {
 574-		p.expect(token.String, "Expected string literal in '__asm__' block.")
 575+		p.expect(token.String, "Expected string literal in '__asm__' block")
 576 		codeParts = append(codeParts, p.previous.Value)
 577 		p.match(token.Comma)
 578 	}
 579-	p.expect(token.RParen, "Expected ')' to close '__asm__' block.")
 580+	p.expect(token.RParen, "Expected ')' to close '__asm__' block")
 581 	asmCode := strings.Join(codeParts, "\n")
 582 	body := ast.NewAsmStmt(asmTok, asmCode)
 583 
 584 	if !p.check(token.LBrace) {
 585-		p.expect(token.Semi, "Expected ';' or '{' after '__asm__' definition.")
 586+		p.expect(token.Semi, "Expected ';' or '{' after '__asm__' definition")
 587 	} else {
 588 		p.parseBlockStmt()
 589 	}
 590@@ -668,34 +578,75 @@ func (p *Parser) parseAsmFuncDef(nameToken token.Token) *ast.Node {
 591 
 592 func (p *Parser) parseTypeDecl() *ast.Node {
 593 	typeTok := p.previous
 594-	var underlyingType *ast.BxType
 595 
 596-	if p.check(token.Struct) {
 597-		p.advance()
 598-		underlyingType = p.parseStructDef()
 599-	} else {
 600-		util.Error(typeTok, "Expected 'struct' after 'type'.")
 601-		p.advance()
 602-		return nil
 603-	}
 604+	if p.match(token.Enum) { return p.parseEnumDef(typeTok) }
 605 
 606-	var name string
 607-	if p.check(token.Ident) {
 608-		name = p.current.Value
 609-		p.advance()
 610-	} else {
 611-		if underlyingType.StructTag == "" {
 612-			util.Error(typeTok, "Typedef for anonymous struct must have a name.")
 613-			return nil
 614+	if p.match(token.Struct) {
 615+		underlyingType := p.parseStructDef()
 616+		var name string
 617+		if p.check(token.Ident) {
 618+			name = p.current.Value
 619+			p.advance()
 620+		} else {
 621+			if underlyingType.StructTag == "" {
 622+				util.Error(typeTok, "Typedef for anonymous struct must have a name")
 623+				return nil
 624+			}
 625+			name = underlyingType.StructTag
 626 		}
 627-		name = underlyingType.StructTag
 628+
 629+		p.typeNames[name] = true
 630+		underlyingType.Name = name
 631+
 632+		p.expect(token.Semi, "Expected ';' after type declaration")
 633+		return ast.NewTypeDecl(typeTok, name, underlyingType)
 634 	}
 635 
 636+	util.Error(typeTok, "Expected 'struct' or 'enum' after 'type'")
 637+	p.advance()
 638+	return nil
 639+}
 640+
 641+func (p *Parser) parseEnumDef(typeTok token.Token) *ast.Node {
 642+	p.expect(token.Ident, "Expected enum name")
 643+	nameToken := p.previous
 644+	name := nameToken.Value
 645 	p.typeNames[name] = true
 646-	underlyingType.Name = name
 647 
 648-	p.expect(token.Semi, "Expected ';' after type declaration.")
 649-	return ast.NewTypeDecl(typeTok, name, underlyingType)
 650+	p.expect(token.LBrace, "Expected '{' to open enum definition")
 651+
 652+	var members []*ast.Node
 653+	var currentValue int64 = 0
 654+
 655+	for !p.check(token.RBrace) && !p.check(token.EOF) {
 656+		p.expect(token.Ident, "Expected enum member name")
 657+		memberToken := p.previous
 658+		memberName := memberToken.Value
 659+
 660+		if p.match(token.Eq) {
 661+			valExpr := p.parseExpr()
 662+			foldedVal := ast.FoldConstants(valExpr)
 663+			if foldedVal.Type != ast.Number {
 664+				util.Error(valExpr.Tok, "Enum member initializer must be a constant integer")
 665+				currentValue++
 666+			} else {
 667+				currentValue = foldedVal.Data.(ast.NumberNode).Value
 668+			}
 669+		}
 670+
 671+		initExpr := ast.NewNumber(memberToken, currentValue)
 672+		memberDecl := ast.NewVarDecl(memberToken, memberName, ast.TypeInt, []*ast.Node{initExpr}, nil, false, false, true)
 673+		members = append(members, memberDecl)
 674+
 675+		currentValue++
 676+
 677+		if !p.match(token.Comma) { break }
 678+	}
 679+
 680+	p.expect(token.RBrace, "Expected '}' to close enum definition")
 681+	p.expect(token.Semi, "Expected ';' after enum declaration")
 682+
 683+	return ast.NewEnumDecl(typeTok, name, members)
 684 }
 685 
 686 func (p *Parser) parseTypedVarOrFuncDecl(isTopLevel bool) *ast.Node {
 687@@ -703,11 +654,11 @@ func (p *Parser) parseTypedVarOrFuncDecl(isTopLevel bool) *ast.Node {
 688 	declType := p.parseType()
 689 
 690 	if p.match(token.Define) {
 691-		util.Error(p.previous, "Cannot use ':=' in a typed declaration. Use '=' instead.")
 692+		util.Error(p.previous, "Cannot use ':=' in a typed declaration; use '=' instead")
 693 		return p.parseTypedVarDeclBody(startTok, declType, p.previous)
 694 	}
 695 
 696-	p.expect(token.Ident, "Expected identifier after type.")
 697+	p.expect(token.Ident, "Expected identifier after type")
 698 	nameToken := p.previous
 699 
 700 	if p.check(token.LParen) { return p.parseFuncDecl(declType, nameToken) }
 701@@ -727,33 +678,27 @@ func (p *Parser) parseTypedVarDeclBody(startTok token.Token, declType *ast.BxTyp
 702 
 703 		if p.match(token.LBracket) {
 704 			isArr, isBracketed = true, true
 705-			if !p.check(token.RBracket) {
 706-				sizeExpr = p.parseExpr()
 707-			}
 708-			p.expect(token.RBracket, "Expected ']' after array size.")
 709+			if !p.check(token.RBracket) { sizeExpr = p.parseExpr() }
 710+			p.expect(token.RBracket, "Expected ']' after array size")
 711 			finalType = &ast.BxType{Kind: ast.TYPE_ARRAY, Base: declType, ArraySize: sizeExpr, IsConst: declType.IsConst}
 712 		}
 713 
 714 		var initList []*ast.Node
 715 		if p.match(token.Eq) {
 716 			initList = append(initList, p.parseAssignmentExpr())
 717-		} else {
 718-			if !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
 719-				util.Error(nameToken, "Initialized typed declaration is required in this mode")
 720-			}
 721+		} else if !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
 722+			util.Error(nameToken, "Initialized typed declaration is required in this mode")
 723 		}
 724 
 725 		decls = append(decls, ast.NewVarDecl(currentNameToken, name, finalType, initList, sizeExpr, isArr, isBracketed, false))
 726 
 727-		if !p.match(token.Comma) {
 728-			break
 729-		}
 730+		if !p.match(token.Comma) { break }
 731 
 732-		p.expect(token.Ident, "Expected identifier after comma in declaration list.")
 733+		p.expect(token.Ident, "Expected identifier after comma in declaration list")
 734 		currentNameToken = p.previous
 735 	}
 736 
 737-	p.expect(token.Semi, "Expected ';' after typed variable declaration.")
 738+	p.expect(token.Semi, "Expected ';' after typed variable declaration")
 739 
 740 	if len(decls) == 1 { return decls[0] }
 741 	return ast.NewMultiVarDecl(startTok, decls)
 742@@ -766,7 +711,7 @@ func (p *Parser) parseType() *ast.BxType {
 743 	var baseType *ast.BxType
 744 
 745 	if p.match(token.LBracket) {
 746-		p.expect(token.RBracket, "Expected ']' to complete array type specifier.")
 747+		p.expect(token.RBracket, "Expected ']' to complete array type specifier")
 748 		elemType := p.parseType()
 749 		baseType = &ast.BxType{Kind: ast.TYPE_ARRAY, Base: elemType}
 750 	} else {
 751@@ -779,6 +724,15 @@ func (p *Parser) parseType() *ast.BxType {
 752 			} else {
 753 				baseType = p.parseStructDef()
 754 			}
 755+		} else if p.match(token.Enum) {
 756+			if p.check(token.Ident) {
 757+				tagName := p.current.Value
 758+				p.advance()
 759+				baseType = &ast.BxType{Kind: ast.TYPE_ENUM, Name: tagName}
 760+			} else {
 761+				util.Error(tok, "Anonymous enums are not supported as types")
 762+				baseType = ast.TypeUntyped
 763+			}
 764 		} else if p.isBuiltinType(tok) {
 765 			p.advance()
 766 			var typeName string
 767@@ -793,6 +747,8 @@ func (p *Parser) parseType() *ast.BxType {
 768 				baseType = ast.TypeVoid
 769 			} else if p.previous.Type == token.StringKeyword {
 770 				baseType = ast.TypeString
 771+			} else if p.previous.Type >= token.Float && p.previous.Type <= token.Float64 {
 772+				baseType = &ast.BxType{Kind: ast.TYPE_FLOAT, Name: typeName}
 773 			} else {
 774 				if typeName == "" {
 775 					util.Error(tok, "Internal parser error: could not find string for builtin type %v", tok.Type)
 776@@ -803,20 +759,22 @@ func (p *Parser) parseType() *ast.BxType {
 777 		} else if p.check(token.Ident) {
 778 			typeName := p.current.Value
 779 			if !p.isTypeName(typeName) {
 780-				util.Error(p.current, "Unknown type name '%s'.", typeName)
 781+				util.Error(p.current, "Unknown type name '%s'", typeName)
 782 				p.advance()
 783 				return ast.TypeUntyped
 784 			}
 785 			p.advance()
 786 			baseType = &ast.BxType{Kind: ast.TYPE_PRIMITIVE, Name: typeName}
 787 		} else {
 788-			util.Error(p.current, "Expected a type name, 'struct', or '[]'.")
 789+			util.Error(p.current, "Expected a type name, 'struct', 'enum', or '[]'")
 790 			p.advance()
 791 			return ast.TypeUntyped
 792 		}
 793 	}
 794 
 795-	for p.match(token.Star) { baseType = &ast.BxType{Kind: ast.TYPE_POINTER, Base: baseType} }
 796+	for p.match(token.Star) {
 797+		baseType = &ast.BxType{Kind: ast.TYPE_POINTER, Base: baseType}
 798+	}
 799 
 800 	if isConst {
 801 		newType := *baseType
 802@@ -835,18 +793,34 @@ func (p *Parser) parseStructDef() *ast.BxType {
 803 		p.advance()
 804 	}
 805 
 806-	p.expect(token.LBrace, "Expected '{' to open struct definition.")
 807+	p.expect(token.LBrace, "Expected '{' to open struct definition")
 808 
 809 	for !p.check(token.RBrace) && !p.check(token.EOF) {
 810-		p.expect(token.Ident, "Expected field name in struct.")
 811-		nameToken := p.previous
 812+		var names []token.Token
 813+		p.expect(token.Ident, "Expected field name in struct")
 814+		names = append(names, p.previous)
 815+
 816+		for p.match(token.Comma) {
 817+			if p.isBuiltinType(p.current) || p.isTypeName(p.current.Value) || p.check(token.LBracket) || p.check(token.Star) || p.check(token.Struct) {
 818+				p.pos--
 819+				p.current = p.tokens[p.pos-1]
 820+				break
 821+			}
 822+			p.expect(token.Ident, "Expected field name after comma")
 823+			names = append(names, p.previous)
 824+		}
 825+
 826 		fieldType := p.parseType()
 827-		fieldDecl := ast.NewVarDecl(nameToken, nameToken.Value, fieldType, nil, nil, false, false, false)
 828-		structType.Fields = append(structType.Fields, fieldDecl)
 829-		p.expect(token.Semi, "Expected ';' after struct field declaration.")
 830+
 831+		for _, nameToken := range names {
 832+			fieldDecl := ast.NewVarDecl(nameToken, nameToken.Value, fieldType, nil, nil, false, false, false)
 833+			structType.Fields = append(structType.Fields, fieldDecl)
 834+		}
 835+
 836+		p.expect(token.Semi, "Expected ';' after struct field declaration")
 837 	}
 838 
 839-	p.expect(token.RBrace, "Expected '}' to close struct definition.")
 840+	p.expect(token.RBrace, "Expected '}' to close struct definition")
 841 	if structType.StructTag != "" { structType.Name = structType.StructTag }
 842 	return structType
 843 }
 844@@ -876,7 +850,7 @@ func (p *Parser) parseUntypedParameters() ([]*ast.Node, bool) {
 845 				hasVarargs = true
 846 				break
 847 			}
 848-			p.expect(token.Ident, "Expected parameter name or '...'.")
 849+			p.expect(token.Ident, "Expected parameter name or '...'")
 850 			params = append(params, ast.NewIdent(p.previous, p.previous.Value))
 851 			if !p.match(token.Comma) { break }
 852 		}
 853@@ -907,7 +881,7 @@ func (p *Parser) parseTypedParameters() ([]*ast.Node, bool) {
 854 			params = append(params, paramNode)
 855 		} else {
 856 			var names []token.Token
 857-			p.expect(token.Ident, "Expected parameter name.")
 858+			p.expect(token.Ident, "Expected parameter name")
 859 			names = append(names, p.previous)
 860 			for p.match(token.Comma) {
 861 				if p.isBuiltinType(p.current) || p.isTypeName(p.current.Value) || p.check(token.LBracket) || p.check(token.Star) || p.check(token.RParen) || p.check(token.Dots) {
 862@@ -915,7 +889,7 @@ func (p *Parser) parseTypedParameters() ([]*ast.Node, bool) {
 863 					p.current = p.tokens[p.pos-1]
 864 					break
 865 				}
 866-				p.expect(token.Ident, "Expected parameter name.")
 867+				p.expect(token.Ident, "Expected parameter name")
 868 				names = append(names, p.previous)
 869 			}
 870 
 871@@ -952,7 +926,7 @@ func (p *Parser) parseExpr() *ast.Node { return p.parseAssignmentExpr() }
 872 func (p *Parser) parseAssignmentExpr() *ast.Node {
 873 	left := p.parseTernaryExpr()
 874 	if op := p.current.Type; op >= token.Eq && op <= token.EqShr {
 875-		if !isLValue(left) { util.Error(p.current, "Invalid target for assignment.") }
 876+		if !isLValue(left) { util.Error(p.current, "Invalid target for assignment") }
 877 		tok := p.current
 878 		p.advance()
 879 		right := p.parseAssignmentExpr()
 880@@ -966,7 +940,7 @@ func (p *Parser) parseTernaryExpr() *ast.Node {
 881 	if p.match(token.Question) {
 882 		tok := p.previous
 883 		thenExpr := p.parseExpr()
 884-		p.expect(token.Colon, "Expected ':' for ternary operator.")
 885+		p.expect(token.Colon, "Expected ':' for ternary operator")
 886 		elseExpr := p.parseAssignmentExpr()
 887 		return ast.NewTernary(tok, cond, thenExpr, elseExpr)
 888 	}
 889@@ -994,17 +968,13 @@ func (p *Parser) parseUnaryExpr() *ast.Node {
 890 		op, opToken := p.previous.Type, p.previous
 891 		operand := p.parseUnaryExpr()
 892 
 893-		if op == token.Star {
 894-			return ast.NewIndirection(tok, operand)
 895-		}
 896+		if op == token.Star { return ast.NewIndirection(tok, operand) }
 897 		if op == token.And {
 898-			if !isLValue(operand) {
 899-				util.Error(opToken, "Address-of operator '&' requires an l-value.")
 900-			}
 901+			if !isLValue(operand) { util.Error(opToken, "Address-of operator '&' requires an l-value") }
 902 			return ast.NewAddressOf(tok, operand)
 903 		}
 904 		if (op == token.Inc || op == token.Dec) && !isLValue(operand) {
 905-			util.Error(opToken, "Prefix '++' or '--' requires an l-value.")
 906+			util.Error(opToken, "Prefix '++' or '--' requires an l-value")
 907 		}
 908 		return ast.NewUnaryOp(tok, op, operand)
 909 	}
 910@@ -1024,45 +994,109 @@ func (p *Parser) parsePostfixExpr() *ast.Node {
 911 					if !p.match(token.Comma) { break }
 912 				}
 913 			}
 914-			p.expect(token.RParen, "Expected ')' after function arguments.")
 915+			p.expect(token.RParen, "Expected ')' after function arguments")
 916 			expr = ast.NewFuncCall(tok, expr, args)
 917 		} else if p.match(token.LBracket) {
 918 			index := p.parseExpr()
 919-			p.expect(token.RBracket, "Expected ']' after array index.")
 920+			p.expect(token.RBracket, "Expected ']' after array index")
 921 			expr = ast.NewSubscript(tok, expr, index)
 922 		} else if p.isTypedPass && p.match(token.Dot) {
 923-			p.expect(token.Ident, "Expected member name after '.'.")
 924+			p.expect(token.Ident, "Expected member name after '.'")
 925 			member := ast.NewIdent(p.previous, p.previous.Value)
 926 			expr = ast.NewMemberAccess(tok, expr, member)
 927 		} else if p.match(token.Inc, token.Dec) {
 928-			if !isLValue(expr) { util.Error(p.previous, "Postfix '++' or '--' requires an l-value.") }
 929+			if !isLValue(expr) { util.Error(p.previous, "Postfix '++' or '--' requires an l-value") }
 930 			expr = ast.NewPostfixOp(p.previous, p.previous.Type, expr)
 931-		} else { break }
 932+		} else {
 933+			break
 934+		}
 935 	}
 936 	return expr
 937 }
 938 
 939+func (p *Parser) parseStructLiteral(typeNode *ast.Node) *ast.Node {
 940+	startTok := p.current
 941+	p.expect(token.LBrace, "Expected '{' for struct literal")
 942+
 943+	var values []*ast.Node
 944+	var names []*ast.Node
 945+	hasNames, hasPositional := false, false
 946+
 947+	for !p.check(token.RBrace) && !p.check(token.EOF) {
 948+		if p.check(token.Ident) && p.peek().Type == token.Colon {
 949+			hasNames = true
 950+			if hasPositional { util.Error(p.current, "Cannot mix named and positional fields in struct literal") }
 951+			p.expect(token.Ident, "Expected field name")
 952+			names = append(names, ast.NewIdent(p.previous, p.previous.Value))
 953+			p.expect(token.Colon, "Expected ':' after field name")
 954+			values = append(values, p.parseAssignmentExpr())
 955+		} else {
 956+			hasPositional = true
 957+			if hasNames { util.Error(p.current, "Cannot mix named and positional fields in struct literal") }
 958+			names = append(names, nil)
 959+			values = append(values, p.parseAssignmentExpr())
 960+		}
 961+
 962+		if !p.match(token.Comma) { break }
 963+	}
 964+
 965+	p.expect(token.RBrace, "Expected '}' to close struct literal")
 966+
 967+	if hasPositional && !hasNames { names = nil }
 968+
 969+	return ast.NewStructLiteral(startTok, typeNode, values, names)
 970+}
 971+
 972 func (p *Parser) parsePrimaryExpr() *ast.Node {
 973 	tok := p.current
 974 	if p.match(token.Number) {
 975-		val, _ := strconv.ParseInt(p.previous.Value, 10, 64)
 976+		valStr := p.previous.Value
 977+		val, err := strconv.ParseInt(valStr, 0, 64)
 978+		if err != nil {
 979+			uval, uerr := strconv.ParseUint(valStr, 0, 64)
 980+			if uerr != nil { util.Error(tok, "Invalid integer literal: %s", valStr) }
 981+			val = int64(uval)
 982+		}
 983 		return ast.NewNumber(tok, val)
 984 	}
 985+	if p.match(token.FloatNumber) {
 986+		val, _ := strconv.ParseFloat(p.previous.Value, 64)
 987+		return ast.NewFloatNumber(tok, val)
 988+	}
 989 	if p.match(token.String) { return ast.NewString(tok, p.previous.Value) }
 990-	if p.match(token.Ident) { return ast.NewIdent(tok, p.previous.Value) }
 991+	if p.match(token.Nil) { return ast.NewNil(tok) }
 992+	if p.match(token.Null) {
 993+		util.Warn(p.cfg, config.WarnExtra, tok, "Use of 'null' is discouraged, prefer 'nil' for idiomatic Bx code")
 994+		return ast.NewNil(tok)
 995+	}
 996+	if p.match(token.Ident) {
 997+		identTok := p.previous
 998+		if p.isTypedPass && p.isTypeName(identTok.Value) && p.check(token.LBrace) {
 999+			typeNode := ast.NewIdent(identTok, identTok.Value)
1000+			return p.parseStructLiteral(typeNode)
1001+		}
1002+		return ast.NewIdent(tok, p.previous.Value)
1003+	}
1004+	if p.isTypedPass && p.isBuiltinType(p.current) {
1005+		tokType := p.current.Type
1006+		p.advance()
1007+		if keyword, ok := token.TypeStrings[tokType]; ok {
1008+			return ast.NewIdent(tok, keyword)
1009+		}
1010+	}
1011 	if p.match(token.TypeKeyword) {
1012-		util.Warn(p.cfg, config.WarnExtra, p.previous, "Using keyword 'type' as an identifier.")
1013+		util.Warn(p.cfg, config.WarnExtra, p.previous, "Using keyword 'type' as an identifier")
1014 		return ast.NewIdent(tok, "type")
1015 	}
1016 	if p.match(token.LParen) {
1017 		if p.isTypedPass && (p.isBuiltinType(p.current) || p.isTypeName(p.current.Value)) {
1018 			castType := p.parseType()
1019-			p.expect(token.RParen, "Expected ')' after type in cast.")
1020+			p.expect(token.RParen, "Expected ')' after type in cast")
1021 			exprToCast := p.parseUnaryExpr()
1022 			return ast.NewTypeCast(tok, exprToCast, castType)
1023 		}
1024 		expr := p.parseExpr()
1025-		p.expect(token.RParen, "Expected ')' after expression.")
1026+		p.expect(token.RParen, "Expected ')' after expression")
1027 		return expr
1028 	}
1029 	if p.match(token.Auto) {
1030@@ -1070,7 +1104,7 @@ func (p *Parser) parsePrimaryExpr() *ast.Node {
1031 			allocTok := p.previous
1032 			p.advance()
1033 			sizeExpr := p.parseExpr()
1034-			p.expect(token.RBracket, "Expected ']' after auto allocation size.")
1035+			p.expect(token.RBracket, "Expected ']' after auto allocation size")
1036 			return ast.NewAutoAlloc(allocTok, sizeExpr)
1037 		}
1038 		p.pos--
1039@@ -1078,53 +1112,7 @@ func (p *Parser) parsePrimaryExpr() *ast.Node {
1040 	}
1041 
1042 	if !p.check(token.EOF) && !p.check(token.RBrace) && !p.check(token.Semi) {
1043-		util.Error(tok, "Expected an expression.")
1044+		util.Error(tok, "Expected an expression")
1045 	}
1046 	return nil
1047 }
1048-
1049-func (p *Parser) buildSwitchJumpTable(switchNode *ast.Node) {
1050-	if switchNode == nil || switchNode.Type != ast.Switch { return }
1051-	p.findCasesRecursive(switchNode.Data.(ast.SwitchNode).Body, switchNode)
1052-}
1053-
1054-func (p *Parser) findCasesRecursive(node, switchNode *ast.Node) {
1055-	if node == nil || (node.Type == ast.Switch && node != switchNode) { return }
1056-
1057-	swData := switchNode.Data.(ast.SwitchNode)
1058-
1059-	if node.Type == ast.Case {
1060-		caseData := node.Data.(ast.CaseNode)
1061-		foldedValue := ast.FoldConstants(caseData.Value)
1062-		if foldedValue.Type != ast.Number {
1063-			util.Error(node.Tok, "Case value must be a constant integer.")
1064-		} else {
1065-			caseData.Value = foldedValue
1066-			caseVal := foldedValue.Data.(ast.NumberNode).Value
1067-			labelName := fmt.Sprintf("@case_%d_%d", caseVal, node.Tok.Line)
1068-			swData.CaseLabels = append(swData.CaseLabels, ast.CaseLabelNode{Value: caseVal, LabelName: labelName})
1069-			caseData.QbeLabel = labelName
1070-			node.Data = caseData
1071-			switchNode.Data = swData
1072-		}
1073-	} else if node.Type == ast.Default {
1074-		defData := node.Data.(ast.DefaultNode)
1075-		if swData.DefaultLabelName != "" { util.Error(node.Tok, "Multiple 'default' labels in one switch statement.") }
1076-		labelName := fmt.Sprintf("@default_%d", node.Tok.Line)
1077-		swData.DefaultLabelName = labelName
1078-		defData.QbeLabel = labelName
1079-		node.Data = defData
1080-		switchNode.Data = swData
1081-	}
1082-
1083-	switch d := node.Data.(type) {
1084-	case ast.IfNode:
1085-		p.findCasesRecursive(d.ThenBody, switchNode)
1086-		p.findCasesRecursive(d.ElseBody, switchNode)
1087-	case ast.WhileNode: p.findCasesRecursive(d.Body, switchNode)
1088-	case ast.BlockNode: for _, stmt := range d.Stmts { p.findCasesRecursive(stmt, switchNode) }
1089-	case ast.LabelNode: p.findCasesRecursive(d.Stmt, switchNode)
1090-	case ast.CaseNode: p.findCasesRecursive(d.Body, switchNode)
1091-	case ast.DefaultNode: p.findCasesRecursive(d.Body, switchNode)
1092-	}
1093-}
M pkg/token/token.go
+19, -20
  1@@ -3,17 +3,13 @@ package token
  2 type Type int
  3 
  4 const (
  5-	// Meta
  6 	EOF Type = iota
  7 	Comment
  8 	Directive
  9-
 10-	// Literals
 11 	Ident
 12 	Number
 13+	FloatNumber
 14 	String
 15-
 16-	// Keywords
 17 	Auto
 18 	Extrn
 19 	If
 20@@ -26,14 +22,13 @@ const (
 21 	Default
 22 	Break
 23 	Continue
 24-	Asm // `__asm__`
 25-
 26-	// Bx Type System Keywords that are not types themselves
 27-	TypeKeyword // 'type'
 28+	Asm
 29+	Nil
 30+	Null
 31+	TypeKeyword
 32 	Struct
 33+	Enum
 34 	Const
 35-
 36-	// Bx Type System Keywords that ARE types
 37 	Void
 38 	Bool
 39 	Byte
 40@@ -50,10 +45,8 @@ const (
 41 	Float
 42 	Float32
 43 	Float64
 44-	StringKeyword // 'string'
 45+	StringKeyword
 46 	Any
 47-
 48-	// Punctuation
 49 	LParen
 50 	RParen
 51 	LBrace
 52@@ -66,8 +59,6 @@ const (
 53 	Question
 54 	Dots
 55 	Dot
 56-
 57-	// Assignment Operators
 58 	Eq
 59 	Define
 60 	PlusEq
 61@@ -90,8 +81,6 @@ const (
 62 	EqXor
 63 	EqShl
 64 	EqShr
 65-
 66-	// Binary Operators
 67 	Plus
 68 	Minus
 69 	Star
 70@@ -110,8 +99,6 @@ const (
 71 	Lte
 72 	AndAnd
 73 	OrOr
 74-
 75-	// Unary & Postfix Operators
 76 	Not
 77 	Complement
 78 	Inc
 79@@ -132,9 +119,12 @@ var KeywordMap = map[string]Type{
 80 	"__asm__":  Asm,
 81 	"break":    Break,
 82 	"continue": Continue,
 83+	"nil":      Nil,
 84+	"null":     Null,
 85 	"void":     Void,
 86 	"type":     TypeKeyword,
 87 	"struct":   Struct,
 88+	"enum":     Enum,
 89 	"const":    Const,
 90 	"bool":     Bool,
 91 	"byte":     Byte,
 92@@ -155,6 +145,15 @@ var KeywordMap = map[string]Type{
 93 	"any":      Any,
 94 }
 95 
 96+// Reverse mapping from Type to the keyword string.
 97+var TypeStrings = make(map[Type]string)
 98+
 99+func init() {
100+	for str, typ := range KeywordMap {
101+		TypeStrings[typ] = str
102+	}
103+}
104+
105 type Token struct {
106 	Type      Type
107 	Value     string
M pkg/typeChecker/typeChecker.go
+417, -201
  1@@ -52,6 +52,14 @@ func (tc *TypeChecker) exitScope() {
  2 	}
  3 }
  4 
  5+func (tc *TypeChecker) typeErrorOrWarn(tok token.Token, format string, args ...interface{}) {
  6+	if tc.cfg.IsFeatureEnabled(config.FeatStrictTypes) {
  7+		util.Error(tok, format, args...)
  8+	} else {
  9+		util.Warn(tc.cfg, config.WarnType, tok, format, args...)
 10+	}
 11+}
 12+
 13 func (tc *TypeChecker) addSymbol(node *ast.Node) *Symbol {
 14 	var name string
 15 	var typ *ast.BxType
 16@@ -64,6 +72,20 @@ func (tc *TypeChecker) addSymbol(node *ast.Node) *Symbol {
 17 		name, typ, isFunc = d.Name, d.ReturnType, true
 18 	case ast.TypeDeclNode:
 19 		name, typ, isType = d.Name, d.Type, true
 20+	case ast.EnumDeclNode:
 21+		name, isType = d.Name, true
 22+		typ = &ast.BxType{Kind: ast.TYPE_ENUM, Name: d.Name, EnumMembers: d.Members, Base: ast.TypeInt}
 23+		for _, memberNode := range d.Members {
 24+			memberData := memberNode.Data.(ast.VarDeclNode)
 25+			if tc.findSymbol(memberData.Name, false) == nil {
 26+				memberSym := &Symbol{
 27+					Name: memberData.Name, Type: ast.TypeInt, Node: memberNode, Next: tc.currentScope.Symbols,
 28+				}
 29+				tc.currentScope.Symbols = memberSym
 30+			} else {
 31+				util.Warn(tc.cfg, config.WarnExtra, memberNode.Tok, "Redefinition of '%s' in enum", memberData.Name)
 32+			}
 33+		}
 34 	case ast.ExtrnDeclNode:
 35 		for _, nameNode := range d.Names {
 36 			ident := nameNode.Data.(ast.IdentNode)
 37@@ -73,15 +95,13 @@ func (tc *TypeChecker) addSymbol(node *ast.Node) *Symbol {
 38 			}
 39 		}
 40 		return nil
 41-	case ast.IdentNode: // untyped function parameters
 42+	case ast.IdentNode:
 43 		name, typ = d.Name, ast.TypeUntyped
 44 	default:
 45 		return nil
 46 	}
 47 
 48-	if typ == nil {
 49-		typ = ast.TypeUntyped
 50-	}
 51+	if typ == nil { typ = ast.TypeUntyped }
 52 
 53 	if existing := tc.findSymbol(name, isType); existing != nil && tc.currentScope == tc.globalScope {
 54 		isExistingExtrn := existing.Node != nil && existing.Node.Type == ast.ExtrnDecl
 55@@ -89,7 +109,7 @@ func (tc *TypeChecker) addSymbol(node *ast.Node) *Symbol {
 56 			existing.Type, existing.IsFunc, existing.IsType, existing.Node = typ, isFunc, isType, node
 57 			return existing
 58 		}
 59-		util.Warn(tc.cfg, config.WarnExtra, node.Tok, "Redefinition of '%s'", name)
 60+		util.Error(node.Tok, "Redefinition of '%s'", name)
 61 		existing.Type, existing.IsFunc, existing.IsType, existing.Node = typ, isFunc, isType, node
 62 		return existing
 63 	}
 64@@ -110,15 +130,37 @@ func (tc *TypeChecker) findSymbol(name string, findTypes bool) *Symbol {
 65 	return nil
 66 }
 67 
 68-func (tc *TypeChecker) getSizeof(typ *ast.BxType) int64 {
 69-	if typ == nil || typ.Kind == ast.TYPE_UNTYPED {
 70-		return int64(tc.wordSize)
 71+func (tc *TypeChecker) getAlignof(typ *ast.BxType) int64 {
 72+	if typ == nil { return int64(tc.wordSize) }
 73+
 74+	if (typ.Kind == ast.TYPE_PRIMITIVE || typ.Kind == ast.TYPE_STRUCT) && typ.Name != "" {
 75+		if sym := tc.findSymbol(typ.Name, true); sym != nil {
 76+			if sym.Type != typ { return tc.getAlignof(sym.Type) }
 77+		}
 78 	}
 79+
 80+	if typ.Kind == ast.TYPE_UNTYPED { return int64(tc.wordSize) }
 81 	switch typ.Kind {
 82-	case ast.TYPE_VOID:
 83-		return 0
 84-	case ast.TYPE_POINTER:
 85-		return int64(tc.wordSize)
 86+	case ast.TYPE_VOID: return 1
 87+	case ast.TYPE_POINTER: return int64(tc.wordSize)
 88+	case ast.TYPE_ARRAY: return tc.getAlignof(typ.Base)
 89+	case ast.TYPE_PRIMITIVE, ast.TYPE_FLOAT, ast.TYPE_ENUM: return tc.getSizeof(typ)
 90+	case ast.TYPE_STRUCT:
 91+		var maxAlign int64 = 1
 92+		for _, field := range typ.Fields {
 93+			fieldAlign := tc.getAlignof(field.Data.(ast.VarDeclNode).Type)
 94+			if fieldAlign > maxAlign { maxAlign = fieldAlign }
 95+		}
 96+		return maxAlign
 97+	}
 98+	return int64(tc.wordSize)
 99+}
100+
101+func (tc *TypeChecker) getSizeof(typ *ast.BxType) int64 {
102+	if typ == nil || typ.Kind == ast.TYPE_UNTYPED { return int64(tc.wordSize) }
103+	switch typ.Kind {
104+	case ast.TYPE_VOID: return 0
105+	case ast.TYPE_POINTER: return int64(tc.wordSize)
106 	case ast.TYPE_ARRAY:
107 		elemSize := tc.getSizeof(typ.Base)
108 		var arrayLen int64 = 1
109@@ -126,55 +168,55 @@ func (tc *TypeChecker) getSizeof(typ *ast.BxType) int64 {
110 			if folded := ast.FoldConstants(typ.ArraySize); folded.Type == ast.Number {
111 				arrayLen = folded.Data.(ast.NumberNode).Value
112 			} else {
113-				util.Error(typ.ArraySize.Tok, "Array size must be a constant expression.")
114+				util.Error(typ.ArraySize.Tok, "Array size must be a constant expression")
115 			}
116 		}
117 		return elemSize * arrayLen
118-	case ast.TYPE_PRIMITIVE:
119+	case ast.TYPE_PRIMITIVE, ast.TYPE_UNTYPED_INT:
120 		switch typ.Name {
121-		case "int", "uint", "string":
122-			return int64(tc.wordSize)
123-		case "int64", "uint64":
124-			return 8
125-		case "int32", "uint32":
126-			return 4
127-		case "int16", "uint16":
128-			return 2
129-		case "byte", "bool", "int8", "uint8":
130-			return 1
131+		case "int", "uint", "string": return int64(tc.wordSize)
132+		case "int64", "uint64": return 8
133+		case "int32", "uint32": return 4
134+		case "int16", "uint16": return 2
135+		case "byte", "bool", "int8", "uint8": return 1
136 		default:
137-			if sym := tc.findSymbol(typ.Name, true); sym != nil {
138-				return tc.getSizeof(sym.Type)
139-			}
140+			if sym := tc.findSymbol(typ.Name, true); sym != nil { return tc.getSizeof(sym.Type) }
141 			return int64(tc.wordSize)
142 		}
143+	case ast.TYPE_ENUM: return tc.getSizeof(ast.TypeInt)
144+	case ast.TYPE_FLOAT, ast.TYPE_UNTYPED_FLOAT:
145+		switch typ.Name {
146+		case "float", "float32": return 4
147+		case "float64": return 8
148+		default: return 4
149+		}
150 	case ast.TYPE_STRUCT:
151-		var totalSize int64
152+		var totalSize, maxAlign int64 = 0, 1
153 		for _, field := range typ.Fields {
154-			totalSize += tc.getSizeof(field.Data.(ast.VarDeclNode).Type)
155+			fieldData := field.Data.(ast.VarDeclNode)
156+			fieldAlign := tc.getAlignof(fieldData.Type)
157+			if fieldAlign > maxAlign { maxAlign = fieldAlign }
158+			totalSize = util.AlignUp(totalSize, fieldAlign)
159+			totalSize += tc.getSizeof(fieldData.Type)
160 		}
161-		// NOTE: does not account for alignment/padding
162-		return totalSize
163+		if maxAlign == 0 { maxAlign = 1 }
164+		return util.AlignUp(totalSize, maxAlign)
165 	}
166 	return int64(tc.wordSize)
167 }
168 
169 func (tc *TypeChecker) Check(root *ast.Node) {
170-	if !tc.cfg.IsFeatureEnabled(config.FeatTyped) {
171-		return
172-	}
173+	if !tc.cfg.IsFeatureEnabled(config.FeatTyped) { return }
174 	tc.collectGlobals(root)
175 	tc.checkNode(root)
176 	tc.annotateGlobalDecls(root)
177 }
178 
179 func (tc *TypeChecker) collectGlobals(node *ast.Node) {
180-	if node == nil || node.Type != ast.Block {
181-		return
182-	}
183+	if node == nil || node.Type != ast.Block { return }
184 	for _, stmt := range node.Data.(ast.BlockNode).Stmts {
185 		switch stmt.Type {
186-		case ast.VarDecl, ast.FuncDecl, ast.ExtrnDecl, ast.TypeDecl:
187+		case ast.VarDecl, ast.FuncDecl, ast.ExtrnDecl, ast.TypeDecl, ast.EnumDecl:
188 			tc.addSymbol(stmt)
189 		case ast.MultiVarDecl:
190 			for _, subStmt := range stmt.Data.(ast.MultiVarDeclNode).Decls {
191@@ -185,15 +227,11 @@ func (tc *TypeChecker) collectGlobals(node *ast.Node) {
192 }
193 
194 func (tc *TypeChecker) annotateGlobalDecls(root *ast.Node) {
195-	if root == nil || root.Type != ast.Block {
196-		return
197-	}
198+	if root == nil || root.Type != ast.Block { return }
199 	for _, stmt := range root.Data.(ast.BlockNode).Stmts {
200 		if stmt.Type == ast.VarDecl {
201 			d, ok := stmt.Data.(ast.VarDeclNode)
202-			if !ok {
203-				continue
204-			}
205+			if !ok { continue }
206 			if globalSym := tc.findSymbol(d.Name, false); globalSym != nil {
207 				if (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) && (globalSym.Type != nil && globalSym.Type.Kind != ast.TYPE_UNTYPED) {
208 					d.Type = globalSym.Type
209@@ -205,21 +243,15 @@ func (tc *TypeChecker) annotateGlobalDecls(root *ast.Node) {
210 }
211 
212 func (tc *TypeChecker) checkNode(node *ast.Node) {
213-	if node == nil {
214-		return
215-	}
216+	if node == nil { return }
217 	switch node.Type {
218 	case ast.Block:
219 		d := node.Data.(ast.BlockNode)
220-		if !d.IsSynthetic {
221-			tc.enterScope()
222-		}
223+		if !d.IsSynthetic { tc.enterScope() }
224 		for _, stmt := range d.Stmts {
225 			tc.checkNode(stmt)
226 		}
227-		if !d.IsSynthetic {
228-			tc.exitScope()
229-		}
230+		if !d.IsSynthetic { tc.exitScope() }
231 	case ast.FuncDecl:
232 		tc.checkFuncDecl(node)
233 	case ast.VarDecl:
234@@ -241,10 +273,12 @@ func (tc *TypeChecker) checkNode(node *ast.Node) {
235 		tc.checkReturn(node)
236 	case ast.Switch:
237 		d := node.Data.(ast.SwitchNode)
238-		tc.checkExprAsCondition(d.Expr)
239+		tc.checkExpr(d.Expr)
240 		tc.checkNode(d.Body)
241 	case ast.Case:
242-		tc.checkExpr(node.Data.(ast.CaseNode).Value)
243+		for _, valueExpr := range node.Data.(ast.CaseNode).Values {
244+			tc.checkExpr(valueExpr)
245+		}
246 		tc.checkNode(node.Data.(ast.CaseNode).Body)
247 	case ast.Default:
248 		tc.checkNode(node.Data.(ast.DefaultNode).Body)
249@@ -252,9 +286,9 @@ func (tc *TypeChecker) checkNode(node *ast.Node) {
250 		tc.checkNode(node.Data.(ast.LabelNode).Stmt)
251 	case ast.ExtrnDecl:
252 		tc.addSymbol(node)
253-	case ast.TypeDecl, ast.Goto, ast.Break, ast.Continue, ast.AsmStmt, ast.Directive:
254+	case ast.TypeDecl, ast.EnumDecl, ast.Goto, ast.Break, ast.Continue, ast.AsmStmt, ast.Directive:
255 	default:
256-		if node.Type <= ast.TypeCast {
257+		if node.Type <= ast.StructLiteral {
258 			tc.checkExpr(node)
259 		}
260 	}
261@@ -262,9 +296,7 @@ func (tc *TypeChecker) checkNode(node *ast.Node) {
262 
263 func (tc *TypeChecker) checkFuncDecl(node *ast.Node) {
264 	d := node.Data.(ast.FuncDeclNode)
265-	if d.Body == nil || d.Body.Type == ast.AsmStmt {
266-		return
267-	}
268+	if d.Body == nil || d.Body.Type == ast.AsmStmt { return }
269 	prevFunc := tc.currentFunc
270 	tc.currentFunc = &d
271 	defer func() { tc.currentFunc = prevFunc }()
272@@ -279,11 +311,9 @@ func (tc *TypeChecker) checkFuncDecl(node *ast.Node) {
273 func (tc *TypeChecker) checkVarDecl(node *ast.Node) {
274 	d := node.Data.(ast.VarDeclNode)
275 	if d.IsDefine && tc.findSymbol(d.Name, false) != nil {
276-		util.Error(node.Tok, "No new variables on left side of :=")
277-	}
278-	if tc.currentFunc != nil {
279-		tc.addSymbol(node)
280+		util.Error(node.Tok, "Trying to assign to undeclared identifier, use := or define with a explicit type or auto")
281 	}
282+	if tc.currentFunc != nil { tc.addSymbol(node) }
283 	if len(d.InitList) == 0 {
284 		if (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) && !tc.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
285 			util.Error(node.Tok, "Uninitialized variable '%s' is not allowed in this mode", d.Name)
286@@ -294,9 +324,7 @@ func (tc *TypeChecker) checkVarDecl(node *ast.Node) {
287 
288 	initExpr := d.InitList[0]
289 	initType := tc.checkExpr(initExpr)
290-	if initType == nil || initType.Kind == ast.TYPE_UNTYPED {
291-		return
292-	}
293+	if initType == nil { return }
294 
295 	if d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED {
296 		d.Type = initType
297@@ -304,21 +332,44 @@ func (tc *TypeChecker) checkVarDecl(node *ast.Node) {
298 		if sym := tc.findSymbol(d.Name, false); sym != nil {
299 			sym.Type = initType
300 		}
301+	} else if initType.Kind == ast.TYPE_UNTYPED_INT || initType.Kind == ast.TYPE_UNTYPED_FLOAT {
302+		if tc.isNumericType(d.Type) || d.Type.Kind == ast.TYPE_POINTER || d.Type.Kind == ast.TYPE_BOOL {
303+			initExpr.Typ = d.Type
304+			initType = d.Type
305+		}
306 	}
307 	if !tc.areTypesCompatible(d.Type, initType, initExpr) {
308-		util.Warn(tc.cfg, config.WarnType, node.Tok, "Initializing variable of type '%s' with expression of incompatible type '%s'", typeToString(d.Type), typeToString(initType))
309+		tc.typeErrorOrWarn(node.Tok, "Initializing variable of type '%s' with expression of incompatible type '%s'", typeToString(d.Type), typeToString(initType))
310 	}
311 	node.Typ = d.Type
312 }
313 
314+func (tc *TypeChecker) isSymbolLocal(name string) bool {
315+	for s := tc.currentScope; s != nil && s != tc.globalScope; s = s.Parent {
316+		for sym := s.Symbols; sym != nil; sym = sym.Next {
317+			if sym.Name == name && !sym.IsType { return true }
318+		}
319+	}
320+	return false
321+}
322+
323 func (tc *TypeChecker) checkReturn(node *ast.Node) {
324 	d := node.Data.(ast.ReturnNode)
325 	if tc.currentFunc == nil {
326-		if d.Expr != nil {
327-			util.Error(node.Tok, "Return with value used outside of a function.")
328-		}
329+		if d.Expr != nil { util.Error(node.Tok, "Return with value used outside of a function") }
330 		return
331 	}
332+
333+	if d.Expr != nil && d.Expr.Type == ast.AddressOf {
334+		lval := d.Expr.Data.(ast.AddressOfNode).LValue
335+		if lval.Type == ast.Ident {
336+			name := lval.Data.(ast.IdentNode).Name
337+			if tc.isSymbolLocal(name) {
338+				util.Warn(tc.cfg, config.WarnLocalAddress, d.Expr.Tok, "Returning address of local variable '%s'", name)
339+			}
340+		}
341+	}
342+
343 	if !tc.currentFunc.IsTyped {
344 		tc.checkExpr(d.Expr)
345 		if d.Expr == nil {
346@@ -340,23 +391,21 @@ func (tc *TypeChecker) checkReturn(node *ast.Node) {
347 		if retType.Kind == ast.TYPE_VOID {
348 			util.Error(node.Tok, "Return with a value in function returning void")
349 		} else if !tc.areTypesCompatible(retType, exprType, d.Expr) {
350-			util.Warn(tc.cfg, config.WarnType, node.Tok, "Returning type '%s' is incompatible with function return type '%s'", typeToString(exprType), typeToString(retType))
351+			tc.typeErrorOrWarn(node.Tok, "Returning type '%s' is incompatible with function return type '%s'", typeToString(exprType), typeToString(retType))
352 		}
353 	}
354 }
355 
356 func (tc *TypeChecker) checkExprAsCondition(node *ast.Node) {
357 	typ := tc.checkExpr(node)
358-	if !(tc.isScalarType(typ) || typ.Kind == ast.TYPE_UNTYPED) {
359+	if !(tc.isScalarType(typ) || typ.Kind == ast.TYPE_UNTYPED || typ.Kind == ast.TYPE_UNTYPED_INT) {
360 		util.Warn(tc.cfg, config.WarnType, node.Tok, "Expression of type '%s' used as a condition", typeToString(typ))
361 	}
362 }
363 
364 func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
365-	if node == nil {
366-		return ast.TypeUntyped
367-	}
368-	if node.Typ != nil {
369+	if node == nil { return ast.TypeUntyped }
370+	if node.Typ != nil && node.Typ.Kind != ast.TYPE_UNTYPED_INT && node.Typ.Kind != ast.TYPE_UNTYPED_FLOAT {
371 		return node.Typ
372 	}
373 	var typ *ast.BxType
374@@ -364,13 +413,11 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
375 	case ast.AssignNode:
376 		lhsType, rhsType := tc.checkExpr(d.Lhs), tc.checkExpr(d.Rhs)
377 
378-		// Handle type promotion on assignment (e.g., int var = ptr_val).
379-		// This is common in B where variables can change type implicitly.
380 		isLhsScalar := tc.isScalarType(lhsType) && lhsType.Kind != ast.TYPE_POINTER
381 		isRhsPtr := rhsType != nil && rhsType.Kind == ast.TYPE_POINTER
382 		if isLhsScalar && isRhsPtr && d.Lhs.Type == ast.Ident {
383 			if sym := tc.findSymbol(d.Lhs.Data.(ast.IdentNode).Name, false); sym != nil {
384-				sym.Type = rhsType // Promote the variable's type to the pointer type
385+				sym.Type = rhsType
386 				lhsType = rhsType
387 			}
388 		}
389@@ -395,22 +442,24 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
390 			}
391 		}
392 		if !tc.areTypesCompatible(lhsType, rhsType, d.Rhs) {
393-			util.Warn(tc.cfg, config.WarnType, node.Tok, "Assigning to type '%s' from incompatible type '%s'", typeToString(lhsType), typeToString(rhsType))
394+			tc.typeErrorOrWarn(node.Tok, "Assigning to type '%s' from incompatible type '%s'", typeToString(lhsType), typeToString(rhsType))
395+		} else if rhsType.Kind == ast.TYPE_UNTYPED_INT || rhsType.Kind == ast.TYPE_UNTYPED_FLOAT {
396+			if tc.isNumericType(lhsType) || lhsType.Kind == ast.TYPE_POINTER || lhsType.Kind == ast.TYPE_BOOL {
397+				d.Rhs.Typ = lhsType
398+			}
399 		}
400 		typ = lhsType
401 	case ast.BinaryOpNode:
402 		leftType, rightType := tc.checkExpr(d.Left), tc.checkExpr(d.Right)
403-		typ = tc.getBinaryOpResultType(d.Op, leftType, rightType, node.Tok)
404+		typ = tc.getBinaryOpResultType(d.Op, leftType, rightType, node.Tok, d.Left, d.Right)
405 	case ast.UnaryOpNode:
406 		operandType := tc.checkExpr(d.Expr)
407 		switch d.Op {
408-		case token.Star: // Dereference
409+		case token.Star:
410 			resolvedOpType := tc.resolveType(operandType)
411 			if resolvedOpType.Kind == ast.TYPE_POINTER || resolvedOpType.Kind == ast.TYPE_ARRAY {
412 				typ = resolvedOpType.Base
413 			} else if resolvedOpType.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(resolvedOpType) {
414-				// An untyped or integer variable is being dereferenced.
415-				// Very common pattern in B. Promote it to a pointer to an untyped base
416 				promotedType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: ast.TypeUntyped}
417 				d.Expr.Typ = promotedType
418 				if d.Expr.Type == ast.Ident {
419@@ -425,9 +474,9 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
420 				util.Error(node.Tok, "Cannot dereference non-pointer type '%s'", typeToString(operandType))
421 				typ = ast.TypeUntyped
422 			}
423-		case token.And: // Address-of
424+		case token.And:
425 			typ = &ast.BxType{Kind: ast.TYPE_POINTER, Base: operandType}
426-		default: // ++, --, -, +, !, ~
427+		default:
428 			typ = operandType
429 		}
430 	case ast.PostfixOpNode:
431@@ -436,28 +485,24 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
432 		tc.checkExprAsCondition(d.Cond)
433 		thenType, elseType := tc.checkExpr(d.ThenExpr), tc.checkExpr(d.ElseExpr)
434 		if !tc.areTypesCompatible(thenType, elseType, d.ElseExpr) {
435-			util.Warn(tc.cfg, config.WarnType, node.Tok, "Type mismatch in ternary expression branches ('%s' vs '%s')", typeToString(thenType), typeToString(elseType))
436+			tc.typeErrorOrWarn(node.Tok, "Type mismatch in ternary expression branches ('%s' vs '%s')", typeToString(thenType), typeToString(elseType))
437 		}
438-		// Type promotion rules for ternary operator: pointer types take precedence.
439 		if thenType != nil && thenType.Kind == ast.TYPE_POINTER {
440 			typ = thenType
441 		} else if elseType != nil && elseType.Kind == ast.TYPE_POINTER {
442 			typ = elseType
443 		} else {
444-			typ = thenType // Default to 'then' type if no pointers involved
445+			typ = thenType
446 		}
447 	case ast.SubscriptNode:
448 		arrayType, indexType := tc.checkExpr(d.Array), tc.checkExpr(d.Index)
449-		if !tc.isIntegerType(indexType) && indexType.Kind != ast.TYPE_UNTYPED {
450-			util.Warn(tc.cfg, config.WarnType, d.Index.Tok, "Array subscript is not an integer type ('%s')", typeToString(indexType))
451+		if !tc.isIntegerType(indexType) && indexType.Kind != ast.TYPE_UNTYPED && indexType.Kind != ast.TYPE_UNTYPED_INT {
452+			tc.typeErrorOrWarn(d.Index.Tok, "Array subscript is not an integer type ('%s')", typeToString(indexType))
453 		}
454 		resolvedArrayType := tc.resolveType(arrayType)
455 		if resolvedArrayType.Kind == ast.TYPE_ARRAY || resolvedArrayType.Kind == ast.TYPE_POINTER {
456 			typ = resolvedArrayType.Base
457 		} else if resolvedArrayType.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(resolvedArrayType) {
458-			// An untyped or integer variable is being used as a pointer
459-			// Another super common pattern in B. Promote it to a pointer to an untyped base
460-			// The base type will be inferred from usage (e.g., assignment)
461 			promotedType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: ast.TypeUntyped}
462 			d.Array.Typ = promotedType
463 
464@@ -480,10 +525,16 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
465 	case ast.TypeCastNode:
466 		tc.checkExpr(d.Expr)
467 		typ = d.TargetType
468+	case ast.StructLiteralNode:
469+		typ = tc.checkStructLiteral(node)
470 	case ast.NumberNode:
471-		typ = ast.TypeInt
472+		typ = ast.TypeUntypedInt
473+	case ast.FloatNumberNode:
474+		typ = ast.TypeUntypedFloat
475 	case ast.StringNode:
476 		typ = ast.TypeString
477+	case ast.NilNode:
478+		typ = ast.TypeNil
479 	case ast.IdentNode:
480 		if sym := tc.findSymbol(d.Name, false); sym != nil {
481 			if node.Parent != nil && node.Parent.Type == ast.FuncCall && node.Parent.Data.(ast.FuncCallNode).FuncExpr == node && !sym.IsFunc {
482@@ -507,50 +558,81 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
483 	return typ
484 }
485 
486+func (tc *TypeChecker) findStructWithMember(memberName string) *ast.BxType {
487+	for s := tc.currentScope; s != nil; s = s.Parent {
488+		for sym := s.Symbols; sym != nil; sym = sym.Next {
489+			if sym.IsType {
490+				typ := tc.resolveType(sym.Type)
491+				if typ.Kind == ast.TYPE_STRUCT {
492+					for _, field := range typ.Fields {
493+						if field.Data.(ast.VarDeclNode).Name == memberName {
494+							return typ
495+						}
496+					}
497+				}
498+			}
499+		}
500+	}
501+	return nil
502+}
503+
504 func (tc *TypeChecker) checkMemberAccess(node *ast.Node) *ast.BxType {
505 	d := node.Data.(ast.MemberAccessNode)
506 	exprType := tc.checkExpr(d.Expr)
507-	baseType := exprType
508-	if exprType.Kind == ast.TYPE_POINTER {
509-		baseType = exprType.Base
510+
511+	baseType := tc.resolveType(exprType)
512+
513+	if baseType != nil && baseType.Kind == ast.TYPE_POINTER { baseType = baseType.Base }
514+
515+	resolvedStructType := tc.resolveType(baseType)
516+
517+	if resolvedStructType != nil && resolvedStructType.Kind == ast.TYPE_UNTYPED {
518+		memberName := d.Member.Data.(ast.IdentNode).Name
519+		if inferredType := tc.findStructWithMember(memberName); inferredType != nil {
520+			if d.Expr.Typ.Kind == ast.TYPE_POINTER {
521+				d.Expr.Typ.Base = inferredType
522+			} else {
523+				d.Expr.Typ = inferredType
524+			}
525+			if d.Expr.Type == ast.Ident {
526+				if sym := tc.findSymbol(d.Expr.Data.(ast.IdentNode).Name, false); sym != nil {
527+					sym.Type = d.Expr.Typ
528+				}
529+			}
530+			resolvedStructType = inferredType
531+		}
532 	}
533-	resolvedBaseType := tc.resolveType(baseType)
534-	if resolvedBaseType.Kind != ast.TYPE_STRUCT {
535-		util.Error(node.Tok, "Request for member '%s' in non-struct type", d.Member.Data.(ast.IdentNode).Name)
536+
537+	if resolvedStructType == nil || resolvedStructType.Kind != ast.TYPE_STRUCT {
538+		memberName := d.Member.Data.(ast.IdentNode).Name
539+		util.Error(node.Tok, "request for member '%s' in non-struct type '%s'", memberName, typeToString(exprType))
540 		return ast.TypeUntyped
541 	}
542 
543-	var offset int64
544-	var memberType *ast.BxType
545-	found := false
546 	memberName := d.Member.Data.(ast.IdentNode).Name
547-	for _, fieldNode := range resolvedBaseType.Fields {
548+	for _, fieldNode := range resolvedStructType.Fields {
549 		fieldData := fieldNode.Data.(ast.VarDeclNode)
550 		if fieldData.Name == memberName {
551-			memberType, found = fieldData.Type, true
552-			break
553+			node.Typ = fieldData.Type
554+			return fieldData.Type
555 		}
556-		offset += tc.getSizeof(fieldData.Type)
557-	}
558-	if !found {
559-		util.Error(node.Tok, "No member named '%s' in struct '%s'", memberName, typeToString(resolvedBaseType))
560-		return ast.TypeUntyped
561 	}
562 
563-	var structAddrNode *ast.Node
564-	if exprType.Kind == ast.TYPE_POINTER {
565-		structAddrNode = d.Expr
566-	} else {
567-		structAddrNode = ast.NewAddressOf(d.Expr.Tok, d.Expr)
568-		tc.checkExpr(structAddrNode)
569-	}
570+	util.Error(node.Tok, "no member named '%s' in struct '%s'", memberName, typeToString(resolvedStructType))
571+	return ast.TypeUntyped
572+}
573+
574+func (tc *TypeChecker) typeFromName(name string) *ast.BxType {
575+	if sym := tc.findSymbol(name, true); sym != nil && sym.IsType { return sym.Type }
576 
577-	offsetNode := ast.NewNumber(d.Member.Tok, offset)
578-	offsetNode.Typ = ast.TypeInt
579-	addNode := ast.NewBinaryOp(node.Tok, token.Plus, structAddrNode, offsetNode)
580-	addNode.Typ = &ast.BxType{Kind: ast.TYPE_POINTER, Base: memberType}
581-	node.Type, node.Data, node.Typ = ast.Indirection, ast.IndirectionNode{Expr: addNode}, memberType
582-	return memberType
583+	tokType, isKeyword := token.KeywordMap[name]
584+	if isKeyword && tokType >= token.Void && tokType <= token.Any {
585+		if tokType == token.Void { return ast.TypeVoid }
586+		if tokType == token.StringKeyword { return ast.TypeString }
587+		if tokType >= token.Float && tokType <= token.Float64 { return &ast.BxType{Kind: ast.TYPE_FLOAT, Name: name} }
588+		return &ast.BxType{Kind: ast.TYPE_PRIMITIVE, Name: name}
589+	}
590+	return nil
591 }
592 
593 func (tc *TypeChecker) checkFuncCall(node *ast.Node) *ast.BxType {
594@@ -569,9 +651,7 @@ func (tc *TypeChecker) checkFuncCall(node *ast.Node) *ast.BxType {
595 					targetType = sym.Type
596 				}
597 			}
598-			if targetType == nil {
599-				targetType = tc.checkExpr(arg)
600-			}
601+			if targetType == nil { targetType = tc.checkExpr(arg) }
602 			if targetType == nil {
603 				util.Error(arg.Tok, "Cannot determine type for sizeof argument")
604 				return ast.TypeUntyped
605@@ -579,6 +659,18 @@ func (tc *TypeChecker) checkFuncCall(node *ast.Node) *ast.BxType {
606 			node.Type, node.Data, node.Typ = ast.Number, ast.NumberNode{Value: tc.getSizeof(targetType)}, ast.TypeInt
607 			return ast.TypeInt
608 		}
609+
610+		if targetType := tc.typeFromName(name); targetType != nil {
611+			if len(d.Args) != 1 {
612+				util.Error(node.Tok, "Type cast expects exactly one argument")
613+			} else {
614+				tc.checkExpr(d.Args[0])
615+			}
616+			node.Type = ast.TypeCast
617+			node.Data = ast.TypeCastNode{Expr: d.Args[0], TargetType: targetType}
618+			node.Typ = targetType
619+			return targetType
620+		}
621 	}
622 
623 	if len(d.Args) == 1 {
624@@ -605,106 +697,228 @@ func (tc *TypeChecker) checkFuncCall(node *ast.Node) *ast.BxType {
625 		}
626 	}
627 
628+	funcExprType := tc.checkExpr(d.FuncExpr)
629+
630 	if d.FuncExpr.Type == ast.Ident {
631 		name := d.FuncExpr.Data.(ast.IdentNode).Name
632-		if sym := tc.findSymbol(name, false); sym == nil {
633+		if sym := tc.findSymbol(name, false); sym != nil && sym.IsFunc && sym.Type.Kind == ast.TYPE_UNTYPED {
634+			funcExprType = ast.TypeUntyped
635+		} else if sym == nil {
636 			util.Warn(tc.cfg, config.WarnImplicitDecl, d.FuncExpr.Tok, "Implicit declaration of function '%s'", name)
637-			tc.globalScope.Symbols = &Symbol{Name: name, Type: ast.TypeInt, IsFunc: true, Node: d.FuncExpr, Next: tc.globalScope.Symbols}
638-		} else {
639-			sym.IsFunc = true
640+			sym = tc.addSymbol(ast.NewFuncDecl(d.FuncExpr.Tok, name, nil, nil, false, false, ast.TypeUntyped))
641+			funcExprType = ast.TypeUntyped
642 		}
643 	}
644-	funcExprType := tc.checkExpr(d.FuncExpr)
645+
646 	for _, arg := range d.Args {
647 		tc.checkExpr(arg)
648 	}
649+
650+	resolvedType := tc.resolveType(funcExprType)
651+	if resolvedType != nil && resolvedType.Kind == ast.TYPE_STRUCT { return resolvedType }
652+
653 	return funcExprType
654 }
655 
656-func (tc *TypeChecker) getBinaryOpResultType(op token.Type, left, right *ast.BxType, tok token.Token) *ast.BxType {
657-	resLeft, resRight := tc.resolveType(left), tc.resolveType(right)
658-	if resLeft.Kind == ast.TYPE_UNTYPED {
659-		return resRight
660+func (tc *TypeChecker) checkStructLiteral(node *ast.Node) *ast.BxType {
661+	d := node.Data.(ast.StructLiteralNode)
662+
663+	typeIdent, ok := d.TypeNode.Data.(ast.IdentNode)
664+	if !ok {
665+		util.Error(d.TypeNode.Tok, "Invalid type expression in struct literal")
666+		return ast.TypeUntyped
667 	}
668-	if resRight.Kind == ast.TYPE_UNTYPED {
669-		return resLeft
670+
671+	sym := tc.findSymbol(typeIdent.Name, true)
672+	if sym == nil || !sym.IsType {
673+		util.Error(d.TypeNode.Tok, "Unknown type name '%s' in struct literal", typeIdent.Name)
674+		return ast.TypeUntyped
675 	}
676-	if op >= token.EqEq && op <= token.OrOr {
677-		return ast.TypeInt
678+
679+	structType := tc.resolveType(sym.Type)
680+	if structType.Kind != ast.TYPE_STRUCT {
681+		util.Error(d.TypeNode.Tok, "'%s' is not a struct type", typeIdent.Name)
682+		return ast.TypeUntyped
683 	}
684 
685-	switch op {
686-	case token.Plus, token.Minus:
687-		if resLeft.Kind == ast.TYPE_POINTER && tc.isIntegerType(resRight) {
688-			return resLeft
689+	if d.Names == nil {
690+		if len(d.Values) > 0 {
691+			if len(structType.Fields) > 0 {
692+				firstFieldType := tc.resolveType(structType.Fields[0].Data.(ast.VarDeclNode).Type)
693+				for i := 1; i < len(structType.Fields); i++ {
694+					currentFieldType := tc.resolveType(structType.Fields[i].Data.(ast.VarDeclNode).Type)
695+					if !tc.areTypesEqual(firstFieldType, currentFieldType) {
696+						util.Error(node.Tok, "positional struct literal for '%s' is only allowed if all fields have the same type, but found '%s' and '%s'",
697+							typeIdent.Name, typeToString(firstFieldType), typeToString(currentFieldType))
698+						break
699+					}
700+				}
701+			}
702 		}
703-		if tc.isIntegerType(resLeft) && resRight.Kind == ast.TYPE_POINTER && op == token.Plus {
704-			return resRight
705+
706+		if len(d.Values) != 0 && len(d.Values) > len(structType.Fields) {
707+			util.Error(node.Tok, "Wrong number of initializers for struct '%s'. Expected %d, got %d", typeIdent.Name, len(structType.Fields), len(d.Values))
708+			return structType
709 		}
710-		if op == token.Minus && resLeft.Kind == ast.TYPE_POINTER && resRight.Kind == ast.TYPE_POINTER {
711-			return ast.TypeInt
712+
713+		for i, valNode := range d.Values {
714+			field := structType.Fields[i].Data.(ast.VarDeclNode)
715+			valType := tc.checkExpr(valNode)
716+			if !tc.areTypesCompatible(field.Type, valType, valNode) {
717+				tc.typeErrorOrWarn(valNode.Tok, "Initializer for field '%s' has wrong type. Expected '%s', got '%s'", field.Name, typeToString(field.Type), typeToString(valType))
718+			}
719+		}
720+	} else {
721+		if len(d.Values) > len(structType.Fields) { util.Error(node.Tok, "Too many initializers for struct '%s'", typeIdent.Name) }
722+
723+		fieldMap := make(map[string]*ast.Node)
724+		for _, fieldNode := range structType.Fields {
725+			fieldData := fieldNode.Data.(ast.VarDeclNode)
726+			fieldMap[fieldData.Name] = fieldNode
727+		}
728+
729+		usedFields := make(map[string]bool)
730+
731+		for i, nameNode := range d.Names {
732+			if nameNode == nil { continue }
733+			fieldName := nameNode.Data.(ast.IdentNode).Name
734+
735+			if usedFields[fieldName] {
736+				util.Error(nameNode.Tok, "Duplicate field '%s' in struct literal", fieldName)
737+				continue
738+			}
739+			usedFields[fieldName] = true
740+
741+			field, ok := fieldMap[fieldName]
742+			if !ok {
743+				util.Error(nameNode.Tok, "Struct '%s' has no field named '%s'", typeIdent.Name, fieldName)
744+				continue
745+			}
746+
747+			valNode := d.Values[i]
748+			valType := tc.checkExpr(valNode)
749+			fieldType := field.Data.(ast.VarDeclNode).Type
750+
751+			if !tc.areTypesCompatible(fieldType, valType, valNode) {
752+				tc.typeErrorOrWarn(valNode.Tok, "Initializer for field '%s' has wrong type. Expected '%s', got '%s'", fieldName, typeToString(fieldType), typeToString(valType))
753+			}
754 		}
755 	}
756 
757-	if tc.isNumericType(resLeft) && tc.isNumericType(resRight) {
758-		if resLeft.Kind == ast.TYPE_FLOAT || resRight.Kind == ast.TYPE_FLOAT {
759-			return ast.TypeFloat
760+	return structType
761+}
762+
763+func (tc *TypeChecker) getBinaryOpResultType(op token.Type, left, right *ast.BxType, tok token.Token, leftNode, rightNode *ast.Node) *ast.BxType {
764+	resLeft, resRight := tc.resolveType(left), tc.resolveType(right)
765+	lType, rType := resLeft, resRight
766+
767+	if lType.Kind == ast.TYPE_UNTYPED_INT && tc.isIntegerType(rType) {
768+		if tc.getSizeof(rType) < tc.getSizeof(ast.TypeInt) {
769+			lType, rType = ast.TypeInt, ast.TypeInt
770+			if rightNode.Typ.Kind != ast.TYPE_UNTYPED_INT { rightNode.Typ = ast.TypeInt }
771+		} else {
772+			lType = rType
773 		}
774-		return ast.TypeInt
775+		leftNode.Typ = lType
776+	}
777+	if rType.Kind == ast.TYPE_UNTYPED_INT && tc.isIntegerType(lType) {
778+		if tc.getSizeof(lType) < tc.getSizeof(ast.TypeInt) {
779+			lType, rType = ast.TypeInt, ast.TypeInt
780+			if leftNode.Typ.Kind != ast.TYPE_UNTYPED_INT { leftNode.Typ = ast.TypeInt }
781+		} else {
782+			rType = lType
783+		}
784+		rightNode.Typ = rType
785+	}
786+
787+	if lType.Kind == ast.TYPE_UNTYPED_FLOAT && tc.isFloatType(rType) {
788+		lType = rType
789+		leftNode.Typ = rType
790+	}
791+	if rType.Kind == ast.TYPE_UNTYPED_FLOAT && tc.isFloatType(lType) {
792+		rType = lType
793+		rightNode.Typ = rType
794+	}
795+
796+	resLeft, resRight = lType, rType
797+
798+	if op >= token.EqEq && op <= token.OrOr { return ast.TypeInt }
799+
800+	if tc.isNumericType(resLeft) && tc.isNumericType(resRight) {
801+		if tc.isFloatType(resLeft) || tc.isFloatType(resRight) { return ast.TypeFloat }
802+		if tc.getSizeof(resLeft) > tc.getSizeof(resRight) { return resLeft }
803+		return resRight
804+	}
805+
806+	if op == token.Plus || op == token.Minus {
807+		if resLeft.Kind == ast.TYPE_POINTER && tc.isIntegerType(resRight) { return resLeft }
808+		if tc.isIntegerType(resLeft) && resRight.Kind == ast.TYPE_POINTER && op == token.Plus { return resRight }
809+		if op == token.Minus && resLeft.Kind == ast.TYPE_POINTER && resRight.Kind == ast.TYPE_POINTER { return ast.TypeInt }
810 	}
811 
812-	util.Warn(tc.cfg, config.WarnType, tok, "Invalid binary operation between types '%s' and '%s'", typeToString(left), typeToString(right))
813+	tc.typeErrorOrWarn(tok, "Invalid binary operation between types '%s' and '%s'", typeToString(left), typeToString(right))
814 	return ast.TypeInt
815 }
816 
817 func (tc *TypeChecker) areTypesCompatible(a, b *ast.BxType, bNode *ast.Node) bool {
818-	if a == nil || b == nil || a.Kind == ast.TYPE_UNTYPED || b.Kind == ast.TYPE_UNTYPED {
819-		return true
820-	}
821+	if a == nil || b == nil || a.Kind == ast.TYPE_UNTYPED { return true }
822+
823+	if b.Kind == ast.TYPE_UNTYPED_INT { return tc.isNumericType(a) || a.Kind == ast.TYPE_POINTER || a.Kind == ast.TYPE_BOOL }
824+	if b.Kind == ast.TYPE_UNTYPED_FLOAT { return tc.isFloatType(a) }
825+	if b.Kind == ast.TYPE_UNTYPED { return true }
826+
827 	resA, resB := tc.resolveType(a), tc.resolveType(b)
828+
829+	if resA.Kind == ast.TYPE_POINTER && tc.isIntegerType(resB) { return true }
830+	if tc.isIntegerType(resA) && resB.Kind == ast.TYPE_POINTER { return true }
831+
832+	if resA.Kind == ast.TYPE_NIL { return resB.Kind == ast.TYPE_POINTER || resB.Kind == ast.TYPE_ARRAY || resB.Kind == ast.TYPE_NIL }
833+	if resB.Kind == ast.TYPE_NIL { return resA.Kind == ast.TYPE_POINTER || resA.Kind == ast.TYPE_ARRAY }
834+
835 	if resA.Kind == resB.Kind {
836 		switch resA.Kind {
837 		case ast.TYPE_POINTER:
838-			if (resA.Base != nil && resA.Base.Kind == ast.TYPE_VOID) || (resB.Base != nil && resB.Base.Kind == ast.TYPE_VOID) {
839-				return true
840-			}
841-			if (resA.Base != nil && resA.Base == ast.TypeByte) || (resB.Base != nil && resB.Base == ast.TypeByte) {
842-				return true
843-			}
844+			if (resA.Base != nil && resA.Base.Kind == ast.TYPE_VOID) || (resB.Base != nil && resB.Base.Kind == ast.TYPE_VOID) { return true }
845+			if (resA.Base != nil && resA.Base == ast.TypeByte) || (resB.Base != nil && resB.Base == ast.TypeByte) { return true }
846 			return tc.areTypesCompatible(resA.Base, resB.Base, nil)
847 		case ast.TYPE_ARRAY:
848 			return tc.areTypesCompatible(resA.Base, resB.Base, nil)
849 		case ast.TYPE_STRUCT:
850 			return resA == resB || (resA.Name != "" && resA.Name == resB.Name)
851+		case ast.TYPE_ENUM:
852+			return true
853 		default:
854 			return true
855 		}
856 	}
857-	if bNode != nil && bNode.Type == ast.Number && bNode.Data.(ast.NumberNode).Value == 0 && resA.Kind == ast.TYPE_POINTER && tc.isIntegerType(resB) {
858-		return true
859-	}
860-	if resA.Kind == ast.TYPE_POINTER && resB.Kind == ast.TYPE_ARRAY {
861-		return tc.areTypesCompatible(resA.Base, resB.Base, nil)
862-	}
863-	if tc.isNumericType(resA) && tc.isNumericType(resB) {
864-		return true
865-	}
866-	if (resA.Kind == ast.TYPE_BOOL && tc.isScalarType(resB)) || (tc.isScalarType(resA) && resB.Kind == ast.TYPE_BOOL) {
867+	if bNode != nil && bNode.Type == ast.Number && bNode.Data.(ast.NumberNode).Value == 0 && resA.Kind == ast.TYPE_POINTER && tc.isIntegerType(resB) { return true }
868+	if resA.Kind == ast.TYPE_POINTER && resB.Kind == ast.TYPE_ARRAY { return tc.areTypesCompatible(resA.Base, resB.Base, nil) }
869+	if (resA.Kind == ast.TYPE_ENUM && tc.isIntegerType(resB)) || (tc.isIntegerType(resA) && resB.Kind == ast.TYPE_ENUM) { return true }
870+	if tc.isNumericType(resA) && tc.isNumericType(resB) { return true }
871+	if (resA.Kind == ast.TYPE_BOOL && tc.isScalarType(resB)) || (tc.isScalarType(resA) && resB.Kind == ast.TYPE_BOOL) { return true }
872+	return false
873+}
874+
875+func (tc *TypeChecker) areTypesEqual(a, b *ast.BxType) bool {
876+	if a == nil || b == nil { return a == b }
877+	resA, resB := tc.resolveType(a), tc.resolveType(b)
878+	if resA.Kind != resB.Kind { return false }
879+	switch resA.Kind {
880+	case ast.TYPE_POINTER, ast.TYPE_ARRAY:
881+		return tc.areTypesEqual(resA.Base, resB.Base)
882+	case ast.TYPE_STRUCT, ast.TYPE_ENUM, ast.TYPE_PRIMITIVE, ast.TYPE_FLOAT:
883+		return resA.Name == resB.Name
884+	default:
885 		return true
886 	}
887-	return false
888 }
889 
890 func (tc *TypeChecker) resolveType(typ *ast.BxType) *ast.BxType {
891-	if typ == nil {
892-		return ast.TypeUntyped
893-	}
894-	if tc.resolving[typ] {
895-		return typ
896-	}
897+	if typ == nil { return ast.TypeUntyped }
898+	if tc.resolving[typ] { return typ }
899 	tc.resolving[typ] = true
900 	defer func() { delete(tc.resolving, typ) }()
901-	if (typ.Kind == ast.TYPE_PRIMITIVE || typ.Kind == ast.TYPE_STRUCT) && typ.Name != "" {
902+	if (typ.Kind == ast.TYPE_PRIMITIVE || typ.Kind == ast.TYPE_STRUCT || typ.Kind == ast.TYPE_ENUM) && typ.Name != "" {
903 		if sym := tc.findSymbol(typ.Name, true); sym != nil {
904 			resolved := tc.resolveType(sym.Type)
905 			if typ.IsConst {
906@@ -719,39 +933,32 @@ func (tc *TypeChecker) resolveType(typ *ast.BxType) *ast.BxType {
907 }
908 
909 func (tc *TypeChecker) isIntegerType(t *ast.BxType) bool {
910-	if t == nil {
911-		return false
912-	}
913+	if t == nil { return false }
914 	resolved := tc.resolveType(t)
915-	return resolved.Kind == ast.TYPE_PRIMITIVE && resolved.Name != "float" && resolved.Name != "float32" && resolved.Name != "float64"
916+	return resolved.Kind == ast.TYPE_PRIMITIVE || resolved.Kind == ast.TYPE_UNTYPED_INT
917 }
918 
919 func (tc *TypeChecker) isFloatType(t *ast.BxType) bool {
920-	if t == nil {
921-		return false
922-	}
923-	return tc.resolveType(t).Kind == ast.TYPE_FLOAT
924+	if t == nil { return false }
925+	resolved := tc.resolveType(t)
926+	return resolved.Kind == ast.TYPE_FLOAT || resolved.Kind == ast.TYPE_UNTYPED_FLOAT
927 }
928 
929-func (tc *TypeChecker) isNumericType(t *ast.BxType) bool { return tc.isIntegerType(t) || tc.isFloatType(t) }
930+func (tc *TypeChecker) isNumericType(t *ast.BxType) bool {
931+	return tc.isIntegerType(t) || tc.isFloatType(t)
932+}
933 func (tc *TypeChecker) isScalarType(t *ast.BxType) bool {
934-	if t == nil {
935-		return false
936-	}
937+	if t == nil { return false }
938 	resolved := tc.resolveType(t)
939 	return tc.isNumericType(resolved) || resolved.Kind == ast.TYPE_POINTER || resolved.Kind == ast.TYPE_BOOL
940 }
941 
942 func typeToString(t *ast.BxType) string {
943-	if t == nil {
944-		return "<nil>"
945-	}
946+	if t == nil { return "<nil>" }
947 	var sb strings.Builder
948-	if t.IsConst {
949-		sb.WriteString("const ")
950-	}
951+	if t.IsConst { sb.WriteString("const ") }
952 	switch t.Kind {
953-	case ast.TYPE_PRIMITIVE, ast.TYPE_BOOL, ast.TYPE_FLOAT:
954+	case ast.TYPE_PRIMITIVE, ast.TYPE_BOOL, ast.TYPE_FLOAT, ast.TYPE_UNTYPED_INT, ast.TYPE_UNTYPED_FLOAT:
955 		sb.WriteString(t.Name)
956 	case ast.TYPE_POINTER:
957 		sb.WriteString(typeToString(t.Base))
958@@ -768,10 +975,19 @@ func typeToString(t *ast.BxType) string {
959 		} else {
960 			sb.WriteString("<anonymous>")
961 		}
962+	case ast.TYPE_ENUM:
963+		sb.WriteString("enum ")
964+		if t.Name != "" {
965+			sb.WriteString(t.Name)
966+		} else {
967+			sb.WriteString("<anonymous>")
968+		}
969 	case ast.TYPE_VOID:
970 		sb.WriteString("void")
971 	case ast.TYPE_UNTYPED:
972 		sb.WriteString("untyped")
973+	case ast.TYPE_NIL:
974+		sb.WriteString("nil")
975 	default:
976 		sb.WriteString(fmt.Sprintf("<unknown_type_kind_%d>", t.Kind))
977 	}
M pkg/util/util.go
+16, -26
  1@@ -11,7 +11,6 @@ import (
  2 	"github.com/xplshn/gbc/pkg/token"
  3 )
  4 
  5-// ANSI color and formatting constants
  6 const (
  7 	colorRed      = "\033[31m"
  8 	colorYellow   = "\033[33m"
  9@@ -21,7 +20,6 @@ const (
 10 	formatItalic  = "\033[3m"
 11 )
 12 
 13-// SourceFileRecord stores a file's name and content
 14 type SourceFileRecord struct {
 15 	Name    string
 16 	Content []rune
 17@@ -29,12 +27,8 @@ type SourceFileRecord struct {
 18 
 19 var sourceFiles []SourceFileRecord
 20 
 21-// SetSourceFiles updates the global source files list
 22-func SetSourceFiles(files []SourceFileRecord) {
 23-	sourceFiles = files
 24-}
 25+func SetSourceFiles(files []SourceFileRecord) { sourceFiles = files }
 26 
 27-// findFileAndLine extracts file name, line, and column from a token
 28 func findFileAndLine(tok token.Token) (string, int, int) {
 29 	if tok.FileIndex < 0 || tok.FileIndex >= len(sourceFiles) {
 30 		return "<unknown>", tok.Line, tok.Column
 31@@ -42,7 +36,6 @@ func findFileAndLine(tok token.Token) (string, int, int) {
 32 	return filepath.Base(sourceFiles[tok.FileIndex].Name), tok.Line, tok.Column
 33 }
 34 
 35-// callerFile retrieves the caller's file name, skipping specified stack frames
 36 func callerFile(skip int) string {
 37 	_, file, _, ok := runtime.Caller(skip)
 38 	if !ok {
 39@@ -51,7 +44,6 @@ func callerFile(skip int) string {
 40 	return filepath.Base(file)
 41 }
 42 
 43-// printSourceContext prints source code context with line numbers, caret, message, and caller info
 44 func printSourceContext(stream *os.File, tok token.Token, isError bool, msg, caller string) {
 45 	if tok.FileIndex < 0 || tok.FileIndex >= len(sourceFiles) || tok.Line <= 0 {
 46 		return
 47@@ -76,11 +68,11 @@ func printSourceContext(stream *os.File, tok token.Token, isError bool, msg, cal
 48 		line := strings.ReplaceAll(lines[i], "\t", "    ")
 49 		isErrorLine := lineNum == tok.Line
 50 
 51-		gutter := fmt.Sprintf("%s%*d | ", linePrefix, lineNumWidth, lineNum)
 52+		var gutter string
 53 		if isErrorLine {
 54-			gutter = boldGray(gutter)
 55+			gutter = boldGray(fmt.Sprintf("%s%*d | ", linePrefix, lineNumWidth, lineNum))
 56 		} else {
 57-			gutter = gray(gutter)
 58+			gutter = gray(fmt.Sprintf("%s%*d | ", linePrefix, lineNumWidth, lineNum))
 59 		}
 60 
 61 		fmt.Fprintf(stream, " %s%s\n", gutter, line)
 62@@ -92,26 +84,20 @@ func printSourceContext(stream *os.File, tok token.Token, isError bool, msg, cal
 63 				caretLine += strings.Repeat("~", tok.Len-1)
 64 			}
 65 
 66-			caretGutter := strings.Repeat("-", lineNumWidth) + " | "
 67-			caretGutter = boldGray(caretGutter)
 68-
 69+			caretGutter := boldGray(strings.Repeat("-", lineNumWidth) + " | ")
 70 			var caretColored, msgColored, callerColored string
 71 			if isError {
 72-				caretColored = red(caretLine)
 73-				msgColored = italic(msg)
 74+				caretColored, msgColored = red(caretLine), italic(msg)
 75 			} else {
 76-				caretColored = yellow(caretLine)
 77-				msgColored = italic(msg)
 78+				caretColored, msgColored = yellow(caretLine), italic(msg)
 79 			}
 80 			callerColored = italic(gray(fmt.Sprintf("(emitted from %s)", boldGray(caller))))
 81-
 82 			fmt.Fprintf(stream, " %s%s%s %s %s%s\n", linePrefix, caretGutter, caretColored, msgColored, callerColored, colorReset)
 83 		}
 84 	}
 85 	fmt.Fprintln(stream)
 86 }
 87 
 88-// caretColumn calculates the display column accounting for tabs
 89 func caretColumn(line string, col int) int {
 90 	if col < 1 {
 91 		col = 1
 92@@ -128,17 +114,14 @@ func caretColumn(line string, col int) int {
 93 	return pos + 1
 94 }
 95 
 96-// ANSI formatting helpers
 97 func italic(s string) string   { return formatItalic + s + colorReset }
 98 func gray(s string) string     { return colorGray + s + colorReset }
 99 func boldGray(s string) string { return colorBoldGray + s + colorReset }
100 func red(s string) string      { return colorRed + s + colorReset }
101 func yellow(s string) string   { return colorYellow + s + colorReset }
102 
103-// Error prints an error message with source context and exits
104 func Error(tok token.Token, format string, args ...interface{}) {
105 	msg := fmt.Sprintf(format, args...)
106-	// Handle non-source related errors gracefully
107 	if tok.FileIndex < 0 || tok.FileIndex >= len(sourceFiles) || tok.Line <= 0 {
108 		fmt.Fprintf(os.Stderr, "gbc: %serror:%s %s\n", colorRed, colorReset, msg)
109 		os.Exit(1)
110@@ -152,14 +135,12 @@ func Error(tok token.Token, format string, args ...interface{}) {
111 	os.Exit(1)
112 }
113 
114-// Warn prints a warning message with source context if the warning is enabled
115 func Warn(cfg *config.Config, wt config.Warning, tok token.Token, format string, args ...interface{}) {
116 	if !cfg.IsWarningEnabled(wt) {
117 		return
118 	}
119 	msg := fmt.Sprintf(format, args...) + fmt.Sprintf(" [-W%s]", cfg.Warnings[wt].Name)
120 
121-	// Handle non-source related warnings gracefully
122 	if tok.FileIndex < 0 || tok.FileIndex >= len(sourceFiles) || tok.Line <= 0 {
123 		fmt.Fprintf(os.Stderr, "gbc: %swarning:%s %s\n", colorYellow, colorReset, msg)
124 		return
125@@ -171,3 +152,12 @@ func Warn(cfg *config.Config, wt config.Warning, tok token.Token, format string,
126 	fmt.Fprintf(os.Stderr, "%s:%d:%d: %swarning%s:\n", filename, line, col, colorYellow, colorReset)
127 	printSourceContext(os.Stderr, tok, false, msg, caller)
128 }
129+
130+// AlignUp rounds n up to the next multiple of a
131+// a must be a power of 2
132+func AlignUp(n, a int64) int64 {
133+	if a == 0 {
134+		return n
135+	}
136+	return (n + a - 1) &^ (a - 1)
137+}