* 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)
 |