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