作者 yangfu

fix: data append error

package astexpr
import (
"errors"
"fmt"
"strconv"
"strings"
)
type AST struct {
Tokens []*Token
source string
currTok *Token
currIndex int
depth int
Err error
}
func NewAST(toks []*Token, s string) *AST {
a := &AST{
Tokens: toks,
source: s,
}
if a.Tokens == nil || len(a.Tokens) == 0 {
a.Err = errors.New("empty token")
} else {
a.currIndex = 0
a.currTok = a.Tokens[0]
}
return a
}
// ParseExpression 解析表达式
func (a *AST) ParseExpression() ExprAST {
a.depth++ // called depth
lhs := a.parsePrimary()
r := a.parseBinOpRHS(0, lhs)
a.depth--
if a.depth == 0 && a.currIndex != len(a.Tokens) && a.Err == nil {
a.Err = errors.New(
fmt.Sprintf("bad expression, reaching the end or missing the operator\n%s",
ErrPos(a.source, a.currTok.Offset)))
}
return r
}
func (a *AST) getNextToken() *Token {
a.currIndex++
if a.currIndex < len(a.Tokens) {
a.currTok = a.Tokens[a.currIndex]
return a.currTok
}
return nil
}
func (a *AST) getTokPrecedence() int {
if p, ok := precedence[a.currTok.Tok]; ok {
return p
}
return -1
}
func (a *AST) parseNumber() NumberExprAST {
f64, err := strconv.ParseFloat(a.currTok.Tok, 64)
if err != nil {
a.Err = errors.New(
fmt.Sprintf("%v\nwant '(' or '0-9' but get '%s'\n%s",
err.Error(),
a.currTok.Tok,
ErrPos(a.source, a.currTok.Offset)))
return NumberExprAST{}
}
n := NumberExprAST{
Val: f64,
Str: a.currTok.Tok,
}
a.getNextToken()
return n
}
func (a *AST) parseFunCallerOrConst() ExprAST {
name := a.currTok.Tok
a.getNextToken()
// call func
if a.currTok.Tok == "(" {
f := FunCallerExprAST{}
if _, ok := defFunc[name]; !ok {
a.Err = errors.New(
fmt.Sprintf("function `%s` is undefined\n%s",
name,
ErrPos(a.source, a.currTok.Offset)))
return f
}
a.getNextToken()
exprs := make([]ExprAST, 0)
if a.currTok.Tok == ")" {
// function call without parameters
// ignore the process of parameter resolution
} else {
exprs = append(exprs, a.ParseExpression())
for a.currTok.Tok != ")" && a.getNextToken() != nil {
if a.currTok.Type == COMMA {
continue
}
exprs = append(exprs, a.ParseExpression())
}
}
def := defFunc[name]
if def.argc >= 0 && len(exprs) != def.argc {
a.Err = errors.New(
fmt.Sprintf("wrong way calling function `%s`, parameters want %d but get %d\n%s",
name,
def.argc,
len(exprs),
ErrPos(a.source, a.currTok.Offset)))
}
a.getNextToken()
f.Name = name
f.Args = exprs
return f
}
// call const
if v, ok := defConst[name]; ok {
return NumberExprAST{
Val: v,
Str: strconv.FormatFloat(v, 'f', 0, 64),
}
} else {
if strings.Contains(name, ".") {
return FieldExprAST{
Str: name,
}
}
a.Err = errors.New(
fmt.Sprintf("const `%s` is undefined\n%s",
name,
ErrPos(a.source, a.currTok.Offset)))
return NumberExprAST{}
}
}
func (a *AST) parsePrimary() ExprAST {
switch a.currTok.Type {
case Identifier:
return a.parseFunCallerOrConst()
case Literal:
return a.parseNumber()
case StringArgs:
e := ValueExprAST{
Str: a.currTok.Tok,
}
a.getNextToken()
return e
case Operator:
if a.currTok.Tok == "(" {
t := a.getNextToken()
if t == nil {
a.Err = errors.New(
fmt.Sprintf("want '(' or '0-9' but get EOF\n%s",
ErrPos(a.source, a.currTok.Offset)))
return nil
}
e := a.ParseExpression()
if e == nil {
return nil
}
if a.currTok.Tok != ")" {
a.Err = errors.New(
fmt.Sprintf("want ')' but get %s\n%s",
a.currTok.Tok,
ErrPos(a.source, a.currTok.Offset)))
return nil
}
a.getNextToken()
return e
} else if a.currTok.Tok == "-" {
if a.getNextToken() == nil {
a.Err = errors.New(
fmt.Sprintf("want '0-9' but get '-'\n%s",
ErrPos(a.source, a.currTok.Offset)))
return nil
}
bin := BinaryExprAST{
Op: "-",
Lhs: NumberExprAST{},
Rhs: a.parsePrimary(),
}
return bin
} else {
return a.parseNumber()
}
case COMMA:
a.Err = errors.New(
fmt.Sprintf("want '(' or '0-9' but get %s\n%s",
a.currTok.Tok,
ErrPos(a.source, a.currTok.Offset)))
return nil
default:
return nil
}
}
func (a *AST) parseBinOpRHS(execPrec int, lhs ExprAST) ExprAST {
for {
tokPrec := a.getTokPrecedence()
if tokPrec < execPrec {
return lhs
}
binOp := a.currTok.Tok
if a.getNextToken() == nil {
a.Err = errors.New(
fmt.Sprintf("want '(' or '0-9' but get EOF\n%s",
ErrPos(a.source, a.currTok.Offset)))
return nil
}
rhs := a.parsePrimary()
if rhs == nil {
return nil
}
nextPrec := a.getTokPrecedence()
if tokPrec < nextPrec {
rhs = a.parseBinOpRHS(tokPrec+1, rhs)
if rhs == nil {
return nil
}
}
lhs = BinaryExprAST{
Op: binOp,
Lhs: lhs,
Rhs: rhs,
}
}
}
... ...
package astexpr
import (
"errors"
"math"
)
const (
RadianMode = iota
AngleMode
)
type defS struct {
argc int
fun func(expr ...ExprAST) float64
}
// TrigonometricMode enum "RadianMode", "AngleMode"
var TrigonometricMode = RadianMode
var defConst = map[string]float64{
"pi": math.Pi,
}
var defFunc map[string]defS
func init() {
defFunc = map[string]defS{
"sin": {1, defSin},
"cos": {1, defCos},
"tan": {1, defTan},
"cot": {1, defCot},
"sec": {1, defSec},
"csc": {1, defCsc},
"abs": {1, defAbs},
"ceil": {1, defCeil},
"floor": {1, defFloor},
"round": {1, defRound},
"sqrt": {1, defSqrt},
"cbrt": {1, defCbrt},
"noerr": {1, defNoErr},
"max": {-1, defMax},
"min": {-1, defMin},
// excel support
"sum": {-1, defNone},
"if": {-1, defNone},
"sumif": {-1, defNone},
"and": {-1, defNone},
"or": {-1, defNone},
"month": {-1, defNone},
"year": {-1, defNone},
//"round": {-1, defNone},
"rounddown": {-1, defNone},
"roundup": {-1, defNone},
"count": {-1, defNone},
"countif": {-1, defNone},
//"&": {-1, defNone},
"concat": {-1, defNone},
}
}
// sin(pi/2) = 1
func defSin(expr ...ExprAST) float64 {
return math.Sin(expr2Radian(expr[0]))
}
// cos(0) = 1
func defCos(expr ...ExprAST) float64 {
return math.Cos(expr2Radian(expr[0]))
}
// tan(pi/4) = 1
func defTan(expr ...ExprAST) float64 {
return math.Tan(expr2Radian(expr[0]))
}
// cot(pi/4) = 1
func defCot(expr ...ExprAST) float64 {
return 1 / defTan(expr...)
}
// sec(0) = 1
func defSec(expr ...ExprAST) float64 {
return 1 / defCos(expr...)
}
// csc(pi/2) = 1
func defCsc(expr ...ExprAST) float64 {
return 1 / defSin(expr...)
}
// abs(-2) = 2
func defAbs(expr ...ExprAST) float64 {
return math.Abs(ExprASTResult(expr[0]))
}
// ceil(4.2) = ceil(4.8) = 5
func defCeil(expr ...ExprAST) float64 {
return math.Ceil(ExprASTResult(expr[0]))
}
// floor(4.2) = floor(4.8) = 4
func defFloor(expr ...ExprAST) float64 {
return math.Floor(ExprASTResult(expr[0]))
}
// round(4.2) = 4
// round(4.6) = 5
func defRound(expr ...ExprAST) float64 {
return math.Round(ExprASTResult(expr[0]))
}
// sqrt(4) = 2
// sqrt(4) = abs(sqrt(4))
// returns only the absolute value of the result
func defSqrt(expr ...ExprAST) float64 {
return math.Sqrt(ExprASTResult(expr[0]))
}
// cbrt(27) = 3
func defCbrt(expr ...ExprAST) float64 {
return math.Cbrt(ExprASTResult(expr[0]))
}
// max(2) = 2
// max(2, 3) = 3
// max(2, 3, 1) = 3
func defMax(expr ...ExprAST) float64 {
if len(expr) == 0 {
panic(errors.New("calling function `max` must have at least one parameter."))
}
if len(expr) == 1 {
return ExprASTResult(expr[0])
}
maxV := ExprASTResult(expr[0])
for i := 1; i < len(expr); i++ {
v := ExprASTResult(expr[i])
maxV = math.Max(maxV, v)
}
return maxV
}
// min(2) = 2
// min(2, 3) = 2
// min(2, 3, 1) = 1
func defMin(expr ...ExprAST) float64 {
if len(expr) == 0 {
panic(errors.New("calling function `min` must have at least one parameter."))
}
if len(expr) == 1 {
return ExprASTResult(expr[0])
}
maxV := ExprASTResult(expr[0])
for i := 1; i < len(expr); i++ {
v := ExprASTResult(expr[i])
maxV = math.Min(maxV, v)
}
return maxV
}
// noerr(1/0) = 0
// noerr(2.5/(1-1)) = 0
func defNoErr(expr ...ExprAST) (r float64) {
defer func() {
if e := recover(); e != nil {
r = 0
}
}()
return ExprASTResult(expr[0])
}
func defNone(expr ...ExprAST) (r float64) {
return 0
}
... ...
package domain
package astexpr
import (
"encoding/json"
"fmt"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
)
const (
... ... @@ -16,6 +17,11 @@ type ExprAST interface {
toStr() string
}
type NumberExprAST struct {
Val float64
Str string
}
type ValueExprAST struct {
ExprType string `json:"exprType"`
Val string `json:"val"`
... ... @@ -25,7 +31,7 @@ type ValueExprAST struct {
type FieldExprAST struct {
ExprType string `json:"exprType"`
Str string `json:"str"`
Field *TableField `json:"field"`
Field *domain.TableField `json:"field"`
}
type BinaryExprAST struct {
... ... @@ -42,6 +48,13 @@ type FunCallerExprAST struct {
Args []ExprAST `json:"args"`
}
func (n NumberExprAST) toStr() string {
return fmt.Sprintf(
"NumberExprAST:%s",
n.Str,
)
}
func (n ValueExprAST) toStr() string {
return fmt.Sprintf(
"ValueExprAST:%s",
... ... @@ -84,7 +97,7 @@ type CloneBinaryExprAST struct {
Rhs json.RawMessage `json:"rhs"`
}
func AstExprUnmarshalJSON(data []byte) (interface{}, error) {
func ExprUnmarshal(data []byte) (interface{}, error) {
var m = make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil {
return nil, err
... ... @@ -108,7 +121,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{
}
exprReturn := &FunCallerExprAST{Name: expr.Name, ExprType: TypeFunCallerExprAST}
for i := range expr.Arg {
subExpr, err := AstExprUnmarshalJSON(expr.Arg[i])
subExpr, err := ExprUnmarshal(expr.Arg[i])
if err != nil {
return nil, err
}
... ... @@ -126,7 +139,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{
}
exprReturn := &BinaryExprAST{Op: expr.Op, ExprType: TypeBinaryExprAST}
if len(expr.Lhs) > 0 {
subExpr, err := AstExprUnmarshalJSON(expr.Lhs)
subExpr, err := ExprUnmarshal(expr.Lhs)
if err != nil {
return nil, err
}
... ... @@ -135,7 +148,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{
}
}
if len(expr.Rhs) > 0 {
subExpr, err := AstExprUnmarshalJSON(expr.Rhs)
subExpr, err := ExprUnmarshal(expr.Rhs)
if err != nil {
return nil, err
}
... ...
package domain
package astexpr
import (
"github.com/linmadan/egglib-go/utils/json"
"github.com/stretchr/testify/assert"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"testing"
)
... ... @@ -57,7 +58,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) {
&FieldExprAST{
ExprType: TypeFieldExprAST,
Str: "业绩1",
Field: &TableField{
Field: &domain.TableField{
TableId: 1,
TableName: "测试ABC",
TableSqlName: "table_abc_test",
... ... @@ -83,7 +84,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) {
if err != nil {
t.Fatal(err)
}
v, err := AstExprUnmarshalJSON(data)
v, err := ExprUnmarshal(data)
if err != nil {
t.Fatal(err)
}
... ...
package astexpr
import (
"errors"
"fmt"
"strings"
)
var precedence = map[string]int{
"+": 20, "-": 20, "*": 40, "/": 40, "%": 40, "^": 60,
"=": 10, ">": 10, "<": 10, "<=": 10, ">=": 10, "&": 40,
}
const (
// Identifier 标识符 e.g.函数名、表字段
Identifier = iota
// Literal 文字 e.g. 50
Literal
// Operator 计算操作 e.g. + - * /
Operator
// COMMA 命令, e.g. (
COMMA
// CompareOperator 比较操作 e.g. < = >
CompareOperator
// StringArgs 字符串参数
StringArgs
)
type Token struct {
// raw characters
Tok string
// type with Literal/Operator
Type,
Flag int
Offset int
}
type Parser struct {
Source string
ch byte
offset int
err error
}
func ParseToken(s string) ([]*Token, error) {
p := &Parser{
Source: s,
err: nil,
ch: s[0],
}
toks := p.parse()
if p.err != nil {
return nil, p.err
}
return toks, nil
}
func (p *Parser) parse() []*Token {
toks := make([]*Token, 0)
for {
tok := p.nextTok()
if tok == nil {
break
}
toks = append(toks, tok)
}
return toks
}
func (p *Parser) nextTok() *Token {
if p.offset >= len(p.Source) || p.err != nil {
return nil
}
var err error
for p.isWhitespace(p.ch) && err == nil {
err = p.nextCh()
}
start := p.offset
var tok *Token
switch p.ch {
case
'(',
')',
'+',
'-',
'*',
'/',
'^',
'%',
'&':
tok = &Token{
Tok: string(p.ch),
Type: Operator,
}
tok.Offset = start
err = p.nextCh()
case
'>',
'<',
'=':
if p.isCompareWordChar(p.ch) {
for p.isCompareWordChar(p.ch) && p.nextCh() == nil {
}
tok = &Token{
Tok: p.Source[start:p.offset],
Type: CompareOperator,
}
tok.Offset = start
} else if p.ch != ' ' {
s := fmt.Sprintf("symbol error: unknown '%v', pos [%v:]\n%s",
string(p.ch),
start,
ErrPos(p.Source, start))
p.err = errors.New(s)
}
case
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9':
for p.isDigitNum(p.ch) && p.nextCh() == nil {
if (p.ch == '-' || p.ch == '+') && p.Source[p.offset-1] != 'e' {
break
}
}
tok = &Token{
Tok: strings.ReplaceAll(p.Source[start:p.offset], "_", ""),
Type: Literal,
}
tok.Offset = start
case '"':
for (p.isDigitNum(p.ch) || p.isChar(p.ch)) && p.nextCh() == nil {
if p.ch == '"' {
break
}
}
err = p.nextCh()
tok = &Token{
Tok: p.Source[start:p.offset],
Type: StringArgs,
}
tok.Offset = start
case ',':
tok = &Token{
Tok: string(p.ch),
Type: COMMA,
}
tok.Offset = start
err = p.nextCh()
default:
if p.isChar(p.ch) {
for p.isWordChar(p.ch) && p.nextCh() == nil {
}
tok = &Token{
Tok: p.Source[start:p.offset],
Type: Identifier,
}
tok.Offset = start
} else if p.ch != ' ' {
s := fmt.Sprintf("symbol error: unknown '%v', pos [%v:]\n%s",
string(p.ch),
start,
ErrPos(p.Source, start))
p.err = errors.New(s)
}
}
return tok
}
func (p *Parser) nextCh() error {
p.offset++
if p.offset < len(p.Source) {
p.ch = p.Source[p.offset]
return nil
}
return errors.New("EOF")
}
func (p *Parser) isWhitespace(c byte) bool {
return c == ' ' ||
c == '\t' ||
c == '\n' ||
c == '\v' ||
c == '\f' ||
c == '\r'
}
func (p *Parser) isDigitNum(c byte) bool {
return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+'
}
func (p *Parser) isChar(c byte) bool {
return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '.' || c == '"'
}
func (p *Parser) isWordChar(c byte) bool {
return p.isChar(c) || '0' <= c && c <= '9'
}
func (p *Parser) isCompareWordChar(c byte) bool {
return c == '=' || c == '<' || c == '>'
}
... ...
package astexpr
import (
"encoding/json"
"fmt"
"testing"
)
func TestExecA(t *testing.T) {
exp := "1+2"
exec(exp)
}
func TestExecB(t *testing.T) {
exp := "1+2-4"
exec(exp)
}
func TestExecC(t *testing.T) {
exp := "1+2-4*3-8"
exec(exp)
}
func TestExecD(t *testing.T) {
exp := "1+2-(4*3-8)"
exec(exp)
}
func TestExecE(t *testing.T) {
exp := "1+2-(4*3+(1-8))"
exec(exp)
}
func TestExecF(t *testing.T) {
exp := "1+(2-(4*3+(1-8)))"
exec(exp)
}
func TestExecG(t *testing.T) {
exp := "((1-2)*(3-8))*((((9+2222))))"
exec(exp)
}
func TestExecH(t *testing.T) {
exp := "0.8888-0.1 * 444 -0.2"
exec(exp)
}
func TestExecI(t *testing.T) {
exp := "0.8888-0.1 * (444 -0.2)"
exec(exp)
}
func TestExecJ(t *testing.T) {
exp := "1_234_567*2-3"
exec(exp)
}
func TestExecK(t *testing.T) {
exp := "2.3e4*4/3"
exec(exp)
}
func TestExecL(t *testing.T) {
exp := "-1+9-88"
exec(exp)
}
func TestExecM(t *testing.T) {
exp := "-1+9-88+(88)"
exec(exp)
}
func TestExecN(t *testing.T) {
exp := "-1+9-88+(-88)*666-1"
exec(exp)
}
func TestExecO(t *testing.T) {
exp := "-(1)+(3)-(-3)*7-((-3))"
exec(exp)
}
func TestExecP(t *testing.T) {
exp := "-(-9+3)"
exec(exp)
}
func TestExecQ(t *testing.T) {
exp := "2e-3*2+2e2+1"
exec(exp)
}
func TestExecR(t *testing.T) {
exp := "3.8 - 56 / (1-1) - 4"
exec(exp)
}
func TestExecS(t *testing.T) {
exp := "noerr(3.8 - 56 / (1-1) - 4)"
exec(exp)
}
func TestFunCaller(t *testing.T) {
funs := []struct {
Name string
Argc int
Fun func(expr ...ExprAST) float64
Exp string
R float64
}{
//{
// "double",
// 1,
// func(expr ...engine.ExprAST) float64 {
// return engine.ExprASTResult(expr[0]) * 2
// },
// "double(6)",
// 12,
//},
{
"sum",
-1,
nil,
"sum(if(100+10,table.a,20))",
10,
},
{
"sum",
-1,
nil,
"sum(if(100<10,table.a,20))",
10,
},
{
"sum",
-1,
nil,
"sum(if(100<10,table.a,20+30))",
10,
},
{
"sum",
-1,
nil,
"sum(if(table.a<table.b,table.a,20+30))",
10,
},
{
"sum",
-1,
nil,
"sum(if(table.a<=table.b,table.a,20+30))",
10,
},
}
for _, f := range funs {
if f.Fun != nil {
_ = RegFunction(f.Name, f.Argc, f.Fun)
}
r, err := Parse(f.Exp)
if err != nil {
}
if r != 0 {
}
}
}
func TestFunCaller2(t *testing.T) {
funs := []struct {
Name string
Exp []string
}{
{
"sum",
[]string{"sum(table.a)"},
},
{
"sumif",
[]string{"sumif(table.month,10,table.count)"},
},
{
"if",
[]string{"if(table.month>10,table.count1,table.count2)"},
},
{
"and",
[]string{"and(table.year=2011,table.month=6)"},
},
{
"or",
[]string{"or(table.year=2011,table.year=2012)"},
},
{
"month",
[]string{"month(\"1991-1-1\")"},
},
{
"year",
[]string{"year(\"1991-1-1\")"},
},
{
"round",
[]string{
"round(1.56)",
"round(table.a)",
},
},
{
"rounddown",
[]string{
"rounddown(1.56)",
"rounddown(table.a)",
},
},
{
"roundup",
[]string{
"roundup(1.56)",
"roundup(table.a)",
},
},
{
"count",
[]string{
"count(1.56)",
"count(table.a)",
},
},
{
"&",
[]string{
"table.a&table.b",
},
},
}
for _, f := range funs {
for _, exp := range f.Exp {
r, err := Parse(exp)
if err != nil {
t.Error(err)
}
if r != 0 {
}
}
}
}
func Parse(s string) (r float64, err error) {
toks, err := ParseToken(s)
if err != nil {
return 0, err
}
ast := NewAST(toks, s)
if ast.Err != nil {
return 0, ast.Err
}
ar := ast.ParseExpression()
if ast.Err != nil {
return 0, ast.Err
}
defer func() {
if e := recover(); e != nil {
err = e.(error)
}
}()
if ar != nil {
fmt.Printf("ExprAST: %+v\n", ar)
}
arData, _ := json.Marshal(ar)
fmt.Printf("%s\n", string(arData))
return 0, err
}
// call engine
func exec(exp string) {
// input text -> []token
toks, err := ParseToken(exp)
if err != nil {
fmt.Println("ERROR: " + err.Error())
return
}
// []token -> AST Tree
ast := NewAST(toks, exp)
if ast.Err != nil {
fmt.Println("ERROR: " + ast.Err.Error())
return
}
// AST builder
ar := ast.ParseExpression()
if ast.Err != nil {
fmt.Println("ERROR: " + ast.Err.Error())
return
}
fmt.Printf("ExprAST: %+v\n", ar)
// catch runtime errors
defer func() {
if e := recover(); e != nil {
fmt.Println("ERROR: ", e)
}
}()
// AST traversal -> result
r := ExprASTResult(ar)
fmt.Println("progressing ...\t", r)
fmt.Printf("%s = %v\n", exp, r)
}
... ...
package astexpr
import (
"errors"
"fmt"
"math"
"math/big"
"strconv"
"strings"
)
// Top level function
// Analytical expression and execution
// err is not nil if an error occurs (including arithmetic runtime errors)
func ParseAndExec(s string) (r float64, err error) {
toks, err := ParseToken(s)
if err != nil {
return 0, err
}
ast := NewAST(toks, s)
if ast.Err != nil {
return 0, ast.Err
}
ar := ast.ParseExpression()
if ast.Err != nil {
return 0, ast.Err
}
defer func() {
if e := recover(); e != nil {
err = e.(error)
}
}()
return ExprASTResult(ar), err
}
func ErrPos(s string, pos int) string {
r := strings.Repeat("-", len(s)) + "\n"
s += "\n"
for i := 0; i < pos; i++ {
s += " "
}
s += "^\n"
return r + s + r
}
// the integer power of a number
func Pow(x float64, n float64) float64 {
return math.Pow(x, n)
}
func expr2Radian(expr ExprAST) float64 {
r := ExprASTResult(expr)
if TrigonometricMode == AngleMode {
r = r / 180 * math.Pi
}
return r
}
// Float64ToStr float64 -> string
func Float64ToStr(f float64) string {
return strconv.FormatFloat(f, 'f', -1, 64)
}
// RegFunction is Top level function
// register a new function to use in expressions
// name: be register function name. the same function name only needs to be registered once.
// argc: this is a number of parameter signatures. should be -1, 0, or a positive integer
//
// -1 variable-length argument; >=0 fixed numbers argument
//
// fun: function handler
func RegFunction(name string, argc int, fun func(...ExprAST) float64) error {
if len(name) == 0 {
return errors.New("RegFunction name is not empty")
}
if argc < -1 {
return errors.New("RegFunction argc should be -1, 0, or a positive integer")
}
if _, ok := defFunc[name]; ok {
return errors.New("RegFunction name is already exist")
}
defFunc[name] = defS{argc, fun}
return nil
}
// ExprASTResult is a Top level function
// AST traversal
// if an arithmetic runtime error occurs, a panic exception is thrown
func ExprASTResult(expr ExprAST) float64 {
var l, r float64
switch expr.(type) {
case BinaryExprAST:
ast := expr.(BinaryExprAST)
l = ExprASTResult(ast.Lhs)
r = ExprASTResult(ast.Rhs)
switch ast.Op {
case "+":
lh, _ := new(big.Float).SetString(Float64ToStr(l))
rh, _ := new(big.Float).SetString(Float64ToStr(r))
f, _ := new(big.Float).Add(lh, rh).Float64()
return f
case "-":
lh, _ := new(big.Float).SetString(Float64ToStr(l))
rh, _ := new(big.Float).SetString(Float64ToStr(r))
f, _ := new(big.Float).Sub(lh, rh).Float64()
return f
case "*":
f, _ := new(big.Float).Mul(new(big.Float).SetFloat64(l), new(big.Float).SetFloat64(r)).Float64()
return f
case "/":
if r == 0 {
panic(errors.New(
fmt.Sprintf("violation of arithmetic specification: a division by zero in ExprASTResult: [%g/%g]",
l,
r)))
}
f, _ := new(big.Float).Quo(new(big.Float).SetFloat64(l), new(big.Float).SetFloat64(r)).Float64()
return f
case "%":
if r == 0 {
panic(errors.New(
fmt.Sprintf("violation of arithmetic specification: a division by zero in ExprASTResult: [%g%%%g]",
l,
r)))
}
return float64(int(l) % int(r))
case "^":
return Pow(l, r)
default:
}
case NumberExprAST:
return expr.(NumberExprAST).Val
case FunCallerExprAST:
f := expr.(FunCallerExprAST)
def := defFunc[f.Name]
return def.fun(f.Args...)
}
return 0.0
}
... ...
package astexpr
import (
"math/rand"
"testing"
"time"
)
func TestParseAndExecSimple(t *testing.T) {
type U struct {
Expr string
R float64
}
exprs := []U{
{"1", 1},
{"--1", 1},
{"1+2", 3},
{"-1+2", 1},
{"-(1+2)", -3},
{"-(1+2)*5", -15},
{"-(1+2)*5/3", -5},
{"1+(-(1+2)*5/3)", -4},
{"3^4", 81},
{"3^4.5", 140.29611541307906},
{"3.5^4.5", 280.7412308013823},
{"8%2", 0},
{"8%3", 2},
{"8%3.5", 2},
{"1e2", 100},
{"1e+2", 100},
{"1e-2", 0.01},
{"1e-2+1e2", 100.01},
{"1e-2+1e2*6/3", 200.01},
{"(1e-2+1e2)*6/3", 200.02},
{"(88*8)+(1+1+1+1)+(6/1.5)-(99%9*(2^4))", 712},
{"1/3*3", 1},
{"123_456_789", 123456789},
{"123_456_789___", 123456789},
{"pi", 3.141592653589793},
{"abs(1)", 1},
{"abs(-1)", 1},
{"ceil(90.2)", 91},
{"ceil(90.8)", 91},
{"ceil(90.0)", 90},
{"floor(90.2)", 90},
{"floor(90.8)", 90},
{"floor(90.0)", 90},
{"round(90.0)", 90},
{"round(90.4)", 90},
{"round(90.5)", 91},
{"round(90.9)", 91},
{"sqrt(4)", 2},
{"cbrt(27)", 3},
{"sqrt(4) + cbrt(27)", 5},
{"sqrt(2^2) + cbrt(3^3)", 5},
{"127^2+5/2-sqrt(2^2) + cbrt(3^3)", 16132.5},
{"max(2)", 2},
{"max(abs(1)+10)", 11},
{"max(abs(1)+10)*2-1", 21},
{"max(2,3.5)", 3.5},
{"max(2^3,3+abs(-1)*6)", 9},
{"max(2^3,3+abs(-1)*6, 20)", 20},
{"max(2^3,3+abs(-1)*6,ceil(9.4))", 10},
{"max(1,2,3,4,5,6,10,7,4,5,6,9.8)", 10},
{"min(3.5)", 3.5},
{"min(ceil(1.2))", 2},
{"min(2,3.5)", 2},
{"min(2^3,3+abs(-1)*6)", 8},
{"min(2^3,3+abs(-1)*6,1^10)", 1},
{"min(99.1,0.2,3,4,5,6,10,7,4,5,6,9.8)", 0.2},
{"max(2^3,3^2)", 9},
{"min(2^3,3^2)", 8},
{"noerr(1/0)", 0},
{"noerr(1/(1-1))", 0},
{"0.1+0.2", 0.3},
{"0.3-0.1", 0.2},
{"10^-1", 0.1},
{"10^-2", 0.01},
{"10^-1*100", 10},
{"10%0", 0},
}
for _, e := range exprs {
r, _ := ParseAndExec(e.Expr)
if r != e.R {
t.Error(e, " ParseAndExec:", r)
}
}
}
func TestParseAndExecTrigonometric(t *testing.T) {
type U struct {
Expr string
RadianMode float64
AngleMode float64
}
exprs := []U{
{"sin(pi/2)", 1, 0.027412133592044294},
{"csc(pi/2)", 1, 36.48019577324057},
{"cos(0)", 1, 1},
{"sec(0)", 1, 1},
{"tan(pi/4)", 1, 0.013708642534394057},
{"cot(pi/4)", 1, 72.94668290394674},
{"sin(90)", 0.893996663600558, 1},
{"csc(90)", 1.1185724071637082, 1},
{"cos(0)", 1, 1},
{"sec(0)", 1, 1},
{"tan(45)", 1.6197751905438615, 1},
{"cot(45)", 0.6173696237835551, 1},
}
for _, e := range exprs {
TrigonometricMode = RadianMode
r, _ := ParseAndExec(e.Expr)
if r != e.RadianMode {
t.Error(e, " ParseAndExec RadianMode:", r)
}
TrigonometricMode = AngleMode
r, _ = ParseAndExec(e.Expr)
if r != e.AngleMode {
t.Error(e, " ParseAndExec AngleMode:", r)
}
}
}
func TestRegFunction(t *testing.T) {
funs := []struct {
Name string
Argc int
Fun func(expr ...ExprAST) float64
Exp string
R float64
}{
{
"double",
1,
func(expr ...ExprAST) float64 {
return ExprASTResult(expr[0]) * 2
},
"double(6)",
12,
},
{
"percentage50",
1,
func(expr ...ExprAST) float64 {
return ExprASTResult(expr[0]) / 2
},
"percentage50(6)",
3,
},
{
"range",
0,
func(expr ...ExprAST) float64 {
return 10.0
},
"range()",
10,
},
{
"choice",
-1,
func(expr ...ExprAST) float64 {
rand.Seed(time.Now().UnixNano())
return ExprASTResult(expr[rand.Intn(len(expr))])
},
"choice(1.1, 9.8, 2.5, 100)",
10,
},
}
for _, f := range funs {
_ = RegFunction(f.Name, f.Argc, f.Fun)
r, err := ParseAndExec(f.Exp)
if f.Name == "choice" {
if !inSlices(r, []float64{1.1, 9.8, 2.5, 100}) {
t.Error(err, "RegFunction errors when register new function: ", f.Name)
}
continue
} else if r != f.R {
t.Error(err, "RegFunction errors when register new function: ", f.Name)
}
}
}
func TestParseAndExecError(t *testing.T) {
exprs := []string{
"(",
"((((((",
"((xscdfddff",
"(1",
"(1+",
"1+",
"1*",
"+2344",
"3+(",
"4+(90-",
"3-(4*7-2)+",
"3-(4*7-2)+98*",
"1#1",
"_123_456_789___",
"1ee3+3",
"sin()",
"sin",
"pi(",
"sin(1, 50)",
"max",
"max()",
"max(1,)",
"max(1,4,6,7,5,)",
"min",
"min(,)",
"min()",
"min(1,)",
"min(1,998,4,23,234,2,)",
"min(1,998,4,23,234,2,,,)",
"1/0",
"99.9 / (2-1-1)",
"(1+2)3",
"1+1 111",
"1+1 111+2",
"1 3",
"1 3-",
}
for _, e := range exprs {
_, err := ParseAndExec(e)
if err == nil {
t.Error(e, " this is error expr!")
}
}
}
func inSlices(target float64, s []float64) bool {
for _, v := range s {
if v == target {
return true
}
}
return false
}
... ...
... ... @@ -28,6 +28,16 @@ type Field struct {
Order string `json:"order,omitempty"`
}
func (f *Field) SqlTypeEqual(compare *Field) bool {
if f.SQLType == compare.SQLType {
return true
}
if SQLType(f.SQLType).IsString() == SQLType(compare.SQLType).IsString() {
return true
}
return false
}
func (f *Field) Valid() error {
if _, ok := SQLTypeMap[strings.ToUpper(f.SQLType)]; !ok {
return fmt.Errorf("unknown sql type:%v", f.SQLType)
... ...
... ... @@ -79,6 +79,12 @@ func (ptr *AppendDataToTableService) AppendData(ctx *domain.Context, fileId int,
continue
}
}
if fromField.SQLType != "" && !toField.SqlTypeEqual(fromField) {
//return nil, fmt.Errorf("字段【%s】的类型与导入数据表的类型不匹配", toField.Name)
return map[string]interface{}{
"result": fmt.Sprintf("字段【%s】的类型与导入数据表的类型不匹配", toField.Name),
}, nil
}
requestData.To = append(requestData.To, toField)
requestData.From = append(requestData.From, fromField)
}
... ...