src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

printf.go (4199B)


      1 // Package printf implements a parser for fmt.Printf-style format
      2 // strings.
      3 //
      4 // It parses verbs according to the following syntax:
      5 //
      6 //	Numeric -> '0'-'9'
      7 //	Letter -> 'a'-'z' | 'A'-'Z'
      8 //	Index -> '[' Numeric+ ']'
      9 //	Star -> '*'
     10 //	Star -> Index '*'
     11 //
     12 //	Precision -> Numeric+ | Star
     13 //	Width -> Numeric+ | Star
     14 //
     15 //	WidthAndPrecision -> Width '.' Precision
     16 //	WidthAndPrecision -> Width '.'
     17 //	WidthAndPrecision -> Width
     18 //	WidthAndPrecision -> '.' Precision
     19 //	WidthAndPrecision -> '.'
     20 //
     21 //	Flag -> '+' | '-' | '#' | ' ' | '0'
     22 //	Verb -> Letter | '%'
     23 //
     24 //	Input -> '%' [ Flag+ ] [ WidthAndPrecision ] [ Index ] Verb
     25 package printf
     26 
     27 import (
     28 	"errors"
     29 	"regexp"
     30 	"strconv"
     31 	"strings"
     32 )
     33 
     34 // ErrInvalid is returned for invalid format strings or verbs.
     35 var ErrInvalid = errors.New("invalid format string")
     36 
     37 type Verb struct {
     38 	Letter rune
     39 	Flags  string
     40 
     41 	Width     Argument
     42 	Precision Argument
     43 	// Which value in the argument list the verb uses.
     44 	// -1 denotes the next argument,
     45 	// values > 0 denote explicit arguments.
     46 	// The value 0 denotes that no argument is consumed. This is the case for %%.
     47 	Value int
     48 
     49 	Raw string
     50 }
     51 
     52 // Argument is an implicit or explicit width or precision.
     53 type Argument interface {
     54 	isArgument()
     55 }
     56 
     57 // The Default value, when no width or precision is provided.
     58 type Default struct{}
     59 
     60 // Zero is the implicit zero value.
     61 // This value may only appear for precisions in format strings like %6.f
     62 type Zero struct{}
     63 
     64 // Star is a * value, which may either refer to the next argument (Index == -1) or an explicit argument.
     65 type Star struct{ Index int }
     66 
     67 // A Literal value, such as 6 in %6d.
     68 type Literal int
     69 
     70 func (Default) isArgument() {}
     71 func (Zero) isArgument()    {}
     72 func (Star) isArgument()    {}
     73 func (Literal) isArgument() {}
     74 
     75 // Parse parses f and returns a list of actions.
     76 // An action may either be a literal string, or a Verb.
     77 func Parse(f string) ([]any, error) {
     78 	var out []any
     79 	for len(f) > 0 {
     80 		if f[0] == '%' {
     81 			v, n, err := ParseVerb(f)
     82 			if err != nil {
     83 				return nil, err
     84 			}
     85 			f = f[n:]
     86 			out = append(out, v)
     87 		} else {
     88 			n := strings.IndexByte(f, '%')
     89 			if n > -1 {
     90 				out = append(out, f[:n])
     91 				f = f[n:]
     92 			} else {
     93 				out = append(out, f)
     94 				f = ""
     95 			}
     96 		}
     97 	}
     98 
     99 	return out, nil
    100 }
    101 
    102 func atoi(s string) int {
    103 	n, _ := strconv.Atoi(s)
    104 	return n
    105 }
    106 
    107 // ParseVerb parses the verb at the beginning of f.
    108 // It returns the verb, how much of the input was consumed, and an error, if any.
    109 func ParseVerb(f string) (Verb, int, error) {
    110 	if len(f) < 2 {
    111 		return Verb{}, 0, ErrInvalid
    112 	}
    113 	const (
    114 		flags = 1
    115 
    116 		width      = 2
    117 		widthStar  = 3
    118 		widthIndex = 5
    119 
    120 		dot       = 6
    121 		prec      = 7
    122 		precStar  = 8
    123 		precIndex = 10
    124 
    125 		verbIndex = 11
    126 		verb      = 12
    127 	)
    128 
    129 	m := re.FindStringSubmatch(f)
    130 	if m == nil {
    131 		return Verb{}, 0, ErrInvalid
    132 	}
    133 
    134 	v := Verb{
    135 		Letter: []rune(m[verb])[0],
    136 		Flags:  m[flags],
    137 		Raw:    m[0],
    138 	}
    139 
    140 	if m[width] != "" {
    141 		// Literal width
    142 		v.Width = Literal(atoi(m[width]))
    143 	} else if m[widthStar] != "" {
    144 		// Star width
    145 		if m[widthIndex] != "" {
    146 			v.Width = Star{atoi(m[widthIndex])}
    147 		} else {
    148 			v.Width = Star{-1}
    149 		}
    150 	} else {
    151 		// Default width
    152 		v.Width = Default{}
    153 	}
    154 
    155 	if m[dot] == "" {
    156 		// default precision
    157 		v.Precision = Default{}
    158 	} else {
    159 		if m[prec] != "" {
    160 			// Literal precision
    161 			v.Precision = Literal(atoi(m[prec]))
    162 		} else if m[precStar] != "" {
    163 			// Star precision
    164 			if m[precIndex] != "" {
    165 				v.Precision = Star{atoi(m[precIndex])}
    166 			} else {
    167 				v.Precision = Star{-1}
    168 			}
    169 		} else {
    170 			// Zero precision
    171 			v.Precision = Zero{}
    172 		}
    173 	}
    174 
    175 	if m[verb] == "%" {
    176 		v.Value = 0
    177 	} else if m[verbIndex] != "" {
    178 		v.Value = atoi(m[verbIndex])
    179 	} else {
    180 		v.Value = -1
    181 	}
    182 
    183 	return v, len(m[0]), nil
    184 }
    185 
    186 const (
    187 	flags             = `([+#0 -]*)`
    188 	verb              = `([a-zA-Z%])`
    189 	index             = `(?:\[([0-9]+)\])`
    190 	star              = `((` + index + `)?\*)`
    191 	width1            = `([0-9]+)`
    192 	width2            = star
    193 	width             = `(?:` + width1 + `|` + width2 + `)`
    194 	precision         = width
    195 	widthAndPrecision = `(?:(?:` + width + `)?(?:(\.)(?:` + precision + `)?)?)`
    196 )
    197 
    198 var re = regexp.MustCompile(`^%` + flags + widthAndPrecision + `?` + index + `?` + verb)