repos / gbc

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

commit
90279ca
parent
619de3e
author
xplshn
date
2025-08-16 13:35:15 +0000 UTC
fix: all examples

Signed-off-by: xplshn <[email protected]>
13 files changed,  +379, -332
M go.mod
M go.sum
M .gitattributes
+1, -0
1@@ -5,4 +5,5 @@ pgit-blacklist="Modula-2"
2 pgit-blacklist="XML"
3 pgit-blacklist="GDScript3"
4 pgit-blacklist="JSON"
5+pgit-blacklist="Zed"
6 pgit-blacklist="YAML"
M examples/.txtfmt.bx.json
+6, -6
 1@@ -1,21 +1,21 @@
 2 {
 3-  "binary_path": "/tmp/gtest-2334624446/ad7b72066c8c4fb1",
 4+  "binary_path": "/tmp/gtest-2125050678/b4ec78171c1e3a7d",
 5   "compile": {
 6-    "stdout": "----------------------\nTokenizing 1 source file(s) (Typed Pass: true)...\nParsing tokens into AST...\nConstant folding...\nType checking...\nQBE Codegen...\nCalling libqbe on our QBE IR...\nAssembling and linking to create '/tmp/gtest-2334624446/ad7b72066c8c4fb1'...\n----------------------\nCompilation successful!\n",
 7+    "stdout": "----------------------\nTokenizing 1 source file(s) (Typed Pass: true)...\nParsing tokens into AST...\nConstant folding...\nType checking...\nQBE Codegen...\nCalling libqbe on our QBE IR...\nAssembling and linking to create '/tmp/gtest-2125050678/b4ec78171c1e3a7d'...\n----------------------\nCompilation successful!\n",
 8     "stderr": "gbc: info: no target specified, defaulting to host target 'amd64_sysv'\n",
 9     "exitCode": 0,
10-    "duration": 21923577,
11+    "duration": 24357369,
12     "timed_out": false
13   },
14   "runs": [
15     {
16       "name": "fold",
17-      "input": "09876543210123456789009887654321012345678900987654321098765432101234567890098876543210123456789009876543210\n\n",
18+      "input": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\n\n",
19       "result": {
20-        "stdout": "0987654321012345678900988765432101234567890098765\n",
21+        "stdout": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\n",
22         "stderr": "",
23         "exitCode": -1,
24-        "duration": 5000642058,
25+        "duration": 5001114381,
26         "timed_out": true
27       }
28     }
M examples/hashTable.bx
+3, -4
 1@@ -10,9 +10,9 @@ extrn putchar, getchar, printf, scanf, strlen, strcpy, strcmp, malloc;
 2 int TABLE_SIZE = 101;
 3 
 4 type struct Entry {
 5-    byte* key;
 6-    byte* value;
 7-    Entry* next;
 8+    key   byte*;
 9+    value byte*;
10+    next  Entry*;
11 };
12 
13 Entry* table[TABLE_SIZE];
14@@ -24,7 +24,6 @@ int hash(key byte*) {
15         result = (31 * result + key[i]);
16         i = i + 1;
17     }
18-    // Ensure the result is non-negative before the final modulo.
19     return ((result % TABLE_SIZE + TABLE_SIZE) % TABLE_SIZE);
20 }
21 
M examples/txtfmt.bx
+12, -39
  1@@ -1,29 +1,12 @@
  2-/*
  3- * textfmt.b - A simple text formatter.
  4- *
  5- * This program reads text from standard input, formats it to a fixed
  6- * line width, and prints it to standard output. It demonstrates a mix
  7- * of classic, untyped B for the main logic and new, typed Bx for
  8- * helper functions where type clarity is beneficial.
  9- *
 10- * Compile with: gbc textfmt.b -o textfmt
 11- * Run with: ./textfmt < input.txt
 12- */
 13-
 14 extrn putchar, getchar;
 15 
 16-/* --- Global Variables --- */
 17-
 18-MAX_WIDTH 70; /* The maximum width of a line of text. */
 19+MAX_WIDTH 70;
 20 
 21-byte line_buf[100]; /* Buffer to hold the current line. */
 22-byte word_buf[50];  /* Buffer to hold the current word. */
 23-int line_len;      /* Current length of the line in line_buf. */
 24-int word_len;      /* Current length of the word in word_buf. */
 25+byte line_buf[100];
 26+byte word_buf[50];
 27+int line_len;
 28+int word_len;
 29 
 30-/*
 31- * Bx: A typed helper function to print a string.
 32- */
 33 void print_string(s byte*, len int) {
 34     auto i;
 35     i = 0;
 36@@ -33,9 +16,6 @@ void print_string(s byte*, len int) {
 37     }
 38 }
 39 
 40-/*
 41- * Bx: A typed helper function to flush the current line buffer.
 42- */
 43 void flush_line() {
 44     if (line_len > 0) {
 45         print_string(line_buf, line_len);
 46@@ -44,13 +24,9 @@ void flush_line() {
 47     line_len = 0;
 48 }
 49 
 50-/*
 51- * Bx: A typed function to append a word to the current line.
 52- */
 53 void append_word_to_line() {
 54     auto i;
 55 
 56-    /* Add a space before the word if the line isn't empty. */
 57     if (line_len > 0) {
 58         line_buf[line_len] = ' ';
 59         line_len++;
 60@@ -62,35 +38,32 @@ void append_word_to_line() {
 61         line_len++;
 62         i++;
 63     }
 64-    word_len = 0; /* Reset word buffer after appending */
 65+    word_len = 0;
 66 }
 67 
 68-/*
 69- * main() - The main program logic.
 70- */
 71 main() {
 72     auto c;
 73 
 74     line_len = 0;
 75     word_len = 0;
 76 
 77-    while ((c = getchar()) != -1) { /* -1 is EOF */
 78+    while ((c = getchar()) != -1) { // -1 == EOF
 79         if (c == ' ' || c == '\t' || c == '\n') {
 80-            /* Whitespace detected, process the completed word. */
 81+            // Whitespace detected, process the completed word
 82             if (word_len > 0) {
 83                 if (line_len + word_len + 1 > MAX_WIDTH) {
 84-                    /* Word doesn't fit, flush the current line first. */
 85+                    // Word doesn't fit, flush the current line first
 86                     flush_line();
 87                 }
 88                 append_word_to_line();
 89             }
 90 
 91             if (c == '\n') {
 92-                /* An explicit newline in the input flushes the current line. */
 93+                // An explicit newline in the input flushes the current line
 94                 flush_line();
 95             }
 96         } else {
 97-            /* It's a character for the current word. */
 98+            // It's a character for the current word
 99             if (word_len < 49) {
100                 word_buf[word_len] = c;
101                 word_len++;
102@@ -98,7 +71,7 @@ main() {
103         }
104     }
105 
106-    /* End of input, process any remaining word and flush the last line. */
107+    // End of input, process any remaining word and flush the last line
108     if (word_len > 0) {
109         if (line_len + word_len + 1 > MAX_WIDTH) {
110             flush_line();
M go.mod
+7, -5
 1@@ -1,24 +1,26 @@
 2 module github.com/xplshn/gbc
 3 
 4-go 1.23.0
 5+go 1.24.0
 6+
 7+toolchain go1.24.6
 8 
 9 require (
10+	github.com/cespare/xxhash/v2 v2.3.0
11 	github.com/google/go-cmp v0.7.0
12-	modernc.org/libqbe v0.3.20
13+	modernc.org/libqbe v0.3.21
14 )
15 
16 require (
17-	github.com/cespare/xxhash/v2 v2.3.0 // indirect
18 	github.com/dustin/go-humanize v1.0.1 // indirect
19 	github.com/goforj/godump v1.5.0 // indirect
20 	github.com/google/uuid v1.6.0 // indirect
21 	github.com/mattn/go-isatty v0.0.20 // indirect
22 	github.com/ncruces/go-strftime v0.1.9 // indirect
23 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
24-	golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
25+	golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
26 	golang.org/x/sys v0.35.0 // indirect
27 	modernc.org/goabi0 v0.2.0 // indirect
28-	modernc.org/libc v1.66.6 // indirect
29+	modernc.org/libc v1.66.7 // indirect
30 	modernc.org/mathutil v1.7.1 // indirect
31 	modernc.org/memory v1.11.0 // indirect
32 	modernc.org/token v1.1.0 // indirect
M go.sum
+8, -0
 1@@ -20,8 +20,11 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
 2 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 3 golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
 4 golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
 5+golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
 6+golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
 7 golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
 8 golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
 9+golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
10 golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
11 golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
12 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
13@@ -31,6 +34,7 @@ golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
14 golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
15 golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
16 golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
17+golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
18 modernc.org/cc/v4 v4.26.3 h1:yEN8dzrkRFnn4PUUKXLYIqVf2PJYAEjMTFjO3BDGc3I=
19 modernc.org/cc/v4 v4.26.3/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
20 modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
21@@ -46,6 +50,8 @@ modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
22 modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
23 modernc.org/libc v1.66.6 h1:RyQpwAhM/19nXD8y3iejM/AjmKwY2TjxZTlUWTsWw2U=
24 modernc.org/libc v1.66.6/go.mod h1:j8z0EYAuumoMQ3+cWXtmw6m+LYn3qm8dcZDFtFTSq+M=
25+modernc.org/libc v1.66.7 h1:rjhZ8OSCybKWxS1CJr0hikpEi6Vg+944Ouyrd+bQsoY=
26+modernc.org/libc v1.66.7/go.mod h1:ln6tbWX0NH+mzApEoDRvilBvAWFt1HX7AUA4VDdVDPM=
27 modernc.org/libqbe v0.3.17 h1:2vnU1Y9ay4FFNtnieI1i+tm6uwQwIT+bpJDOqohQF0g=
28 modernc.org/libqbe v0.3.17/go.mod h1:7OLbdGw1qk5BrO3MpJidWbFAUH3RCDk1fI1RbEN98yY=
29 modernc.org/libqbe v0.3.18 h1:tDqVm12NvVJd9eYfYDTxSKDLqrvkonzFy4CwGdf46bI=
30@@ -54,6 +60,8 @@ modernc.org/libqbe v0.3.19 h1:u/JP8fjPYjg8Cbiu42lBNB+Q+x0q7kQKFZnDv7QI1C0=
31 modernc.org/libqbe v0.3.19/go.mod h1:v9jfQ3pPqP0lloc3x9s/O0QyTrAyWl7nBRDc3CA1EKY=
32 modernc.org/libqbe v0.3.20 h1:MQ7/yQ1YOww6iYUrYo+ffrm8v+7L0FR/ZTHtWJmJaQ8=
33 modernc.org/libqbe v0.3.20/go.mod h1:v9jfQ3pPqP0lloc3x9s/O0QyTrAyWl7nBRDc3CA1EKY=
34+modernc.org/libqbe v0.3.21 h1:qDlRpTO1aQ4gPUXZv/6SLblMq1nOajWsi4ibsPIaZVY=
35+modernc.org/libqbe v0.3.21/go.mod h1:v9jfQ3pPqP0lloc3x9s/O0QyTrAyWl7nBRDc3CA1EKY=
36 modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
37 modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
38 modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
M pkg/ast/ast.go
+63, -38
  1@@ -1,4 +1,3 @@
  2-// Package ast defines the types used to represent the Abstract Syntax Tree (AST)
  3 package ast
  4 
  5 import (
  6@@ -6,10 +5,8 @@ import (
  7 	"github.com/xplshn/gbc/pkg/util"
  8 )
  9 
 10-// NodeType defines the kind of a node in the AST
 11 type NodeType int
 12 
 13-// Node types enum
 14 const (
 15 	// Expressions
 16 	Number NodeType = iota
 17@@ -49,19 +46,17 @@ const (
 18 	Directive
 19 )
 20 
 21-// Node represents a node in the Abstract Syntax Tree
 22+// Node represents a node in the Abstract Syntax Tree.
 23 type Node struct {
 24 	Type   NodeType
 25 	Tok    token.Token
 26 	Parent *Node
 27 	Data   interface{}
 28-	Typ    *BxType // Set by the type checker
 29+	Typ    *BxType // Set by the type checker.
 30 }
 31 
 32-// BxTypeKind defines the kind of a BxType
 33 type BxTypeKind int
 34 
 35-// BxType kinds enum
 36 const (
 37 	TYPE_PRIMITIVE BxTypeKind = iota
 38 	TYPE_POINTER
 39@@ -73,18 +68,18 @@ const (
 40 	TYPE_UNTYPED
 41 )
 42 
 43-// BxType represents a type in the Bx type system
 44+// BxType represents a type in the Bx type system.
 45 type BxType struct {
 46 	Kind      BxTypeKind
 47-	Base      *BxType // Base type for pointers or arrays
 48-	Name      string  // Name for primitive types or the typedef name
 49+	Base      *BxType // Base type for pointers or arrays.
 50+	Name      string  // Name for primitive types or the typedef name.
 51 	ArraySize *Node
 52 	IsConst   bool
 53-	StructTag string  // The name immediately following the 'struct' keyword
 54-	Fields    []*Node // List of *VarDecl nodes for struct members
 55+	StructTag string  // The name immediately following the 'struct' keyword.
 56+	Fields    []*Node // List of *VarDecl nodes for struct members.
 57 }
 58 
 59-// Pre-defined types
 60+// Pre-defined types.
 61 var (
 62 	TypeInt     = &BxType{Kind: TYPE_PRIMITIVE, Name: "int"}
 63 	TypeUint    = &BxType{Kind: TYPE_PRIMITIVE, Name: "uint"}
 64@@ -106,7 +101,6 @@ var (
 65 	TypeString  = &BxType{Kind: TYPE_POINTER, Base: TypeByte, Name: "string"}
 66 )
 67 
 68-// --- Node Data Structs ---
 69 type NumberNode struct{ Value int64 }
 70 type StringNode struct{ Value string }
 71 type IdentNode struct{ Name string }
 72@@ -157,8 +151,6 @@ type LabelNode struct{ Name string; Stmt *Node }
 73 type AsmStmtNode struct{ Code string }
 74 type DirectiveNode struct{ Name string }
 75 
 76-// --- Node Constructors ---
 77-
 78 func newNode(tok token.Token, nodeType NodeType, data interface{}, children ...*Node) *Node {
 79 	node := &Node{Type: nodeType, Tok: tok, Data: data}
 80 	for _, child := range children {
 81@@ -299,13 +291,12 @@ func NewDirective(tok token.Token, name string) *Node {
 82 	return newNode(tok, Directive, DirectiveNode{Name: name})
 83 }
 84 
 85-// FoldConstants performs compile-time constant evaluation on the AST
 86+// FoldConstants performs compile-time constant evaluation on the AST.
 87 func FoldConstants(node *Node) *Node {
 88 	if node == nil {
 89 		return nil
 90 	}
 91 
 92-	// Recursively fold children first
 93 	switch d := node.Data.(type) {
 94 	case AssignNode:
 95 		d.Rhs = FoldConstants(d.Rhs)
 96@@ -330,7 +321,6 @@ func FoldConstants(node *Node) *Node {
 97 		node.Data = d
 98 	}
 99 
100-	// Then, attempt to fold the current node.
101 	switch node.Type {
102 	case BinaryOp:
103 		d := node.Data.(BinaryOpNode)
104@@ -339,25 +329,55 @@ func FoldConstants(node *Node) *Node {
105 			var res int64
106 			folded := true
107 			switch d.Op {
108-			case token.Plus: res = l + r
109-			case token.Minus: res = l - r
110-			case token.Star: res = l * r
111-			case token.And: res = l & r
112-			case token.Or: res = l | r
113-			case token.Xor: res = l ^ r
114-			case token.Shl: res = l << uint64(r)
115-			case token.Shr: res = l >> uint64(r)
116-			case token.EqEq: if l == r { res = 1 }
117-			case token.Neq: if l != r { res = 1 }
118-			case token.Lt: if l < r { res = 1 }
119-			case token.Gt: if l > r { res = 1 }
120-			case token.Lte: if l <= r { res = 1 }
121-			case token.Gte: if l >= r { res = 1 }
122+			case token.Plus:
123+				res = l + r
124+			case token.Minus:
125+				res = l - r
126+			case token.Star:
127+				res = l * r
128+			case token.And:
129+				res = l & r
130+			case token.Or:
131+				res = l | r
132+			case token.Xor:
133+				res = l ^ r
134+			case token.Shl:
135+				res = l << uint64(r)
136+			case token.Shr:
137+				res = l >> uint64(r)
138+			case token.EqEq:
139+				if l == r {
140+					res = 1
141+				}
142+			case token.Neq:
143+				if l != r {
144+					res = 1
145+				}
146+			case token.Lt:
147+				if l < r {
148+					res = 1
149+				}
150+			case token.Gt:
151+				if l > r {
152+					res = 1
153+				}
154+			case token.Lte:
155+				if l <= r {
156+					res = 1
157+				}
158+			case token.Gte:
159+				if l >= r {
160+					res = 1
161+				}
162 			case token.Slash:
163-				if r == 0 { util.Error(node.Tok, "Compile-time division by zero.") }
164+				if r == 0 {
165+					util.Error(node.Tok, "Compile-time division by zero.")
166+				}
167 				res = l / r
168 			case token.Rem:
169-				if r == 0 { util.Error(node.Tok, "Compile-time modulo by zero.") }
170+				if r == 0 {
171+					util.Error(node.Tok, "Compile-time modulo by zero.")
172+				}
173 				res = l % r
174 			default:
175 				folded = false
176@@ -373,9 +393,14 @@ func FoldConstants(node *Node) *Node {
177 			var res int64
178 			folded := true
179 			switch d.Op {
180-			case token.Minus: res = -val
181-			case token.Complement: res = ^val
182-			case token.Not: if val == 0 { res = 1 }
183+			case token.Minus:
184+				res = -val
185+			case token.Complement:
186+				res = ^val
187+			case token.Not:
188+				if val == 0 {
189+					res = 1
190+				}
191 			default:
192 				folded = false
193 			}
M pkg/codegen/codegen.go
+10, -1
 1@@ -821,8 +821,17 @@ func (ctx *Context) codegenExpr(node *ast.Node) (result, predLabel string, termi
 2 			return sym.QbeName, startBlockLabel, false
 3 		}
 4 
 5-		if sym.Type == symFunc || sym.Type == symExtrn {
 6+		switch sym.Type {
 7+		case symFunc:
 8 			return sym.QbeName, startBlockLabel, false
 9+		case symExtrn:
10+			isCall := node.Parent != nil && node.Parent.Type == ast.FuncCall && node.Parent.Data.(ast.FuncCallNode).FuncExpr == node
11+			if isCall {
12+				return sym.QbeName, startBlockLabel, false
13+			} else {
14+				// It's an external variable (e.g., stderr), which is a pointer. Load its value.
15+				return ctx.genLoad(sym.QbeName, sym.BxType), startBlockLabel, false
16+			}
17 		}
18 
19 		isArr := sym.IsVector || (sym.BxType != nil && sym.BxType.Kind == ast.TYPE_ARRAY)
M pkg/config/config.go
+5, -11
 1@@ -120,7 +120,7 @@ func NewConfig() *Config {
 2 	return cfg
 3 }
 4 
 5-// SetTarget configures the compiler for a specific architecture and QBE target
 6+// SetTarget configures the compiler for a specific architecture and QBE target.
 7 func (c *Config) SetTarget(goos, goarch, qbeTarget string) {
 8 	if qbeTarget == "" {
 9 		c.QbeTarget = libqbe.DefaultTarget(goos, goarch)
10@@ -132,22 +132,16 @@ func (c *Config) SetTarget(goos, goarch, qbeTarget string) {
11 
12 	c.TargetArch = goarch
13 
14-	// QBE only supports these:
15 	switch c.QbeTarget {
16 	case "amd64_sysv", "amd64_apple", "arm64", "arm64_apple", "rv64":
17-		c.WordSize = 8
18-		c.WordType = "l"
19-		c.StackAlignment = 16
20+		c.WordSize, c.WordType, c.StackAlignment = 8, "l", 16
21+	case "arm", "rv32":
22+		c.WordSize, c.WordType, c.StackAlignment = 4, "w", 8
23 	default:
24 		fmt.Fprintf(os.Stderr, "gbc: warning: unrecognized or unsupported QBE target '%s'.\n", c.QbeTarget)
25 		fmt.Fprintf(os.Stderr, "gbc: warning: defaulting to 64-bit properties. Compilation may fail.\n")
26-		c.WordSize = 8
27-		c.WordType = "l"
28-		c.StackAlignment = 16
29+		c.WordSize, c.WordType, c.StackAlignment = 8, "l", 16
30 	}
31-	// TODO: C target
32-	// TODO: GameBoyColor target
33-	// TODO: Experiment with the GOABI0 target of modernc.org/libqbe
34 }
35 
36 func (c *Config) SetFeature(ft Feature, enabled bool) {
M pkg/lexer/lexer.go
+120, -58
  1@@ -1,4 +1,3 @@
  2-// Package lexer is responsible for breaking B source code into a stream of tokens
  3 package lexer
  4 
  5 import (
  6@@ -11,7 +10,6 @@ import (
  7 	"github.com/xplshn/gbc/pkg/util"
  8 )
  9 
 10-// Lexer holds the state required for tokenizing a source string
 11 type Lexer struct {
 12 	source    []rune
 13 	fileIndex int
 14@@ -21,14 +19,12 @@ type Lexer struct {
 15 	cfg       *config.Config
 16 }
 17 
 18-// NewLexer creates and initializes a new Lexer instance
 19 func NewLexer(source []rune, fileIndex int, cfg *config.Config) *Lexer {
 20 	return &Lexer{
 21 		source: source, fileIndex: fileIndex, line: 1, column: 1, cfg: cfg,
 22 	}
 23 }
 24 
 25-// Next consumes and returns the next token from the source
 26 func (l *Lexer) Next() token.Token {
 27 	for {
 28 		l.skipWhitespaceAndComments()
 29@@ -38,7 +34,6 @@ func (l *Lexer) Next() token.Token {
 30 			return l.makeToken(token.EOF, "", startPos, startCol, startLine)
 31 		}
 32 
 33-		// Handle directives before other tokens.
 34 		if !l.cfg.IsFeatureEnabled(config.FeatNoDirectives) && l.peek() == '/' && l.peekNext() == '/' {
 35 			if tok, isDirective := l.lineCommentOrDirective(startPos, startCol, startLine); isDirective {
 36 				return tok
 37@@ -54,36 +49,61 @@ func (l *Lexer) Next() token.Token {
 38 		}
 39 
 40 		switch ch {
 41-		case '(': return l.makeToken(token.LParen, "", startPos, startCol, startLine)
 42-		case ')': return l.makeToken(token.RParen, "", startPos, startCol, startLine)
 43-		case '{': return l.makeToken(token.LBrace, "", startPos, startCol, startLine)
 44-		case '}': return l.makeToken(token.RBrace, "", startPos, startCol, startLine)
 45-		case '[': return l.makeToken(token.LBracket, "", startPos, startCol, startLine)
 46-		case ']': return l.makeToken(token.RBracket, "", startPos, startCol, startLine)
 47-		case ';': return l.makeToken(token.Semi, "", startPos, startCol, startLine)
 48-		case ',': return l.makeToken(token.Comma, "", startPos, startCol, startLine)
 49-		case '?': return l.makeToken(token.Question, "", startPos, startCol, startLine)
 50-		case '~': return l.makeToken(token.Complement, "", startPos, startCol, startLine)
 51-		case ':': return l.matchThen('=', token.Define, token.Colon, startPos, startCol, startLine)
 52-		case '!': return l.matchThen('=', token.Neq, token.Not, startPos, startCol, startLine)
 53-		case '^': return l.matchThen('=', token.XorEq, token.Xor, startPos, startCol, startLine)
 54-		case '%': return l.matchThen('=', token.RemEq, token.Rem, startPos, startCol, startLine)
 55-		case '+': return l.plus(startPos, startCol, startLine)
 56-		case '-': return l.minus(startPos, startCol, startLine)
 57-		case '*': return l.star(startPos, startCol, startLine)
 58-		case '/': return l.slash(startPos, startCol, startLine)
 59-		case '&': return l.ampersand(startPos, startCol, startLine)
 60-		case '|': return l.pipe(startPos, startCol, startLine)
 61-		case '<': return l.less(startPos, startCol, startLine)
 62-		case '>': return l.greater(startPos, startCol, startLine)
 63-		case '=': return l.equal(startPos, startCol, startLine)
 64+		case '(':
 65+			return l.makeToken(token.LParen, "", startPos, startCol, startLine)
 66+		case ')':
 67+			return l.makeToken(token.RParen, "", startPos, startCol, startLine)
 68+		case '{':
 69+			return l.makeToken(token.LBrace, "", startPos, startCol, startLine)
 70+		case '}':
 71+			return l.makeToken(token.RBrace, "", startPos, startCol, startLine)
 72+		case '[':
 73+			return l.makeToken(token.LBracket, "", startPos, startCol, startLine)
 74+		case ']':
 75+			return l.makeToken(token.RBracket, "", startPos, startCol, startLine)
 76+		case ';':
 77+			return l.makeToken(token.Semi, "", startPos, startCol, startLine)
 78+		case ',':
 79+			return l.makeToken(token.Comma, "", startPos, startCol, startLine)
 80+		case '?':
 81+			return l.makeToken(token.Question, "", startPos, startCol, startLine)
 82+		case '~':
 83+			return l.makeToken(token.Complement, "", startPos, startCol, startLine)
 84+		case ':':
 85+			return l.matchThen('=', token.Define, token.Colon, startPos, startCol, startLine)
 86+		case '!':
 87+			return l.matchThen('=', token.Neq, token.Not, startPos, startCol, startLine)
 88+		case '^':
 89+			return l.matchThen('=', token.XorEq, token.Xor, startPos, startCol, startLine)
 90+		case '%':
 91+			return l.matchThen('=', token.RemEq, token.Rem, startPos, startCol, startLine)
 92+		case '+':
 93+			return l.plus(startPos, startCol, startLine)
 94+		case '-':
 95+			return l.minus(startPos, startCol, startLine)
 96+		case '*':
 97+			return l.star(startPos, startCol, startLine)
 98+		case '/':
 99+			return l.slash(startPos, startCol, startLine)
100+		case '&':
101+			return l.ampersand(startPos, startCol, startLine)
102+		case '|':
103+			return l.pipe(startPos, startCol, startLine)
104+		case '<':
105+			return l.less(startPos, startCol, startLine)
106+		case '>':
107+			return l.greater(startPos, startCol, startLine)
108+		case '=':
109+			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 '"': return l.stringLiteral(startPos, startCol, startLine)
116-		case '\'': return l.charLiteral(startPos, startCol, startLine)
117+		case '"':
118+			return l.stringLiteral(startPos, startCol, startLine)
119+		case '\'':
120+			return l.charLiteral(startPos, startCol, startLine)
121 		}
122 
123 		tok := l.makeToken(token.EOF, "", startPos, startCol, startLine)
124@@ -93,17 +113,23 @@ func (l *Lexer) Next() token.Token {
125 }
126 
127 func (l *Lexer) peek() rune {
128-	if l.isAtEnd() { return 0 }
129+	if l.isAtEnd() {
130+		return 0
131+	}
132 	return l.source[l.pos]
133 }
134 
135 func (l *Lexer) peekNext() rune {
136-	if l.pos+1 >= len(l.source) { return 0 }
137+	if l.pos+1 >= len(l.source) {
138+		return 0
139+	}
140 	return l.source[l.pos+1]
141 }
142 
143 func (l *Lexer) advance() rune {
144-	if l.isAtEnd() { return 0 }
145+	if l.isAtEnd() {
146+		return 0
147+	}
148 	ch := l.source[l.pos]
149 	if ch == '\n' {
150 		l.line++
151@@ -160,7 +186,8 @@ func (l *Lexer) blockComment() {
152 	l.advance() // Consume '*'
153 	for !l.isAtEnd() {
154 		if l.peek() == '*' && l.peekNext() == '/' {
155-			l.advance(); l.advance()
156+			l.advance()
157+			l.advance()
158 			return
159 		}
160 		l.advance()
161@@ -190,7 +217,6 @@ func (l *Lexer) lineCommentOrDirective(startPos, startCol, startLine int) (token
162 		return l.makeToken(token.Directive, directiveContent, startPos, startCol, startLine), true
163 	}
164 
165-	// It wasn't a directive, so rewind to before we consumed the comment.
166 	l.pos, l.column, l.line = preCommentPos, preCommentCol, preCommentLine
167 	return token.Token{}, false
168 }
169@@ -291,63 +317,99 @@ func (l *Lexer) matchThen(expected rune, thenType, elseType token.Type, sPos, sC
170 }
171 
172 func (l *Lexer) plus(sPos, sCol, sLine int) token.Token {
173-	if l.match('+') { return l.makeToken(token.Inc, "", sPos, sCol, sLine) }
174-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.PlusEq, "", sPos, sCol, sLine) }
175+	if l.match('+') {
176+		return l.makeToken(token.Inc, "", sPos, sCol, sLine)
177+	}
178+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
179+		return l.makeToken(token.PlusEq, "", sPos, sCol, sLine)
180+	}
181 	return l.makeToken(token.Plus, "", sPos, sCol, sLine)
182 }
183 
184 func (l *Lexer) minus(sPos, sCol, sLine int) token.Token {
185-	if l.match('-') { return l.makeToken(token.Dec, "", sPos, sCol, sLine) }
186-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.MinusEq, "", sPos, sCol, sLine) }
187+	if l.match('-') {
188+		return l.makeToken(token.Dec, "", sPos, sCol, sLine)
189+	}
190+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
191+		return l.makeToken(token.MinusEq, "", sPos, sCol, sLine)
192+	}
193 	return l.makeToken(token.Minus, "", sPos, sCol, sLine)
194 }
195 
196 func (l *Lexer) star(sPos, sCol, sLine int) token.Token {
197-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.StarEq, "", sPos, sCol, sLine) }
198+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
199+		return l.makeToken(token.StarEq, "", sPos, sCol, sLine)
200+	}
201 	return l.makeToken(token.Star, "", sPos, sCol, sLine)
202 }
203 
204 func (l *Lexer) slash(sPos, sCol, sLine int) token.Token {
205-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.SlashEq, "", sPos, sCol, sLine) }
206+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
207+		return l.makeToken(token.SlashEq, "", sPos, sCol, sLine)
208+	}
209 	return l.makeToken(token.Slash, "", sPos, sCol, sLine)
210 }
211 
212 func (l *Lexer) ampersand(sPos, sCol, sLine int) token.Token {
213-	if l.match('&') { return l.makeToken(token.AndAnd, "", sPos, sCol, sLine) }
214-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.AndEq, "", sPos, sCol, sLine) }
215+	if l.match('&') {
216+		return l.makeToken(token.AndAnd, "", sPos, sCol, sLine)
217+	}
218+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
219+		return l.makeToken(token.AndEq, "", sPos, sCol, sLine)
220+	}
221 	return l.makeToken(token.And, "", sPos, sCol, sLine)
222 }
223 
224 func (l *Lexer) pipe(sPos, sCol, sLine int) token.Token {
225-	if l.match('|') { return l.makeToken(token.OrOr, "", sPos, sCol, sLine) }
226-	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') { return l.makeToken(token.OrEq, "", sPos, sCol, sLine) }
227+	if l.match('|') {
228+		return l.makeToken(token.OrOr, "", sPos, sCol, sLine)
229+	}
230+	if l.cfg.IsFeatureEnabled(config.FeatCOps) && l.match('=') {
231+		return l.makeToken(token.OrEq, "", sPos, sCol, sLine)
232+	}
233 	return l.makeToken(token.Or, "", sPos, sCol, sLine)
234 }
235 
236 func (l *Lexer) less(sPos, sCol, sLine int) token.Token {
237-	if l.match('<') { return l.matchThen('=', token.ShlEq, token.Shl, sPos, sCol, sLine) }
238+	if l.match('<') {
239+		return l.matchThen('=', token.ShlEq, token.Shl, sPos, sCol, sLine)
240+	}
241 	return l.matchThen('=', token.Lte, token.Lt, sPos, sCol, sLine)
242 }
243 
244 func (l *Lexer) greater(sPos, sCol, sLine int) token.Token {
245-	if l.match('>') { return l.matchThen('=', token.ShrEq, token.Shr, sPos, sCol, sLine) }
246+	if l.match('>') {
247+		return l.matchThen('=', token.ShrEq, token.Shr, sPos, sCol, sLine)
248+	}
249 	return l.matchThen('=', token.Gte, token.Gt, sPos, sCol, sLine)
250 }
251 
252 func (l *Lexer) equal(sPos, sCol, sLine int) token.Token {
253-	if l.match('=') { return l.makeToken(token.EqEq, "", sPos, sCol, sLine) }
254+	if l.match('=') {
255+		return l.makeToken(token.EqEq, "", sPos, sCol, sLine)
256+	}
257 	if l.cfg.IsFeatureEnabled(config.FeatBOps) {
258 		switch {
259-		case l.match('+'): return l.makeToken(token.EqPlus, "", sPos, sCol, sLine)
260-		case l.match('-'): return l.makeToken(token.EqMinus, "", sPos, sCol, sLine)
261-		case l.match('*'): return l.makeToken(token.EqStar, "", sPos, sCol, sLine)
262-		case l.match('/'): return l.makeToken(token.EqSlash, "", sPos, sCol, sLine)
263-		case l.match('%'): return l.makeToken(token.EqRem, "", sPos, sCol, sLine)
264-		case l.match('&'): return l.makeToken(token.EqAnd, "", sPos, sCol, sLine)
265-		case l.match('|'): return l.makeToken(token.EqOr, "", sPos, sCol, sLine)
266-		case l.match('^'): return l.makeToken(token.EqXor, "", sPos, sCol, sLine)
267-		case l.match('<') && l.match('<'): return l.makeToken(token.EqShl, "", sPos, sCol, sLine)
268-		case l.match('>') && l.match('>'): return l.makeToken(token.EqShr, "", sPos, sCol, sLine)
269+		case l.match('+'):
270+			return l.makeToken(token.EqPlus, "", sPos, sCol, sLine)
271+		case l.match('-'):
272+			return l.makeToken(token.EqMinus, "", sPos, sCol, sLine)
273+		case l.match('*'):
274+			return l.makeToken(token.EqStar, "", sPos, sCol, sLine)
275+		case l.match('/'):
276+			return l.makeToken(token.EqSlash, "", sPos, sCol, sLine)
277+		case l.match('%'):
278+			return l.makeToken(token.EqRem, "", sPos, sCol, sLine)
279+		case l.match('&'):
280+			return l.makeToken(token.EqAnd, "", sPos, sCol, sLine)
281+		case l.match('|'):
282+			return l.makeToken(token.EqOr, "", sPos, sCol, sLine)
283+		case l.match('^'):
284+			return l.makeToken(token.EqXor, "", sPos, sCol, sLine)
285+		case l.match('<') && l.match('<'):
286+			return l.makeToken(token.EqShl, "", sPos, sCol, sLine)
287+		case l.match('>') && l.match('>'):
288+			return l.makeToken(token.EqShr, "", sPos, sCol, sLine)
289 		}
290 	}
291 	return l.makeToken(token.Eq, "", sPos, sCol, sLine)
M pkg/parser/parser.go
+45, -92
  1@@ -1,5 +1,3 @@
  2-// Package parser is responsible for constructing an Abstract Syntax Tree (AST)
  3-// from a stream of tokens provided by the lexer
  4 package parser
  5 
  6 import (
  7@@ -13,7 +11,6 @@ import (
  8 	"github.com/xplshn/gbc/pkg/util"
  9 )
 10 
 11-// Parser holds the state for the parsing process
 12 type Parser struct {
 13 	tokens      []token.Token
 14 	pos         int
 15@@ -24,7 +21,6 @@ type Parser struct {
 16 	typeNames   map[string]bool
 17 }
 18 
 19-// NewParser creates and initializes a new Parser from a token stream
 20 func NewParser(tokens []token.Token, cfg *config.Config) *Parser {
 21 	p := &Parser{
 22 		tokens:      tokens,
 23@@ -36,7 +32,6 @@ func NewParser(tokens []token.Token, cfg *config.Config) *Parser {
 24 		p.current = p.tokens[0]
 25 	}
 26 
 27-	// Pre-populate type names with built-in types if the type system is enabled
 28 	if p.isTypedPass {
 29 		for keyword, tokType := range token.KeywordMap {
 30 			if tokType >= token.Void && tokType <= token.Any {
 31@@ -44,12 +39,9 @@ func NewParser(tokens []token.Token, cfg *config.Config) *Parser {
 32 			}
 33 		}
 34 	}
 35-
 36 	return p
 37 }
 38 
 39-// --- Parser Helpers ---
 40-
 41 func (p *Parser) advance() {
 42 	if p.pos < len(p.tokens) {
 43 		p.previous = p.current
 44@@ -64,7 +56,7 @@ func (p *Parser) peek() token.Token {
 45 	if p.pos+1 < len(p.tokens) {
 46 		return p.tokens[p.pos+1]
 47 	}
 48-	return p.tokens[len(p.tokens)-1] // Return EOF token
 49+	return p.tokens[len(p.tokens)-1]
 50 }
 51 
 52 func (p *Parser) check(tokType token.Type) bool {
 53@@ -109,9 +101,6 @@ func isLValue(node *ast.Node) bool {
 54 	}
 55 }
 56 
 57-// --- Main Parsing Logic ---
 58-
 59-// Parse is the entry point for the parser. It constructs the AST
 60 func (p *Parser) Parse() *ast.Node {
 61 	var stmts []*ast.Node
 62 	tok := p.current
 63@@ -123,7 +112,6 @@ func (p *Parser) Parse() *ast.Node {
 64 		}
 65 
 66 		stmt := p.parseTopLevel()
 67-
 68 		if stmt != nil {
 69 			if stmt.Type == ast.MultiVarDecl {
 70 				stmts = append(stmts, stmt.Data.(ast.MultiVarDeclNode).Decls...)
 71@@ -168,7 +156,7 @@ func (p *Parser) parseTopLevel() *ast.Node {
 72 		peekTok := p.peek()
 73 
 74 		if peekTok.Type == token.LParen {
 75-			p.advance() // Consume identifier
 76+			p.advance()
 77 			stmt = p.parseFuncDecl(nil, identTok)
 78 		} else if peekTok.Type == token.Asm {
 79 			p.advance()
 80@@ -184,11 +172,9 @@ func (p *Parser) parseTopLevel() *ast.Node {
 81 		if p.isTypedPass && (p.isBuiltinType(currentTok) || p.check(token.Const)) {
 82 			stmt = p.parseTypedVarOrFuncDecl(true)
 83 		} else {
 84-			// Attempt to parse a top-level expression statement (like a function call for pre-main execution)
 85 			stmt = p.parseExpr()
 86 			if stmt != nil {
 87 				if stmt.Type == ast.FuncCall {
 88-					// This is a top-level function call, which in B is a function declaration with an empty body
 89 					funcCallData := stmt.Data.(ast.FuncCallNode)
 90 					if funcCallData.FuncExpr.Type == ast.Ident {
 91 						funcName := funcCallData.FuncExpr.Data.(ast.IdentNode).Name
 92@@ -202,7 +188,6 @@ func (p *Parser) parseTopLevel() *ast.Node {
 93 			}
 94 		}
 95 	}
 96-
 97 	return stmt
 98 }
 99 
100@@ -213,7 +198,6 @@ func (p *Parser) isBxDeclarationAhead() bool {
101 	if p.check(token.Auto) {
102 		p.advance()
103 	}
104-
105 	if !p.check(token.Ident) {
106 		return false
107 	}
108@@ -224,33 +208,21 @@ func (p *Parser) isBxDeclarationAhead() bool {
109 		}
110 		p.advance()
111 	}
112-
113-	if p.check(token.Eq) && p.cfg.IsFeatureEnabled(config.FeatBxDeclarations) {
114-		return true
115-	}
116-	if p.check(token.Define) && p.cfg.IsFeatureEnabled(config.FeatShortDecl) {
117-		return true
118-	}
119-
120-	return false
121+	return p.check(token.Eq) || p.check(token.Define)
122 }
123 
124 func (p *Parser) isBuiltinType(tok token.Token) bool {
125 	return tok.Type >= token.Void && tok.Type <= token.Any
126 }
127 
128-// --- Statement Parsing ---
129-
130 func (p *Parser) parseStmt() *ast.Node {
131 	tok := p.current
132 
133-	// Check for a label definition ("pinkFloyd:")
134 	isLabelAhead := false
135 	if p.peek().Type == token.Colon {
136 		if p.check(token.Ident) {
137 			isLabelAhead = true
138 		} else {
139-			// Check if the current token is any keyword
140 			for _, kwType := range token.KeywordMap {
141 				if p.check(kwType) {
142 					isLabelAhead = true
143@@ -265,7 +237,6 @@ func (p *Parser) parseStmt() *ast.Node {
144 		if p.check(token.Ident) {
145 			labelName = p.current.Value
146 		} else {
147-			// Find the keyword string for the current token type
148 			for kw, typ := range token.KeywordMap {
149 				if p.current.Type == typ {
150 					labelName = kw
151@@ -276,7 +247,6 @@ func (p *Parser) parseStmt() *ast.Node {
152 		p.advance() // consume label name
153 		p.advance() // consume ':'
154 		if p.check(token.RBrace) {
155-			// Handle empty labeled statement like `label: }`
156 			return ast.NewLabel(tok, labelName, ast.NewBlock(p.current, nil, true))
157 		}
158 		return ast.NewLabel(tok, labelName, p.parseStmt())
159@@ -334,12 +304,10 @@ func (p *Parser) parseStmt() *ast.Node {
160 		return ast.NewDefault(tok, body)
161 	case p.match(token.Goto):
162 		var labelName string
163-		// Check if the label is an identifier
164 		if p.check(token.Ident) {
165 			labelName = p.current.Value
166 			p.advance()
167 		} else {
168-			// Check if the label is a keyword
169 			isKeyword := false
170 			for kw, typ := range token.KeywordMap {
171 				if p.current.Type == typ {
172@@ -350,7 +318,6 @@ func (p *Parser) parseStmt() *ast.Node {
173 			}
174 			if !isKeyword {
175 				util.Error(p.current, "Expected label name after 'goto'.")
176-				// Synchronize to the next semicolon
177 				for !p.check(token.Semi) && !p.check(token.EOF) {
178 					p.advance()
179 				}
180@@ -368,7 +335,7 @@ func (p *Parser) parseStmt() *ast.Node {
181 		var expr *ast.Node
182 		if !p.check(token.Semi) {
183 			p.expect(token.LParen, "Expected '(' after 'return' with value.")
184-			if !p.check(token.RParen) { // Handles `return()` which is valid for returning 0
185+			if !p.check(token.RParen) {
186 				expr = p.parseExpr()
187 			}
188 			p.expect(token.RParen, "Expected ')' after return value.")
189@@ -432,8 +399,6 @@ func (p *Parser) parseBlockStmt() *ast.Node {
190 	return ast.NewBlock(tok, stmts, false)
191 }
192 
193-// --- Declaration Parsing ---
194-
195 func (p *Parser) parseDeclaration(hasAuto bool) *ast.Node {
196 	declTok := p.current
197 	if hasAuto {
198@@ -461,8 +426,6 @@ func (p *Parser) parseDeclaration(hasAuto bool) *ast.Node {
199 	}
200 
201 	if op != 0 {
202-		// This branch handles Bx-style declarations that MUST be initialized
203-		// e.g., `auto x = 1` or `x := 1`
204 		for {
205 			inits = append(inits, p.parseAssignmentExpr())
206 			if !p.match(token.Comma) {
207@@ -473,9 +436,7 @@ func (p *Parser) parseDeclaration(hasAuto bool) *ast.Node {
208 			util.Error(declTok, "Mismatched number of variables and initializers (%d vs %d)", len(names), len(inits))
209 		}
210 	} else {
211-		// This branch handles declarations that MIGHT be uninitialized
212-		// e.g., `auto x;` which is B syntax
213-		if p.cfg.IsFeatureEnabled(config.FeatStrictDecl) || !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
214+		if !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
215 			util.Error(declTok, "Uninitialized declaration is not allowed in this mode")
216 		}
217 	}
218@@ -487,8 +448,6 @@ func (p *Parser) parseDeclaration(hasAuto bool) *ast.Node {
219 			initList = append(initList, inits[i])
220 		}
221 		name := nameNode.Data.(ast.IdentNode).Name
222-		// Mark as a Bx-style `auto` or `:=` declaration by setting isDefine
223-		// The type checker will infer the type later
224 		decls = append(decls, ast.NewVarDecl(nameNode.Tok, name, ast.TypeUntyped, initList, nil, false, false, isDefine || op == token.Eq))
225 	}
226 
227@@ -514,11 +473,28 @@ func (p *Parser) parseUntypedDeclarationList(declType token.Type, declTok token.
228 		return ast.NewExtrnDecl(declTok, names)
229 	}
230 
231-	// This handles B `auto name;` and `auto name size;`
232 	var decls []*ast.Node
233 	for {
234-		p.expect(token.Ident, "Expected identifier in declaration.")
235-		name, itemToken := p.previous.Value, p.previous
236+		var name string
237+		var itemToken token.Token
238+
239+		if p.check(token.Ident) {
240+			itemToken = p.current
241+			name = p.current.Value
242+			p.advance()
243+		} else if p.check(token.TypeKeyword) {
244+			itemToken = p.current
245+			name = "type"
246+			util.Warn(p.cfg, config.WarnExtra, itemToken, "Using keyword 'type' as an identifier.")
247+			p.advance()
248+		} else {
249+			p.expect(token.Ident, "Expected identifier in declaration.")
250+			if p.check(token.Comma) || p.check(token.Semi) {
251+				continue
252+			}
253+			break
254+		}
255+
256 		var sizeExpr *ast.Node
257 		isVector, isBracketed := false, false
258 
259@@ -536,8 +512,8 @@ func (p *Parser) parseUntypedDeclarationList(declType token.Type, declTok token.
260 			sizeExpr = p.parsePrimaryExpr()
261 		}
262 
263-		if sizeExpr == nil && !isBracketed { // simple `auto name;`
264-			if p.cfg.IsFeatureEnabled(config.FeatStrictDecl) || !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
265+		if sizeExpr == nil && !isBracketed {
266+			if !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
267 				util.Error(itemToken, "Uninitialized declaration of '%s' is not allowed in this mode", name)
268 			}
269 		}
270@@ -560,7 +536,7 @@ func (p *Parser) parseUntypedGlobalDefinition(nameToken token.Token) *ast.Node {
271 	if p.isTypeName(name) {
272 		util.Error(nameToken, "Variable name '%s' shadows a type.", name)
273 	}
274-	p.advance() // Consume identifier
275+	p.advance()
276 
277 	var sizeExpr *ast.Node
278 	isVector, isBracketed := false, false
279@@ -592,7 +568,7 @@ func (p *Parser) parseUntypedGlobalDefinition(nameToken token.Token) *ast.Node {
280 	}
281 
282 	if len(initList) == 0 && sizeExpr == nil && !isBracketed {
283-		if p.cfg.IsFeatureEnabled(config.FeatStrictDecl) || !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
284+		if !p.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
285 			util.Error(nameToken, "Uninitialized declaration of '%s' is not allowed in this mode", name)
286 		}
287 	}
288@@ -635,7 +611,6 @@ func (p *Parser) parseFuncDecl(returnType *ast.BxType, nameToken token.Token) *a
289 	if p.check(token.LBrace) {
290 		body = p.parseBlockStmt()
291 	} else {
292-		// A single statement is a valid body. A semicolon is a null statement
293 		body = p.parseStmt()
294 	}
295 
296@@ -653,10 +628,9 @@ func (p *Parser) parseFuncDecl(returnType *ast.BxType, nameToken token.Token) *a
297 	}
298 
299 	if returnType == nil {
300+		returnType = ast.TypeUntyped
301 		if isTyped {
302 			returnType = ast.TypeInt
303-		} else {
304-			returnType = ast.TypeUntyped
305 		}
306 	}
307 
308@@ -692,13 +666,12 @@ func (p *Parser) parseAsmFuncDef(nameToken token.Token) *ast.Node {
309 	return ast.NewFuncDecl(nameToken, nameToken.Value, nil, body, false, false, nil)
310 }
311 
312-// --- Bx Type System Parsing ---
313-
314 func (p *Parser) parseTypeDecl() *ast.Node {
315 	typeTok := p.previous
316 	var underlyingType *ast.BxType
317 
318 	if p.check(token.Struct) {
319+		p.advance()
320 		underlyingType = p.parseStructDef()
321 	} else {
322 		util.Error(typeTok, "Expected 'struct' after 'type'.")
323@@ -731,7 +704,6 @@ func (p *Parser) parseTypedVarOrFuncDecl(isTopLevel bool) *ast.Node {
324 
325 	if p.match(token.Define) {
326 		util.Error(p.previous, "Cannot use ':=' in a typed declaration. Use '=' instead.")
327-		// Attempt to recover by treating it as '='
328 		return p.parseTypedVarDeclBody(startTok, declType, p.previous)
329 	}
330 
331@@ -816,7 +788,6 @@ func (p *Parser) parseType() *ast.BxType {
332 		} else if p.isBuiltinType(tok) {
333 			p.advance()
334 			var typeName string
335-			// Find the keyword string from the token type, since token.Value is empty for keywords
336 			for keyword, t := range token.KeywordMap {
337 				if t == p.previous.Type {
338 					typeName = keyword
339@@ -856,17 +827,14 @@ func (p *Parser) parseType() *ast.BxType {
340 	}
341 
342 	if isConst {
343-		// Create a new type so we don't modify a global like ast.TypeVoid
344 		newType := *baseType
345 		newType.IsConst = true
346 		return &newType
347 	}
348-
349 	return baseType
350 }
351 
352 func (p *Parser) parseStructDef() *ast.BxType {
353-	p.expect(token.Struct, "Expected 'struct' keyword.")
354 	structType := &ast.BxType{Kind: ast.TYPE_STRUCT}
355 
356 	if p.check(token.Ident) {
357@@ -880,12 +848,12 @@ func (p *Parser) parseStructDef() *ast.BxType {
358 	p.expect(token.LBrace, "Expected '{' to open struct definition.")
359 
360 	for !p.check(token.RBrace) && !p.check(token.EOF) {
361-		fieldDecl := p.parseTypedVarOrFuncDecl(false)
362-		if fieldDecl.Type == ast.MultiVarDecl {
363-			structType.Fields = append(structType.Fields, fieldDecl.Data.(ast.MultiVarDeclNode).Decls...)
364-		} else {
365-			structType.Fields = append(structType.Fields, fieldDecl)
366-		}
367+		p.expect(token.Ident, "Expected field name in struct.")
368+		nameToken := p.previous
369+		fieldType := p.parseType()
370+		fieldDecl := ast.NewVarDecl(nameToken, nameToken.Value, fieldType, nil, nil, false, false, false)
371+		structType.Fields = append(structType.Fields, fieldDecl)
372+		p.expect(token.Semi, "Expected ';' after struct field declaration.")
373 	}
374 
375 	p.expect(token.RBrace, "Expected '}' to close struct definition.")
376@@ -895,41 +863,32 @@ func (p *Parser) parseStructDef() *ast.BxType {
377 	return structType
378 }
379 
380-// --- Parameter List Parsing ---
381-
382 func (p *Parser) isTypedParameterList() bool {
383 	originalPos, originalCurrent := p.pos, p.current
384 	defer func() { p.pos, p.current = originalPos, originalCurrent }()
385 
386 	if p.check(token.RParen) {
387-		return false // An empty parameter list is untyped by default
388+		return false
389 	}
390 	if p.check(token.Void) && p.peek().Type == token.RParen {
391-		return true // `(void)` is explicitly typed
392+		return true
393 	}
394-
395-	// Heuristic: If the first item is a known type, it's a typed list
396 	if p.isBuiltinType(p.current) || p.isTypeName(p.current.Value) {
397 		return true
398 	}
399 
400-	// Lookahead to see if a type follows a name list
401-	// This is the ambiguous case: `(a, b, c int)` vs `(a, b, c)`
402 	for {
403 		if !p.check(token.Ident) {
404-			return false // Not a name, can't be an untyped list starting this way
405+			return false
406 		}
407 		p.advance()
408 		if !p.match(token.Comma) {
409 			break
410 		}
411 	}
412-
413-	// After a list of identifiers, is there a type?
414 	return p.isBuiltinType(p.current) || p.isTypeName(p.current.Value) || p.check(token.LBracket) || p.check(token.Star)
415 }
416 
417-
418 func (p *Parser) parseUntypedParameters() ([]*ast.Node, bool) {
419 	var params []*ast.Node
420 	var hasVarargs bool
421@@ -969,23 +928,19 @@ func (p *Parser) parseTypedParameters() ([]*ast.Node, bool) {
422 			break
423 		}
424 
425-		// Check for the `(int, float)` anonymous parameter case
426 		if p.isBuiltinType(p.current) || p.isTypeName(p.current.Value) {
427 			paramType := p.parseType()
428-			// Create a placeholder name for the anonymous parameter
429 			name := fmt.Sprintf("anonparam%d", len(params))
430 			paramNode := ast.NewVarDecl(p.previous, name, paramType, nil, nil, false, false, false)
431 			params = append(params, paramNode)
432 		} else {
433-			// Handle named parameters, potentially in a list `(a, b int)`
434 			var names []token.Token
435 			p.expect(token.Ident, "Expected parameter name.")
436 			names = append(names, p.previous)
437 			for p.match(token.Comma) {
438-				// If the next token looks like a type, we've finished the name list
439 				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) {
440 					p.pos--
441-					p.current = p.tokens[p.pos-1] // Rewind to before the comma
442+					p.current = p.tokens[p.pos-1]
443 					break
444 				}
445 				p.expect(token.Ident, "Expected parameter name.")
446@@ -993,7 +948,6 @@ func (p *Parser) parseTypedParameters() ([]*ast.Node, bool) {
447 			}
448 
449 			paramType := p.parseType()
450-
451 			for _, nameTok := range names {
452 				paramNode := ast.NewVarDecl(nameTok, nameTok.Value, paramType, nil, nil, false, false, false)
453 				params = append(params, paramNode)
454@@ -1007,9 +961,6 @@ func (p *Parser) parseTypedParameters() ([]*ast.Node, bool) {
455 	return params, hasVarargs
456 }
457 
458-
459-// --- Expression Parsing ---
460-
461 func getBinaryOpPrecedence(op token.Type) int {
462 	switch op {
463 	case token.OrOr:
464@@ -1160,6 +1111,10 @@ func (p *Parser) parsePrimaryExpr() *ast.Node {
465 	if p.match(token.Ident) {
466 		return ast.NewIdent(tok, p.previous.Value)
467 	}
468+	if p.match(token.TypeKeyword) {
469+		util.Warn(p.cfg, config.WarnExtra, p.previous, "Using keyword 'type' as an identifier.")
470+		return ast.NewIdent(tok, "type")
471+	}
472 	if p.match(token.LParen) {
473 		if p.isTypedPass && (p.isBuiltinType(p.current) || p.isTypeName(p.current.Value)) {
474 			castType := p.parseType()
475@@ -1189,8 +1144,6 @@ func (p *Parser) parsePrimaryExpr() *ast.Node {
476 	return nil
477 }
478 
479-// --- Switch/Case Handling ---
480-
481 func (p *Parser) buildSwitchJumpTable(switchNode *ast.Node) {
482 	if switchNode == nil || switchNode.Type != ast.Switch {
483 		return
M pkg/token/token.go
+50, -54
  1@@ -1,19 +1,17 @@
  2 package token
  3 
  4-// Type represents the type of a lexical token.
  5 type Type int
  6 
  7-// The list of all lexical tokens.
  8 const (
  9-	// Meta tokens
 10+	// Meta
 11 	EOF Type = iota
 12 	Comment
 13-	Directive // '// [b]: ...'
 14+	Directive
 15 
 16 	// Literals
 17-	Ident  // main
 18-	Number // 123, 0x7b, 0173
 19-	String // "A saucerful of secrets"
 20+	Ident
 21+	Number
 22+	String
 23 
 24 	// Keywords
 25 	Auto
 26@@ -30,11 +28,13 @@ const (
 27 	Continue
 28 	Asm // `__asm__`
 29 
 30-	// Bx Type System Keywords
 31-	Void
 32+	// Bx Type System Keywords that are not types themselves
 33 	TypeKeyword // 'type'
 34 	Struct
 35 	Const
 36+
 37+	// Bx Type System Keywords that ARE types
 38+	Void
 39 	Bool
 40 	Byte
 41 	Int
 42@@ -51,47 +51,45 @@ const (
 43 	Float32
 44 	Float64
 45 	StringKeyword // 'string'
 46-	Any           // For untyped, and symbols marked explicitely with any()
 47+	Any
 48 
 49 	// Punctuation
 50-	LParen   // (
 51-	RParen   // )
 52-	LBrace   // {
 53-	RBrace   // }
 54-	LBracket // [
 55-	RBracket // ]
 56-	Semi     // ;
 57-	Comma    // ,
 58-	Colon    // :
 59-	Question // ?
 60-	Dots     // ...
 61-	Dot      // .
 62-
 63-	// --- Operator Groups ---
 64+	LParen
 65+	RParen
 66+	LBrace
 67+	RBrace
 68+	LBracket
 69+	RBracket
 70+	Semi
 71+	Comma
 72+	Colon
 73+	Question
 74+	Dots
 75+	Dot
 76 
 77 	// Assignment Operators
 78-	Eq      // =
 79-	Define  // :=
 80-	PlusEq  // += (C-style)
 81-	MinusEq // -= (C-style)
 82-	StarEq  // *= (C-style)
 83-	SlashEq // /= (C-style)
 84-	RemEq   // %= (C-style)
 85-	AndEq   // &= (C-style)
 86-	OrEq    // |= (C-style)
 87-	XorEq   // ^= (C-style)
 88-	ShlEq   // <<= (C-style)
 89-	ShrEq   // >>= (C-style)
 90-	EqPlus  // =+ (B-style)
 91-	EqMinus // =- (B-style)
 92-	EqStar  // =* (B-style)
 93-	EqSlash // =/ (B-style)
 94-	EqRem   // =% (B-style)
 95-	EqAnd   // =& (B-style)
 96-	EqOr    // =| (B-style)
 97-	EqXor   // =^ (B-style)
 98-	EqShl   // =<< (B-style)
 99-	EqShr   // =>> (B-style)
100+	Eq
101+	Define
102+	PlusEq
103+	MinusEq
104+	StarEq
105+	SlashEq
106+	RemEq
107+	AndEq
108+	OrEq
109+	XorEq
110+	ShlEq
111+	ShrEq
112+	EqPlus
113+	EqMinus
114+	EqStar
115+	EqSlash
116+	EqRem
117+	EqAnd
118+	EqOr
119+	EqXor
120+	EqShl
121+	EqShr
122 
123 	// Binary Operators
124 	Plus
125@@ -110,17 +108,16 @@ const (
126 	Gt
127 	Gte
128 	Lte
129-	AndAnd // &&
130-	OrOr   // ||
131+	AndAnd
132+	OrOr
133 
134 	// Unary & Postfix Operators
135-	Not        // !
136-	Complement // ~
137-	Inc        // ++
138-	Dec        // --
139+	Not
140+	Complement
141+	Inc
142+	Dec
143 )
144 
145-// KeywordMap maps keyword strings to their corresponding token Type
146 var KeywordMap = map[string]Type{
147 	"auto":     Auto,
148 	"if":       If,
149@@ -158,7 +155,6 @@ var KeywordMap = map[string]Type{
150 	"any":      Any,
151 }
152 
153-// Token represents a single lexical unit from the source code
154 type Token struct {
155 	Type      Type
156 	Value     string
M pkg/typeChecker/typeChecker.go
+49, -24
  1@@ -1,4 +1,3 @@
  2-// Package typeChecker traverses the AST to ensure type compatibility
  3 package typeChecker
  4 
  5 import (
  6@@ -11,23 +10,20 @@ import (
  7 	"github.com/xplshn/gbc/pkg/util"
  8 )
  9 
 10-// Symbol represents an entry in the type checker's symbol table
 11 type Symbol struct {
 12 	Name   string
 13 	Type   *ast.BxType
 14 	IsFunc bool
 15-	IsType bool // True only for typedef statements
 16+	IsType bool
 17 	Node   *ast.Node
 18 	Next   *Symbol
 19 }
 20 
 21-// Scope represents a lexical scope
 22 type Scope struct {
 23 	Symbols *Symbol
 24 	Parent  *Scope
 25 }
 26 
 27-// TypeChecker holds the state for the type checking pass
 28 type TypeChecker struct {
 29 	currentScope *Scope
 30 	currentFunc  *ast.FuncDeclNode
 31@@ -37,7 +33,6 @@ type TypeChecker struct {
 32 	wordSize     int
 33 }
 34 
 35-// NewTypeChecker creates a new type checker
 36 func NewTypeChecker(cfg *config.Config) *TypeChecker {
 37 	globalScope := newScope(nil)
 38 	return &TypeChecker{
 39@@ -78,7 +73,7 @@ func (tc *TypeChecker) addSymbol(node *ast.Node) *Symbol {
 40 			}
 41 		}
 42 		return nil
 43-	case ast.IdentNode: // For untyped function parameters
 44+	case ast.IdentNode: // untyped function parameters
 45 		name, typ = d.Name, ast.TypeUntyped
 46 	default:
 47 		return nil
 48@@ -137,12 +132,10 @@ func (tc *TypeChecker) getSizeof(typ *ast.BxType) int64 {
 49 		return elemSize * arrayLen
 50 	case ast.TYPE_PRIMITIVE:
 51 		switch typ.Name {
 52-		case "int", "uint":
 53+		case "int", "uint", "string":
 54 			return int64(tc.wordSize)
 55 		case "int64", "uint64":
 56 			return 8
 57-		case "string":
 58-			return int64(tc.wordSize)
 59 		case "int32", "uint32":
 60 			return 4
 61 		case "int16", "uint16":
 62@@ -160,16 +153,12 @@ func (tc *TypeChecker) getSizeof(typ *ast.BxType) int64 {
 63 		for _, field := range typ.Fields {
 64 			totalSize += tc.getSizeof(field.Data.(ast.VarDeclNode).Type)
 65 		}
 66-
 67-		// NOTE: This does not account for alignment/padding within the struct
 68-		// For the current examples, this is fine as all members are pointers
 69-		// TODO: A more robust implementation is required to handle alignment
 70+		// NOTE: does not account for alignment/padding
 71 		return totalSize
 72 	}
 73 	return int64(tc.wordSize)
 74 }
 75 
 76-// Check is the main entry point for the type checking pass
 77 func (tc *TypeChecker) Check(root *ast.Node) {
 78 	if !tc.cfg.IsFeatureEnabled(config.FeatTyped) {
 79 		return
 80@@ -264,7 +253,6 @@ func (tc *TypeChecker) checkNode(node *ast.Node) {
 81 	case ast.ExtrnDecl:
 82 		tc.addSymbol(node)
 83 	case ast.TypeDecl, ast.Goto, ast.Break, ast.Continue, ast.AsmStmt, ast.Directive:
 84-		// No type checking needed
 85 	default:
 86 		if node.Type <= ast.TypeCast {
 87 			tc.checkExpr(node)
 88@@ -295,9 +283,9 @@ func (tc *TypeChecker) checkVarDecl(node *ast.Node) {
 89 	}
 90 	if tc.currentFunc != nil {
 91 		tc.addSymbol(node)
 92-	} // Add local vars to scope
 93+	}
 94 	if len(d.InitList) == 0 {
 95-		if (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) && (tc.cfg.IsFeatureEnabled(config.FeatStrictDecl) || !tc.cfg.IsFeatureEnabled(config.FeatAllowUninitialized)) {
 96+		if (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) && !tc.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
 97 			util.Error(node.Tok, "Uninitialized variable '%s' is not allowed in this mode", d.Name)
 98 		}
 99 		node.Typ = d.Type
100@@ -375,6 +363,19 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
101 	switch d := node.Data.(type) {
102 	case ast.AssignNode:
103 		lhsType, rhsType := tc.checkExpr(d.Lhs), tc.checkExpr(d.Rhs)
104+		if d.Lhs.Type == ast.Subscript {
105+			subscript := d.Lhs.Data.(ast.SubscriptNode)
106+			arrayExpr := subscript.Array
107+			if arrayExpr.Typ != nil && arrayExpr.Typ.Kind == ast.TYPE_POINTER && arrayExpr.Typ.Base.Kind == ast.TYPE_UNTYPED {
108+				arrayExpr.Typ.Base = rhsType
109+				lhsType = rhsType
110+				if arrayExpr.Type == ast.Ident {
111+					if sym := tc.findSymbol(arrayExpr.Data.(ast.IdentNode).Name, false); sym != nil {
112+						sym.Type = arrayExpr.Typ
113+					}
114+				}
115+			}
116+		}
117 		if lhsType.Kind == ast.TYPE_UNTYPED && d.Lhs.Type == ast.Ident {
118 			if sym := tc.findSymbol(d.Lhs.Data.(ast.IdentNode).Name, false); sym != nil {
119 				sym.Type, sym.IsFunc = rhsType, false
120@@ -395,10 +396,21 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
121 			resolvedOpType := tc.resolveType(operandType)
122 			if resolvedOpType.Kind == ast.TYPE_POINTER || resolvedOpType.Kind == ast.TYPE_ARRAY {
123 				typ = resolvedOpType.Base
124-			} else if resolvedOpType.Kind != ast.TYPE_UNTYPED {
125-				util.Error(node.Tok, "Cannot dereference non-pointer type '%s'", typeToString(operandType))
126-				typ = ast.TypeUntyped
127+			} else if resolvedOpType.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(resolvedOpType) {
128+				// An untyped or integer variable is being dereferenced.
129+				// Very common pattern in B. Promote it to a pointer to an untyped base
130+				promotedType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: ast.TypeUntyped}
131+				d.Expr.Typ = promotedType
132+				if d.Expr.Type == ast.Ident {
133+					if sym := tc.findSymbol(d.Expr.Data.(ast.IdentNode).Name, false); sym != nil {
134+						if sym.Type == nil || sym.Type.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(sym.Type) {
135+							sym.Type = promotedType
136+						}
137+					}
138+				}
139+				typ = promotedType.Base
140 			} else {
141+				util.Error(node.Tok, "Cannot dereference non-pointer type '%s'", typeToString(operandType))
142 				typ = ast.TypeUntyped
143 			}
144 		case token.And: // Address-of
145@@ -423,10 +435,23 @@ func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
146 		resolvedArrayType := tc.resolveType(arrayType)
147 		if resolvedArrayType.Kind == ast.TYPE_ARRAY || resolvedArrayType.Kind == ast.TYPE_POINTER {
148 			typ = resolvedArrayType.Base
149-		} else if resolvedArrayType.Kind != ast.TYPE_UNTYPED {
150-			util.Error(node.Tok, "Cannot subscript non-array/pointer type '%s'", typeToString(arrayType))
151-			typ = ast.TypeUntyped
152+		} else if resolvedArrayType.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(resolvedArrayType) {
153+			// An untyped or integer variable is being used as a pointer
154+			// Another super common pattern in B. Promote it to a pointer to an untyped base
155+			// The base type will be inferred from usage (e.g., assignment)
156+			promotedType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: ast.TypeUntyped}
157+			d.Array.Typ = promotedType
158+
159+			if d.Array.Type == ast.Ident {
160+				if sym := tc.findSymbol(d.Array.Data.(ast.IdentNode).Name, false); sym != nil {
161+					if sym.Type == nil || sym.Type.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(sym.Type) {
162+						sym.Type = promotedType
163+					}
164+				}
165+			}
166+			typ = promotedType.Base
167 		} else {
168+			util.Error(node.Tok, "Cannot subscript non-array/pointer type '%s'", typeToString(arrayType))
169 			typ = ast.TypeUntyped
170 		}
171 	case ast.MemberAccessNode: