mirror of
https://github.com/gonum/gonum.git
synced 2025-10-23 07:09:27 +08:00
280 lines
5.5 KiB
Go
280 lines
5.5 KiB
Go
// Copyright ©2020 The Gonum Authors. All rights reserved.
|
|
// Use of this code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file
|
|
|
|
package cscalar
|
|
|
|
import (
|
|
"fmt"
|
|
"math/cmplx"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// parse converts the string s to a complex128. The string may be parenthesized and
|
|
// has the format [±]N±Ni. The order of the components is not strict.
|
|
func parse(s string) (complex128, error) {
|
|
if len(s) == 0 {
|
|
return 0, parseError{state: -1}
|
|
}
|
|
orig := s
|
|
|
|
wantClose := s[0] == '('
|
|
if wantClose {
|
|
if s[len(s)-1] != ')' {
|
|
return 0, parseError{string: orig, state: -1}
|
|
}
|
|
s = s[1 : len(s)-1]
|
|
}
|
|
if len(s) == 0 {
|
|
return 0, parseError{string: orig, state: -1}
|
|
}
|
|
switch s[0] {
|
|
case 'n', 'N':
|
|
if strings.ToLower(s) == "nan" {
|
|
return cmplx.NaN(), nil
|
|
}
|
|
case 'i', 'I':
|
|
if strings.ToLower(s) == "inf" {
|
|
return cmplx.Inf(), nil
|
|
}
|
|
}
|
|
|
|
var q complex128
|
|
var parts byte
|
|
for i := 0; i < 4; i++ {
|
|
beg, end, p, err := floatPart(s)
|
|
if err != nil {
|
|
return q, parseError{string: orig, state: -1}
|
|
}
|
|
if parts&(1<<p) != 0 {
|
|
return q, parseError{string: orig, state: -1}
|
|
}
|
|
parts |= 1 << p
|
|
var v float64
|
|
switch s[:end] {
|
|
case "-":
|
|
if len(s[end:]) == 0 {
|
|
return q, parseError{string: orig, state: -1}
|
|
}
|
|
v = -1
|
|
case "+":
|
|
if len(s[end:]) == 0 {
|
|
return q, parseError{string: orig, state: -1}
|
|
}
|
|
v = 1
|
|
default:
|
|
v, err = strconv.ParseFloat(s[beg:end], 64)
|
|
if err != nil {
|
|
return q, err
|
|
}
|
|
}
|
|
s = s[end:]
|
|
switch p {
|
|
case 0:
|
|
q += complex(v, 0)
|
|
case 1:
|
|
q += complex(0, v)
|
|
s = s[1:]
|
|
}
|
|
if len(s) == 0 {
|
|
return q, nil
|
|
}
|
|
if !isSign(rune(s[0])) {
|
|
return q, parseError{string: orig, state: -1}
|
|
}
|
|
}
|
|
|
|
return q, parseError{string: orig, state: -1}
|
|
}
|
|
|
|
func floatPart(s string) (beg, end int, part uint, err error) {
|
|
const (
|
|
wantMantSign = iota
|
|
wantMantIntInit
|
|
wantMantInt
|
|
wantMantFrac
|
|
wantExpSign
|
|
wantExpInt
|
|
|
|
wantInfN
|
|
wantInfF
|
|
wantCloseInf
|
|
|
|
wantNaNA
|
|
wantNaNN
|
|
wantCloseNaN
|
|
)
|
|
var i, state int
|
|
var r rune
|
|
for i, r = range s {
|
|
switch state {
|
|
case wantMantSign:
|
|
switch {
|
|
default:
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
case isSign(r):
|
|
state = wantMantIntInit
|
|
case isDigit(r):
|
|
state = wantMantInt
|
|
case isDot(r):
|
|
state = wantMantFrac
|
|
case r == 'i', r == 'I':
|
|
state = wantInfN
|
|
case r == 'n', r == 'N':
|
|
state = wantNaNA
|
|
}
|
|
|
|
case wantMantIntInit:
|
|
switch {
|
|
default:
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
case isDigit(r):
|
|
state = wantMantInt
|
|
case isDot(r):
|
|
state = wantMantFrac
|
|
case r == 'i':
|
|
// We need to sneak a look-ahead here.
|
|
if i == len(s)-1 || s[i+1] == '-' || s[i+1] == '+' {
|
|
return 0, i, 1, nil
|
|
}
|
|
fallthrough
|
|
case r == 'I':
|
|
state = wantInfN
|
|
case r == 'n', r == 'N':
|
|
state = wantNaNA
|
|
}
|
|
|
|
case wantMantInt:
|
|
switch {
|
|
default:
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
case isDigit(r):
|
|
// Do nothing
|
|
case isDot(r):
|
|
state = wantMantFrac
|
|
case isExponent(r):
|
|
state = wantExpSign
|
|
case isSign(r):
|
|
return 0, i, 0, nil
|
|
case r == 'i':
|
|
return 0, i, 1, nil
|
|
}
|
|
|
|
case wantMantFrac:
|
|
switch {
|
|
default:
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
case isDigit(r):
|
|
// Do nothing
|
|
case isExponent(r):
|
|
state = wantExpSign
|
|
case isSign(r):
|
|
return 0, i, 0, nil
|
|
case r == 'i':
|
|
return 0, i, 1, nil
|
|
}
|
|
|
|
case wantExpSign:
|
|
switch {
|
|
default:
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
case isSign(r) || isDigit(r):
|
|
state = wantExpInt
|
|
}
|
|
|
|
case wantExpInt:
|
|
switch {
|
|
default:
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
case isDigit(r):
|
|
// Do nothing
|
|
case isSign(r):
|
|
return 0, i, 0, nil
|
|
case r == 'i':
|
|
return 0, i, 1, nil
|
|
}
|
|
|
|
case wantInfN:
|
|
if r != 'n' && r != 'N' {
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
}
|
|
state = wantInfF
|
|
case wantInfF:
|
|
if r != 'f' && r != 'F' {
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
}
|
|
state = wantCloseInf
|
|
case wantCloseInf:
|
|
switch {
|
|
default:
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
case isSign(r):
|
|
return 0, i, 0, nil
|
|
case r == 'i':
|
|
return 0, i, 1, nil
|
|
}
|
|
|
|
case wantNaNA:
|
|
if r != 'a' && r != 'A' {
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
}
|
|
state = wantNaNN
|
|
case wantNaNN:
|
|
if r != 'n' && r != 'N' {
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
}
|
|
state = wantCloseNaN
|
|
case wantCloseNaN:
|
|
if isSign(rune(s[0])) {
|
|
beg = 1
|
|
}
|
|
switch {
|
|
default:
|
|
return beg, i, 0, parseError{string: s, state: state, rune: r}
|
|
case isSign(r):
|
|
return beg, i, 0, nil
|
|
case r == 'i':
|
|
return beg, i, 1, nil
|
|
}
|
|
}
|
|
}
|
|
switch state {
|
|
case wantMantSign, wantExpSign, wantExpInt:
|
|
if state == wantExpInt && isDigit(r) {
|
|
break
|
|
}
|
|
return 0, i, 0, parseError{string: s, state: state, rune: r}
|
|
}
|
|
return 0, len(s), 0, nil
|
|
}
|
|
|
|
func isSign(r rune) bool {
|
|
return r == '+' || r == '-'
|
|
}
|
|
|
|
func isDigit(r rune) bool {
|
|
return '0' <= r && r <= '9'
|
|
}
|
|
|
|
func isExponent(r rune) bool {
|
|
return r == 'e' || r == 'E'
|
|
}
|
|
|
|
func isDot(r rune) bool {
|
|
return r == '.'
|
|
}
|
|
|
|
type parseError struct {
|
|
string string
|
|
state int
|
|
rune rune
|
|
}
|
|
|
|
func (e parseError) Error() string {
|
|
if e.state < 0 {
|
|
return fmt.Sprintf("quat: failed to parse: %q", e.string)
|
|
}
|
|
return fmt.Sprintf("quat: failed to parse in state %d with %q: %q", e.state, e.rune, e.string)
|
|
}
|