cuelang.org/go@v0.10.1/cue/literal/num.go (about)

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package literal
    16  
    17  import (
    18  	"cuelang.org/go/cue/errors"
    19  	"cuelang.org/go/cue/token"
    20  	"github.com/cockroachdb/apd/v3"
    21  )
    22  
    23  // We avoid cuelang.org/go/internal.Context as that would be an import cycle.
    24  var baseContext apd.Context
    25  
    26  func init() {
    27  	baseContext = apd.BaseContext
    28  	baseContext.Precision = 34
    29  }
    30  
    31  // NumInfo contains information about a parsed numbers.
    32  //
    33  // Reusing a NumInfo across parses may avoid memory allocations.
    34  type NumInfo struct {
    35  	pos token.Pos
    36  	src string
    37  	p   int
    38  	ch  byte
    39  	buf []byte
    40  
    41  	mul     Multiplier
    42  	base    byte
    43  	neg     bool
    44  	UseSep  bool
    45  	isFloat bool
    46  	err     error
    47  }
    48  
    49  // String returns a canonical string representation of the number so that
    50  // it can be parsed with math.Float.Parse.
    51  func (p *NumInfo) String() string {
    52  	if len(p.buf) > 0 && p.base == 10 && p.mul == 0 {
    53  		return string(p.buf)
    54  	}
    55  	var d apd.Decimal
    56  	_ = p.decimal(&d)
    57  	return d.String()
    58  }
    59  
    60  type decimal = apd.Decimal
    61  
    62  // Decimal is for internal use.
    63  func (p *NumInfo) Decimal(v *decimal) error {
    64  	return p.decimal(v)
    65  }
    66  
    67  func (p *NumInfo) decimal(v *apd.Decimal) error {
    68  	if p.base != 10 {
    69  		_, _, _ = v.SetString("0")
    70  		b := p.buf
    71  		if p.buf[0] == '-' {
    72  			v.Negative = p.neg
    73  			b = p.buf[1:]
    74  		}
    75  		v.Coeff.SetString(string(b), int(p.base))
    76  		return nil
    77  	}
    78  	_ = v.UnmarshalText(p.buf)
    79  	if p.mul != 0 {
    80  		_, _ = baseContext.Mul(v, v, mulToRat[p.mul])
    81  		cond, _ := baseContext.RoundToIntegralExact(v, v)
    82  		if cond.Inexact() {
    83  			return p.errorf("number cannot be represented as int")
    84  		}
    85  	}
    86  	return nil
    87  }
    88  
    89  // Multiplier reports which multiplier was used in an integral number.
    90  func (p *NumInfo) Multiplier() Multiplier {
    91  	return p.mul
    92  }
    93  
    94  // IsInt reports whether the number is an integral number.
    95  func (p *NumInfo) IsInt() bool {
    96  	return !p.isFloat
    97  }
    98  
    99  // ParseNum parses s and populates NumInfo with the result.
   100  func ParseNum(s string, n *NumInfo) error {
   101  	*n = NumInfo{pos: n.pos, src: s, buf: n.buf[:0]}
   102  	if !n.next() {
   103  		return n.errorf("invalid number %q", s)
   104  	}
   105  	switch n.ch {
   106  	case '-':
   107  		n.neg = true
   108  		n.buf = append(n.buf, '-')
   109  		n.next()
   110  	case '+':
   111  		n.next()
   112  	}
   113  	seenDecimalPoint := false
   114  	if n.ch == '.' {
   115  		n.next()
   116  		seenDecimalPoint = true
   117  	}
   118  	err := n.scanNumber(seenDecimalPoint)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	if n.err != nil {
   123  		return n.err
   124  	}
   125  	if n.p < len(n.src) {
   126  		return n.errorf("invalid number %q", s)
   127  	}
   128  	if len(n.buf) == 0 {
   129  		n.buf = append(n.buf, '0')
   130  	}
   131  	return nil
   132  }
   133  
   134  func (p *NumInfo) errorf(format string, args ...interface{}) error {
   135  	return errors.Newf(p.pos, format, args...)
   136  }
   137  
   138  // A Multiplier indicates a multiplier indicator used in the literal.
   139  type Multiplier byte
   140  
   141  const (
   142  	mul1 Multiplier = 1 + iota
   143  	mul2
   144  	mul3
   145  	mul4
   146  	mul5
   147  	mul6
   148  	mul7
   149  	mul8
   150  
   151  	mulBin = 0x10
   152  	mulDec = 0x20
   153  
   154  	K = mulDec | mul1
   155  	M = mulDec | mul2
   156  	G = mulDec | mul3
   157  	T = mulDec | mul4
   158  	P = mulDec | mul5
   159  	E = mulDec | mul6
   160  	Z = mulDec | mul7
   161  	Y = mulDec | mul8
   162  
   163  	Ki = mulBin | mul1
   164  	Mi = mulBin | mul2
   165  	Gi = mulBin | mul3
   166  	Ti = mulBin | mul4
   167  	Pi = mulBin | mul5
   168  	Ei = mulBin | mul6
   169  	Zi = mulBin | mul7
   170  	Yi = mulBin | mul8
   171  )
   172  
   173  func (p *NumInfo) next() bool {
   174  	if p.p >= len(p.src) {
   175  		p.ch = 0
   176  		return false
   177  	}
   178  	p.ch = p.src[p.p]
   179  	p.p++
   180  	if p.ch == '.' {
   181  		if len(p.buf) == 0 {
   182  			p.buf = append(p.buf, '0')
   183  		}
   184  		p.buf = append(p.buf, '.')
   185  	}
   186  	return true
   187  }
   188  
   189  func (p *NumInfo) digitVal(ch byte) (d int) {
   190  	switch {
   191  	case '0' <= ch && ch <= '9':
   192  		d = int(ch - '0')
   193  	case ch == '_':
   194  		p.UseSep = true
   195  		return 0
   196  	case 'a' <= ch && ch <= 'f':
   197  		d = int(ch - 'a' + 10)
   198  	case 'A' <= ch && ch <= 'F':
   199  		d = int(ch - 'A' + 10)
   200  	default:
   201  		return 16 // larger than any legal digit val
   202  	}
   203  	return d
   204  }
   205  
   206  func (p *NumInfo) scanMantissa(base int) bool {
   207  	hasDigit := false
   208  	var last byte
   209  	for p.digitVal(p.ch) < base {
   210  		if p.ch != '_' {
   211  			p.buf = append(p.buf, p.ch)
   212  			hasDigit = true
   213  		}
   214  		last = p.ch
   215  		p.next()
   216  	}
   217  	if last == '_' {
   218  		p.err = p.errorf("illegal '_' in number")
   219  	}
   220  	return hasDigit
   221  }
   222  
   223  func (p *NumInfo) scanNumber(seenDecimalPoint bool) error {
   224  	p.base = 10
   225  
   226  	if seenDecimalPoint {
   227  		p.isFloat = true
   228  		if !p.scanMantissa(10) {
   229  			return p.errorf("illegal fraction %q", p.src)
   230  		}
   231  		goto exponent
   232  	}
   233  
   234  	if p.ch == '0' {
   235  		// int or float
   236  		p.next()
   237  		switch p.ch {
   238  		case 'x', 'X':
   239  			p.base = 16
   240  			// hexadecimal int
   241  			p.next()
   242  			if !p.scanMantissa(16) {
   243  				// only scanned "0x" or "0X"
   244  				return p.errorf("illegal hexadecimal number %q", p.src)
   245  			}
   246  		case 'b':
   247  			p.base = 2
   248  			// binary int
   249  			p.next()
   250  			if !p.scanMantissa(2) {
   251  				// only scanned "0b"
   252  				return p.errorf("illegal binary number %q", p.src)
   253  			}
   254  		case 'o':
   255  			p.base = 8
   256  			// octal int
   257  			p.next()
   258  			if !p.scanMantissa(8) {
   259  				// only scanned "0o"
   260  				return p.errorf("illegal octal number %q", p.src)
   261  			}
   262  		default:
   263  			// int (base 8 or 10) or float
   264  			p.scanMantissa(8)
   265  			if p.ch == '8' || p.ch == '9' {
   266  				p.scanMantissa(10)
   267  				if p.ch != '.' && p.ch != 'e' && p.ch != 'E' {
   268  					return p.errorf("illegal integer number %q", p.src)
   269  				}
   270  			}
   271  			switch p.ch {
   272  			case 'e', 'E':
   273  				if len(p.buf) == 0 {
   274  					p.buf = append(p.buf, '0')
   275  				}
   276  				fallthrough
   277  			case '.':
   278  				goto fraction
   279  			}
   280  			if len(p.buf) > 0 {
   281  				p.base = 8
   282  			}
   283  		}
   284  		goto exit
   285  	}
   286  
   287  	// decimal int or float
   288  	if !p.scanMantissa(10) {
   289  		return p.errorf("illegal number start %q", p.src)
   290  	}
   291  
   292  fraction:
   293  	if p.ch == '.' {
   294  		p.isFloat = true
   295  		p.next()
   296  		p.scanMantissa(10)
   297  	}
   298  
   299  exponent:
   300  	switch p.ch {
   301  	case 'K', 'M', 'G', 'T', 'P':
   302  		p.mul = charToMul[p.ch]
   303  		p.next()
   304  		if p.ch == 'i' {
   305  			p.mul |= mulBin
   306  			p.next()
   307  		} else {
   308  			p.mul |= mulDec
   309  		}
   310  		var v apd.Decimal
   311  		p.isFloat = false
   312  		return p.decimal(&v)
   313  
   314  	case 'e', 'E':
   315  		p.isFloat = true
   316  		p.next()
   317  		p.buf = append(p.buf, 'e')
   318  		if p.ch == '-' || p.ch == '+' {
   319  			p.buf = append(p.buf, p.ch)
   320  			p.next()
   321  		}
   322  		if !p.scanMantissa(10) {
   323  			return p.errorf("illegal exponent %q", p.src)
   324  		}
   325  	}
   326  
   327  exit:
   328  	return nil
   329  }
   330  
   331  var charToMul = map[byte]Multiplier{
   332  	'K': mul1,
   333  	'M': mul2,
   334  	'G': mul3,
   335  	'T': mul4,
   336  	'P': mul5,
   337  	'E': mul6,
   338  	'Z': mul7,
   339  	'Y': mul8,
   340  }
   341  
   342  var mulToRat = map[Multiplier]*apd.Decimal{}
   343  
   344  func init() {
   345  	d := apd.New(1, 0)
   346  	b := apd.New(1, 0)
   347  	dm := apd.New(1000, 0)
   348  	bm := apd.New(1024, 0)
   349  
   350  	c := apd.BaseContext
   351  	for i := Multiplier(1); int(i) < len(charToMul); i++ {
   352  		// TODO: may we write to one of the sources?
   353  		var bn, dn apd.Decimal
   354  		_, _ = c.Mul(&dn, d, dm)
   355  		d = &dn
   356  		_, _ = c.Mul(&bn, b, bm)
   357  		b = &bn
   358  		mulToRat[mulDec|i] = d
   359  		mulToRat[mulBin|i] = b
   360  	}
   361  }