1
2
3
4
5 package objabi
6
7 import (
8 "flag"
9 "fmt"
10 "internal/bisect"
11 "internal/buildcfg"
12 "io"
13 "log"
14 "os"
15 "reflect"
16 "sort"
17 "strconv"
18 "strings"
19 )
20
21 func Flagcount(name, usage string, val *int) {
22 flag.Var((*count)(val), name, usage)
23 }
24
25 func Flagfn1(name, usage string, f func(string)) {
26 flag.Var(fn1(f), name, usage)
27 }
28
29 func Flagprint(w io.Writer) {
30 flag.CommandLine.SetOutput(w)
31 flag.PrintDefaults()
32 }
33
34 func Flagparse(usage func()) {
35 flag.Usage = usage
36 os.Args = expandArgs(os.Args)
37 flag.Parse()
38 }
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 func expandArgs(in []string) (out []string) {
54
55 for i, s := range in {
56 if strings.HasPrefix(s, "@") {
57 if out == nil {
58 out = make([]string, 0, len(in)*2)
59 out = append(out, in[:i]...)
60 }
61 slurp, err := os.ReadFile(s[1:])
62 if err != nil {
63 log.Fatal(err)
64 }
65 args := ParseArgs(slurp)
66 out = append(out, expandArgs(args)...)
67 } else if out != nil {
68 out = append(out, s)
69 }
70 }
71 if out == nil {
72 return in
73 }
74 return
75 }
76
77
78
79
80
81
82
83
84
85
86
87
88 func ParseArgs(s []byte) []string {
89 var args []string
90 var arg strings.Builder
91 hasArg := false
92 inSingleQuote := false
93 inDoubleQuote := false
94 i := 0
95
96 for i < len(s) {
97 c := s[i]
98
99 if inSingleQuote {
100 if c == '\'' {
101 inSingleQuote = false
102 } else {
103 arg.WriteByte(c)
104 }
105 i++
106 continue
107 }
108
109 if inDoubleQuote {
110 if c == '\\' && i+1 < len(s) {
111 next := s[i+1]
112 switch next {
113 case '\\':
114 arg.WriteByte('\\')
115 i += 2
116 case '"':
117 arg.WriteByte('"')
118 i += 2
119 case '$':
120 arg.WriteByte('$')
121 i += 2
122 case '`':
123 arg.WriteByte('`')
124 i += 2
125 case '\n':
126
127 i += 2
128 case '\r':
129
130 if i+2 < len(s) && s[i+2] == '\n' {
131 i += 3
132 } else {
133 arg.WriteByte(c)
134 i++
135 }
136 default:
137
138 arg.WriteByte(c)
139 i++
140 }
141 } else if c == '"' {
142 inDoubleQuote = false
143 i++
144 } else {
145 arg.WriteByte(c)
146 i++
147 }
148 continue
149 }
150
151
152 switch c {
153 case ' ', '\t', '\n', '\r':
154 if arg.Len() > 0 || hasArg {
155 args = append(args, arg.String())
156 arg.Reset()
157 hasArg = false
158 }
159 case '\'':
160 inSingleQuote = true
161 hasArg = true
162 case '"':
163 inDoubleQuote = true
164 hasArg = true
165 case '\\':
166
167
168 if i+1 < len(s) {
169 next := s[i+1]
170 if next == '\n' {
171 i += 2
172 continue
173 }
174 if next == '\r' && i+2 < len(s) && s[i+2] == '\n' {
175 i += 3
176 continue
177 }
178
179 arg.WriteByte(next)
180 hasArg = true
181 i += 2
182 continue
183 }
184
185 i++
186 continue
187 default:
188 arg.WriteByte(c)
189 }
190 i++
191 }
192
193
194 if arg.Len() > 0 || hasArg {
195 args = append(args, arg.String())
196 }
197
198 return args
199 }
200
201 func AddVersionFlag() {
202 flag.Var(versionFlag{}, "V", "print version and exit")
203 }
204
205 var buildID string
206
207 type versionFlag struct{}
208
209 func (versionFlag) IsBoolFlag() bool { return true }
210 func (versionFlag) Get() any { return nil }
211 func (versionFlag) String() string { return "" }
212 func (versionFlag) Set(s string) error {
213 name := os.Args[0]
214 name = name[strings.LastIndex(name, `/`)+1:]
215 name = name[strings.LastIndex(name, `\`)+1:]
216 name = strings.TrimSuffix(name, ".exe")
217
218 p := ""
219
220
221
222 if goexperiment := buildcfg.Experiment.String(); goexperiment != "" {
223 p = " X:" + goexperiment
224 }
225
226
227
228
229
230
231 if s == "full" {
232 if strings.Contains(buildcfg.Version, "devel") {
233 p += " buildID=" + buildID
234 }
235 }
236
237 fmt.Printf("%s version %s%s\n", name, buildcfg.Version, p)
238 os.Exit(0)
239 return nil
240 }
241
242
243
244
245 type count int
246
247 func (c *count) String() string {
248 return fmt.Sprint(int(*c))
249 }
250
251 func (c *count) Set(s string) error {
252 switch s {
253 case "true":
254 *c++
255 case "false":
256 *c = 0
257 default:
258 n, err := strconv.Atoi(s)
259 if err != nil {
260 return fmt.Errorf("invalid count %q", s)
261 }
262 *c = count(n)
263 }
264 return nil
265 }
266
267 func (c *count) Get() any {
268 return int(*c)
269 }
270
271 func (c *count) IsBoolFlag() bool {
272 return true
273 }
274
275 func (c *count) IsCountFlag() bool {
276 return true
277 }
278
279 type fn1 func(string)
280
281 func (f fn1) Set(s string) error {
282 f(s)
283 return nil
284 }
285
286 func (f fn1) String() string { return "" }
287
288 type debugField struct {
289 name string
290 help string
291 concurrentOk bool
292 val any
293 }
294
295 type DebugFlag struct {
296 tab map[string]debugField
297 concurrentOk *bool
298 debugSSA DebugSSA
299 }
300
301
302
303
304
305 type DebugSSA func(phase, flag string, val int, valString string) string
306
307
308
309
310
311
312
313
314
315
316
317
318
319 func NewDebugFlag(debug any, debugSSA DebugSSA) *DebugFlag {
320 flag := &DebugFlag{
321 tab: make(map[string]debugField),
322 debugSSA: debugSSA,
323 }
324
325 v := reflect.ValueOf(debug).Elem()
326 t := v.Type()
327 for i := 0; i < t.NumField(); i++ {
328 f := t.Field(i)
329 ptr := v.Field(i).Addr().Interface()
330 if f.Name == "ConcurrentOk" {
331 switch ptr := ptr.(type) {
332 default:
333 panic("debug.ConcurrentOk must have type bool")
334 case *bool:
335 flag.concurrentOk = ptr
336 }
337 continue
338 }
339 name := strings.ToLower(f.Name)
340 help := f.Tag.Get("help")
341 if help == "" {
342 panic(fmt.Sprintf("debug.%s is missing help text", f.Name))
343 }
344 concurrent := f.Tag.Get("concurrent")
345
346 switch ptr.(type) {
347 default:
348 panic(fmt.Sprintf("debug.%s has invalid type %v (must be int, string, or *bisect.Matcher)", f.Name, f.Type))
349 case *int, *string, **bisect.Matcher:
350
351 }
352 flag.tab[name] = debugField{name, help, concurrent == "ok", ptr}
353 }
354
355 return flag
356 }
357
358 func (f *DebugFlag) Set(debugstr string) error {
359 if debugstr == "" {
360 return nil
361 }
362 for name := range strings.SplitSeq(debugstr, ",") {
363 if name == "" {
364 continue
365 }
366
367 if name == "help" {
368 fmt.Print(debugHelpHeader)
369 maxLen, names := 0, []string{}
370 if f.debugSSA != nil {
371 maxLen = len("ssa/help")
372 }
373 for name := range f.tab {
374 if len(name) > maxLen {
375 maxLen = len(name)
376 }
377 names = append(names, name)
378 }
379 sort.Strings(names)
380
381 nl := fmt.Sprintf("\n\t%-*s\t", maxLen, "")
382 for _, name := range names {
383 help := f.tab[name].help
384 fmt.Printf("\t%-*s\t%s\n", maxLen, name, strings.ReplaceAll(help, "\n", nl))
385 }
386 if f.debugSSA != nil {
387
388 fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging")
389 }
390 os.Exit(0)
391 }
392
393 val, valstring, haveInt := 1, "", true
394 if i := strings.IndexAny(name, "=:"); i >= 0 {
395 var err error
396 name, valstring = name[:i], name[i+1:]
397 val, err = strconv.Atoi(valstring)
398 if err != nil {
399 val, haveInt = 1, false
400 }
401 }
402
403 if t, ok := f.tab[name]; ok {
404 switch vp := t.val.(type) {
405 case nil:
406
407 case *string:
408 *vp = valstring
409 case *int:
410 if !haveInt {
411 log.Fatalf("invalid debug value %v", name)
412 }
413 *vp = val
414 case **bisect.Matcher:
415 var err error
416 *vp, err = bisect.New(valstring)
417 if err != nil {
418 log.Fatalf("debug flag %v: %v", name, err)
419 }
420 default:
421 panic("bad debugtab type")
422 }
423
424 if !t.concurrentOk && f.concurrentOk != nil {
425 *f.concurrentOk = false
426 }
427 } else if f.debugSSA != nil && strings.HasPrefix(name, "ssa/") {
428
429
430
431 phase := name[4:]
432 flag := "debug"
433 if i := strings.Index(phase, "/"); i >= 0 {
434 flag = phase[i+1:]
435 phase = phase[:i]
436 }
437 err := f.debugSSA(phase, flag, val, valstring)
438 if err != "" {
439 log.Fatal(err)
440 }
441
442
443
444 *f.concurrentOk = false
445
446 } else {
447 return fmt.Errorf("unknown debug key %s\n", name)
448 }
449 }
450
451 return nil
452 }
453
454 const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]
455
456 <key> is one of:
457
458 `
459
460 func (f *DebugFlag) String() string {
461 return ""
462 }
463
View as plain text