- 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
+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 }
+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 }
+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()
+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 }
+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 }
+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 }
+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+}
+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 }
+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 {
+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+}
+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
+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-}
+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
+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 }
+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+}