xplshn
·
2025-09-14
config.go
Go
1package config
2
3import (
4 "errors"
5 "fmt"
6 "os"
7 "strings"
8
9 "github.com/xplshn/gbc/pkg/cli"
10 "github.com/xplshn/gbc/pkg/token"
11 "modernc.org/libqbe"
12)
13
14type Feature int
15
16const (
17 FeatExtrn Feature = iota
18 FeatAsm
19 FeatBEsc
20 FeatCEsc
21 FeatBOps
22 FeatCOps
23 FeatCComments
24 FeatTyped
25 FeatShortDecl
26 FeatBxDeclarations
27 FeatAllowUninitialized
28 FeatStrictDecl
29 FeatNoDirectives
30 FeatContinue
31 FeatFloat
32 FeatStrictTypes
33 FeatPromTypes
34 FeatCount
35)
36
37type Warning int
38
39const (
40 WarnCEsc Warning = iota
41 WarnBEsc
42 WarnBOps
43 WarnCOps
44 WarnUnrecognizedEscape
45 WarnTruncatedChar
46 WarnLongCharConst
47 WarnCComments
48 WarnOverflow
49 WarnPedantic
50 WarnUnreachableCode
51 WarnImplicitDecl
52 WarnExtra
53 WarnFloat
54 WarnLocalAddress
55 WarnDebugComp
56 WarnPromTypes
57 WarnCount
58)
59
60type Info struct {
61 Name string
62 Enabled bool
63 Description string
64}
65
66type Target struct {
67 GOOS string
68 GOARCH string
69 BackendName string
70 BackendTarget string
71 WordSize int
72 StackAlignment int
73}
74
75var archTranslations = map[string]string{
76 "amd64": "x86_64",
77 "386": "i686",
78 "arm64": "aarch64",
79 "arm": "arm",
80 "riscv64": "riscv64",
81 "x86_64": "amd64",
82 "i386": "386",
83 "i686": "386",
84 "aarch64": "arm64",
85}
86
87var archProperties = map[string]struct {
88 WordSize int
89 StackAlignment int
90}{
91 "amd64": {WordSize: 8, StackAlignment: 16},
92 "arm64": {WordSize: 8, StackAlignment: 16},
93 "386": {WordSize: 4, StackAlignment: 8},
94 "arm": {WordSize: 4, StackAlignment: 8},
95 "riscv64": {WordSize: 8, StackAlignment: 16},
96}
97
98type Config struct {
99 Features map[Feature]Info
100 Warnings map[Warning]Info
101 FeatureMap map[string]Feature
102 WarningMap map[string]Warning
103 StdName string
104 Target
105 LinkerArgs []string
106 LibRequests []string
107 UserIncludePaths []string
108}
109
110func NewConfig() *Config {
111 cfg := &Config{
112 Features: make(map[Feature]Info),
113 Warnings: make(map[Warning]Info),
114 FeatureMap: make(map[string]Feature),
115 WarningMap: make(map[string]Warning),
116 LinkerArgs: make([]string, 0),
117 LibRequests: make([]string, 0),
118 UserIncludePaths: make([]string, 0),
119 }
120
121 features := map[Feature]Info{
122 FeatExtrn: {"extrn", true, "Allow the 'extrn' keyword"},
123 FeatAsm: {"asm", true, "Allow `__asm__` blocks for inline assembly"},
124 FeatBEsc: {"b-esc", false, "Recognize B-style '*' character escapes"},
125 FeatCEsc: {"c-esc", true, "Recognize C-style '\\' character escapes"},
126 FeatBOps: {"b-ops", false, "Recognize B-style assignment operators like '=+'"},
127 FeatCOps: {"c-ops", true, "Recognize C-style assignment operators like '+='"},
128 FeatCComments: {"c-comments", true, "Recognize C-style '//' line comments"},
129 FeatTyped: {"typed", true, "Enable the Bx opt-in & backwards-compatible type system"},
130 FeatShortDecl: {"short-decl", true, "Enable Bx-style short declaration `:=`"},
131 FeatBxDeclarations: {"bx-decl", true, "Enable Bx-style `auto name = val` declarations"},
132 FeatAllowUninitialized: {"allow-uninitialized", true, "Allow declarations without an initializer (`var;` or `auto var;`)"},
133 FeatStrictDecl: {"strict-decl", false, "Require all declarations to be initialized"},
134 FeatContinue: {"continue", true, "Allow the Bx keyword `continue` to be used"},
135 FeatNoDirectives: {"no-directives", false, "Disable `// [b]:` directives"},
136 FeatFloat: {"float", true, "Enable support for floating-point numbers"},
137 FeatStrictTypes: {"strict-types", false, "Disallow all incompatible type operations"},
138 FeatPromTypes: {"prom-types", false, "Enable type promotions - promote untyped literals to compatible types"},
139 }
140
141 warnings := map[Warning]Info{
142 WarnCEsc: {"c-esc", false, "Warn on usage of C-style '\\' escapes"},
143 WarnBEsc: {"b-esc", true, "Warn on usage of B-style '*' escapes"},
144 WarnBOps: {"b-ops", true, "Warn on usage of B-style assignment operators like '=+'"},
145 WarnCOps: {"c-ops", false, "Warn on usage of C-style assignment operators like '+='"},
146 WarnUnrecognizedEscape: {"u-esc", true, "Warn on unrecognized character escape sequences"},
147 WarnTruncatedChar: {"truncated-char", true, "Warn when a character escape value is truncated"},
148 WarnLongCharConst: {"long-char-const", true, "Warn when a multi-character constant is too long for a word"},
149 WarnCComments: {"c-comments", false, "Warn on usage of non-standard C-style '//' comments"},
150 WarnOverflow: {"overflow", true, "Warn when an integer constant is out of range for its type"},
151 WarnPedantic: {"pedantic", false, "Issue all warnings demanded by the strict standard"},
152 WarnUnreachableCode: {"unreachable-code", true, "Warn about code that will never be executed"},
153 WarnImplicitDecl: {"implicit-decl", true, "Warn about implicit function or variable declarations"},
154 WarnExtra: {"extra", true, "Enable extra miscellaneous warnings"},
155 WarnFloat: {"float", false, "Warn when floating-point numbers are used"},
156 WarnLocalAddress: {"local-address", true, "Warn when the address of a local variable is returned"},
157 WarnDebugComp: {"debug-comp", false, "Debug warning for type promotions and conversions"},
158 WarnPromTypes: {"prom-types", true, "Warn when type promotions occur"},
159 }
160
161 cfg.Features, cfg.Warnings = features, warnings
162 for ft, info := range features {
163 cfg.FeatureMap[info.Name] = ft
164 }
165 for wt, info := range warnings {
166 cfg.WarningMap[info.Name] = wt
167 }
168
169 return cfg
170}
171
172func (c *Config) SetTarget(hostOS, hostArch, targetFlag string) {
173 c.GOOS, c.GOARCH, c.BackendName = hostOS, hostArch, "qbe"
174
175 if targetFlag != "" {
176 parts := strings.SplitN(targetFlag, "/", 2)
177 c.BackendName = parts[0]
178 if len(parts) > 1 { c.BackendTarget = parts[1] }
179 }
180
181 validQBETargets := map[string]string{
182 "amd64_apple": "amd64", "amd64_sysv": "amd64", "arm64": "arm64",
183 "arm64_apple": "arm64", "rv64": "riscv64",
184 }
185
186 if c.BackendName == "qbe" {
187 if c.BackendTarget == "" {
188 c.BackendTarget = libqbe.DefaultTarget(hostOS, hostArch)
189 fmt.Fprintf(os.Stderr, "gbc: info: no target specified, defaulting to host target '%s' for backend '%s'\n", c.BackendTarget, c.BackendName)
190 }
191 if goArch, ok := validQBETargets[c.BackendTarget]; ok {
192 c.GOARCH = goArch
193 } else {
194 fmt.Fprintf(os.Stderr, "gbc: warning: unsupported QBE target '%s', defaulting to GOARCH '%s'\n", c.BackendTarget, c.GOARCH)
195 }
196 } else {
197 if c.BackendTarget == "" {
198 tradArch := archTranslations[hostArch]
199 if tradArch == "" {
200 tradArch = hostArch
201 }
202 c.BackendTarget = fmt.Sprintf("%s-unknown-%s-unknown", tradArch, hostOS)
203 fmt.Fprintf(os.Stderr, "gbc: info: no target specified, defaulting to host target '%s' for backend '%s'\n", c.BackendTarget, c.BackendName)
204 }
205 parts := strings.Split(c.BackendTarget, "-")
206 if len(parts) > 0 {
207 if goArch, ok := archTranslations[parts[0]]; ok {
208 c.GOARCH = goArch
209 } else {
210 c.GOARCH = parts[0]
211 }
212 }
213 if len(parts) > 2 && parts[2] != "unknown" {
214 c.GOOS = parts[2]
215 }
216 }
217
218 if props, ok := archProperties[c.GOARCH]; ok {
219 c.WordSize, c.StackAlignment = props.WordSize, props.StackAlignment
220 } else {
221 fmt.Fprintf(os.Stderr, "gbc: warning: unrecognized architecture '%s'\n", c.GOARCH)
222 fmt.Fprintf(os.Stderr, "gbc: warning: defaulting to 64-bit properties; compilation may fail\n")
223 c.WordSize, c.StackAlignment = 8, 16
224 }
225
226 fmt.Fprintf(os.Stderr, "gbc: info: using backend '%s' with target '%s' (GOOS=%s, GOARCH=%s)\n", c.BackendName, c.BackendTarget, c.GOOS, c.GOARCH)
227}
228
229func (c *Config) SetFeature(ft Feature, enabled bool) {
230 if info, ok := c.Features[ft]; ok {
231 info.Enabled = enabled
232 c.Features[ft] = info
233 }
234}
235
236func (c *Config) IsFeatureEnabled(ft Feature) bool { return c.Features[ft].Enabled }
237
238func (c *Config) SetWarning(wt Warning, enabled bool) {
239 if info, ok := c.Warnings[wt]; ok {
240 info.Enabled = enabled
241 c.Warnings[wt] = info
242 }
243}
244
245func (c *Config) IsWarningEnabled(wt Warning) bool { return c.Warnings[wt].Enabled }
246
247func (c *Config) ApplyStd(stdName string) error {
248 c.StdName = stdName
249 isPedantic := c.IsWarningEnabled(WarnPedantic)
250
251 type stdSettings struct {
252 feature Feature
253 bValue, bxValue bool
254 }
255
256 settings := []stdSettings{
257 {FeatAllowUninitialized, true, !isPedantic}, {FeatBOps, true, false},
258 {FeatBEsc, true, false}, {FeatCOps, !isPedantic, true},
259 {FeatCEsc, !isPedantic, true}, {FeatCComments, !isPedantic, true},
260 {FeatExtrn, !isPedantic, true}, {FeatAsm, !isPedantic, true},
261 {FeatTyped, false, true}, {FeatShortDecl, false, true},
262 {FeatBxDeclarations, false, true}, {FeatStrictDecl, false, isPedantic},
263 {FeatContinue, false, true}, {FeatNoDirectives, false, false},
264 {FeatFloat, false, true}, {FeatStrictTypes, false, false},
265 {FeatPromTypes, false, false},
266 }
267
268 switch stdName {
269 case "B":
270 for _, s := range settings {
271 c.SetFeature(s.feature, s.bValue)
272 }
273 if isPedantic {
274 c.SetFeature(FeatFloat, false)
275 }
276 c.SetWarning(WarnBOps, false)
277 c.SetWarning(WarnBEsc, false)
278 c.SetWarning(WarnCOps, true)
279 c.SetWarning(WarnCEsc, true)
280 c.SetWarning(WarnCComments, true)
281 c.SetWarning(WarnFloat, true)
282 case "Bx":
283 for _, s := range settings {
284 c.SetFeature(s.feature, s.bxValue)
285 }
286 c.SetWarning(WarnBOps, true)
287 c.SetWarning(WarnBEsc, true)
288 c.SetWarning(WarnCOps, false)
289 c.SetWarning(WarnCEsc, false)
290 c.SetWarning(WarnCComments, false)
291 c.SetWarning(WarnFloat, false)
292 default:
293 return fmt.Errorf("unsupported standard '%s'; supported: 'B', 'Bx'", stdName)
294 }
295 return nil
296}
297
298func (c *Config) SetupFlagGroups(fs *cli.FlagSet) ([]cli.FlagGroupEntry, []cli.FlagGroupEntry) {
299 var warningFlags, featureFlags []cli.FlagGroupEntry
300
301 for i := Warning(0); i < WarnCount; i++ {
302 pEnable, pDisable := new(bool), new(bool)
303 *pEnable = c.Warnings[i].Enabled
304 warningFlags = append(warningFlags, cli.FlagGroupEntry{
305 Name: c.Warnings[i].Name, Prefix: "W", Usage: c.Warnings[i].Description,
306 Enabled: pEnable, Disabled: pDisable,
307 })
308 }
309
310 for i := Feature(0); i < FeatCount; i++ {
311 pEnable, pDisable := new(bool), new(bool)
312 *pEnable = c.Features[i].Enabled
313 featureFlags = append(featureFlags, cli.FlagGroupEntry{
314 Name: c.Features[i].Name, Prefix: "F", Usage: c.Features[i].Description,
315 Enabled: pEnable, Disabled: pDisable,
316 })
317 }
318
319 fs.AddFlagGroup("Warning Flags", "Enable or disable specific warnings", "warning flag", "Available Warning Flags:", warningFlags)
320 fs.AddFlagGroup("Feature Flags", "Enable or disable specific features", "feature flag", "Available feature flags:", featureFlags)
321
322 return warningFlags, featureFlags
323}
324
325func ParseCLIString(s string) ([]string, error) {
326 var args []string
327 var current strings.Builder
328 inQuote := false
329 for _, r := range s {
330 switch {
331 case r == '\'':
332 inQuote = !inQuote
333 case r == ' ' && !inQuote:
334 if current.Len() > 0 {
335 args = append(args, current.String())
336 current.Reset()
337 }
338 default:
339 current.WriteRune(r)
340 }
341 }
342 if inQuote {
343 return nil, errors.New("unterminated single quote in argument string")
344 }
345 if current.Len() > 0 {
346 args = append(args, current.String())
347 }
348 return args, nil
349}
350
351func (c *Config) ProcessArgs(args []string) error {
352 for i := 0; i < len(args); i++ {
353 arg := args[i]
354 switch {
355 case strings.HasPrefix(arg, "-l"):
356 c.LibRequests = append(c.LibRequests, strings.TrimPrefix(arg, "-l"))
357 case strings.HasPrefix(arg, "-L"):
358 val := strings.TrimPrefix(arg, "-L")
359 if val == "" {
360 if i+1 >= len(args) {
361 return fmt.Errorf("missing argument for flag: %s", arg)
362 }
363 i++
364 val = args[i]
365 }
366 c.LinkerArgs = append(c.LinkerArgs, "-L"+val)
367 case strings.HasPrefix(arg, "-I"):
368 val := strings.TrimPrefix(arg, "-I")
369 if val == "" {
370 if i+1 >= len(args) {
371 return fmt.Errorf("missing argument for flag: %s", arg)
372 }
373 i++
374 val = args[i]
375 }
376 c.UserIncludePaths = append(c.UserIncludePaths, val)
377 case strings.HasPrefix(arg, "-C"):
378 val := strings.TrimPrefix(arg, "-C")
379 if val == "" {
380 if i+1 >= len(args) {
381 return fmt.Errorf("missing argument for flag: %s", arg)
382 }
383 i++
384 val = args[i]
385 }
386 if parts := strings.SplitN(val, "=", 2); len(parts) == 2 && parts[0] == "linker_args" {
387 linkerArgs, err := ParseCLIString(parts[1])
388 if err != nil {
389 return fmt.Errorf("failed to parse linker_args: %w", err)
390 }
391 c.LinkerArgs = append(c.LinkerArgs, linkerArgs...)
392 }
393 case strings.HasPrefix(arg, "-W"):
394 flagName := strings.TrimPrefix(arg, "-W")
395 if strings.HasPrefix(flagName, "no-") {
396 warnName := strings.TrimPrefix(flagName, "no-")
397 if wt, ok := c.WarningMap[warnName]; ok {
398 c.SetWarning(wt, false)
399 } else {
400 return fmt.Errorf("unknown warning flag: %s", arg)
401 }
402 } else {
403 if wt, ok := c.WarningMap[flagName]; ok {
404 c.SetWarning(wt, true)
405 } else {
406 return fmt.Errorf("unknown warning flag: %s", arg)
407 }
408 }
409 case strings.HasPrefix(arg, "-F"):
410 flagName := strings.TrimPrefix(arg, "-F")
411 if strings.HasPrefix(flagName, "no-") {
412 featName := strings.TrimPrefix(flagName, "no-")
413 if ft, ok := c.FeatureMap[featName]; ok {
414 c.SetFeature(ft, false)
415 } else {
416 return fmt.Errorf("unknown feature flag: %s", arg)
417 }
418 } else {
419 if ft, ok := c.FeatureMap[flagName]; ok {
420 c.SetFeature(ft, true)
421 } else {
422 return fmt.Errorf("unknown feature flag: %s", arg)
423 }
424 }
425 default:
426 return fmt.Errorf("unrecognized argument: %s", arg)
427 }
428 }
429 return nil
430}
431
432func (c *Config) ProcessDirectiveFlags(flagStr string, tok token.Token) error {
433 args, err := ParseCLIString(flagStr)
434 if err != nil {
435 return err
436 }
437 return c.ProcessArgs(args)
438}