package base
 
-import (
-       "fmt"
-       "log"
-       "os"
-       "reflect"
-       "strconv"
-       "strings"
-)
-
 // Debug holds the parsed debugging configuration values.
 var Debug DebugFlags
 
 // Each setting is name=value; for ints, name is short for name=1.
 type DebugFlags struct {
        Append               int    `help:"print information about append compilation"`
-       Checkptr             int    `help:"instrument unsafe pointer conversions"`
+       Checkptr             int    `help:"instrument unsafe pointer conversions\n0: instrumentation disabled\n1: conversions involving unsafe.Pointer are instrumented\n2: conversions to unsafe.Pointer force heap allocation"`
        Closure              int    `help:"print information about closure compilation"`
        DclStack             int    `help:"run internal dclstack check"`
        Defer                int    `help:"print information about defer compilation"`
        LocationLists        int    `help:"print information about DWARF location list creation"`
        Nil                  int    `help:"print information about nil checks"`
        NoOpenDefer          int    `help:"disable open-coded defers"`
-       PCTab                string `help:"print named pc-value table"`
+       PCTab                string `help:"print named pc-value table\nOne of: pctospadj, pctofile, pctoline, pctoinline, pctopcdata"`
        Panic                int    `help:"show all compiler panics"`
        Slice                int    `help:"print information about slice compilation"`
        SoftFloat            int    `help:"force compiler to emit soft-float code"`
        WB                   int    `help:"print information about write barriers"`
        ABIWrap              int    `help:"print information about ABI wrapper generation"`
 
-       any bool // set when any of the values have been set
-}
-
-// Any reports whether any of the debug flags have been set.
-func (d *DebugFlags) Any() bool { return d.any }
-
-type debugField struct {
-       name string
-       help string
-       val  interface{} // *int or *string
-}
-
-var debugTab []debugField
-
-func init() {
-       v := reflect.ValueOf(&Debug).Elem()
-       t := v.Type()
-       for i := 0; i < t.NumField(); i++ {
-               f := t.Field(i)
-               if f.Name == "any" {
-                       continue
-               }
-               name := strings.ToLower(f.Name)
-               help := f.Tag.Get("help")
-               if help == "" {
-                       panic(fmt.Sprintf("base.Debug.%s is missing help text", f.Name))
-               }
-               ptr := v.Field(i).Addr().Interface()
-               switch ptr.(type) {
-               default:
-                       panic(fmt.Sprintf("base.Debug.%s has invalid type %v (must be int or string)", f.Name, f.Type))
-               case *int, *string:
-                       // ok
-               }
-               debugTab = append(debugTab, debugField{name, help, ptr})
-       }
+       Any bool // set when any of the debug flags have been set
 }
 
 // DebugSSA is called to set a -d ssa/... option.
 // If nil, those options are reported as invalid options.
 // If DebugSSA returns a non-empty string, that text is reported as a compiler error.
 var DebugSSA func(phase, flag string, val int, valString string) string
-
-// parseDebug parses the -d debug string argument.
-func parseDebug(debugstr string) {
-       // parse -d argument
-       if debugstr == "" {
-               return
-       }
-       Debug.any = true
-Split:
-       for _, name := range strings.Split(debugstr, ",") {
-               if name == "" {
-                       continue
-               }
-               // display help about the -d option itself and quit
-               if name == "help" {
-                       fmt.Print(debugHelpHeader)
-                       maxLen := len("ssa/help")
-                       for _, t := range debugTab {
-                               if len(t.name) > maxLen {
-                                       maxLen = len(t.name)
-                               }
-                       }
-                       for _, t := range debugTab {
-                               fmt.Printf("\t%-*s\t%s\n", maxLen, t.name, t.help)
-                       }
-                       // ssa options have their own help
-                       fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging")
-                       fmt.Print(debugHelpFooter)
-                       os.Exit(0)
-               }
-               val, valstring, haveInt := 1, "", true
-               if i := strings.IndexAny(name, "=:"); i >= 0 {
-                       var err error
-                       name, valstring = name[:i], name[i+1:]
-                       val, err = strconv.Atoi(valstring)
-                       if err != nil {
-                               val, haveInt = 1, false
-                       }
-               }
-               for _, t := range debugTab {
-                       if t.name != name {
-                               continue
-                       }
-                       switch vp := t.val.(type) {
-                       case nil:
-                               // Ignore
-                       case *string:
-                               *vp = valstring
-                       case *int:
-                               if !haveInt {
-                                       log.Fatalf("invalid debug value %v", name)
-                               }
-                               *vp = val
-                       default:
-                               panic("bad debugtab type")
-                       }
-                       continue Split
-               }
-               // special case for ssa for now
-               if DebugSSA != nil && strings.HasPrefix(name, "ssa/") {
-                       // expect form ssa/phase/flag
-                       // e.g. -d=ssa/generic_cse/time
-                       // _ in phase name also matches space
-                       phase := name[4:]
-                       flag := "debug" // default flag is debug
-                       if i := strings.Index(phase, "/"); i >= 0 {
-                               flag = phase[i+1:]
-                               phase = phase[:i]
-                       }
-                       err := DebugSSA(phase, flag, val, valstring)
-                       if err != "" {
-                               log.Fatalf(err)
-                       }
-                       continue Split
-               }
-               log.Fatalf("unknown debug key -d %s\n", name)
-       }
-}
-
-const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]
-
-<key> is one of:
-
-`
-
-const debugHelpFooter = `
-<value> is key-specific.
-
-Key "checkptr" supports values:
-       "0": instrumentation disabled
-       "1": conversions involving unsafe.Pointer are instrumented
-       "2": conversions to unsafe.Pointer force heap allocation
-
-Key "pctab" supports values:
-       "pctospadj", "pctofile", "pctoline", "pctoinline", "pctopcdata"
-`
 
        // V is added by objabi.AddVersionFlag
        W CountFlag "help:\"debug parse tree after type checking\""
 
-       LowerC int          "help:\"concurrency during compilation (1 means no concurrency)\""
-       LowerD func(string) "help:\"enable debugging settings; try -d help\""
-       LowerE CountFlag    "help:\"no limit on number of errors reported\""
-       LowerH CountFlag    "help:\"halt on error\""
-       LowerJ CountFlag    "help:\"debug runtime-initialized variables\""
-       LowerL CountFlag    "help:\"disable inlining\""
-       LowerM CountFlag    "help:\"print optimization decisions\""
-       LowerO string       "help:\"write output to `file`\""
-       LowerP *string      "help:\"set expected package import `path`\"" // &Ctxt.Pkgpath, set below
-       LowerR CountFlag    "help:\"debug generated wrappers\""
-       LowerT bool         "help:\"enable tracing for debugging the compiler\""
-       LowerW CountFlag    "help:\"debug type checking\""
-       LowerV *bool        "help:\"increase debug verbosity\""
+       LowerC int        "help:\"concurrency during compilation (1 means no concurrency)\""
+       LowerD flag.Value "help:\"enable debugging settings; try -d help\""
+       LowerE CountFlag  "help:\"no limit on number of errors reported\""
+       LowerH CountFlag  "help:\"halt on error\""
+       LowerJ CountFlag  "help:\"debug runtime-initialized variables\""
+       LowerL CountFlag  "help:\"disable inlining\""
+       LowerM CountFlag  "help:\"print optimization decisions\""
+       LowerO string     "help:\"write output to `file`\""
+       LowerP *string    "help:\"set expected package import `path`\"" // &Ctxt.Pkgpath, set below
+       LowerR CountFlag  "help:\"debug generated wrappers\""
+       LowerT bool       "help:\"enable tracing for debugging the compiler\""
+       LowerW CountFlag  "help:\"debug type checking\""
+       LowerV *bool      "help:\"increase debug verbosity\""
 
        // Special characters
        Percent          int  "flag:\"%\" help:\"debug non-static initializers\""
        Flag.I = addImportDir
 
        Flag.LowerC = 1
-       Flag.LowerD = parseDebug
+       Flag.LowerD = objabi.NewDebugFlag(&Debug, DebugSSA)
        Flag.LowerP = &Ctxt.Pkgpath
        Flag.LowerV = &Ctxt.Debugvlog
 
                        f := v.Field(i).Interface().(func(string))
                        objabi.Flagfn1(name, help, f)
                default:
-                       panic(fmt.Sprintf("base.Flag.%s has unexpected type %s", f.Name, f.Type))
+                       if val, ok := v.Field(i).Interface().(flag.Value); ok {
+                               flag.Var(val, name, help)
+                       } else {
+                               panic(fmt.Sprintf("base.Flag.%s has unexpected type %s", f.Name, f.Type))
+                       }
                }
        }
 }
        // while writing the object file, and that is non-concurrent.
        // Adding Debug_vlog, however, causes Debug.S to also print
        // while flushing the plist, which happens concurrently.
-       if Ctxt.Debugvlog || Debug.Any() || Flag.Live > 0 {
+       if Ctxt.Debugvlog || Debug.Any || Flag.Live > 0 {
                return false
        }
        // TODO: Test and delete this condition.
 
        "io/ioutil"
        "log"
        "os"
+       "reflect"
+       "sort"
        "strconv"
        "strings"
 )
        }
        return b.String()
 }
+
+type debugField struct {
+       name string
+       help string
+       val  interface{} // *int or *string
+}
+
+type DebugFlag struct {
+       tab map[string]debugField
+       any *bool
+
+       debugSSA DebugSSA
+}
+
+// A DebugSSA function is called to set a -d ssa/... option.
+// If nil, those options are reported as invalid options.
+// If DebugSSA returns a non-empty string, that text is reported as a compiler error.
+// If phase is "help", it should print usage information and terminate the process.
+type DebugSSA func(phase, flag string, val int, valString string) string
+
+// NewDebugFlag constructs a DebugFlag for the fields of debug, which
+// must be a pointer to a struct.
+//
+// Each field of *debug is a different value, named for the lower-case of the field name.
+// Each field must be an int or string and must have a `help` struct tag.
+// There may be an "Any bool" field, which will be set if any debug flags are set.
+//
+// The returned flag takes a comma-separated list of settings.
+// Each setting is name=value; for ints, name is short for name=1.
+//
+// If debugSSA is non-nil, any debug flags of the form ssa/... will be
+// passed to debugSSA for processing.
+func NewDebugFlag(debug interface{}, debugSSA DebugSSA) *DebugFlag {
+       flag := &DebugFlag{
+               tab:      make(map[string]debugField),
+               debugSSA: debugSSA,
+       }
+
+       v := reflect.ValueOf(debug).Elem()
+       t := v.Type()
+       for i := 0; i < t.NumField(); i++ {
+               f := t.Field(i)
+               ptr := v.Field(i).Addr().Interface()
+               if f.Name == "Any" {
+                       switch ptr := ptr.(type) {
+                       default:
+                               panic("debug.Any must have type bool")
+                       case *bool:
+                               flag.any = ptr
+                       }
+                       continue
+               }
+               name := strings.ToLower(f.Name)
+               help := f.Tag.Get("help")
+               if help == "" {
+                       panic(fmt.Sprintf("debug.%s is missing help text", f.Name))
+               }
+               switch ptr.(type) {
+               default:
+                       panic(fmt.Sprintf("debug.%s has invalid type %v (must be int or string)", f.Name, f.Type))
+               case *int, *string:
+                       // ok
+               }
+               flag.tab[name] = debugField{name, help, ptr}
+       }
+
+       return flag
+}
+
+func (f *DebugFlag) Set(debugstr string) error {
+       if debugstr == "" {
+               return nil
+       }
+       if f.any != nil {
+               *f.any = true
+       }
+       for _, name := range strings.Split(debugstr, ",") {
+               if name == "" {
+                       continue
+               }
+               // display help about the debug option itself and quit
+               if name == "help" {
+                       fmt.Print(debugHelpHeader)
+                       maxLen, names := 0, []string{}
+                       if f.debugSSA != nil {
+                               maxLen = len("ssa/help")
+                       }
+                       for name := range f.tab {
+                               if len(name) > maxLen {
+                                       maxLen = len(name)
+                               }
+                               names = append(names, name)
+                       }
+                       sort.Strings(names)
+                       // Indent multi-line help messages.
+                       nl := fmt.Sprintf("\n\t%-*s\t", maxLen, "")
+                       for _, name := range names {
+                               help := f.tab[name].help
+                               fmt.Printf("\t%-*s\t%s\n", maxLen, name, strings.Replace(help, "\n", nl, -1))
+                       }
+                       if f.debugSSA != nil {
+                               // ssa options have their own help
+                               fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging")
+                       }
+                       os.Exit(0)
+               }
+
+               val, valstring, haveInt := 1, "", true
+               if i := strings.IndexAny(name, "=:"); i >= 0 {
+                       var err error
+                       name, valstring = name[:i], name[i+1:]
+                       val, err = strconv.Atoi(valstring)
+                       if err != nil {
+                               val, haveInt = 1, false
+                       }
+               }
+
+               if t, ok := f.tab[name]; ok {
+                       switch vp := t.val.(type) {
+                       case nil:
+                               // Ignore
+                       case *string:
+                               *vp = valstring
+                       case *int:
+                               if !haveInt {
+                                       log.Fatalf("invalid debug value %v", name)
+                               }
+                               *vp = val
+                       default:
+                               panic("bad debugtab type")
+                       }
+               } else if f.debugSSA != nil && strings.HasPrefix(name, "ssa/") {
+                       // expect form ssa/phase/flag
+                       // e.g. -d=ssa/generic_cse/time
+                       // _ in phase name also matches space
+                       phase := name[4:]
+                       flag := "debug" // default flag is debug
+                       if i := strings.Index(phase, "/"); i >= 0 {
+                               flag = phase[i+1:]
+                               phase = phase[:i]
+                       }
+                       err := f.debugSSA(phase, flag, val, valstring)
+                       if err != "" {
+                               log.Fatalf(err)
+                       }
+               } else {
+                       return fmt.Errorf("unknown debug key %s\n", name)
+               }
+       }
+
+       return nil
+}
+
+const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]
+
+<key> is one of:
+
+`
+
+func (f *DebugFlag) String() string {
+       return ""
+}