package number
import (
"errors"
"unicode/utf8"
)
type Pattern struct {
RoundingContext
Affix string
Offset uint16
NegOffset uint16
PadRune rune
FormatWidth uint16
GroupingSize [2 ]uint8
Flags PatternFlag
}
type RoundingContext struct {
MaxSignificantDigits int16
MaxFractionDigits int16
Increment uint32
IncrementScale uint8
Mode RoundingMode
DigitShift uint8
MinIntegerDigits uint8
MaxIntegerDigits uint8
MinFractionDigits uint8
MinSignificantDigits uint8
MinExponentDigits uint8
}
func (r *RoundingContext ) RoundSignificantDigits () (n int ) {
if r .MaxFractionDigits == 0 && r .MaxSignificantDigits > 0 {
return int (r .MaxSignificantDigits )
} else if r .isScientific () && r .MaxIntegerDigits == 1 {
if r .MaxSignificantDigits == 0 ||
int (r .MaxFractionDigits +1 ) == int (r .MaxSignificantDigits ) {
return int (r .MaxFractionDigits ) + 1
}
}
return -1
}
func (r *RoundingContext ) RoundFractionDigits () (n int ) {
if r .MinExponentDigits == 0 &&
r .MaxSignificantDigits == 0 &&
r .MaxFractionDigits >= 0 {
return int (r .MaxFractionDigits ) + int (r .DigitShift )
}
return -1
}
func (r *RoundingContext ) SetScale (scale int ) {
r .MinFractionDigits = uint8 (scale )
r .MaxFractionDigits = int16 (scale )
}
func (r *RoundingContext ) SetPrecision (prec int ) {
r .MaxSignificantDigits = int16 (prec )
}
func (r *RoundingContext ) isScientific () bool {
return r .MinExponentDigits > 0
}
func (f *Pattern ) needsSep (pos int ) bool {
p := pos - 1
size := int (f .GroupingSize [0 ])
if size == 0 || p == 0 {
return false
}
if p == size {
return true
}
if p -= size ; p < 0 {
return false
}
if x := int (f .GroupingSize [1 ]); x != 0 {
size = x
}
return p %size == 0
}
type PatternFlag uint8
const (
AlwaysSign PatternFlag = 1 << iota
ElideSign
AlwaysExpSign
AlwaysDecimalSeparator
ParenthesisForNegative
PadAfterNumber
PadAfterAffix
PadBeforePrefix = 0
PadAfterPrefix = PadAfterAffix
PadBeforeSuffix = PadAfterNumber
PadAfterSuffix = PadAfterNumber | PadAfterAffix
PadMask = PadAfterNumber | PadAfterAffix
)
type parser struct {
*Pattern
leadingSharps int
pos int
err error
doNotTerminate bool
groupingCount uint
hasGroup bool
buf []byte
}
func (p *parser ) setError (err error ) {
if p .err == nil {
p .err = err
}
}
func (p *parser ) updateGrouping () {
if p .hasGroup &&
0 < p .groupingCount && p .groupingCount < 255 {
p .GroupingSize [1 ] = p .GroupingSize [0 ]
p .GroupingSize [0 ] = uint8 (p .groupingCount )
}
p .groupingCount = 0
p .hasGroup = true
}
var (
errMultiplePadSpecifiers = errors .New ("format: pattern has multiple pad specifiers" )
errInvalidPadSpecifier = errors .New ("format: invalid pad specifier" )
errInvalidQuote = errors .New ("format: invalid quote" )
errAffixTooLarge = errors .New ("format: prefix or suffix exceeds maximum UTF-8 length of 256 bytes" )
errDuplicatePercentSign = errors .New ("format: duplicate percent sign" )
errDuplicatePermilleSign = errors .New ("format: duplicate permille sign" )
errUnexpectedEnd = errors .New ("format: unexpected end of pattern" )
)
func ParsePattern (s string ) (f *Pattern , err error ) {
p := parser {Pattern : &Pattern {}}
s = p .parseSubPattern (s )
if s != "" {
if s [0 ] != ';' {
p .setError (errors .New ("format: error parsing first sub pattern" ))
return nil , p .err
}
neg := parser {Pattern : &Pattern {}}
s = neg .parseSubPattern (s [len (";" ):])
p .NegOffset = uint16 (len (p .buf ))
p .buf = append (p .buf , neg .buf ...)
}
if s != "" {
p .setError (errors .New ("format: spurious characters at end of pattern" ))
}
if p .err != nil {
return nil , p .err
}
if affix := string (p .buf ); affix == "\x00\x00" || affix == "\x00\x00\x00\x00" {
p .NegOffset = 0
} else {
p .Affix = affix
}
if p .Increment == 0 {
p .IncrementScale = 0
}
return p .Pattern , nil
}
func (p *parser ) parseSubPattern (s string ) string {
s = p .parsePad (s , PadBeforePrefix )
s = p .parseAffix (s )
s = p .parsePad (s , PadAfterPrefix )
s = p .parse (p .number , s )
p .updateGrouping ()
s = p .parsePad (s , PadBeforeSuffix )
s = p .parseAffix (s )
s = p .parsePad (s , PadAfterSuffix )
return s
}
func (p *parser ) parsePad (s string , f PatternFlag ) (tail string ) {
if len (s ) >= 2 && s [0 ] == '*' {
r , sz := utf8 .DecodeRuneInString (s [1 :])
if p .PadRune != 0 {
p .err = errMultiplePadSpecifiers
} else {
p .Flags |= f
p .PadRune = r
}
return s [1 +sz :]
}
return s
}
func (p *parser ) parseAffix (s string ) string {
x := len (p .buf )
p .buf = append (p .buf , 0 )
s = p .parse (p .affix , s )
n := len (p .buf ) - x - 1
if n > 0xFF {
p .setError (errAffixTooLarge )
}
p .buf [x ] = uint8 (n )
return s
}
type state func (r rune ) state
func (p *parser ) parse (fn state , s string ) (tail string ) {
for i , r := range s {
p .doNotTerminate = false
if fn = fn (r ); fn == nil || p .err != nil {
return s [i :]
}
p .FormatWidth ++
}
if p .doNotTerminate {
p .setError (errUnexpectedEnd )
}
return ""
}
func (p *parser ) affix (r rune ) state {
switch r {
case '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' ,
'#' , '@' , '.' , '*' , ',' , ';' :
return nil
case '\'' :
p .FormatWidth --
return p .escapeFirst
case '%' :
if p .DigitShift != 0 {
p .setError (errDuplicatePercentSign )
}
p .DigitShift = 2
case '\u2030' :
if p .DigitShift != 0 {
p .setError (errDuplicatePermilleSign )
}
p .DigitShift = 3
}
p .buf = append (p .buf , string (r )...)
return p .affix
}
func (p *parser ) escapeFirst (r rune ) state {
switch r {
case '\'' :
p .buf = append (p .buf , "\\'" ...)
return p .affix
default :
p .buf = append (p .buf , '\'' )
p .buf = append (p .buf , string (r )...)
}
return p .escape
}
func (p *parser ) escape (r rune ) state {
switch r {
case '\'' :
p .FormatWidth --
p .buf = append (p .buf , '\'' )
return p .affix
default :
p .buf = append (p .buf , string (r )...)
}
return p .escape
}
func (p *parser ) number (r rune ) state {
switch r {
case '#' :
p .groupingCount ++
p .leadingSharps ++
case '@' :
p .groupingCount ++
p .leadingSharps = 0
p .MaxFractionDigits = -1
return p .sigDigits (r )
case ',' :
if p .leadingSharps == 0 {
return nil
}
p .updateGrouping ()
case 'E' :
p .MaxIntegerDigits = uint8 (p .leadingSharps )
return p .exponent
case '.' :
p .updateGrouping ()
return p .fraction
case '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' :
return p .integer (r )
default :
return nil
}
return p .number
}
func (p *parser ) integer (r rune ) state {
if !('0' <= r && r <= '9' ) {
var next state
switch r {
case 'E' :
if p .leadingSharps > 0 {
p .MaxIntegerDigits = uint8 (p .leadingSharps ) + p .MinIntegerDigits
}
next = p .exponent
case '.' :
next = p .fraction
case ',' :
next = p .integer
}
p .updateGrouping ()
return next
}
p .Increment = p .Increment *10 + uint32 (r -'0' )
p .groupingCount ++
p .MinIntegerDigits ++
return p .integer
}
func (p *parser ) sigDigits (r rune ) state {
switch r {
case '@' :
p .groupingCount ++
p .MaxSignificantDigits ++
p .MinSignificantDigits ++
case '#' :
return p .sigDigitsFinal (r )
case 'E' :
p .updateGrouping ()
return p .normalizeSigDigitsWithExponent ()
default :
p .updateGrouping ()
return nil
}
return p .sigDigits
}
func (p *parser ) sigDigitsFinal (r rune ) state {
switch r {
case '#' :
p .groupingCount ++
p .MaxSignificantDigits ++
case 'E' :
p .updateGrouping ()
return p .normalizeSigDigitsWithExponent ()
default :
p .updateGrouping ()
return nil
}
return p .sigDigitsFinal
}
func (p *parser ) normalizeSigDigitsWithExponent () state {
p .MinIntegerDigits , p .MaxIntegerDigits = 1 , 1
p .MinFractionDigits = p .MinSignificantDigits - 1
p .MaxFractionDigits = p .MaxSignificantDigits - 1
p .MinSignificantDigits , p .MaxSignificantDigits = 0 , 0
return p .exponent
}
func (p *parser ) fraction (r rune ) state {
switch r {
case '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' :
p .Increment = p .Increment *10 + uint32 (r -'0' )
p .IncrementScale ++
p .MinFractionDigits ++
p .MaxFractionDigits ++
case '#' :
p .MaxFractionDigits ++
case 'E' :
if p .leadingSharps > 0 {
p .MaxIntegerDigits = uint8 (p .leadingSharps ) + p .MinIntegerDigits
}
return p .exponent
default :
return nil
}
return p .fraction
}
func (p *parser ) exponent (r rune ) state {
switch r {
case '+' :
if p .Flags &AlwaysExpSign != 0 || p .MinExponentDigits > 0 {
break
}
p .Flags |= AlwaysExpSign
p .doNotTerminate = true
return p .exponent
case '0' :
p .MinExponentDigits ++
return p .exponent
}
if p .MinExponentDigits == 0 {
p .setError (errors .New ("format: need at least one digit" ))
}
return nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .