diff --git a/compileopts/config.go b/compileopts/config.go index 435dda80..f9fc01fe 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -129,8 +129,8 @@ func (c *Config) NeedsStackObjects() bool { } } -// Scheduler returns the scheduler implementation. Valid values are "coroutines" -// and "tasks". +// Scheduler returns the scheduler implementation. Valid values are "none", +//"coroutines" and "tasks". func (c *Config) Scheduler() string { if c.Options.Scheduler != "" { return c.Options.Scheduler diff --git a/compileopts/options.go b/compileopts/options.go index 7336ee3f..ae2c2cec 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -1,5 +1,17 @@ package compileopts +import ( + "fmt" + "strings" +) + +var ( + validGCOptions = []string{"none", "leaking", "extalloc", "conservative"} + validSchedulerOptions = []string{"none", "tasks", "coroutines"} + validPrintSizeOptions = []string{"none", "short", "full"} + validPanicStrategyOptions = []string{"print", "trap"} +) + // Options contains extra options to give to the compiler. These options are // usually passed from the command line. type Options struct { @@ -21,3 +33,53 @@ type Options struct { TestConfig TestConfig Programmer string } + +// Verify performs a validation on the given options, raising an error if options are not valid. +func (o *Options) Verify() error { + if o.GC != "" { + valid := isInArray(validGCOptions, o.GC) + if !valid { + return fmt.Errorf(`invalid gc option '%s': valid values are %s`, + o.GC, + strings.Join(validGCOptions, ", ")) + } + } + + if o.Scheduler != "" { + valid := isInArray(validSchedulerOptions, o.Scheduler) + if !valid { + return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`, + o.Scheduler, + strings.Join(validSchedulerOptions, ", ")) + } + } + + if o.PrintSizes != "" { + valid := isInArray(validPrintSizeOptions, o.PrintSizes) + if !valid { + return fmt.Errorf(`invalid size option '%s': valid values are %s`, + o.PrintSizes, + strings.Join(validPrintSizeOptions, ", ")) + } + } + + if o.PanicStrategy != "" { + valid := isInArray(validPanicStrategyOptions, o.PanicStrategy) + if !valid { + return fmt.Errorf(`invalid panic option '%s': valid values are %s`, + o.PanicStrategy, + strings.Join(validPanicStrategyOptions, ", ")) + } + } + + return nil +} + +func isInArray(arr []string, item string) bool { + for _, i := range arr { + if i == item { + return true + } + } + return false +} diff --git a/compileopts/options_test.go b/compileopts/options_test.go new file mode 100644 index 00000000..1ff532cc --- /dev/null +++ b/compileopts/options_test.go @@ -0,0 +1,138 @@ +package compileopts_test + +import ( + "errors" + "testing" + + "github.com/tinygo-org/tinygo/compileopts" +) + +func TestVerifyOptions(t *testing.T) { + + expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, extalloc, conservative`) + expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, coroutines`) + expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`) + expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`) + + testCases := []struct { + name string + opts compileopts.Options + expectedError error + }{ + { + name: "OptionsEmpty", + opts: compileopts.Options{}, + }, + { + name: "InvalidGCOption", + opts: compileopts.Options{ + GC: "incorrect", + }, + expectedError: expectedGCError, + }, + { + name: "GCOptionNone", + opts: compileopts.Options{ + GC: "none", + }, + }, + { + name: "GCOptionLeaking", + opts: compileopts.Options{ + GC: "leaking", + }, + }, + { + name: "GCOptionExtalloc", + opts: compileopts.Options{ + GC: "extalloc", + }, + }, + { + name: "GCOptionConservative", + opts: compileopts.Options{ + GC: "conservative", + }, + }, + { + name: "InvalidSchedulerOption", + opts: compileopts.Options{ + Scheduler: "incorrect", + }, + expectedError: expectedSchedulerError, + }, + { + name: "SchedulerOptionNone", + opts: compileopts.Options{ + Scheduler: "none", + }, + }, + { + name: "SchedulerOptionTasks", + opts: compileopts.Options{ + Scheduler: "tasks", + }, + }, + { + name: "SchedulerOptionCoroutines", + opts: compileopts.Options{ + Scheduler: "coroutines", + }, + }, + { + name: "InvalidPrintSizeOption", + opts: compileopts.Options{ + PrintSizes: "incorrect", + }, + expectedError: expectedPrintSizeError, + }, + { + name: "PrintSizeOptionNone", + opts: compileopts.Options{ + PrintSizes: "none", + }, + }, + { + name: "PrintSizeOptionShort", + opts: compileopts.Options{ + PrintSizes: "short", + }, + }, + { + name: "PrintSizeOptionFull", + opts: compileopts.Options{ + PrintSizes: "full", + }, + }, + { + name: "InvalidPanicOption", + opts: compileopts.Options{ + PanicStrategy: "incorrect", + }, + expectedError: expectedPanicStrategyError, + }, + { + name: "PanicOptionPrint", + opts: compileopts.Options{ + PanicStrategy: "print", + }, + }, + { + name: "PanicOptionTrap", + opts: compileopts.Options{ + PanicStrategy: "trap", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.opts.Verify() + if tc.expectedError != err { + if tc.expectedError.Error() != err.Error() { + t.Errorf("expected %v, got %v", tc.expectedError, err) + } + } + }) + } +} diff --git a/main.go b/main.go index 976591a9..1d203212 100644 --- a/main.go +++ b/main.go @@ -699,7 +699,7 @@ func main() { opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)") panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") - scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)") + scheduler := flag.String("scheduler", "", "which scheduler to use (none, coroutines, tasks)") printIR := flag.Bool("printir", false, "print LLVM IR") dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR") @@ -759,12 +759,6 @@ func main() { options.LDFlags = strings.Split(*ldFlags, " ") } - if *panicStrategy != "print" && *panicStrategy != "trap" { - fmt.Fprintln(os.Stderr, "Panic strategy must be either print or trap.") - usage() - os.Exit(1) - } - var err error if options.HeapSize, err = parseSize(*heapSize); err != nil { fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize) @@ -774,6 +768,13 @@ func main() { os.Setenv("CC", "clang -target="+*target) + err = options.Verify() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + usage() + os.Exit(1) + } + switch command { case "build": if *outpath == "" { @@ -792,6 +793,7 @@ func main() { if options.Target == "" && filepath.Ext(*outpath) == ".wasm" { options.Target = "wasm" } + err := Build(pkgName, *outpath, options) handleCompilerError(err) case "build-library":