- 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
+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"
+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 }
+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
+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=
+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 }
+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)
+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) {
+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)
+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
+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
+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: