mirror of
https://github.com/miniflux/v2.git
synced 2024-08-31 00:27:24 +02:00
486 lines
12 KiB
Go
486 lines
12 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package number
|
|
|
|
import (
|
|
"errors"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// This file contains a parser for the CLDR number patterns as described in
|
|
// http://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns.
|
|
//
|
|
// The following BNF is derived from this standard.
|
|
//
|
|
// pattern := subpattern (';' subpattern)?
|
|
// subpattern := affix? number exponent? affix?
|
|
// number := decimal | sigDigits
|
|
// decimal := '#'* '0'* ('.' fraction)? | '#' | '0'
|
|
// fraction := '0'* '#'*
|
|
// sigDigits := '#'* '@' '@'* '#'*
|
|
// exponent := 'E' '+'? '0'* '0'
|
|
// padSpec := '*' \L
|
|
//
|
|
// Notes:
|
|
// - An affix pattern may contain any runes, but runes with special meaning
|
|
// should be escaped.
|
|
// - Sequences of digits, '#', and '@' in decimal and sigDigits may have
|
|
// interstitial commas.
|
|
|
|
// TODO: replace special characters in affixes (-, +, ¤) with control codes.
|
|
|
|
// Pattern holds information for formatting numbers. It is designed to hold
|
|
// information from CLDR number patterns.
|
|
//
|
|
// This pattern is precompiled for all patterns for all languages. Even though
|
|
// the number of patterns is not very large, we want to keep this small.
|
|
//
|
|
// This type is only intended for internal use.
|
|
type Pattern struct {
|
|
RoundingContext
|
|
|
|
Affix string // includes prefix and suffix. First byte is prefix length.
|
|
Offset uint16 // Offset into Affix for prefix and suffix
|
|
NegOffset uint16 // Offset into Affix for negative prefix and suffix or 0.
|
|
PadRune rune
|
|
FormatWidth uint16
|
|
|
|
GroupingSize [2]uint8
|
|
Flags PatternFlag
|
|
}
|
|
|
|
// A RoundingContext indicates how a number should be converted to digits.
|
|
// It contains all information needed to determine the "visible digits" as
|
|
// required by the pluralization rules.
|
|
type RoundingContext struct {
|
|
// TODO: unify these two fields so that there is a more unambiguous meaning
|
|
// of how precision is handled.
|
|
MaxSignificantDigits int16 // -1 is unlimited
|
|
MaxFractionDigits int16 // -1 is unlimited
|
|
|
|
Increment uint32
|
|
IncrementScale uint8 // May differ from printed scale.
|
|
|
|
Mode RoundingMode
|
|
|
|
DigitShift uint8 // Number of decimals to shift. Used for % and ‰.
|
|
|
|
// Number of digits.
|
|
MinIntegerDigits uint8
|
|
|
|
MaxIntegerDigits uint8
|
|
MinFractionDigits uint8
|
|
MinSignificantDigits uint8
|
|
|
|
MinExponentDigits uint8
|
|
}
|
|
|
|
// RoundSignificantDigits returns the number of significant digits an
|
|
// implementation of Convert may round to or n < 0 if there is no maximum or
|
|
// a maximum is not recommended.
|
|
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) {
|
|
// Note: don't add DigitShift: it is only used for decimals.
|
|
return int(r.MaxFractionDigits) + 1
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// RoundFractionDigits returns the number of fraction digits an implementation
|
|
// of Convert may round to or n < 0 if there is no maximum or a maximum is not
|
|
// recommended.
|
|
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
|
|
}
|
|
|
|
// SetScale fixes the RoundingContext to a fixed number of fraction digits.
|
|
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
|
|
}
|
|
// TODO: make second groupingsize the same as first if 0 so that we can
|
|
// avoid this check.
|
|
if x := int(f.GroupingSize[1]); x != 0 {
|
|
size = x
|
|
}
|
|
return p%size == 0
|
|
}
|
|
|
|
// A PatternFlag is a bit mask for the flag field of a Pattern.
|
|
type PatternFlag uint8
|
|
|
|
const (
|
|
AlwaysSign PatternFlag = 1 << iota
|
|
ElideSign // Use space instead of plus sign. AlwaysSign must be true.
|
|
AlwaysExpSign
|
|
AlwaysDecimalSeparator
|
|
ParenthesisForNegative // Common pattern. Saves space.
|
|
|
|
PadAfterNumber
|
|
PadAfterAffix
|
|
|
|
PadBeforePrefix = 0 // Default
|
|
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 (
|
|
// TODO: more sensible and localizeable error messages.
|
|
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")
|
|
)
|
|
|
|
// ParsePattern extracts formatting information from a CLDR number pattern.
|
|
//
|
|
// See http://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns.
|
|
func ParsePattern(s string) (f *Pattern, err error) {
|
|
p := parser{Pattern: &Pattern{}}
|
|
|
|
s = p.parseSubPattern(s)
|
|
|
|
if s != "" {
|
|
// Parse negative sub pattern.
|
|
if s[0] != ';' {
|
|
p.setError(errors.New("format: error parsing first sub pattern"))
|
|
return nil, p.err
|
|
}
|
|
neg := parser{Pattern: &Pattern{}} // just for extracting the affixes.
|
|
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" {
|
|
// No prefix or suffixes.
|
|
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) // placeholder for affix length
|
|
|
|
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
|
|
}
|
|
|
|
// state implements a state transition. It returns the new state. A state
|
|
// function may set an error on the parser or may simply return on an incorrect
|
|
// token and let the next phase fail.
|
|
type state func(r rune) state
|
|
|
|
// parse repeatedly applies a state function on the given string until a
|
|
// termination condition is reached.
|
|
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': // ‰ Per mille
|
|
if p.DigitShift != 0 {
|
|
p.setError(errDuplicatePermilleSign)
|
|
}
|
|
p.DigitShift = 3
|
|
// TODO: handle currency somehow: ¤, ¤¤, ¤¤¤, ¤¤¤¤
|
|
}
|
|
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
|
|
}
|
|
|
|
// number parses a number. The BNF says the integer part should always have
|
|
// a '0', but that does not appear to be the case according to the rest of the
|
|
// documentation. We will allow having only '#' numbers.
|
|
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 { // no leading commas
|
|
return nil
|
|
}
|
|
p.updateGrouping()
|
|
case 'E':
|
|
p.MaxIntegerDigits = uint8(p.leadingSharps)
|
|
return p.exponent
|
|
case '.': // allow ".##" etc.
|
|
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 '+':
|
|
// Set mode and check it wasn't already set.
|
|
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
|
|
}
|
|
// termination condition
|
|
if p.MinExponentDigits == 0 {
|
|
p.setError(errors.New("format: need at least one digit"))
|
|
}
|
|
return nil
|
|
}
|