repos / gbc

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

gbc / pkg / config
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}