mirror of https://github.com/tidwall/tile38.git
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)
|