 6a979fb40d
			
		
	
	
		6a979fb40d
		
			
		
	
	
	
	
		
			
			* update staticcheck Don't fork staticcheck: use the upstream version. Remove unneeded SSA loading. * Cache go/analysis facts Don't load unneeded packages for go/analysis. Repeated run of go/analysis linters now 10x faster (2s vs 20s on this repo) than before.
		
			
				
	
	
		
			198 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Package printf implements a parser for fmt.Printf-style format
 | |
| // strings.
 | |
| //
 | |
| // It parses verbs according to the following syntax:
 | |
| //     Numeric -> '0'-'9'
 | |
| //     Letter -> 'a'-'z' | 'A'-'Z'
 | |
| //     Index -> '[' Numeric+ ']'
 | |
| //     Star -> '*'
 | |
| //     Star -> Index '*'
 | |
| //
 | |
| //     Precision -> Numeric+ | Star
 | |
| //     Width -> Numeric+ | Star
 | |
| //
 | |
| //     WidthAndPrecision -> Width '.' Precision
 | |
| //     WidthAndPrecision -> Width '.'
 | |
| //     WidthAndPrecision -> Width
 | |
| //     WidthAndPrecision -> '.' Precision
 | |
| //     WidthAndPrecision -> '.'
 | |
| //
 | |
| //     Flag -> '+' | '-' | '#' | ' ' | '0'
 | |
| //     Verb -> Letter | '%'
 | |
| //
 | |
| //     Input -> '%' [ Flag+ ] [ WidthAndPrecision ] [ Index ] Verb
 | |
| package printf
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // ErrInvalid is returned for invalid format strings or verbs.
 | |
| var ErrInvalid = errors.New("invalid format string")
 | |
| 
 | |
| type Verb struct {
 | |
| 	Letter rune
 | |
| 	Flags  string
 | |
| 
 | |
| 	Width     Argument
 | |
| 	Precision Argument
 | |
| 	// Which value in the argument list the verb uses.
 | |
| 	// -1 denotes the next argument,
 | |
| 	// values > 0 denote explicit arguments.
 | |
| 	// The value 0 denotes that no argument is consumed. This is the case for %%.
 | |
| 	Value int
 | |
| 
 | |
| 	Raw string
 | |
| }
 | |
| 
 | |
| // Argument is an implicit or explicit width or precision.
 | |
| type Argument interface {
 | |
| 	isArgument()
 | |
| }
 | |
| 
 | |
| // The Default value, when no width or precision is provided.
 | |
| type Default struct{}
 | |
| 
 | |
| // Zero is the implicit zero value.
 | |
| // This value may only appear for precisions in format strings like %6.f
 | |
| type Zero struct{}
 | |
| 
 | |
| // Star is a * value, which may either refer to the next argument (Index == -1) or an explicit argument.
 | |
| type Star struct{ Index int }
 | |
| 
 | |
| // A Literal value, such as 6 in %6d.
 | |
| type Literal int
 | |
| 
 | |
| func (Default) isArgument() {}
 | |
| func (Zero) isArgument()    {}
 | |
| func (Star) isArgument()    {}
 | |
| func (Literal) isArgument() {}
 | |
| 
 | |
| // Parse parses f and returns a list of actions.
 | |
| // An action may either be a literal string, or a Verb.
 | |
| func Parse(f string) ([]interface{}, error) {
 | |
| 	var out []interface{}
 | |
| 	for len(f) > 0 {
 | |
| 		if f[0] == '%' {
 | |
| 			v, n, err := ParseVerb(f)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			f = f[n:]
 | |
| 			out = append(out, v)
 | |
| 		} else {
 | |
| 			n := strings.IndexByte(f, '%')
 | |
| 			if n > -1 {
 | |
| 				out = append(out, f[:n])
 | |
| 				f = f[n:]
 | |
| 			} else {
 | |
| 				out = append(out, f)
 | |
| 				f = ""
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return out, nil
 | |
| }
 | |
| 
 | |
| func atoi(s string) int {
 | |
| 	n, _ := strconv.Atoi(s)
 | |
| 	return n
 | |
| }
 | |
| 
 | |
| // ParseVerb parses the verb at the beginning of f.
 | |
| // It returns the verb, how much of the input was consumed, and an error, if any.
 | |
| func ParseVerb(f string) (Verb, int, error) {
 | |
| 	if len(f) < 2 {
 | |
| 		return Verb{}, 0, ErrInvalid
 | |
| 	}
 | |
| 	const (
 | |
| 		flags = 1
 | |
| 
 | |
| 		width      = 2
 | |
| 		widthStar  = 3
 | |
| 		widthIndex = 5
 | |
| 
 | |
| 		dot       = 6
 | |
| 		prec      = 7
 | |
| 		precStar  = 8
 | |
| 		precIndex = 10
 | |
| 
 | |
| 		verbIndex = 11
 | |
| 		verb      = 12
 | |
| 	)
 | |
| 
 | |
| 	m := re.FindStringSubmatch(f)
 | |
| 	if m == nil {
 | |
| 		return Verb{}, 0, ErrInvalid
 | |
| 	}
 | |
| 
 | |
| 	v := Verb{
 | |
| 		Letter: []rune(m[verb])[0],
 | |
| 		Flags:  m[flags],
 | |
| 		Raw:    m[0],
 | |
| 	}
 | |
| 
 | |
| 	if m[width] != "" {
 | |
| 		// Literal width
 | |
| 		v.Width = Literal(atoi(m[width]))
 | |
| 	} else if m[widthStar] != "" {
 | |
| 		// Star width
 | |
| 		if m[widthIndex] != "" {
 | |
| 			v.Width = Star{atoi(m[widthIndex])}
 | |
| 		} else {
 | |
| 			v.Width = Star{-1}
 | |
| 		}
 | |
| 	} else {
 | |
| 		// Default width
 | |
| 		v.Width = Default{}
 | |
| 	}
 | |
| 
 | |
| 	if m[dot] == "" {
 | |
| 		// default precision
 | |
| 		v.Precision = Default{}
 | |
| 	} else {
 | |
| 		if m[prec] != "" {
 | |
| 			// Literal precision
 | |
| 			v.Precision = Literal(atoi(m[prec]))
 | |
| 		} else if m[precStar] != "" {
 | |
| 			// Star precision
 | |
| 			if m[precIndex] != "" {
 | |
| 				v.Precision = Star{atoi(m[precIndex])}
 | |
| 			} else {
 | |
| 				v.Precision = Star{-1}
 | |
| 			}
 | |
| 		} else {
 | |
| 			// Zero precision
 | |
| 			v.Precision = Zero{}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if m[verb] == "%" {
 | |
| 		v.Value = 0
 | |
| 	} else if m[verbIndex] != "" {
 | |
| 		v.Value = atoi(m[verbIndex])
 | |
| 	} else {
 | |
| 		v.Value = -1
 | |
| 	}
 | |
| 
 | |
| 	return v, len(m[0]), nil
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	flags             = `([+#0 -]*)`
 | |
| 	verb              = `([a-zA-Z%])`
 | |
| 	index             = `(?:\[([0-9]+)\])`
 | |
| 	star              = `((` + index + `)?\*)`
 | |
| 	width1            = `([0-9]+)`
 | |
| 	width2            = star
 | |
| 	width             = `(?:` + width1 + `|` + width2 + `)`
 | |
| 	precision         = width
 | |
| 	widthAndPrecision = `(?:(?:` + width + `)?(?:(\.)(?:` + precision + `)?)?)`
 | |
| )
 | |
| 
 | |
| var re = regexp.MustCompile(`^%` + flags + widthAndPrecision + `?` + index + `?` + verb)
 |