repos / gbc

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

commit
5809671
parent
8d95dbc
author
xplshn
date
2025-08-25 03:27:24 +0000 UTC
Fix: lexing and parsing of directives. | Restructure CLI handling code

Signed-off-by: xplshn <[email protected]>
7 files changed,  +357, -303
M Makefile
+6, -6
 1@@ -2,9 +2,9 @@ OUT = gbc
 2 GTEST = gtest
 3 
 4 GO = go
 5-ifneq ($(shell command -v gore 2>/dev/null),)
 6-GO = gore
 7-endif
 8+#ifneq ($(shell command -v gore 2>/dev/null),)
 9+#GO = gore
10+#endif
11 
12 # `gore` is my wrapper for `go` that adds beautiful errors like this one:
13 #     # $ gore build
14@@ -45,7 +45,7 @@ ARCH := $(shell uname -m)
15 OS := $(shell uname -s)
16 LIBB := ./lib/b/$(ARCH)-$(OS).b
17 
18-badFiles := donut.b
19+badFiles := raylib.b donut.b
20 
21 define filter_files
22 files=""; \
23@@ -62,9 +62,9 @@ endef
24 test: all $(GTEST)
25 	@echo "Running tests..."
26 	@files=$$( $(call filter_files,tests/*.b*,tests) ); \
27-	./cmd/$(GTEST)/$(GTEST) --test-files="$$files" --target-args="$(LIBB)" -v
28+	./cmd/$(GTEST)/$(GTEST) --test-files="$$files" --target-args="$(GBCFLAGS) $(LIBB)" -v
29 
30 examples: all $(GTEST)
31 	@echo "Running examples..."
32 	@files=$$( $(call filter_files,examples/*.b*,examples) ); \
33-	./cmd/$(GTEST)/$(GTEST) --test-files="$$files" --target-args="$(LIBB)" -v --ignore-lines="xs_items"
34+	./cmd/$(GTEST)/$(GTEST) --test-files="$$files" --target-args="$(GBCFLAGS) $(LIBB)" -v --ignore-lines="xs_items"
M cmd/gbc/main.go
+67, -58
  1@@ -19,41 +19,6 @@ import (
  2 	"github.com/xplshn/gbc/pkg/util"
  3 )
  4 
  5-func setupFlags(cfg *config.Config, fs *cli.FlagSet) ([]cli.FlagGroupEntry, []cli.FlagGroupEntry) {
  6-	var warningFlags, featureFlags []cli.FlagGroupEntry
  7-
  8-	for i := config.Warning(0); i < config.WarnCount; i++ {
  9-		pEnable := new(bool)
 10-		*pEnable = cfg.Warnings[i].Enabled
 11-		pDisable := new(bool)
 12-		warningFlags = append(warningFlags, cli.FlagGroupEntry{
 13-			Name:     cfg.Warnings[i].Name,
 14-			Prefix:   "W",
 15-			Usage:    cfg.Warnings[i].Description,
 16-			Enabled:  pEnable,
 17-			Disabled: pDisable,
 18-		})
 19-	}
 20-
 21-	for i := config.Feature(0); i < config.FeatCount; i++ {
 22-		pEnable := new(bool)
 23-		*pEnable = cfg.Features[i].Enabled
 24-		pDisable := new(bool)
 25-		featureFlags = append(featureFlags, cli.FlagGroupEntry{
 26-			Name:     cfg.Features[i].Name,
 27-			Prefix:   "F",
 28-			Usage:    cfg.Features[i].Description,
 29-			Enabled:  pEnable,
 30-			Disabled: pDisable,
 31-		})
 32-	}
 33-
 34-	fs.AddFlagGroup("Warning Flags", "Enable or disable specific warnings", "warning flag", "Available Warning Flags:", warningFlags)
 35-	fs.AddFlagGroup("Feature Flags", "Enable or disable specific features", "feature flag", "Available feature flags:", featureFlags)
 36-
 37-	return warningFlags, featureFlags
 38-}
 39-
 40 func main() {
 41 	app := cli.NewApp("gbc")
 42 	app.Synopsis = "[options] <input.b> ..."
 43@@ -70,22 +35,29 @@ func main() {
 44 		compilerArgs     []string
 45 		userIncludePaths []string
 46 		libRequests      []string
 47+		pedantic         bool
 48 	)
 49 
 50 	fs := app.FlagSet
 51-	fs.String(&outFile, "output", "o", "a.out", "Place the output into <file>", "file")
 52-	fs.String(&std, "std", "", "Bx", "Specify language standard (B, Bx)", "std")
 53-	fs.String(&target, "target", "t", "qbe", "Set the backend and target ABI (e.g., llvm/x86_64-linux-musl)", "backend/target")
 54-	fs.List(&linkerArgs, "linker-arg", "", []string{}, "Pass an argument to the linker", "arg")
 55-	fs.List(&compilerArgs, "compiler-arg", "", []string{}, "Pass a compiler-specific argument (e.g., -C linker_args='-s')", "arg")
 56-	fs.List(&userIncludePaths, "include", "", []string{}, "Add a directory to the include path", "path")
 57+	fs.String(&outFile, "output", "o", "a.out", "Place the output into <file>.", "file")
 58+	fs.String(&target, "target", "t", "qbe", "Set the backend and target ABI.", "backend/target")
 59+	fs.List(&userIncludePaths, "include", "I", []string{}, "Add a directory to the include path.", "path")
 60+	fs.List(&linkerArgs, "linker-arg", "L", []string{}, "Pass an argument to the linker.", "arg")
 61+	fs.List(&compilerArgs, "compiler-arg", "C", []string{}, "Pass a compiler-specific argument (e.g., -C linker_args='-s').", "arg")
 62 	fs.Special(&libRequests, "l", "Link with a library (e.g., -lb for 'b')", "lib")
 63+	fs.String(&std, "std", "", "Bx", "Specify language standard (B, Bx)", "std")
 64+	fs.Bool(&pedantic, "pedantic", "", false, "Issue all warnings demanded by the current B std.")
 65 
 66 	cfg := config.NewConfig()
 67-	warningFlags, featureFlags := setupFlags(cfg, fs)
 68+	warningFlags, featureFlags := cfg.SetupFlagGroups(fs)
 69 
 70 	// Actual compilation pipeline
 71 	app.Action = func(inputFiles []string) error {
 72+		// Handle pedantic flag first, as it can affect other settings.
 73+		if pedantic {
 74+			cfg.SetWarning(config.WarnPedantic, true)
 75+		}
 76+
 77 		// Apply warning flag updates to config
 78 		for i, entry := range warningFlags {
 79 			if entry.Enabled != nil && *entry.Enabled {
 80@@ -114,33 +86,51 @@ func main() {
 81 		// Set target, defaulting to the host if not specified
 82 		cfg.SetTarget(runtime.GOOS, runtime.GOARCH, target)
 83 
 84-		// Process compiler arguments for linker args
 85+		// Populate config from parsed command-line flags
 86+		cfg.LinkerArgs = append(cfg.LinkerArgs, linkerArgs...)
 87+		cfg.LibRequests = append(cfg.LibRequests, libRequests...)
 88+		cfg.UserIncludePaths = append(cfg.UserIncludePaths, userIncludePaths...)
 89+
 90+		// Process compiler-specific arguments (-C)
 91 		for _, carg := range compilerArgs {
 92 			if parts := strings.SplitN(carg, "=", 2); len(parts) == 2 && parts[0] == "linker_args" {
 93-				linkerArgs = append(linkerArgs, strings.Fields(parts[1])...)
 94+				parsedArgs, err := config.ParseCLIString(parts[1])
 95+				if err != nil {
 96+					util.Error(token.Token{}, "invalid -C linker_args value: %v", err)
 97+				}
 98+				cfg.LinkerArgs = append(cfg.LinkerArgs, parsedArgs...)
 99 			}
100 		}
101 
102-		// Process input files, searching for libraries based on the target configuration
103-		finalInputFiles := processInputFiles(inputFiles, libRequests, userIncludePaths, cfg)
104+		// PASS 1: Tokenize and parse initial files to process directives.
105+		fmt.Println("----------------------")
106+		fmt.Println("Pass 1: Scanning for directives...")
107+		records, allTokens := readAndTokenizeFiles(inputFiles, cfg)
108+		util.SetSourceFiles(records)
109+		p := parser.NewParser(allTokens, cfg)
110+		p.Parse() // This populates cfg with directive info.
111+
112+		// Now that all directives are processed, determine the final list of source files.
113+		finalInputFiles := processInputFiles(inputFiles, cfg)
114 		if len(finalInputFiles) == 0 {
115 			util.Error(token.Token{}, "no input files specified.")
116 		}
117 
118-		fmt.Println("----------------------")
119+		// PASS 2: Re-tokenize and parse the complete set of files for compilation.
120+		fmt.Println("Pass 2: Compiling all source files...")
121 		isTyped := cfg.IsFeatureEnabled(config.FeatTyped)
122 		fmt.Printf("Tokenizing %d source file(s) (Typed Pass: %v)...\n", len(finalInputFiles), isTyped)
123-		records, allTokens := readAndTokenizeFiles(finalInputFiles, cfg)
124-		util.SetSourceFiles(records)
125+		fullRecords, fullTokens := readAndTokenizeFiles(finalInputFiles, cfg)
126+		util.SetSourceFiles(fullRecords)
127 
128 		fmt.Println("Parsing tokens into AST...")
129-		p := parser.NewParser(allTokens, cfg)
130-		astRoot := p.Parse()
131+		fullParser := parser.NewParser(fullTokens, cfg)
132+		astRoot := fullParser.Parse()
133 
134 		fmt.Println("Constant folding...")
135 		astRoot = ast.FoldConstants(astRoot)
136 
137-		if isTyped {
138+		if cfg.IsFeatureEnabled(config.FeatTyped) { // Re-check after directives
139 			fmt.Println("Type checking...")
140 			tc := typeChecker.NewTypeChecker(cfg)
141 			tc.Check(astRoot)
142@@ -158,7 +148,7 @@ func main() {
143 		}
144 
145 		fmt.Printf("Assembling and linking to create '%s'...\n", outFile)
146-		if err := assembleAndLink(outFile, backendOutput.String(), inlineAsm, linkerArgs); err != nil {
147+		if err := assembleAndLink(outFile, backendOutput.String(), inlineAsm, cfg.LinkerArgs); err != nil {
148 			util.Error(token.Token{}, "assembler/linker failed: %v", err)
149 		}
150 
151@@ -172,11 +162,27 @@ func main() {
152 	}
153 }
154 
155-func processInputFiles(args []string, libRequests []string, userPaths []string, cfg *config.Config) []string {
156+func processInputFiles(args []string, cfg *config.Config) []string {
157+	// Use a map to avoid duplicate library entries
158+	uniqueLibs := make(map[string]bool)
159+	for _, lib := range cfg.LibRequests {
160+		uniqueLibs[lib] = true
161+	}
162+
163 	inputFiles := args
164-	for _, libName := range libRequests {
165-		if libPath := findLibrary(libName, userPaths, cfg); libPath != "" {
166-			inputFiles = append(inputFiles, libPath)
167+	for libName := range uniqueLibs {
168+		if libPath := findLibrary(libName, cfg.UserIncludePaths, cfg); libPath != "" {
169+			// Avoid adding the same library file path multiple times
170+			found := false
171+			for _, inFile := range inputFiles {
172+				if inFile == libPath {
173+					found = true
174+					break
175+				}
176+			}
177+			if !found {
178+				inputFiles = append(inputFiles, libPath)
179+			}
180 		} else {
181 			util.Error(token.Token{}, "could not find library '%s' for target %s/%s", libName, cfg.GOOS, cfg.GOARCH)
182 		}
183@@ -214,6 +220,7 @@ func findLibrary(libName string, userPaths []string, cfg *config.Config) string
184 			}
185 		}
186 	}
187+	util.Error(token.Token{}, "could not find library '%s'", libName)
188 	return ""
189 }
190 
191@@ -276,7 +283,9 @@ func readAndTokenizeFiles(paths []string, cfg *config.Config) ([]util.SourceFile
192 		}
193 	}
194 	finalFileIndex := 0
195-	if len(paths) > 0 { finalFileIndex = len(paths) - 1 }
196+	if len(paths) > 0 {
197+		finalFileIndex = len(paths) - 1
198+	}
199 	allTokens = append(allTokens, token.Token{Type: token.EOF, FileIndex: finalFileIndex})
200 	return records, allTokens
201 }
M examples/game_of_b.b
+4, -3
 1@@ -1,5 +1,6 @@
 2 // To compile this example you need to pass appropriate linker flags to the b compiler:
 3 // $ b 65_game_of_b.b -L -lncurses -L -lpanel -run
 4+// [b]: requires: -lb -C linker_args='-lncurses -lpanel'
 5 
 6 TRUE;
 7 FALSE;
 8@@ -18,7 +19,7 @@ world_win;
 9 info_panel;
10 speeds;
11 
12-int32(array, i) {
13+_int32(array, i) {
14     extrn memcpy;
15     auto val;
16     val = 0;
17@@ -243,8 +244,8 @@ main() {
18         } else if(input == 0x199) { // KEY_MOUSE
19             if (getmouse(mouse_event) == 0) { // OK
20                 auto x, y;
21-                x = int32(mouse_event, 1) / 2;
22-                y = int32(mouse_event, 2);
23+                x = _int32(mouse_event, 1) / 2;
24+                y = _int32(mouse_event, 2);
25                 set_alive(cur_buf, y, x, !is_alive(cur_buf, y, x));
26                 redraw = 1;
27             }
M pkg/cli/cli.go
+25, -9
 1@@ -1,4 +1,4 @@
 2-// package cli is ugly and tries but fails miserable at being a general-purpose, usable CLi library
 3+// package cli is ugly and tries but fails miserably at being a general-purpose, usable CLi library
 4 package cli
 5 
 6 import (
 7@@ -152,17 +152,21 @@ func (f *FlagSet) Special(p *[]string, prefix, usage, expectedType string) {
 8 	f.specialPrefix[prefix] = f.flags[prefix]
 9 }
10 
11-func (f *FlagSet) AddFlagGroup(name, description, groupType, availableFlagsHeader string, entries []FlagGroupEntry) {
12+// DefineGroupFlags registers the enable/disable flags for a set of flag group entries.
13+func (f *FlagSet) DefineGroupFlags(entries []FlagGroupEntry) {
14 	for i := range entries {
15 		if entries[i].Enabled != nil {
16 			f.Bool(entries[i].Enabled, entries[i].Prefix+entries[i].Name, "", *entries[i].Enabled, entries[i].Usage)
17 		}
18 		if entries[i].Disabled != nil {
19-			// Automatically generate the 'disable' usage message. Unreliable and bad.
20 			disableUsage := "Disable '" + entries[i].Name + "'"
21 			f.Bool(entries[i].Disabled, entries[i].Prefix+"no-"+entries[i].Name, "", *entries[i].Disabled, disableUsage)
22 		}
23 	}
24+}
25+
26+func (f *FlagSet) AddFlagGroup(name, description, groupType, availableFlagsHeader string, entries []FlagGroupEntry) {
27+	f.DefineGroupFlags(entries)
28 	f.flagGroups = append(f.flagGroups, FlagGroup{
29 		Name:                 name,
30 		Description:          description,
31@@ -173,9 +177,13 @@ func (f *FlagSet) AddFlagGroup(name, description, groupType, availableFlagsHeade
32 }
33 
34 func (f *FlagSet) Var(value Value, name, shorthand, usage, defValue, expectedType string) {
35-	if name == "" { panic("flag name cannot be empty") }
36+	if name == "" {
37+		panic("flag name cannot be empty")
38+	}
39 	flag := &Flag{Name: name, Shorthand: shorthand, Usage: usage, Value: value, DefValue: defValue, ExpectedType: expectedType}
40-	if _, ok := f.flags[name]; ok { panic(fmt.Sprintf("flag redefined: %s", name)) }
41+	if _, ok := f.flags[name]; ok {
42+		panic(fmt.Sprintf("flag redefined: %s", name))
43+	}
44 	f.flags[name] = flag
45 	if shorthand != "" {
46 		if _, ok := f.shorthands[shorthand]; ok {
47@@ -528,15 +536,23 @@ func (a *App) formatFlagGroup(sb *strings.Builder, group FlagGroup, indent *Inde
48 
49 func getTerminalWidth() int {
50 	width, _, err := term.GetSize(int(os.Stdout.Fd()))
51-	if err != nil { return 80 }
52-	if width < 20 { return 20 }
53+	if err != nil {
54+		return 80
55+	}
56+	if width < 20 {
57+		return 20
58+	}
59 	return width
60 }
61 
62 func wrapText(text string, maxWidth int) []string {
63-	if maxWidth <= 0 { return []string{text} }
64+	if maxWidth <= 0 {
65+		return []string{text}
66+	}
67 	words := strings.Fields(text)
68-	if len(words) == 0 { return []string{} }
69+	if len(words) == 0 {
70+		return []string{}
71+	}
72 
73 	var lines []string
74 	var currentLine strings.Builder
M pkg/config/config.go
+179, -75
  1@@ -1,11 +1,13 @@
  2 package config
  3 
  4 import (
  5+	"errors"
  6 	"fmt"
  7 	"os"
  8 	"strings"
  9 
 10 	"github.com/xplshn/gbc/pkg/cli"
 11+	"github.com/xplshn/gbc/pkg/token"
 12 	"modernc.org/libqbe"
 13 )
 14 
 15@@ -91,54 +93,60 @@ var archProperties = map[string]struct {
 16 }
 17 
 18 type Config struct {
 19-	Features   map[Feature]Info
 20-	Warnings   map[Warning]Info
 21-	FeatureMap map[string]Feature
 22-	WarningMap map[string]Warning
 23-	StdName    string
 24+	Features         map[Feature]Info
 25+	Warnings         map[Warning]Info
 26+	FeatureMap       map[string]Feature
 27+	WarningMap       map[string]Warning
 28+	StdName          string
 29 	Target
 30+	LinkerArgs       []string
 31+	LibRequests      []string
 32+	UserIncludePaths []string
 33 }
 34 
 35 func NewConfig() *Config {
 36 	cfg := &Config{
 37-		Features:   make(map[Feature]Info),
 38-		Warnings:   make(map[Warning]Info),
 39-		FeatureMap: make(map[string]Feature),
 40-		WarningMap: make(map[string]Warning),
 41+		Features:         make(map[Feature]Info),
 42+		Warnings:         make(map[Warning]Info),
 43+		FeatureMap:       make(map[string]Feature),
 44+		WarningMap:       make(map[string]Warning),
 45+		LinkerArgs:       make([]string, 0),
 46+		LibRequests:      make([]string, 0),
 47+		UserIncludePaths: make([]string, 0),
 48 	}
 49 
 50 	features := map[Feature]Info{
 51-		FeatExtrn:              {"extrn",               true, "Allow the 'extrn' keyword."},
 52-		FeatAsm:                {"asm",                 true, "Allow `__asm__` blocks for inline assembly."},
 53-		FeatBEsc:               {"b-esc",               false,"Recognize B-style '*' character escapes."},
 54-		FeatCEsc:               {"c-esc",               true, "Recognize C-style '\\' character escapes."},
 55-		FeatBOps:               {"b-ops",               false,"Recognize B-style assignment operators like '=+'."},
 56-		FeatCOps:               {"c-ops",               true, "Recognize C-style assignment operators like '+='."},
 57-		FeatCComments:          {"c-comments",          true, "Recognize C-style '//' line comments."},
 58-		FeatTyped:              {"typed",               true, "Enable the Bx opt-in & backwards-compatible type system."},
 59-		FeatShortDecl:          {"short-decl",          true, "Enable Bx-style short declaration `:=`."},
 60-		FeatBxDeclarations:     {"bx-decl",             true, "Enable Bx-style `auto name = val` declarations."},
 61+		FeatExtrn:              {"extrn", true, "Allow the 'extrn' keyword."},
 62+		FeatAsm:                {"asm", true, "Allow `__asm__` blocks for inline assembly."},
 63+		FeatBEsc:               {"b-esc", false, "Recognize B-style '*' character escapes."},
 64+		FeatCEsc:               {"c-esc", true, "Recognize C-style '\\' character escapes."},
 65+		FeatBOps:               {"b-ops", false, "Recognize B-style assignment operators like '=+'."},
 66+		FeatCOps:               {"c-ops", true, "Recognize C-style assignment operators like '+='."},
 67+		FeatCComments:          {"c-comments", true, "Recognize C-style '//' line comments."},
 68+		FeatTyped:              {"typed", true, "Enable the Bx opt-in & backwards-compatible type system."},
 69+		FeatShortDecl:          {"short-decl", true, "Enable Bx-style short declaration `:=`."},
 70+		FeatBxDeclarations:     {"bx-decl", true, "Enable Bx-style `auto name = val` declarations."},
 71 		FeatAllowUninitialized: {"allow-uninitialized", true, "Allow declarations without an initializer (`var;` or `auto var;`)."},
 72-		FeatStrictDecl:         {"strict-decl",         false,"Require all declarations to be initialized."},
 73-		FeatContinue:           {"continue",            true, "Allow the Bx keyword `continue` to be used."},
 74-		FeatNoDirectives:       {"no-directives",       false,"Disable `// [b]:` directives."},
 75+		FeatStrictDecl:         {"strict-decl", false, "Require all declarations to be initialized."},
 76+		FeatContinue:           {"continue", true, "Allow the Bx keyword `continue` to be used."},
 77+		FeatNoDirectives:       {"no-directives", false, "Disable `// [b]:` directives."},
 78 	}
 79 
 80 	warnings := map[Warning]Info{
 81-		WarnCEsc:               {"c-esc",           false,"Warn on usage of C-style '\\' escapes."},
 82-		WarnBEsc:               {"b-esc",           true, "Warn on usage of B-style '*' escapes."},
 83-		WarnBOps:               {"b-ops",           true, "Warn on usage of B-style assignment operators like '=+'."},
 84-		WarnCOps:               {"c-ops",           false,"Warn on usage of C-style assignment operators like '+='."},
 85-		WarnUnrecognizedEscape: {"u-esc",           true, "Warn on unrecognized character escape sequences."},
 86-		WarnTruncatedChar:      {"truncated-char",  true, "Warn when a character escape value is truncated."},
 87+		WarnCEsc:               {"c-esc", false, "Warn on usage of C-style '\\' escapes."},
 88+		WarnBEsc:               {"b-esc", true, "Warn on usage of B-style '*' escapes."},
 89+		WarnBOps:               {"b-ops", true, "Warn on usage of B-style assignment operators like '=+'."},
 90+		WarnCOps:               {"c-ops", false, "Warn on usage of C-style assignment operators like '+='."},
 91+		WarnUnrecognizedEscape: {"u-esc", true, "Warn on unrecognized character escape sequences."},
 92+		WarnTruncatedChar:      {"truncated-char", true, "Warn when a character escape value is truncated."},
 93 		WarnLongCharConst:      {"long-char-const", true, "Warn when a multi-character constant is too long for a word."},
 94-		WarnCComments:          {"c-comments",      false,"Warn on usage of non-standard C-style '//' comments."},
 95-		WarnOverflow:           {"overflow",        true, "Warn when an integer constant is out of range for its type."},
 96-		WarnPedantic:           {"pedantic",        false,"Issue all warnings demanded by the strict standard."},
 97-		WarnUnreachableCode:    {"unreachable-code",true, "Warn about code that will never be executed."},
 98-		WarnImplicitDecl:       {"implicit-decl",   true, "Warn about implicit function or variable declarations."},
 99-		WarnType:               {"type",            true, "Warn about type mismatches in expressions and assignments."},
100-		WarnExtra:              {"extra",           true,  "Enable extra miscellaneous warnings."},
101+		WarnCComments:          {"c-comments", false, "Warn on usage of non-standard C-style '//' comments."},
102+		WarnOverflow:           {"overflow", true, "Warn when an integer constant is out of range for its type."},
103+		WarnPedantic:           {"pedantic", false, "Issue all warnings demanded by the strict standard."},
104+		WarnUnreachableCode:    {"unreachable-code", true, "Warn about code that will never be executed."},
105+		WarnImplicitDecl:       {"implicit-decl", true, "Warn about implicit function or variable declarations."},
106+		WarnType:               {"type", true, "Warn about type mismatches in expressions and assignments."},
107+		WarnExtra:              {"extra", true, "Enable extra miscellaneous warnings."},
108 	}
109 
110 	cfg.Features, cfg.Warnings = features, warnings
111@@ -188,7 +196,9 @@ func (c *Config) SetTarget(hostOS, hostArch, targetFlag string) {
112 	} else { // llvm
113 		if c.BackendTarget == "" {
114 			tradArch := archTranslations[hostArch]
115-			if tradArch == "" { tradArch = hostArch } // No target architecture specified
116+			if tradArch == "" {
117+				tradArch = hostArch
118+			} // No target architecture specified
119 			// TODO: ? Infer env ("musl", "gnu", etc..?)
120 			c.BackendTarget = fmt.Sprintf("%s-unknown-%s-unknown", tradArch, hostOS)
121 			fmt.Fprintf(os.Stderr, "gbc: info: no target specified, defaulting to host target '%s' for backend '%s'\n", c.BackendTarget, c.BackendName)
122@@ -286,63 +296,157 @@ func (c *Config) ApplyStd(stdName string) error {
123 	return nil
124 }
125 
126-// ProcessDirectiveFlags parses flags from a directive string using a temporary FlagSet.
127-func (c *Config) ProcessDirectiveFlags(flagStr string) {
128-	fs := cli.NewFlagSet("directive")
129+// SetupFlagGroups populates a FlagSet with warning and feature flag groups
130+// and returns the corresponding entry slices for processing results.
131+func (c *Config) SetupFlagGroups(fs *cli.FlagSet) ([]cli.FlagGroupEntry, []cli.FlagGroupEntry) {
132 	var warningFlags, featureFlags []cli.FlagGroupEntry
133 
134-	// Build warning flags for the directive parser.
135 	for i := Warning(0); i < WarnCount; i++ {
136 		pEnable := new(bool)
137+		*pEnable = c.Warnings[i].Enabled
138 		pDisable := new(bool)
139-		entry := cli.FlagGroupEntry{Name: c.Warnings[i].Name, Prefix: "W", Enabled: pEnable, Disabled: pDisable}
140-		warningFlags = append(warningFlags, entry)
141-		fs.Bool(entry.Enabled, entry.Prefix+entry.Name, "", false, "")
142-		fs.Bool(entry.Disabled, entry.Prefix+"no-"+entry.Name, "", false, "")
143+		warningFlags = append(warningFlags, cli.FlagGroupEntry{
144+			Name:     c.Warnings[i].Name,
145+			Prefix:   "W",
146+			Usage:    c.Warnings[i].Description,
147+			Enabled:  pEnable,
148+			Disabled: pDisable,
149+		})
150 	}
151 
152-	// Build feature flags for the directive parser.
153 	for i := Feature(0); i < FeatCount; i++ {
154 		pEnable := new(bool)
155+		*pEnable = c.Features[i].Enabled
156 		pDisable := new(bool)
157-		entry := cli.FlagGroupEntry{Name: c.Features[i].Name, Prefix: "F", Enabled: pEnable, Disabled: pDisable}
158-		featureFlags = append(featureFlags, entry)
159-		fs.Bool(entry.Enabled, entry.Prefix+entry.Name, "", false, "")
160-		fs.Bool(entry.Disabled, entry.Prefix+"no-"+entry.Name, "", false, "")
161+		featureFlags = append(featureFlags, cli.FlagGroupEntry{
162+			Name:     c.Features[i].Name,
163+			Prefix:   "F",
164+			Usage:    c.Features[i].Description,
165+			Enabled:  pEnable,
166+			Disabled: pDisable,
167+		})
168 	}
169 
170-	// The cli parser expects arguments to start with '-'.
171-	args := strings.Fields(flagStr)
172-	processedArgs := make([]string, len(args))
173-	for i, arg := range args {
174-		if !strings.HasPrefix(arg, "-") {
175-			processedArgs[i] = "-" + arg
176-		} else {
177-			processedArgs[i] = arg
178+	fs.AddFlagGroup("Warning Flags", "Enable or disable specific warnings", "warning flag", "Available Warning Flags:", warningFlags)
179+	fs.AddFlagGroup("Feature Flags", "Enable or disable specific features", "feature flag", "Available feature flags:", featureFlags)
180+
181+	return warningFlags, featureFlags
182+}
183+
184+// ParseCLIString splits a string into arguments, respecting single quotes.
185+func ParseCLIString(s string) ([]string, error) {
186+	var args []string
187+	var current strings.Builder
188+	inQuote := false
189+	for _, r := range s {
190+		switch {
191+		case r == '\'':
192+			inQuote = !inQuote
193+		case r == ' ' && !inQuote:
194+			if current.Len() > 0 {
195+				args = append(args, current.String())
196+				current.Reset()
197+			}
198+		default:
199+			current.WriteRune(r)
200 		}
201 	}
202-
203-	if err := fs.Parse(processedArgs); err != nil {
204-		// Silently ignore errors in directives, as they shouldn't halt compilation.
205-		return
206+	if inQuote {
207+		return nil, errors.New("unterminated single quote in argument string")
208 	}
209+	if current.Len() > 0 {
210+		args = append(args, current.String())
211+	}
212+	return args, nil
213+}
214 
215-	// Apply parsed directive flags to the configuration.
216-	for i, entry := range warningFlags {
217-		if *entry.Enabled {
218-			c.SetWarning(Warning(i), true)
219-		}
220-		if *entry.Disabled {
221-			c.SetWarning(Warning(i), false)
222+// ProcessArgs parses a slice of command-line style arguments and updates the configuration.
223+func (c *Config) ProcessArgs(args []string) error {
224+	for i := 0; i < len(args); i++ {
225+		arg := args[i]
226+		switch {
227+		case strings.HasPrefix(arg, "-l"):
228+			c.LibRequests = append(c.LibRequests, strings.TrimPrefix(arg, "-l"))
229+		case strings.HasPrefix(arg, "-L"):
230+			val := strings.TrimPrefix(arg, "-L")
231+			if val == "" { // Space separated: -L <val>
232+				if i+1 >= len(args) {
233+					return fmt.Errorf("missing argument for flag: %s", arg)
234+				}
235+				i++
236+				val = args[i]
237+			}
238+			c.LinkerArgs = append(c.LinkerArgs, "-L"+val)
239+		case strings.HasPrefix(arg, "-I"):
240+			val := strings.TrimPrefix(arg, "-I")
241+			if val == "" { // Space separated: -I <val>
242+				if i+1 >= len(args) {
243+					return fmt.Errorf("missing argument for flag: %s", arg)
244+				}
245+				i++
246+				val = args[i]
247+			}
248+			c.UserIncludePaths = append(c.UserIncludePaths, val)
249+		case strings.HasPrefix(arg, "-C"):
250+			val := strings.TrimPrefix(arg, "-C")
251+			if val == "" { // Space separated: -C <val>
252+				if i+1 >= len(args) {
253+					return fmt.Errorf("missing argument for flag: %s", arg)
254+				}
255+				i++
256+				val = args[i]
257+			}
258+			if parts := strings.SplitN(val, "=", 2); len(parts) == 2 && parts[0] == "linker_args" {
259+				linkerArgs, err := ParseCLIString(parts[1])
260+				if err != nil {
261+					return fmt.Errorf("failed to parse linker_args: %w", err)
262+				}
263+				c.LinkerArgs = append(c.LinkerArgs, linkerArgs...)
264+			}
265+		case strings.HasPrefix(arg, "-W"):
266+			flagName := strings.TrimPrefix(arg, "-W")
267+			if strings.HasPrefix(flagName, "no-") {
268+				warnName := strings.TrimPrefix(flagName, "no-")
269+				if wt, ok := c.WarningMap[warnName]; ok {
270+					c.SetWarning(wt, false)
271+				} else {
272+					return fmt.Errorf("unknown warning flag: %s", arg)
273+				}
274+			} else {
275+				if wt, ok := c.WarningMap[flagName]; ok {
276+					c.SetWarning(wt, true)
277+				} else {
278+					return fmt.Errorf("unknown warning flag: %s", arg)
279+				}
280+			}
281+		case strings.HasPrefix(arg, "-F"):
282+			flagName := strings.TrimPrefix(arg, "-F")
283+			if strings.HasPrefix(flagName, "no-") {
284+				featName := strings.TrimPrefix(flagName, "no-")
285+				if ft, ok := c.FeatureMap[featName]; ok {
286+					c.SetFeature(ft, false)
287+				} else {
288+					return fmt.Errorf("unknown feature flag: %s", arg)
289+				}
290+			} else {
291+				if ft, ok := c.FeatureMap[flagName]; ok {
292+					c.SetFeature(ft, true)
293+				} else {
294+					return fmt.Errorf("unknown feature flag: %s", arg)
295+				}
296+			}
297+		default:
298+			return fmt.Errorf("unrecognized argument: %s", arg)
299 		}
300 	}
301+	return nil
302+}
303 
304-	for i, entry := range featureFlags {
305-		if *entry.Enabled {
306-			c.SetFeature(Feature(i), true)
307-		}
308-		if *entry.Disabled {
309-			c.SetFeature(Feature(i), false)
310-		}
311+// ProcessDirectiveFlags parses flags from a directive string.
312+func (c *Config) ProcessDirectiveFlags(flagStr string, tok token.Token) error {
313+	args, err := ParseCLIString(flagStr)
314+	if err != nil {
315+		return err
316 	}
317+	return c.ProcessArgs(args)
318 }
M pkg/lexer/lexer.go
+28, -30
 1@@ -34,9 +34,20 @@ func (l *Lexer) Next() token.Token {
 2 			return l.makeToken(token.EOF, "", startPos, startCol, startLine)
 3 		}
 4 
 5-		if !l.cfg.IsFeatureEnabled(config.FeatNoDirectives) && l.peek() == '/' && l.peekNext() == '/' {
 6-			if tok, isDirective := l.lineCommentOrDirective(startPos, startCol, startLine); isDirective {
 7-				return tok
 8+		// Handle directives and line comments
 9+		if l.peek() == '/' && l.peekNext() == '/' {
10+			// Try parsing as a directive first. We consume the line
11+			// if it's a directive, but reset the position if it's not
12+			if !l.cfg.IsFeatureEnabled(config.FeatNoDirectives) {
13+				if tok, isDirective := l.lineCommentOrDirective(startPos, startCol, startLine); isDirective {
14+					return tok
15+				}
16+			}
17+
18+			// If not a directive, treat as a regular C-style comment
19+			if l.cfg.IsFeatureEnabled(config.FeatCComments) {
20+				l.lineComment()
21+				continue // Loop to find the next actual token
22 			}
23 		}
24 
25@@ -169,10 +180,8 @@ func (l *Lexer) skipWhitespaceAndComments() {
26 		case '/':
27 			if l.peekNext() == '*' {
28 				l.blockComment()
29-			} else if l.peekNext() == '/' && l.cfg.IsFeatureEnabled(config.FeatCComments) {
30-				l.lineComment()
31 			} else {
32-				return
33+				return // Next() handles `//` comments
34 			}
35 		default:
36 			return
37@@ -213,10 +222,11 @@ func (l *Lexer) lineCommentOrDirective(startPos, startCol, startLine int) (token
38 	trimmedContent := strings.TrimSpace(commentContent)
39 
40 	if strings.HasPrefix(trimmedContent, "[b]:") {
41-		directiveContent := strings.TrimSpace(trimmedContent[4:])
42+		directiveContent := strings.TrimSpace(strings.TrimPrefix(trimmedContent, "[b]:"))
43 		return l.makeToken(token.Directive, directiveContent, startPos, startCol, startLine), true
44 	}
45 
46+	// It's not a directive, so reset the lexer's position to before the '//'
47 	l.pos, l.column, l.line = preCommentPos, preCommentCol, preCommentLine
48 	return token.Token{}, false
49 }
50@@ -385,31 +395,19 @@ func (l *Lexer) greater(sPos, sCol, sLine int) token.Token {
51 }
52 
53 func (l *Lexer) equal(sPos, sCol, sLine int) token.Token {
54-	if l.match('=') {
55-		return l.makeToken(token.EqEq, "", sPos, sCol, sLine)
56-	}
57+	if l.match('=') { return l.makeToken(token.EqEq, "", sPos, sCol, sLine) }
58 	if l.cfg.IsFeatureEnabled(config.FeatBOps) {
59 		switch {
60-		case l.match('+'):
61-			return l.makeToken(token.EqPlus, "", sPos, sCol, sLine)
62-		case l.match('-'):
63-			return l.makeToken(token.EqMinus, "", sPos, sCol, sLine)
64-		case l.match('*'):
65-			return l.makeToken(token.EqStar, "", sPos, sCol, sLine)
66-		case l.match('/'):
67-			return l.makeToken(token.EqSlash, "", sPos, sCol, sLine)
68-		case l.match('%'):
69-			return l.makeToken(token.EqRem, "", sPos, sCol, sLine)
70-		case l.match('&'):
71-			return l.makeToken(token.EqAnd, "", sPos, sCol, sLine)
72-		case l.match('|'):
73-			return l.makeToken(token.EqOr, "", sPos, sCol, sLine)
74-		case l.match('^'):
75-			return l.makeToken(token.EqXor, "", sPos, sCol, sLine)
76-		case l.match('<') && l.match('<'):
77-			return l.makeToken(token.EqShl, "", sPos, sCol, sLine)
78-		case l.match('>') && l.match('>'):
79-			return l.makeToken(token.EqShr, "", sPos, sCol, sLine)
80+		case l.match('+'): return l.makeToken(token.EqPlus, "", sPos, sCol, sLine)
81+		case l.match('-'): return l.makeToken(token.EqMinus, "", sPos, sCol, sLine)
82+		case l.match('*'): return l.makeToken(token.EqStar, "", sPos, sCol, sLine)
83+		case l.match('/'): return l.makeToken(token.EqSlash, "", sPos, sCol, sLine)
84+		case l.match('%'): return l.makeToken(token.EqRem, "", sPos, sCol, sLine)
85+		case l.match('&'): return l.makeToken(token.EqAnd, "", sPos, sCol, sLine)
86+		case l.match('|'): return l.makeToken(token.EqOr, "", sPos, sCol, sLine)
87+		case l.match('^'): return l.makeToken(token.EqXor, "", sPos, sCol, sLine)
88+		case l.match('<') && l.match('<'): return l.makeToken(token.EqShl, "", sPos, sCol, sLine)
89+		case l.match('>') && l.match('>'): return l.makeToken(token.EqShr, "", sPos, sCol, sLine)
90 		}
91 	}
92 	return l.makeToken(token.Eq, "", sPos, sCol, sLine)
M pkg/parser/parser.go
+48, -122
  1@@ -132,7 +132,9 @@ func (p *Parser) parseTopLevel() *ast.Node {
  2 		directiveVal := currentTok.Value
  3 		if strings.HasPrefix(directiveVal, "requires:") {
  4 			flagStr := strings.TrimSpace(strings.TrimPrefix(directiveVal, "requires:"))
  5-			p.cfg.ProcessDirectiveFlags(flagStr)
  6+			if err := p.cfg.ProcessDirectiveFlags(flagStr, currentTok); err != nil {
  7+				util.Error(currentTok, err.Error())
  8+			}
  9 		} else {
 10 			util.Error(currentTok, "Unknown directive '[b]: %s'", directiveVal)
 11 		}
 12@@ -629,9 +631,7 @@ func (p *Parser) parseFuncDecl(returnType *ast.BxType, nameToken token.Token) *a
 13 
 14 	if returnType == nil {
 15 		returnType = ast.TypeUntyped
 16-		if isTyped {
 17-			returnType = ast.TypeInt
 18-		}
 19+		if isTyped { returnType = ast.TypeInt }
 20 	}
 21 
 22 	return ast.NewFuncDecl(nameToken, name, params, body, hasVarargs, isTyped, returnType)
 23@@ -710,9 +710,7 @@ func (p *Parser) parseTypedVarOrFuncDecl(isTopLevel bool) *ast.Node {
 24 	p.expect(token.Ident, "Expected identifier after type.")
 25 	nameToken := p.previous
 26 
 27-	if p.check(token.LParen) {
 28-		return p.parseFuncDecl(declType, nameToken)
 29-	}
 30+	if p.check(token.LParen) { return p.parseFuncDecl(declType, nameToken) }
 31 
 32 	return p.parseTypedVarDeclBody(startTok, declType, nameToken)
 33 }
 34@@ -757,16 +755,12 @@ func (p *Parser) parseTypedVarDeclBody(startTok token.Token, declType *ast.BxTyp
 35 
 36 	p.expect(token.Semi, "Expected ';' after typed variable declaration.")
 37 
 38-	if len(decls) == 1 {
 39-		return decls[0]
 40-	}
 41+	if len(decls) == 1 { return decls[0] }
 42 	return ast.NewMultiVarDecl(startTok, decls)
 43 }
 44 
 45 func (p *Parser) parseType() *ast.BxType {
 46-	if !p.isTypedPass {
 47-		return nil
 48-	}
 49+	if !p.isTypedPass { return nil }
 50 
 51 	isConst := p.match(token.Const)
 52 	var baseType *ast.BxType
 53@@ -822,9 +816,7 @@ func (p *Parser) parseType() *ast.BxType {
 54 		}
 55 	}
 56 
 57-	for p.match(token.Star) {
 58-		baseType = &ast.BxType{Kind: ast.TYPE_POINTER, Base: baseType}
 59-	}
 60+	for p.match(token.Star) { baseType = &ast.BxType{Kind: ast.TYPE_POINTER, Base: baseType} }
 61 
 62 	if isConst {
 63 		newType := *baseType
 64@@ -839,9 +831,7 @@ func (p *Parser) parseStructDef() *ast.BxType {
 65 
 66 	if p.check(token.Ident) {
 67 		structType.StructTag = p.current.Value
 68-		if p.isTypedPass {
 69-			p.typeNames[structType.StructTag] = true
 70-		}
 71+		if p.isTypedPass { p.typeNames[structType.StructTag] = true }
 72 		p.advance()
 73 	}
 74 
 75@@ -857,9 +847,7 @@ func (p *Parser) parseStructDef() *ast.BxType {
 76 	}
 77 
 78 	p.expect(token.RBrace, "Expected '}' to close struct definition.")
 79-	if structType.StructTag != "" {
 80-		structType.Name = structType.StructTag
 81-	}
 82+	if structType.StructTag != "" { structType.Name = structType.StructTag }
 83 	return structType
 84 }
 85 
 86@@ -867,24 +855,14 @@ func (p *Parser) isTypedParameterList() bool {
 87 	originalPos, originalCurrent := p.pos, p.current
 88 	defer func() { p.pos, p.current = originalPos, originalCurrent }()
 89 
 90-	if p.check(token.RParen) {
 91-		return false
 92-	}
 93-	if p.check(token.Void) && p.peek().Type == token.RParen {
 94-		return true
 95-	}
 96-	if p.isBuiltinType(p.current) || p.isTypeName(p.current.Value) {
 97-		return true
 98-	}
 99+	if p.check(token.RParen) { return false }
100+	if p.check(token.Void) && p.peek().Type == token.RParen { return true }
101+	if p.isBuiltinType(p.current) || p.isTypeName(p.current.Value) { return true }
102 
103 	for {
104-		if !p.check(token.Ident) {
105-			return false
106-		}
107+		if !p.check(token.Ident) { return false }
108 		p.advance()
109-		if !p.match(token.Comma) {
110-			break
111-		}
112+		if !p.match(token.Comma) { break }
113 	}
114 	return p.isBuiltinType(p.current) || p.isTypeName(p.current.Value) || p.check(token.LBracket) || p.check(token.Star)
115 }
116@@ -900,9 +878,7 @@ func (p *Parser) parseUntypedParameters() ([]*ast.Node, bool) {
117 			}
118 			p.expect(token.Ident, "Expected parameter name or '...'.")
119 			params = append(params, ast.NewIdent(p.previous, p.previous.Value))
120-			if !p.match(token.Comma) {
121-				break
122-			}
123+			if !p.match(token.Comma) { break }
124 		}
125 	}
126 	return params, hasVarargs
127@@ -911,18 +887,14 @@ func (p *Parser) parseUntypedParameters() ([]*ast.Node, bool) {
128 func (p *Parser) parseTypedParameters() ([]*ast.Node, bool) {
129 	var params []*ast.Node
130 	var hasVarargs bool
131-	if p.check(token.RParen) {
132-		return params, false
133-	}
134+	if p.check(token.RParen) { return params, false }
135 	if p.check(token.Void) && p.peek().Type == token.RParen {
136 		p.advance()
137 		return params, false
138 	}
139 
140 	for {
141-		if p.check(token.RParen) {
142-			break
143-		}
144+		if p.check(token.RParen) { break }
145 		if p.match(token.Dots) {
146 			hasVarargs = true
147 			break
148@@ -954,50 +926,33 @@ func (p *Parser) parseTypedParameters() ([]*ast.Node, bool) {
149 			}
150 		}
151 
152-		if !p.match(token.Comma) {
153-			break
154-		}
155+		if !p.match(token.Comma) { break }
156 	}
157 	return params, hasVarargs
158 }
159 
160 func getBinaryOpPrecedence(op token.Type) int {
161 	switch op {
162-	case token.OrOr:
163-		return 4
164-	case token.AndAnd:
165-		return 5
166-	case token.Or:
167-		return 6
168-	case token.Xor:
169-		return 7
170-	case token.And:
171-		return 8
172-	case token.EqEq, token.Neq:
173-		return 9
174-	case token.Lt, token.Gt, token.Lte, token.Gte:
175-		return 10
176-	case token.Shl, token.Shr:
177-		return 11
178-	case token.Plus, token.Minus:
179-		return 12
180-	case token.Star, token.Slash, token.Rem:
181-		return 13
182-	default:
183-		return -1
184+	case token.OrOr: return 4
185+	case token.AndAnd: return 5
186+	case token.Or: return 6
187+	case token.Xor: return 7
188+	case token.And: return 8
189+	case token.EqEq, token.Neq: return 9
190+	case token.Lt, token.Gt, token.Lte, token.Gte: return 10
191+	case token.Shl, token.Shr: return 11
192+	case token.Plus, token.Minus: return 12
193+	case token.Star, token.Slash, token.Rem: return 13
194+	default: return -1
195 	}
196 }
197 
198-func (p *Parser) parseExpr() *ast.Node {
199-	return p.parseAssignmentExpr()
200-}
201+func (p *Parser) parseExpr() *ast.Node { return p.parseAssignmentExpr() }
202 
203 func (p *Parser) parseAssignmentExpr() *ast.Node {
204 	left := p.parseTernaryExpr()
205 	if op := p.current.Type; op >= token.Eq && op <= token.EqShr {
206-		if !isLValue(left) {
207-			util.Error(p.current, "Invalid target for assignment.")
208-		}
209+		if !isLValue(left) { util.Error(p.current, "Invalid target for assignment.") }
210 		tok := p.current
211 		p.advance()
212 		right := p.parseAssignmentExpr()
213@@ -1021,14 +976,10 @@ func (p *Parser) parseTernaryExpr() *ast.Node {
214 func (p *Parser) parseBinaryExpr(minPrec int) *ast.Node {
215 	left := p.parseUnaryExpr()
216 	for {
217-		if left == nil {
218-			return nil
219-		}
220+		if left == nil { return nil }
221 		op := p.current.Type
222 		prec := getBinaryOpPrecedence(op)
223-		if prec < minPrec {
224-			break
225-		}
226+		if prec < minPrec { break }
227 		opTok := p.current
228 		p.advance()
229 		right := p.parseBinaryExpr(prec + 1)
230@@ -1063,18 +1014,14 @@ func (p *Parser) parseUnaryExpr() *ast.Node {
231 func (p *Parser) parsePostfixExpr() *ast.Node {
232 	expr := p.parsePrimaryExpr()
233 	for {
234-		if expr == nil {
235-			return nil
236-		}
237+		if expr == nil { return nil }
238 		tok := p.current
239 		if p.match(token.LParen) {
240 			var args []*ast.Node
241 			if !p.check(token.RParen) {
242 				for {
243 					args = append(args, p.parseAssignmentExpr())
244-					if !p.match(token.Comma) {
245-						break
246-					}
247+					if !p.match(token.Comma) { break }
248 				}
249 			}
250 			p.expect(token.RParen, "Expected ')' after function arguments.")
251@@ -1088,13 +1035,9 @@ func (p *Parser) parsePostfixExpr() *ast.Node {
252 			member := ast.NewIdent(p.previous, p.previous.Value)
253 			expr = ast.NewMemberAccess(tok, expr, member)
254 		} else if p.match(token.Inc, token.Dec) {
255-			if !isLValue(expr) {
256-				util.Error(p.previous, "Postfix '++' or '--' requires an l-value.")
257-			}
258+			if !isLValue(expr) { util.Error(p.previous, "Postfix '++' or '--' requires an l-value.") }
259 			expr = ast.NewPostfixOp(p.previous, p.previous.Type, expr)
260-		} else {
261-			break
262-		}
263+		} else { break }
264 	}
265 	return expr
266 }
267@@ -1105,12 +1048,8 @@ func (p *Parser) parsePrimaryExpr() *ast.Node {
268 		val, _ := strconv.ParseInt(p.previous.Value, 10, 64)
269 		return ast.NewNumber(tok, val)
270 	}
271-	if p.match(token.String) {
272-		return ast.NewString(tok, p.previous.Value)
273-	}
274-	if p.match(token.Ident) {
275-		return ast.NewIdent(tok, p.previous.Value)
276-	}
277+	if p.match(token.String) { return ast.NewString(tok, p.previous.Value) }
278+	if p.match(token.Ident) { return ast.NewIdent(tok, p.previous.Value) }
279 	if p.match(token.TypeKeyword) {
280 		util.Warn(p.cfg, config.WarnExtra, p.previous, "Using keyword 'type' as an identifier.")
281 		return ast.NewIdent(tok, "type")
282@@ -1145,16 +1084,12 @@ func (p *Parser) parsePrimaryExpr() *ast.Node {
283 }
284 
285 func (p *Parser) buildSwitchJumpTable(switchNode *ast.Node) {
286-	if switchNode == nil || switchNode.Type != ast.Switch {
287-		return
288-	}
289+	if switchNode == nil || switchNode.Type != ast.Switch { return }
290 	p.findCasesRecursive(switchNode.Data.(ast.SwitchNode).Body, switchNode)
291 }
292 
293 func (p *Parser) findCasesRecursive(node, switchNode *ast.Node) {
294-	if node == nil || (node.Type == ast.Switch && node != switchNode) {
295-		return
296-	}
297+	if node == nil || (node.Type == ast.Switch && node != switchNode) { return }
298 
299 	swData := switchNode.Data.(ast.SwitchNode)
300 
301@@ -1174,9 +1109,7 @@ func (p *Parser) findCasesRecursive(node, switchNode *ast.Node) {
302 		}
303 	} else if node.Type == ast.Default {
304 		defData := node.Data.(ast.DefaultNode)
305-		if swData.DefaultLabelName != "" {
306-			util.Error(node.Tok, "Multiple 'default' labels in one switch statement.")
307-		}
308+		if swData.DefaultLabelName != "" { util.Error(node.Tok, "Multiple 'default' labels in one switch statement.") }
309 		labelName := fmt.Sprintf("@default_%d", node.Tok.Line)
310 		swData.DefaultLabelName = labelName
311 		defData.QbeLabel = labelName
312@@ -1188,17 +1121,10 @@ func (p *Parser) findCasesRecursive(node, switchNode *ast.Node) {
313 	case ast.IfNode:
314 		p.findCasesRecursive(d.ThenBody, switchNode)
315 		p.findCasesRecursive(d.ElseBody, switchNode)
316-	case ast.WhileNode:
317-		p.findCasesRecursive(d.Body, switchNode)
318-	case ast.BlockNode:
319-		for _, stmt := range d.Stmts {
320-			p.findCasesRecursive(stmt, switchNode)
321-		}
322-	case ast.LabelNode:
323-		p.findCasesRecursive(d.Stmt, switchNode)
324-	case ast.CaseNode:
325-		p.findCasesRecursive(d.Body, switchNode)
326-	case ast.DefaultNode:
327-		p.findCasesRecursive(d.Body, switchNode)
328+	case ast.WhileNode: p.findCasesRecursive(d.Body, switchNode)
329+	case ast.BlockNode: for _, stmt := range d.Stmts { p.findCasesRecursive(stmt, switchNode) }
330+	case ast.LabelNode: p.findCasesRecursive(d.Stmt, switchNode)
331+	case ast.CaseNode: p.findCasesRecursive(d.Body, switchNode)
332+	case ast.DefaultNode: p.findCasesRecursive(d.Body, switchNode)
333 	}
334 }