正在显示
20 个修改的文件
包含
1969 行增加
和
15 行删除
| @@ -11,6 +11,7 @@ require ( | @@ -11,6 +11,7 @@ require ( | ||
| 11 | github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect | 11 | github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect |
| 12 | github.com/fatih/structs v1.1.0 // indirect | 12 | github.com/fatih/structs v1.1.0 // indirect |
| 13 | github.com/gavv/httpexpect v2.0.0+incompatible | 13 | github.com/gavv/httpexpect v2.0.0+incompatible |
| 14 | + github.com/go-gota/gota v0.12.0 | ||
| 14 | github.com/go-pg/pg/v10 v10.10.6 | 15 | github.com/go-pg/pg/v10 v10.10.6 |
| 15 | github.com/go-redis/redis v6.15.9+incompatible | 16 | github.com/go-redis/redis v6.15.9+incompatible |
| 16 | github.com/google/go-querystring v1.1.0 // indirect | 17 | github.com/google/go-querystring v1.1.0 // indirect |
| @@ -29,6 +30,7 @@ require ( | @@ -29,6 +30,7 @@ require ( | ||
| 29 | github.com/stretchr/testify v1.7.1 | 30 | github.com/stretchr/testify v1.7.1 |
| 30 | github.com/valyala/fasthttp v1.38.0 // indirect | 31 | github.com/valyala/fasthttp v1.38.0 // indirect |
| 31 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect | 32 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect |
| 33 | + github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect | ||
| 32 | github.com/xuri/excelize/v2 v2.6.0 | 34 | github.com/xuri/excelize/v2 v2.6.0 |
| 33 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect | 35 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect |
| 34 | github.com/yudai/gojsondiff v1.0.0 // indirect | 36 | github.com/yudai/gojsondiff v1.0.0 // indirect |
pkg/domain/astexpr/ast.go
0 → 100644
| 1 | +package astexpr | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "errors" | ||
| 5 | + "fmt" | ||
| 6 | + "strconv" | ||
| 7 | + "strings" | ||
| 8 | +) | ||
| 9 | + | ||
| 10 | +type AST struct { | ||
| 11 | + Tokens []*Token | ||
| 12 | + | ||
| 13 | + source string | ||
| 14 | + currTok *Token | ||
| 15 | + currIndex int | ||
| 16 | + depth int | ||
| 17 | + | ||
| 18 | + Err error | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +func NewAST(toks []*Token, s string) *AST { | ||
| 22 | + a := &AST{ | ||
| 23 | + Tokens: toks, | ||
| 24 | + source: s, | ||
| 25 | + } | ||
| 26 | + if a.Tokens == nil || len(a.Tokens) == 0 { | ||
| 27 | + a.Err = errors.New("empty token") | ||
| 28 | + } else { | ||
| 29 | + a.currIndex = 0 | ||
| 30 | + a.currTok = a.Tokens[0] | ||
| 31 | + } | ||
| 32 | + return a | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +// ParseExpression 解析表达式 | ||
| 36 | +func (a *AST) ParseExpression() ExprAST { | ||
| 37 | + a.depth++ // called depth | ||
| 38 | + lhs := a.parsePrimary() | ||
| 39 | + r := a.parseBinOpRHS(0, lhs) | ||
| 40 | + a.depth-- | ||
| 41 | + if a.depth == 0 && a.currIndex != len(a.Tokens) && a.Err == nil { | ||
| 42 | + a.Err = errors.New( | ||
| 43 | + fmt.Sprintf("bad expression, reaching the end or missing the operator\n%s", | ||
| 44 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 45 | + } | ||
| 46 | + return r | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +func (a *AST) getNextToken() *Token { | ||
| 50 | + a.currIndex++ | ||
| 51 | + if a.currIndex < len(a.Tokens) { | ||
| 52 | + a.currTok = a.Tokens[a.currIndex] | ||
| 53 | + return a.currTok | ||
| 54 | + } | ||
| 55 | + return nil | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +func (a *AST) getTokPrecedence() int { | ||
| 59 | + if p, ok := precedence[a.currTok.Tok]; ok { | ||
| 60 | + return p | ||
| 61 | + } | ||
| 62 | + return -1 | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +func (a *AST) parseNumber() NumberExprAST { | ||
| 66 | + f64, err := strconv.ParseFloat(a.currTok.Tok, 64) | ||
| 67 | + if err != nil { | ||
| 68 | + a.Err = errors.New( | ||
| 69 | + fmt.Sprintf("%v\nwant '(' or '0-9' but get '%s'\n%s", | ||
| 70 | + err.Error(), | ||
| 71 | + a.currTok.Tok, | ||
| 72 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 73 | + return NumberExprAST{} | ||
| 74 | + } | ||
| 75 | + n := NumberExprAST{ | ||
| 76 | + Val: f64, | ||
| 77 | + Str: a.currTok.Tok, | ||
| 78 | + } | ||
| 79 | + a.getNextToken() | ||
| 80 | + return n | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +func (a *AST) parseFunCallerOrConst() ExprAST { | ||
| 84 | + name := a.currTok.Tok | ||
| 85 | + a.getNextToken() | ||
| 86 | + // call func | ||
| 87 | + if a.currTok.Tok == "(" { | ||
| 88 | + f := FunCallerExprAST{} | ||
| 89 | + if _, ok := defFunc[strings.ToLower(name)]; !ok { | ||
| 90 | + a.Err = errors.New( | ||
| 91 | + fmt.Sprintf("function `%s` is undefined\n%s", | ||
| 92 | + name, | ||
| 93 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 94 | + return f | ||
| 95 | + } | ||
| 96 | + a.getNextToken() | ||
| 97 | + exprs := make([]ExprAST, 0) | ||
| 98 | + if a.currTok.Tok == ")" { | ||
| 99 | + // function call without parameters | ||
| 100 | + // ignore the process of parameter resolution | ||
| 101 | + } else { | ||
| 102 | + exprs = append(exprs, a.ParseExpression()) | ||
| 103 | + for a.currTok.Tok != ")" && a.getNextToken() != nil { | ||
| 104 | + if a.currTok.Type == COMMA { | ||
| 105 | + continue | ||
| 106 | + } | ||
| 107 | + exprs = append(exprs, a.ParseExpression()) | ||
| 108 | + } | ||
| 109 | + } | ||
| 110 | + def := defFunc[strings.ToLower(name)] | ||
| 111 | + if def.argc >= 0 && len(exprs) != def.argc { | ||
| 112 | + a.Err = errors.New( | ||
| 113 | + fmt.Sprintf("wrong way calling function `%s`, parameters want %d but get %d\n%s", | ||
| 114 | + name, | ||
| 115 | + def.argc, | ||
| 116 | + len(exprs), | ||
| 117 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 118 | + } | ||
| 119 | + a.getNextToken() | ||
| 120 | + f = NewFunCallerExprAST(name, exprs...) | ||
| 121 | + return f | ||
| 122 | + } | ||
| 123 | + // call const | ||
| 124 | + if v, ok := defConst[name]; ok { | ||
| 125 | + return NumberExprAST{ | ||
| 126 | + Val: v, | ||
| 127 | + Str: strconv.FormatFloat(v, 'f', 0, 64), | ||
| 128 | + } | ||
| 129 | + } else { | ||
| 130 | + if strings.Contains(name, ".") { | ||
| 131 | + return NewFieldExprAST(name) | ||
| 132 | + } | ||
| 133 | + a.Err = errors.New( | ||
| 134 | + fmt.Sprintf("const `%s` is undefined\n%s", | ||
| 135 | + name, | ||
| 136 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 137 | + return NumberExprAST{} | ||
| 138 | + } | ||
| 139 | +} | ||
| 140 | + | ||
| 141 | +func (a *AST) parsePrimary() ExprAST { | ||
| 142 | + switch a.currTok.Type { | ||
| 143 | + case Identifier: | ||
| 144 | + return a.parseFunCallerOrConst() | ||
| 145 | + case Literal: | ||
| 146 | + return a.parseNumber() | ||
| 147 | + case StringArgs: | ||
| 148 | + e := NewValueExprAST(a.currTok.Tok) | ||
| 149 | + a.getNextToken() | ||
| 150 | + return e | ||
| 151 | + case Operator: | ||
| 152 | + if a.currTok.Tok == "(" { | ||
| 153 | + t := a.getNextToken() | ||
| 154 | + if t == nil { | ||
| 155 | + a.Err = errors.New( | ||
| 156 | + fmt.Sprintf("want '(' or '0-9' but get EOF\n%s", | ||
| 157 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 158 | + return nil | ||
| 159 | + } | ||
| 160 | + e := a.ParseExpression() | ||
| 161 | + if e == nil { | ||
| 162 | + return nil | ||
| 163 | + } | ||
| 164 | + if a.currTok.Tok != ")" { | ||
| 165 | + a.Err = errors.New( | ||
| 166 | + fmt.Sprintf("want ')' but get %s\n%s", | ||
| 167 | + a.currTok.Tok, | ||
| 168 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 169 | + return nil | ||
| 170 | + } | ||
| 171 | + a.getNextToken() | ||
| 172 | + return e | ||
| 173 | + } else if a.currTok.Tok == "-" { | ||
| 174 | + if a.getNextToken() == nil { | ||
| 175 | + a.Err = errors.New( | ||
| 176 | + fmt.Sprintf("want '0-9' but get '-'\n%s", | ||
| 177 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 178 | + return nil | ||
| 179 | + } | ||
| 180 | + bin := NewBinaryExprAST("-", NumberExprAST{}, a.parsePrimary()) | ||
| 181 | + return bin | ||
| 182 | + } else { | ||
| 183 | + return a.parseNumber() | ||
| 184 | + } | ||
| 185 | + case COMMA: | ||
| 186 | + a.Err = errors.New( | ||
| 187 | + fmt.Sprintf("want '(' or '0-9' but get %s\n%s", | ||
| 188 | + a.currTok.Tok, | ||
| 189 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 190 | + return nil | ||
| 191 | + default: | ||
| 192 | + return nil | ||
| 193 | + } | ||
| 194 | +} | ||
| 195 | + | ||
| 196 | +func (a *AST) parseBinOpRHS(execPrec int, lhs ExprAST) ExprAST { | ||
| 197 | + for { | ||
| 198 | + tokPrec := a.getTokPrecedence() | ||
| 199 | + if tokPrec < execPrec { | ||
| 200 | + return lhs | ||
| 201 | + } | ||
| 202 | + binOp := a.currTok.Tok | ||
| 203 | + if a.getNextToken() == nil { | ||
| 204 | + a.Err = errors.New( | ||
| 205 | + fmt.Sprintf("want '(' or '0-9' but get EOF\n%s", | ||
| 206 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 207 | + return nil | ||
| 208 | + } | ||
| 209 | + rhs := a.parsePrimary() | ||
| 210 | + if rhs == nil { | ||
| 211 | + return nil | ||
| 212 | + } | ||
| 213 | + nextPrec := a.getTokPrecedence() | ||
| 214 | + if tokPrec < nextPrec { | ||
| 215 | + rhs = a.parseBinOpRHS(tokPrec+1, rhs) | ||
| 216 | + if rhs == nil { | ||
| 217 | + return nil | ||
| 218 | + } | ||
| 219 | + } | ||
| 220 | + lhs = NewBinaryExprAST(binOp, lhs, rhs) | ||
| 221 | + } | ||
| 222 | +} |
pkg/domain/astexpr/ast_def.go
0 → 100644
| 1 | +package astexpr | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "errors" | ||
| 5 | + "math" | ||
| 6 | +) | ||
| 7 | + | ||
| 8 | +const ( | ||
| 9 | + RadianMode = iota | ||
| 10 | + AngleMode | ||
| 11 | +) | ||
| 12 | + | ||
| 13 | +type defS struct { | ||
| 14 | + argc int | ||
| 15 | + fun func(expr ...ExprAST) float64 | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +// TrigonometricMode enum "RadianMode", "AngleMode" | ||
| 19 | +var TrigonometricMode = RadianMode | ||
| 20 | + | ||
| 21 | +var defConst = map[string]float64{ | ||
| 22 | + "pi": math.Pi, | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +var defFunc map[string]defS | ||
| 26 | + | ||
| 27 | +func init() { | ||
| 28 | + defFunc = map[string]defS{ | ||
| 29 | + "sin": {1, defSin}, | ||
| 30 | + "cos": {1, defCos}, | ||
| 31 | + "tan": {1, defTan}, | ||
| 32 | + "cot": {1, defCot}, | ||
| 33 | + "sec": {1, defSec}, | ||
| 34 | + "csc": {1, defCsc}, | ||
| 35 | + | ||
| 36 | + "abs": {1, defAbs}, | ||
| 37 | + "ceil": {1, defCeil}, | ||
| 38 | + "floor": {1, defFloor}, | ||
| 39 | + "round": {1, defRound}, | ||
| 40 | + "sqrt": {1, defSqrt}, | ||
| 41 | + "cbrt": {1, defCbrt}, | ||
| 42 | + | ||
| 43 | + "noerr": {1, defNoErr}, | ||
| 44 | + | ||
| 45 | + "max": {-1, defMax}, | ||
| 46 | + "min": {-1, defMin}, | ||
| 47 | + | ||
| 48 | + // excel support | ||
| 49 | + "sum": {-1, defNone}, | ||
| 50 | + "if": {-1, defNone}, | ||
| 51 | + "sumif": {-1, defNone}, | ||
| 52 | + "and": {-1, defNone}, | ||
| 53 | + "or": {-1, defNone}, | ||
| 54 | + "month": {-1, defNone}, | ||
| 55 | + "year": {-1, defNone}, | ||
| 56 | + //"round": {-1, defNone}, | ||
| 57 | + "rounddown": {-1, defNone}, | ||
| 58 | + "roundup": {-1, defNone}, | ||
| 59 | + "count": {-1, defNone}, | ||
| 60 | + "countifs": {-1, defNone}, | ||
| 61 | + //"&": {-1, defNone}, | ||
| 62 | + "concat": {-1, defNone}, | ||
| 63 | + "sumifs": {-1, defNone}, | ||
| 64 | + } | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +// sin(pi/2) = 1 | ||
| 68 | +func defSin(expr ...ExprAST) float64 { | ||
| 69 | + return math.Sin(expr2Radian(expr[0])) | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +// cos(0) = 1 | ||
| 73 | +func defCos(expr ...ExprAST) float64 { | ||
| 74 | + return math.Cos(expr2Radian(expr[0])) | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +// tan(pi/4) = 1 | ||
| 78 | +func defTan(expr ...ExprAST) float64 { | ||
| 79 | + return math.Tan(expr2Radian(expr[0])) | ||
| 80 | +} | ||
| 81 | + | ||
| 82 | +// cot(pi/4) = 1 | ||
| 83 | +func defCot(expr ...ExprAST) float64 { | ||
| 84 | + return 1 / defTan(expr...) | ||
| 85 | +} | ||
| 86 | + | ||
| 87 | +// sec(0) = 1 | ||
| 88 | +func defSec(expr ...ExprAST) float64 { | ||
| 89 | + return 1 / defCos(expr...) | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +// csc(pi/2) = 1 | ||
| 93 | +func defCsc(expr ...ExprAST) float64 { | ||
| 94 | + return 1 / defSin(expr...) | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +// abs(-2) = 2 | ||
| 98 | +func defAbs(expr ...ExprAST) float64 { | ||
| 99 | + return math.Abs(ExprASTResult(expr[0])) | ||
| 100 | +} | ||
| 101 | + | ||
| 102 | +// ceil(4.2) = ceil(4.8) = 5 | ||
| 103 | +func defCeil(expr ...ExprAST) float64 { | ||
| 104 | + return math.Ceil(ExprASTResult(expr[0])) | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +// floor(4.2) = floor(4.8) = 4 | ||
| 108 | +func defFloor(expr ...ExprAST) float64 { | ||
| 109 | + return math.Floor(ExprASTResult(expr[0])) | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +// round(4.2) = 4 | ||
| 113 | +// round(4.6) = 5 | ||
| 114 | +func defRound(expr ...ExprAST) float64 { | ||
| 115 | + return math.Round(ExprASTResult(expr[0])) | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +// sqrt(4) = 2 | ||
| 119 | +// sqrt(4) = abs(sqrt(4)) | ||
| 120 | +// returns only the absolute value of the result | ||
| 121 | +func defSqrt(expr ...ExprAST) float64 { | ||
| 122 | + return math.Sqrt(ExprASTResult(expr[0])) | ||
| 123 | +} | ||
| 124 | + | ||
| 125 | +// cbrt(27) = 3 | ||
| 126 | +func defCbrt(expr ...ExprAST) float64 { | ||
| 127 | + return math.Cbrt(ExprASTResult(expr[0])) | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +// max(2) = 2 | ||
| 131 | +// max(2, 3) = 3 | ||
| 132 | +// max(2, 3, 1) = 3 | ||
| 133 | +func defMax(expr ...ExprAST) float64 { | ||
| 134 | + if len(expr) == 0 { | ||
| 135 | + panic(errors.New("calling function `max` must have at least one parameter.")) | ||
| 136 | + } | ||
| 137 | + if len(expr) == 1 { | ||
| 138 | + return ExprASTResult(expr[0]) | ||
| 139 | + } | ||
| 140 | + maxV := ExprASTResult(expr[0]) | ||
| 141 | + for i := 1; i < len(expr); i++ { | ||
| 142 | + v := ExprASTResult(expr[i]) | ||
| 143 | + maxV = math.Max(maxV, v) | ||
| 144 | + } | ||
| 145 | + return maxV | ||
| 146 | +} | ||
| 147 | + | ||
| 148 | +// min(2) = 2 | ||
| 149 | +// min(2, 3) = 2 | ||
| 150 | +// min(2, 3, 1) = 1 | ||
| 151 | +func defMin(expr ...ExprAST) float64 { | ||
| 152 | + if len(expr) == 0 { | ||
| 153 | + panic(errors.New("calling function `min` must have at least one parameter.")) | ||
| 154 | + } | ||
| 155 | + if len(expr) == 1 { | ||
| 156 | + return ExprASTResult(expr[0]) | ||
| 157 | + } | ||
| 158 | + maxV := ExprASTResult(expr[0]) | ||
| 159 | + for i := 1; i < len(expr); i++ { | ||
| 160 | + v := ExprASTResult(expr[i]) | ||
| 161 | + maxV = math.Min(maxV, v) | ||
| 162 | + } | ||
| 163 | + return maxV | ||
| 164 | +} | ||
| 165 | + | ||
| 166 | +// noerr(1/0) = 0 | ||
| 167 | +// noerr(2.5/(1-1)) = 0 | ||
| 168 | +func defNoErr(expr ...ExprAST) (r float64) { | ||
| 169 | + defer func() { | ||
| 170 | + if e := recover(); e != nil { | ||
| 171 | + r = 0 | ||
| 172 | + } | ||
| 173 | + }() | ||
| 174 | + return ExprASTResult(expr[0]) | ||
| 175 | +} | ||
| 176 | + | ||
| 177 | +func defNone(expr ...ExprAST) (r float64) { | ||
| 178 | + return 0 | ||
| 179 | +} |
| 1 | -package domain | 1 | +package astexpr |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "encoding/json" | 4 | "encoding/json" |
| 5 | "fmt" | 5 | "fmt" |
| 6 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" | ||
| 6 | ) | 7 | ) |
| 7 | 8 | ||
| 8 | const ( | 9 | const ( |
| @@ -10,12 +11,19 @@ const ( | @@ -10,12 +11,19 @@ const ( | ||
| 10 | TypeBinaryExprAST = "BinaryExprAST" | 11 | TypeBinaryExprAST = "BinaryExprAST" |
| 11 | TypeFieldExprAST = "FieldExprAST" | 12 | TypeFieldExprAST = "FieldExprAST" |
| 12 | TypeValueExprAST = "ValueExprAST" | 13 | TypeValueExprAST = "ValueExprAST" |
| 14 | + TypeNumberExprAST = "NumberExprAST" | ||
| 13 | ) | 15 | ) |
| 14 | 16 | ||
| 15 | type ExprAST interface { | 17 | type ExprAST interface { |
| 16 | toStr() string | 18 | toStr() string |
| 17 | } | 19 | } |
| 18 | 20 | ||
| 21 | +type NumberExprAST struct { | ||
| 22 | + ExprType string `json:"exprType"` | ||
| 23 | + Val float64 | ||
| 24 | + Str string | ||
| 25 | +} | ||
| 26 | + | ||
| 19 | type ValueExprAST struct { | 27 | type ValueExprAST struct { |
| 20 | ExprType string `json:"exprType"` | 28 | ExprType string `json:"exprType"` |
| 21 | Val string `json:"val"` | 29 | Val string `json:"val"` |
| @@ -25,7 +33,7 @@ type ValueExprAST struct { | @@ -25,7 +33,7 @@ type ValueExprAST struct { | ||
| 25 | type FieldExprAST struct { | 33 | type FieldExprAST struct { |
| 26 | ExprType string `json:"exprType"` | 34 | ExprType string `json:"exprType"` |
| 27 | Str string `json:"str"` | 35 | Str string `json:"str"` |
| 28 | - Field *TableField `json:"field"` | 36 | + Field *domain.TableField `json:"field"` |
| 29 | } | 37 | } |
| 30 | 38 | ||
| 31 | type BinaryExprAST struct { | 39 | type BinaryExprAST struct { |
| @@ -36,12 +44,19 @@ type BinaryExprAST struct { | @@ -36,12 +44,19 @@ type BinaryExprAST struct { | ||
| 36 | } | 44 | } |
| 37 | 45 | ||
| 38 | type FunCallerExprAST struct { | 46 | type FunCallerExprAST struct { |
| 39 | - ArrayFlag bool `json:"arrayFlag"` | 47 | + //ArrayFlag bool `json:"arrayFlag"` |
| 40 | ExprType string `json:"exprType"` | 48 | ExprType string `json:"exprType"` |
| 41 | Name string `json:"name"` | 49 | Name string `json:"name"` |
| 42 | Args []ExprAST `json:"args"` | 50 | Args []ExprAST `json:"args"` |
| 43 | } | 51 | } |
| 44 | 52 | ||
| 53 | +func (n NumberExprAST) toStr() string { | ||
| 54 | + return fmt.Sprintf( | ||
| 55 | + "NumberExprAST:%s", | ||
| 56 | + n.Str, | ||
| 57 | + ) | ||
| 58 | +} | ||
| 59 | + | ||
| 45 | func (n ValueExprAST) toStr() string { | 60 | func (n ValueExprAST) toStr() string { |
| 46 | return fmt.Sprintf( | 61 | return fmt.Sprintf( |
| 47 | "ValueExprAST:%s", | 62 | "ValueExprAST:%s", |
| @@ -73,7 +88,7 @@ func (n FunCallerExprAST) toStr() string { | @@ -73,7 +88,7 @@ func (n FunCallerExprAST) toStr() string { | ||
| 73 | } | 88 | } |
| 74 | 89 | ||
| 75 | type CloneFunCallerExprAST struct { | 90 | type CloneFunCallerExprAST struct { |
| 76 | - ArrayFlag bool `json:"arrayFlag"` | 91 | + //ArrayFlag bool `json:"arrayFlag"` |
| 77 | Name string `json:"name"` | 92 | Name string `json:"name"` |
| 78 | Arg []json.RawMessage `json:"args"` | 93 | Arg []json.RawMessage `json:"args"` |
| 79 | } | 94 | } |
| @@ -84,7 +99,7 @@ type CloneBinaryExprAST struct { | @@ -84,7 +99,7 @@ type CloneBinaryExprAST struct { | ||
| 84 | Rhs json.RawMessage `json:"rhs"` | 99 | Rhs json.RawMessage `json:"rhs"` |
| 85 | } | 100 | } |
| 86 | 101 | ||
| 87 | -func AstExprUnmarshalJSON(data []byte) (interface{}, error) { | 102 | +func ExprUnmarshal(data []byte) (interface{}, error) { |
| 88 | var m = make(map[string]interface{}) | 103 | var m = make(map[string]interface{}) |
| 89 | if err := json.Unmarshal(data, &m); err != nil { | 104 | if err := json.Unmarshal(data, &m); err != nil { |
| 90 | return nil, err | 105 | return nil, err |
| @@ -108,7 +123,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | @@ -108,7 +123,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | ||
| 108 | } | 123 | } |
| 109 | exprReturn := &FunCallerExprAST{Name: expr.Name, ExprType: TypeFunCallerExprAST} | 124 | exprReturn := &FunCallerExprAST{Name: expr.Name, ExprType: TypeFunCallerExprAST} |
| 110 | for i := range expr.Arg { | 125 | for i := range expr.Arg { |
| 111 | - subExpr, err := AstExprUnmarshalJSON(expr.Arg[i]) | 126 | + subExpr, err := ExprUnmarshal(expr.Arg[i]) |
| 112 | if err != nil { | 127 | if err != nil { |
| 113 | return nil, err | 128 | return nil, err |
| 114 | } | 129 | } |
| @@ -126,7 +141,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | @@ -126,7 +141,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | ||
| 126 | } | 141 | } |
| 127 | exprReturn := &BinaryExprAST{Op: expr.Op, ExprType: TypeBinaryExprAST} | 142 | exprReturn := &BinaryExprAST{Op: expr.Op, ExprType: TypeBinaryExprAST} |
| 128 | if len(expr.Lhs) > 0 { | 143 | if len(expr.Lhs) > 0 { |
| 129 | - subExpr, err := AstExprUnmarshalJSON(expr.Lhs) | 144 | + subExpr, err := ExprUnmarshal(expr.Lhs) |
| 130 | if err != nil { | 145 | if err != nil { |
| 131 | return nil, err | 146 | return nil, err |
| 132 | } | 147 | } |
| @@ -135,7 +150,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | @@ -135,7 +150,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | ||
| 135 | } | 150 | } |
| 136 | } | 151 | } |
| 137 | if len(expr.Rhs) > 0 { | 152 | if len(expr.Rhs) > 0 { |
| 138 | - subExpr, err := AstExprUnmarshalJSON(expr.Rhs) | 153 | + subExpr, err := ExprUnmarshal(expr.Rhs) |
| 139 | if err != nil { | 154 | if err != nil { |
| 140 | return nil, err | 155 | return nil, err |
| 141 | } | 156 | } |
pkg/domain/astexpr/ast_expr_builder.go
0 → 100644
| 1 | +package astexpr | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" | ||
| 5 | + "strconv" | ||
| 6 | + "strings" | ||
| 7 | +) | ||
| 8 | + | ||
| 9 | +func NewNumberExprAST(val string) NumberExprAST { | ||
| 10 | + num, _ := strconv.ParseFloat(val, 64) | ||
| 11 | + return NumberExprAST{ | ||
| 12 | + ExprType: TypeNumberExprAST, | ||
| 13 | + Val: num, | ||
| 14 | + Str: val, | ||
| 15 | + } | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +func NewValueExprAST(val string) ValueExprAST { | ||
| 19 | + return ValueExprAST{ | ||
| 20 | + ExprType: TypeValueExprAST, | ||
| 21 | + Val: val, | ||
| 22 | + Str: val, | ||
| 23 | + } | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +func NewFieldExprAST(val string) FieldExprAST { | ||
| 27 | + words := strings.Split(val, ".") | ||
| 28 | + table := words[0] | ||
| 29 | + filed := words[0] | ||
| 30 | + if len(words) > 0 { | ||
| 31 | + filed = words[1] | ||
| 32 | + } | ||
| 33 | + return FieldExprAST{ | ||
| 34 | + ExprType: TypeFieldExprAST, | ||
| 35 | + Str: val, | ||
| 36 | + Field: &domain.TableField{ | ||
| 37 | + FieldName: filed, | ||
| 38 | + FieldSqlName: filed, | ||
| 39 | + TableName: table, | ||
| 40 | + }, | ||
| 41 | + } | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +func NewBinaryExprAST(op string, lhs ExprAST, rhs ExprAST) BinaryExprAST { | ||
| 45 | + return BinaryExprAST{ | ||
| 46 | + ExprType: TypeBinaryExprAST, | ||
| 47 | + Op: op, | ||
| 48 | + Lhs: lhs, | ||
| 49 | + Rhs: rhs, | ||
| 50 | + } | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +func NewFunCallerExprAST(name string, args ...ExprAST) FunCallerExprAST { | ||
| 54 | + return FunCallerExprAST{ | ||
| 55 | + ExprType: TypeFunCallerExprAST, | ||
| 56 | + Name: name, | ||
| 57 | + Args: args, | ||
| 58 | + } | ||
| 59 | +} |
pkg/domain/astexpr/ast_expr_calculator.go
0 → 100644
| 1 | +package astexpr | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "fmt" | ||
| 5 | + "github.com/go-gota/gota/dataframe" | ||
| 6 | + "github.com/go-gota/gota/series" | ||
| 7 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" | ||
| 8 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/utils" | ||
| 9 | + "strings" | ||
| 10 | +) | ||
| 11 | + | ||
| 12 | +type Calculator struct { | ||
| 13 | + ExprAST ExprAST | ||
| 14 | + DataTable *domain.DataTable | ||
| 15 | + Result []string | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +func NewCalculator(expr string) (*Calculator, error) { | ||
| 19 | + toks, err := ParseToken(expr) | ||
| 20 | + if err != nil { | ||
| 21 | + return nil, err | ||
| 22 | + } | ||
| 23 | + ast := NewAST(toks, expr) | ||
| 24 | + if ast.Err != nil { | ||
| 25 | + return nil, ast.Err | ||
| 26 | + } | ||
| 27 | + ar := ast.ParseExpression() | ||
| 28 | + if ast.Err != nil { | ||
| 29 | + return nil, ast.Err | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + cal := &Calculator{ | ||
| 33 | + ExprAST: ar, | ||
| 34 | + } | ||
| 35 | + return cal, nil | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +func (cal *Calculator) SetDataTable(t *domain.DataTable) *Calculator { | ||
| 39 | + cal.DataTable = t | ||
| 40 | + return cal | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +func (cal *Calculator) Exec() error { | ||
| 44 | + return nil | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +func (cal *Calculator) ExprASTResult(ast ExprAST) (*param, error) { | ||
| 48 | + switch ast.(type) { | ||
| 49 | + case BinaryExprAST: | ||
| 50 | + var l, r *param | ||
| 51 | + var err error | ||
| 52 | + ast := ast.(BinaryExprAST) | ||
| 53 | + l, err = cal.ExprASTResult(ast.Lhs) | ||
| 54 | + if err != nil { | ||
| 55 | + return nil, err | ||
| 56 | + } | ||
| 57 | + r, err = cal.ExprASTResult(ast.Rhs) | ||
| 58 | + if err != nil { | ||
| 59 | + return nil, err | ||
| 60 | + } | ||
| 61 | + switch ast.Op { | ||
| 62 | + case "+", "-", "*", "/", "%": | ||
| 63 | + return cal.OpCalc(ast.Op, l, r), nil | ||
| 64 | + default: | ||
| 65 | + | ||
| 66 | + } | ||
| 67 | + case NumberExprAST: | ||
| 68 | + f := ast.(NumberExprAST) | ||
| 69 | + return NewResult([]string{f.Str}), nil | ||
| 70 | + case ValueExprAST: | ||
| 71 | + f := ast.(ValueExprAST) | ||
| 72 | + return NewResult([]string{f.Val}), nil | ||
| 73 | + case FieldExprAST: | ||
| 74 | + f := ast.(FieldExprAST) | ||
| 75 | + values := cal.DataTable.Values(&domain.Field{SQLName: f.Field.FieldSqlName}) | ||
| 76 | + return NewResult(values), nil | ||
| 77 | + case FunCallerExprAST: | ||
| 78 | + f := ast.(FunCallerExprAST) | ||
| 79 | + //def := defFunc[f.Name] | ||
| 80 | + //def.fun(f.Args...) | ||
| 81 | + | ||
| 82 | + args := make([]*param, 0) | ||
| 83 | + for i := range f.Args { | ||
| 84 | + argValue, err := cal.ExprASTResult(f.Args[i]) | ||
| 85 | + if err != nil { | ||
| 86 | + return nil, err | ||
| 87 | + } | ||
| 88 | + args = append(args, argValue) | ||
| 89 | + } | ||
| 90 | + return cal.callDef(f.Name, args), nil | ||
| 91 | + } | ||
| 92 | + return nil, nil | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +func (cal *Calculator) callDef(name string, args []*param) *param { | ||
| 96 | + switch strings.ToLower(name) { | ||
| 97 | + case "sum": | ||
| 98 | + return cal.sum(args...) | ||
| 99 | + case "sumifs": | ||
| 100 | + return cal.sumifs(args...) | ||
| 101 | + case "countifs": | ||
| 102 | + return cal.countifs(args...) | ||
| 103 | + } | ||
| 104 | + return cal.sum(args...) | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +func (cal *Calculator) sum(params ...*param) *param { | ||
| 108 | + var res = make([]string, 0) | ||
| 109 | + var total float64 | ||
| 110 | + for _, p := range params { | ||
| 111 | + for _, v := range p.data { | ||
| 112 | + total += utils.NewNumberString(v).MustFloat64() | ||
| 113 | + } | ||
| 114 | + } | ||
| 115 | + res = append(res, utils.AssertString(total)) | ||
| 116 | + return NewResult(res) | ||
| 117 | +} | ||
| 118 | + | ||
| 119 | +func (cal *Calculator) sumifs(params ...*param) *param { | ||
| 120 | + var list = make([]series.Series, 0) | ||
| 121 | + var filters = make([]dataframe.F, 0) | ||
| 122 | + for i := 0; i < len(params)-1; i++ { | ||
| 123 | + col := colName(i) | ||
| 124 | + if i == 0 { | ||
| 125 | + list = append(list, series.New(params[i].Data(), series.Float, col)) | ||
| 126 | + continue | ||
| 127 | + } | ||
| 128 | + if i%2 == 1 { | ||
| 129 | + list = append(list, series.New(params[i+1].Data(), series.String, col)) | ||
| 130 | + if f, ok := cal.resolverFilter(col, params[i]); ok { | ||
| 131 | + filters = append(filters, f) | ||
| 132 | + } | ||
| 133 | + i++ | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | + df := dataframe.New(list...) | ||
| 137 | + df = df.FilterAggregation(dataframe.And, filters...) | ||
| 138 | + s := df.Col("A0") | ||
| 139 | + return NewResult(s.Records()) | ||
| 140 | +} | ||
| 141 | + | ||
| 142 | +func (cal *Calculator) countifs(params ...*param) *param { | ||
| 143 | + var list = make([]series.Series, 0) | ||
| 144 | + var filters = make([]dataframe.F, 0) | ||
| 145 | + for i := 0; i < len(params)-1; i++ { | ||
| 146 | + col := colName(i) | ||
| 147 | + if i%2 == 0 { | ||
| 148 | + list = append(list, series.New(params[i].Data(), series.String, col)) | ||
| 149 | + if f, ok := cal.resolverFilter(col, params[i+1]); ok { | ||
| 150 | + filters = append(filters, f) | ||
| 151 | + } | ||
| 152 | + i++ | ||
| 153 | + } | ||
| 154 | + } | ||
| 155 | + df := dataframe.New(list...) | ||
| 156 | + df = df.FilterAggregation(dataframe.And, filters...) | ||
| 157 | + count := df.Col("A0").Len() | ||
| 158 | + return NewResult([]string{fmt.Sprintf("%d", count)}) | ||
| 159 | +} | ||
| 160 | + | ||
| 161 | +func (cal *Calculator) resolverFilter(key string, param *param) (dataframe.F, bool) { | ||
| 162 | + if len(param.Data()) == 1 { | ||
| 163 | + condition := param.Data()[0] | ||
| 164 | + tokens, _ := ParseToken(formatTok(condition)) | ||
| 165 | + | ||
| 166 | + switch tokens[0].Type { | ||
| 167 | + case Operator, CompareOperator: | ||
| 168 | + if tokens[0].Tok == "*" { | ||
| 169 | + return dataframe.F{Colname: key, Comparator: series.CompFunc, Comparando: func(el series.Element) bool { | ||
| 170 | + return strings.Contains(el.String(), strings.Trim(formatTok(condition), "*")) | ||
| 171 | + }}, true | ||
| 172 | + } | ||
| 173 | + return dataframe.F{Colname: key, Comparator: series.Comparator(tokens[0].Tok), Comparando: formatTok(tokens[1].Tok)}, true | ||
| 174 | + case Identifier, Literal, StringArgs: | ||
| 175 | + if tokens[len(tokens)-1].Tok == "*" || tokens[0].Tok == "*" { | ||
| 176 | + return dataframe.F{Colname: key, Comparator: series.CompFunc, Comparando: func(el series.Element) bool { | ||
| 177 | + return strings.Contains(el.String(), strings.Trim(formatTok(condition), "*")) | ||
| 178 | + }}, true | ||
| 179 | + } | ||
| 180 | + return dataframe.F{Colname: key, Comparator: series.Eq, Comparando: formatTok(condition)}, true | ||
| 181 | + } | ||
| 182 | + } | ||
| 183 | + return dataframe.F{}, false | ||
| 184 | +} | ||
| 185 | + | ||
| 186 | +func colName(i int) string { | ||
| 187 | + return fmt.Sprintf("A%v", i) | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +func formatTok(tok string) string { | ||
| 191 | + return strings.Trim(tok, `"`) | ||
| 192 | +} | ||
| 193 | + | ||
| 194 | +func (cal *Calculator) OpCalc(op string, lp *param, rp *param) *param { | ||
| 195 | + var res = make([]string, 0) | ||
| 196 | + temp := make([]string, 0) | ||
| 197 | + temp = lp.Data() | ||
| 198 | + l := lp.Data() | ||
| 199 | + r := rp.Data() | ||
| 200 | + if lp.Len() < rp.Len() { | ||
| 201 | + l = r | ||
| 202 | + r = temp | ||
| 203 | + } | ||
| 204 | + rIsSingleValue := len(r) == 1 | ||
| 205 | + var rValue string | ||
| 206 | + if rIsSingleValue { | ||
| 207 | + rValue = r[0] | ||
| 208 | + } | ||
| 209 | + for i, lValue := range l { | ||
| 210 | + if rIsSingleValue { | ||
| 211 | + res = append(res, opCalc(op, lValue, rValue)) | ||
| 212 | + continue | ||
| 213 | + } | ||
| 214 | + if i >= len(r) { | ||
| 215 | + break | ||
| 216 | + } | ||
| 217 | + res = append(res, opCalc(op, lValue, r[i])) | ||
| 218 | + } | ||
| 219 | + return NewResult(res) | ||
| 220 | +} | ||
| 221 | + | ||
| 222 | +func opCalc(op, v1, v2 string) string { | ||
| 223 | + fv1 := utils.NumberString(v1).MustFloat64() | ||
| 224 | + fv2 := utils.NumberString(v2).MustFloat64() | ||
| 225 | + switch op { | ||
| 226 | + case "+": | ||
| 227 | + return utils.AssertString(fv1 + fv2) | ||
| 228 | + case "-": | ||
| 229 | + return utils.AssertString(fv1 - fv2) | ||
| 230 | + case "*": | ||
| 231 | + return utils.AssertString(fv1 * fv2) | ||
| 232 | + case "/": | ||
| 233 | + return utils.AssertString(fv1 / fv2) | ||
| 234 | + } | ||
| 235 | + return "" | ||
| 236 | +} | ||
| 237 | + | ||
| 238 | +type param struct { | ||
| 239 | + data []string | ||
| 240 | +} | ||
| 241 | + | ||
| 242 | +func (p *param) Len() int { | ||
| 243 | + return len(p.data) | ||
| 244 | +} | ||
| 245 | + | ||
| 246 | +func (p *param) Data() []string { | ||
| 247 | + return p.data | ||
| 248 | +} | ||
| 249 | + | ||
| 250 | +func NewResult(data []string) *param { | ||
| 251 | + return ¶m{ | ||
| 252 | + data: data, | ||
| 253 | + } | ||
| 254 | +} |
| 1 | +package astexpr | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "github.com/stretchr/testify/assert" | ||
| 5 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" | ||
| 6 | + "testing" | ||
| 7 | +) | ||
| 8 | + | ||
| 9 | +var table = &domain.DataTable{ | ||
| 10 | + Fields: []*domain.Field{ | ||
| 11 | + { | ||
| 12 | + Name: "业绩", | ||
| 13 | + SQLName: "业绩", | ||
| 14 | + }, | ||
| 15 | + { | ||
| 16 | + Name: "绩效", | ||
| 17 | + SQLName: "绩效", | ||
| 18 | + }, | ||
| 19 | + { | ||
| 20 | + Name: "月份", | ||
| 21 | + SQLName: "月份", | ||
| 22 | + }, | ||
| 23 | + }, | ||
| 24 | + Data: [][]string{ | ||
| 25 | + { | ||
| 26 | + "10000", "0.90", "2月", | ||
| 27 | + }, | ||
| 28 | + { | ||
| 29 | + "20000", "0.95", "3月", | ||
| 30 | + }, | ||
| 31 | + { | ||
| 32 | + "20000", "0.95", "3月", | ||
| 33 | + }, | ||
| 34 | + }, | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +var table1 = &domain.DataTable{ | ||
| 38 | + Fields: []*domain.Field{ | ||
| 39 | + { | ||
| 40 | + Name: "产品", | ||
| 41 | + SQLName: "产品", | ||
| 42 | + }, | ||
| 43 | + { | ||
| 44 | + Name: "季度", | ||
| 45 | + SQLName: "季度", | ||
| 46 | + }, | ||
| 47 | + { | ||
| 48 | + Name: "数量", | ||
| 49 | + SQLName: "数量", | ||
| 50 | + }, | ||
| 51 | + }, | ||
| 52 | + Data: [][]string{ | ||
| 53 | + { | ||
| 54 | + "苹果", "第一季度", "800", | ||
| 55 | + }, | ||
| 56 | + { | ||
| 57 | + "香蕉", "第一季度", "906", | ||
| 58 | + }, | ||
| 59 | + { | ||
| 60 | + "西瓜", "第一季度", "968", | ||
| 61 | + }, | ||
| 62 | + { | ||
| 63 | + "菠萝", "第一季度", "227", | ||
| 64 | + }, | ||
| 65 | + { | ||
| 66 | + "芒果", "第一季度", "612", | ||
| 67 | + }, | ||
| 68 | + { | ||
| 69 | + "苹果", "第二季度", "530", | ||
| 70 | + }, | ||
| 71 | + { | ||
| 72 | + "香蕉", "第二季度", "950", | ||
| 73 | + }, | ||
| 74 | + { | ||
| 75 | + "西瓜", "第二季度", "533", | ||
| 76 | + }, | ||
| 77 | + { | ||
| 78 | + "菠萝", "第二季度", "642", | ||
| 79 | + }, | ||
| 80 | + { | ||
| 81 | + "芒果", "第二季度", "489", | ||
| 82 | + }, | ||
| 83 | + }, | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +func TestSumCalculator(t *testing.T) { | ||
| 87 | + inputs := []struct { | ||
| 88 | + expr string | ||
| 89 | + want []string | ||
| 90 | + }{ | ||
| 91 | + { | ||
| 92 | + expr: `sum(1000+销售明细.业绩*销售明细.绩效)`, | ||
| 93 | + want: []string{"50000"}, | ||
| 94 | + }, | ||
| 95 | + { | ||
| 96 | + expr: `sum(销售明细.业绩)`, | ||
| 97 | + want: []string{"50000"}, | ||
| 98 | + }, | ||
| 99 | + { | ||
| 100 | + expr: `sum(销售明细.业绩/10)`, | ||
| 101 | + want: []string{"5000"}, | ||
| 102 | + }, | ||
| 103 | + { | ||
| 104 | + expr: `sum(10000,20000,20000)`, | ||
| 105 | + want: []string{"50000"}, | ||
| 106 | + }, | ||
| 107 | + } | ||
| 108 | + for _, expr := range inputs { | ||
| 109 | + calc, err := NewCalculator(expr.expr) | ||
| 110 | + if err != nil { | ||
| 111 | + t.Fatal(err) | ||
| 112 | + } | ||
| 113 | + calc.SetDataTable(table) | ||
| 114 | + got, err := calc.ExprASTResult(calc.ExprAST) | ||
| 115 | + if err != nil { | ||
| 116 | + t.Fatal(err) | ||
| 117 | + } | ||
| 118 | + assert.Equal(t, expr.want, got.data) | ||
| 119 | + } | ||
| 120 | +} | ||
| 121 | + | ||
| 122 | +func TestSumIfCalculator(t *testing.T) { | ||
| 123 | + inputs := []struct { | ||
| 124 | + expr string | ||
| 125 | + want []string | ||
| 126 | + }{ | ||
| 127 | + { | ||
| 128 | + expr: `sum(sumifs(销售明细.业绩,"3月",销售明细.月份))`, | ||
| 129 | + want: []string{"40000"}, | ||
| 130 | + }, | ||
| 131 | + { | ||
| 132 | + expr: `sum(sumifs(销售明细.业绩,"3月",销售明细.月份,"<25000",销售明细.业绩))`, | ||
| 133 | + want: []string{"40000"}, | ||
| 134 | + }, | ||
| 135 | + { | ||
| 136 | + expr: `sum(sumifs(销售明细.业绩,"3*",销售明细.月份))`, | ||
| 137 | + want: []string{"40000"}, | ||
| 138 | + }, | ||
| 139 | + { | ||
| 140 | + expr: `sum(sumifs(销售明细.业绩,"*月",销售明细.月份))`, | ||
| 141 | + want: []string{"50000"}, | ||
| 142 | + }, | ||
| 143 | + } | ||
| 144 | + for _, expr := range inputs { | ||
| 145 | + calc, err := NewCalculator(expr.expr) | ||
| 146 | + if err != nil { | ||
| 147 | + t.Fatal(err) | ||
| 148 | + } | ||
| 149 | + calc.SetDataTable(table) | ||
| 150 | + got, err := calc.ExprASTResult(calc.ExprAST) | ||
| 151 | + if err != nil { | ||
| 152 | + t.Fatal(err) | ||
| 153 | + } | ||
| 154 | + assert.Equal(t, expr.want, got.data) | ||
| 155 | + } | ||
| 156 | +} | ||
| 157 | + | ||
| 158 | +func TestCountIfCalculator(t *testing.T) { | ||
| 159 | + inputs := []struct { | ||
| 160 | + expr string | ||
| 161 | + want []string | ||
| 162 | + }{ | ||
| 163 | + { | ||
| 164 | + expr: `countifs(水果季度.产品,"苹果")`, | ||
| 165 | + want: []string{"2"}, | ||
| 166 | + }, | ||
| 167 | + { | ||
| 168 | + expr: `countifs(水果季度.产品,"*果")`, | ||
| 169 | + want: []string{"4"}, | ||
| 170 | + }, | ||
| 171 | + { | ||
| 172 | + expr: `countifs(水果季度.季度,"第一季度",水果季度.数量,">600")`, | ||
| 173 | + want: []string{"4"}, | ||
| 174 | + }, | ||
| 175 | + { | ||
| 176 | + expr: `countifs(水果季度.产品,"*果",水果季度.数量,">600")`, | ||
| 177 | + want: []string{"2"}, | ||
| 178 | + }, | ||
| 179 | + } | ||
| 180 | + for _, expr := range inputs { | ||
| 181 | + calc, err := NewCalculator(expr.expr) | ||
| 182 | + if err != nil { | ||
| 183 | + t.Fatal(err) | ||
| 184 | + } | ||
| 185 | + calc.SetDataTable(table1) | ||
| 186 | + got, err := calc.ExprASTResult(calc.ExprAST) | ||
| 187 | + if err != nil { | ||
| 188 | + t.Fatal(err) | ||
| 189 | + } | ||
| 190 | + assert.Equal(t, expr.want, got.data) | ||
| 191 | + } | ||
| 192 | +} |
| 1 | -package domain | 1 | +package astexpr |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | + "fmt" | ||
| 5 | + "testing" | ||
| 6 | + | ||
| 4 | "github.com/linmadan/egglib-go/utils/json" | 7 | "github.com/linmadan/egglib-go/utils/json" |
| 5 | "github.com/stretchr/testify/assert" | 8 | "github.com/stretchr/testify/assert" |
| 6 | - "testing" | 9 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" |
| 7 | ) | 10 | ) |
| 8 | 11 | ||
| 9 | func TestAstExprUnmarshalJSON(t *testing.T) { | 12 | func TestAstExprUnmarshalJSON(t *testing.T) { |
| @@ -57,7 +60,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | @@ -57,7 +60,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | ||
| 57 | &FieldExprAST{ | 60 | &FieldExprAST{ |
| 58 | ExprType: TypeFieldExprAST, | 61 | ExprType: TypeFieldExprAST, |
| 59 | Str: "业绩1", | 62 | Str: "业绩1", |
| 60 | - Field: &TableField{ | 63 | + Field: &domain.TableField{ |
| 61 | TableId: 1, | 64 | TableId: 1, |
| 62 | TableName: "测试ABC", | 65 | TableName: "测试ABC", |
| 63 | TableSqlName: "table_abc_test", | 66 | TableSqlName: "table_abc_test", |
| @@ -83,7 +86,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | @@ -83,7 +86,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | ||
| 83 | if err != nil { | 86 | if err != nil { |
| 84 | t.Fatal(err) | 87 | t.Fatal(err) |
| 85 | } | 88 | } |
| 86 | - v, err := AstExprUnmarshalJSON(data) | 89 | + v, err := ExprUnmarshal(data) |
| 87 | if err != nil { | 90 | if err != nil { |
| 88 | t.Fatal(err) | 91 | t.Fatal(err) |
| 89 | } | 92 | } |
| @@ -93,3 +96,45 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | @@ -93,3 +96,45 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | ||
| 93 | assert.Equal(t, input.data, v) | 96 | assert.Equal(t, input.data, v) |
| 94 | } | 97 | } |
| 95 | } | 98 | } |
| 99 | + | ||
| 100 | +func TestAstExprParse(t *testing.T) { | ||
| 101 | + funs := []struct { | ||
| 102 | + Name string | ||
| 103 | + Exp []string | ||
| 104 | + Debug bool | ||
| 105 | + }{ | ||
| 106 | + { | ||
| 107 | + "COUNTIF 多级嵌套", | ||
| 108 | + []string{ | ||
| 109 | + `COUNTIF(销售明细.业绩,"<=1000")-COUNTIF(销售明细.业绩,"<=100")`, | ||
| 110 | + `SUM(1/(COUNTIF(销售明细.业绩,"<=1000")-COUNTIF(销售明细.业绩,"<=100")))`, | ||
| 111 | + }, | ||
| 112 | + true, | ||
| 113 | + }, | ||
| 114 | + { | ||
| 115 | + "COUNTIF 多级嵌套", | ||
| 116 | + []string{ | ||
| 117 | + `COUNTIF(销售明细.业绩,"<=1000")-COUNTIF(销售明细.业绩,"<=100")`, | ||
| 118 | + `SUM(COUNTIF(销售明细.业绩,"<=1000")-COUNTIF(销售明细.业绩,"<=100"))`, | ||
| 119 | + }, | ||
| 120 | + false, | ||
| 121 | + }, | ||
| 122 | + } | ||
| 123 | + for _, f := range funs { | ||
| 124 | + if !f.Debug { | ||
| 125 | + continue | ||
| 126 | + } | ||
| 127 | + fmt.Println("测试项目", f.Name) | ||
| 128 | + fmt.Println() | ||
| 129 | + for _, exp := range f.Exp { | ||
| 130 | + fmt.Println("表达式:", exp) | ||
| 131 | + r, err := Parse(exp) | ||
| 132 | + if err != nil { | ||
| 133 | + t.Error(err) | ||
| 134 | + } | ||
| 135 | + if r != 0 { | ||
| 136 | + | ||
| 137 | + } | ||
| 138 | + } | ||
| 139 | + } | ||
| 140 | +} |
pkg/domain/astexpr/ast_parser.go
0 → 100644
| 1 | +package astexpr | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "errors" | ||
| 5 | + "fmt" | ||
| 6 | + "strings" | ||
| 7 | +) | ||
| 8 | + | ||
| 9 | +var precedence = map[string]int{ | ||
| 10 | + "+": 20, "-": 20, "*": 40, "/": 40, "%": 40, "^": 60, | ||
| 11 | + "=": 10, ">": 10, "<": 10, "<=": 10, ">=": 10, "&": 40, | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +const ( | ||
| 15 | + // Identifier 标识符 e.g.函数名、表字段 | ||
| 16 | + Identifier = iota | ||
| 17 | + // Literal 文字 e.g. 50 | ||
| 18 | + Literal | ||
| 19 | + // Operator 计算操作 e.g. + - * / | ||
| 20 | + Operator | ||
| 21 | + // COMMA 命令, e.g. ( | ||
| 22 | + COMMA | ||
| 23 | + // CompareOperator 比较操作 e.g. < = > | ||
| 24 | + CompareOperator | ||
| 25 | + // StringArgs 字符串参数 | ||
| 26 | + StringArgs | ||
| 27 | +) | ||
| 28 | + | ||
| 29 | +type Token struct { | ||
| 30 | + // raw characters | ||
| 31 | + Tok string | ||
| 32 | + // type with Literal/Operator | ||
| 33 | + Type, | ||
| 34 | + Flag int | ||
| 35 | + | ||
| 36 | + Offset int | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +type Parser struct { | ||
| 40 | + Source string | ||
| 41 | + SourceRunes []rune | ||
| 42 | + ch rune | ||
| 43 | + offset int | ||
| 44 | + | ||
| 45 | + err error | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +func ParseToken(s string) ([]*Token, error) { | ||
| 49 | + p := &Parser{ | ||
| 50 | + Source: s, | ||
| 51 | + SourceRunes: []rune(s), | ||
| 52 | + err: nil, | ||
| 53 | + //ch: s[0], | ||
| 54 | + } | ||
| 55 | + p.ch = p.SourceRunes[0] | ||
| 56 | + toks := p.parse() | ||
| 57 | + if p.err != nil { | ||
| 58 | + return nil, p.err | ||
| 59 | + } | ||
| 60 | + return toks, nil | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +func (p *Parser) parse() []*Token { | ||
| 64 | + toks := make([]*Token, 0) | ||
| 65 | + for { | ||
| 66 | + tok := p.nextTok() | ||
| 67 | + if tok == nil { | ||
| 68 | + break | ||
| 69 | + } | ||
| 70 | + toks = append(toks, tok) | ||
| 71 | + } | ||
| 72 | + return toks | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +func (p *Parser) nextTok() *Token { | ||
| 76 | + if p.offset >= len(p.SourceRunes) || p.err != nil { | ||
| 77 | + return nil | ||
| 78 | + } | ||
| 79 | + var err error | ||
| 80 | + for p.isWhitespace(p.ch) && err == nil { | ||
| 81 | + err = p.nextCh() | ||
| 82 | + } | ||
| 83 | + start := p.offset | ||
| 84 | + var tok *Token | ||
| 85 | + switch p.ch { | ||
| 86 | + case | ||
| 87 | + '(', | ||
| 88 | + ')', | ||
| 89 | + '+', | ||
| 90 | + '-', | ||
| 91 | + '*', | ||
| 92 | + '/', | ||
| 93 | + '^', | ||
| 94 | + '%', | ||
| 95 | + '&': | ||
| 96 | + tok = &Token{ | ||
| 97 | + Tok: string(p.ch), | ||
| 98 | + Type: Operator, | ||
| 99 | + } | ||
| 100 | + tok.Offset = start | ||
| 101 | + err = p.nextCh() | ||
| 102 | + case | ||
| 103 | + '>', | ||
| 104 | + '<', | ||
| 105 | + '=': | ||
| 106 | + if p.isCompareWordChar(p.ch) { | ||
| 107 | + for p.isCompareWordChar(p.ch) && p.nextCh() == nil { | ||
| 108 | + } | ||
| 109 | + tok = &Token{ | ||
| 110 | + Tok: string(p.SourceRunes[start:p.offset]), | ||
| 111 | + Type: CompareOperator, | ||
| 112 | + } | ||
| 113 | + tok.Offset = start | ||
| 114 | + } else if p.ch != ' ' { | ||
| 115 | + s := fmt.Sprintf("symbol error: unknown '%v', pos [%v:]\n%s", | ||
| 116 | + string(p.ch), | ||
| 117 | + start, | ||
| 118 | + ErrPos(p.Source, start)) | ||
| 119 | + p.err = errors.New(s) | ||
| 120 | + } | ||
| 121 | + case | ||
| 122 | + '0', | ||
| 123 | + '1', | ||
| 124 | + '2', | ||
| 125 | + '3', | ||
| 126 | + '4', | ||
| 127 | + '5', | ||
| 128 | + '6', | ||
| 129 | + '7', | ||
| 130 | + '8', | ||
| 131 | + '9': | ||
| 132 | + for p.isDigitNum(p.ch) && p.nextCh() == nil { | ||
| 133 | + if (p.ch == '-' || p.ch == '+') && p.SourceRunes[p.offset-1] != 'e' { | ||
| 134 | + break | ||
| 135 | + } | ||
| 136 | + } | ||
| 137 | + tok = &Token{ | ||
| 138 | + Tok: strings.ReplaceAll(string(p.SourceRunes[start:p.offset]), "_", ""), | ||
| 139 | + Type: Literal, | ||
| 140 | + } | ||
| 141 | + tok.Offset = start | ||
| 142 | + case '"': | ||
| 143 | + for (p.isDigitNum(p.ch) || p.isChar(p.ch) || p.isCompareWordChar(p.ch) || p.ch == '*') && p.nextCh() == nil { | ||
| 144 | + if p.ch == '"' { | ||
| 145 | + break | ||
| 146 | + } | ||
| 147 | + } | ||
| 148 | + err = p.nextCh() | ||
| 149 | + tok = &Token{ | ||
| 150 | + Tok: string(p.SourceRunes[start:p.offset]), | ||
| 151 | + Type: StringArgs, | ||
| 152 | + } | ||
| 153 | + tok.Offset = start | ||
| 154 | + case ',': | ||
| 155 | + tok = &Token{ | ||
| 156 | + Tok: string(p.ch), | ||
| 157 | + Type: COMMA, | ||
| 158 | + } | ||
| 159 | + tok.Offset = start | ||
| 160 | + err = p.nextCh() | ||
| 161 | + | ||
| 162 | + default: | ||
| 163 | + if p.isChar(p.ch) { | ||
| 164 | + for p.isWordChar(p.ch) && p.nextCh() == nil { | ||
| 165 | + } | ||
| 166 | + tok = &Token{ | ||
| 167 | + Tok: string(p.SourceRunes[start:p.offset]), | ||
| 168 | + Type: Identifier, | ||
| 169 | + } | ||
| 170 | + tok.Offset = start | ||
| 171 | + } else if p.ch != ' ' { | ||
| 172 | + s := fmt.Sprintf("symbol error: unknown '%v', pos [%v:]\n%s", | ||
| 173 | + string(p.ch), | ||
| 174 | + start, | ||
| 175 | + ErrPos(p.Source, start)) | ||
| 176 | + p.err = errors.New(s) | ||
| 177 | + } | ||
| 178 | + } | ||
| 179 | + return tok | ||
| 180 | +} | ||
| 181 | + | ||
| 182 | +func (p *Parser) nextCh() error { | ||
| 183 | + p.offset++ | ||
| 184 | + if p.offset < len(p.SourceRunes) { | ||
| 185 | + p.ch = p.SourceRunes[p.offset] | ||
| 186 | + return nil | ||
| 187 | + } | ||
| 188 | + return errors.New("EOF") | ||
| 189 | +} | ||
| 190 | + | ||
| 191 | +func (p *Parser) isWhitespace(c rune) bool { | ||
| 192 | + return c == ' ' || | ||
| 193 | + c == '\t' || | ||
| 194 | + c == '\n' || | ||
| 195 | + c == '\v' || | ||
| 196 | + c == '\f' || | ||
| 197 | + c == '\r' | ||
| 198 | +} | ||
| 199 | + | ||
| 200 | +func (p *Parser) isDigitNum(c rune) bool { | ||
| 201 | + return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+' | ||
| 202 | +} | ||
| 203 | + | ||
| 204 | +func (p *Parser) isChar(c rune) bool { | ||
| 205 | + return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '.' || c == '"' || isChineseCharacter(c) | ||
| 206 | + //判断是汉字 | ||
| 207 | +} | ||
| 208 | + | ||
| 209 | +func (p *Parser) isWordChar(c rune) bool { | ||
| 210 | + return p.isChar(c) || '0' <= c && c <= '9' | ||
| 211 | +} | ||
| 212 | + | ||
| 213 | +func (p *Parser) isCompareWordChar(c rune) bool { | ||
| 214 | + return c == '=' || c == '<' || c == '>' | ||
| 215 | +} | ||
| 216 | + | ||
| 217 | +func isChineseCharacter(c rune) bool { | ||
| 218 | + return len([]byte(string(c))) > 2 | ||
| 219 | +} |
pkg/domain/astexpr/ast_test.go
0 → 100644
| 1 | +package astexpr | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "bytes" | ||
| 5 | + "encoding/json" | ||
| 6 | + "fmt" | ||
| 7 | + "testing" | ||
| 8 | +) | ||
| 9 | + | ||
| 10 | +func TestExecA(t *testing.T) { | ||
| 11 | + exp := "1+2" | ||
| 12 | + exec(exp) | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +func TestExecB(t *testing.T) { | ||
| 16 | + exp := "1+2-4" | ||
| 17 | + exec(exp) | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +func TestExecC(t *testing.T) { | ||
| 21 | + exp := "1+2-4*3-8" | ||
| 22 | + exec(exp) | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +func TestExecD(t *testing.T) { | ||
| 26 | + exp := "1+2-(4*3-8)" | ||
| 27 | + exec(exp) | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +func TestExecE(t *testing.T) { | ||
| 31 | + exp := "1+2-(4*3+(1-8))" | ||
| 32 | + exec(exp) | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +func TestExecF(t *testing.T) { | ||
| 36 | + exp := "1+(2-(4*3+(1-8)))" | ||
| 37 | + exec(exp) | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +func TestExecG(t *testing.T) { | ||
| 41 | + exp := "((1-2)*(3-8))*((((9+2222))))" | ||
| 42 | + exec(exp) | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +func TestExecH(t *testing.T) { | ||
| 46 | + exp := "0.8888-0.1 * 444 -0.2" | ||
| 47 | + exec(exp) | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +func TestExecI(t *testing.T) { | ||
| 51 | + exp := "0.8888-0.1 * (444 -0.2)" | ||
| 52 | + exec(exp) | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +func TestExecJ(t *testing.T) { | ||
| 56 | + exp := "1_234_567*2-3" | ||
| 57 | + exec(exp) | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +func TestExecK(t *testing.T) { | ||
| 61 | + exp := "2.3e4*4/3" | ||
| 62 | + exec(exp) | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +func TestExecL(t *testing.T) { | ||
| 66 | + exp := "-1+9-88" | ||
| 67 | + exec(exp) | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +func TestExecM(t *testing.T) { | ||
| 71 | + exp := "-1+9-88+(88)" | ||
| 72 | + exec(exp) | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +func TestExecN(t *testing.T) { | ||
| 76 | + exp := "-1+9-88+(-88)*666-1" | ||
| 77 | + exec(exp) | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +func TestExecO(t *testing.T) { | ||
| 81 | + exp := "-(1)+(3)-(-3)*7-((-3))" | ||
| 82 | + exec(exp) | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | +func TestExecP(t *testing.T) { | ||
| 86 | + exp := "-(-9+3)" | ||
| 87 | + exec(exp) | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +func TestExecQ(t *testing.T) { | ||
| 91 | + exp := "2e-3*2+2e2+1" | ||
| 92 | + exec(exp) | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +func TestExecR(t *testing.T) { | ||
| 96 | + exp := "3.8 - 56 / (1-1) - 4" | ||
| 97 | + exec(exp) | ||
| 98 | +} | ||
| 99 | + | ||
| 100 | +func TestExecS(t *testing.T) { | ||
| 101 | + exp := "noerr(3.8 - 56 / (1-1) - 4)" | ||
| 102 | + exec(exp) | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +func TestFunCaller(t *testing.T) { | ||
| 106 | + funs := []struct { | ||
| 107 | + Name string | ||
| 108 | + Argc int | ||
| 109 | + Fun func(expr ...ExprAST) float64 | ||
| 110 | + Exp string | ||
| 111 | + R float64 | ||
| 112 | + }{ | ||
| 113 | + //{ | ||
| 114 | + // "double", | ||
| 115 | + // 1, | ||
| 116 | + // func(expr ...engine.ExprAST) float64 { | ||
| 117 | + // return engine.ExprASTResult(expr[0]) * 2 | ||
| 118 | + // }, | ||
| 119 | + // "double(6)", | ||
| 120 | + // 12, | ||
| 121 | + //}, | ||
| 122 | + { | ||
| 123 | + "sum", | ||
| 124 | + -1, | ||
| 125 | + nil, | ||
| 126 | + "sum(if(100+10,table.a,20))", | ||
| 127 | + 10, | ||
| 128 | + }, | ||
| 129 | + { | ||
| 130 | + "sum", | ||
| 131 | + -1, | ||
| 132 | + nil, | ||
| 133 | + "sum(if(100<10,table.a,20))", | ||
| 134 | + 10, | ||
| 135 | + }, | ||
| 136 | + { | ||
| 137 | + "sum", | ||
| 138 | + -1, | ||
| 139 | + nil, | ||
| 140 | + "sum(if(100<10,table.a,20+30))", | ||
| 141 | + 10, | ||
| 142 | + }, | ||
| 143 | + { | ||
| 144 | + "sum", | ||
| 145 | + -1, | ||
| 146 | + nil, | ||
| 147 | + "sum(if(table.a<table.b,table.a,20+30))", | ||
| 148 | + 10, | ||
| 149 | + }, | ||
| 150 | + { | ||
| 151 | + "sum", | ||
| 152 | + -1, | ||
| 153 | + nil, | ||
| 154 | + "sum(if(table.a<=table.b,table.a,20+30))", | ||
| 155 | + 10, | ||
| 156 | + }, | ||
| 157 | + } | ||
| 158 | + for _, f := range funs { | ||
| 159 | + if f.Fun != nil { | ||
| 160 | + _ = RegFunction(f.Name, f.Argc, f.Fun) | ||
| 161 | + } | ||
| 162 | + r, err := Parse(f.Exp) | ||
| 163 | + if err != nil { | ||
| 164 | + | ||
| 165 | + } | ||
| 166 | + if r != 0 { | ||
| 167 | + | ||
| 168 | + } | ||
| 169 | + } | ||
| 170 | +} | ||
| 171 | + | ||
| 172 | +func TestFunCaller2(t *testing.T) { | ||
| 173 | + funs := []struct { | ||
| 174 | + Name string | ||
| 175 | + Exp []string | ||
| 176 | + }{ | ||
| 177 | + { | ||
| 178 | + "sum", | ||
| 179 | + []string{"sum(table.a)"}, | ||
| 180 | + }, | ||
| 181 | + { | ||
| 182 | + "sumif", | ||
| 183 | + []string{"sumif(table.month,10,table.count)"}, | ||
| 184 | + }, | ||
| 185 | + { | ||
| 186 | + "if", | ||
| 187 | + []string{"if(table.month>10,table.count1,table.count2)"}, | ||
| 188 | + }, | ||
| 189 | + { | ||
| 190 | + "and", | ||
| 191 | + []string{"and(table.year=2011,table.month=6)"}, | ||
| 192 | + }, | ||
| 193 | + { | ||
| 194 | + "or", | ||
| 195 | + []string{"or(table.year=2011,table.year=2012)"}, | ||
| 196 | + }, | ||
| 197 | + { | ||
| 198 | + "month", | ||
| 199 | + []string{"month(\"1991-1-1\")"}, | ||
| 200 | + }, | ||
| 201 | + { | ||
| 202 | + "year", | ||
| 203 | + []string{"year(\"1991-1-1\")"}, | ||
| 204 | + }, | ||
| 205 | + { | ||
| 206 | + "round", | ||
| 207 | + []string{ | ||
| 208 | + "round(1.56)", | ||
| 209 | + "round(table.a)", | ||
| 210 | + }, | ||
| 211 | + }, | ||
| 212 | + { | ||
| 213 | + "rounddown", | ||
| 214 | + []string{ | ||
| 215 | + "rounddown(1.56)", | ||
| 216 | + "rounddown(table.a)", | ||
| 217 | + }, | ||
| 218 | + }, | ||
| 219 | + { | ||
| 220 | + "roundup", | ||
| 221 | + []string{ | ||
| 222 | + "roundup(1.56)", | ||
| 223 | + "roundup(table.a)", | ||
| 224 | + }, | ||
| 225 | + }, | ||
| 226 | + { | ||
| 227 | + "count", | ||
| 228 | + []string{ | ||
| 229 | + "count(1.56)", | ||
| 230 | + "count(table.a)", | ||
| 231 | + }, | ||
| 232 | + }, | ||
| 233 | + { | ||
| 234 | + "&", | ||
| 235 | + []string{ | ||
| 236 | + "table.a&table.b", | ||
| 237 | + }, | ||
| 238 | + }, | ||
| 239 | + } | ||
| 240 | + for _, f := range funs { | ||
| 241 | + for _, exp := range f.Exp { | ||
| 242 | + r, err := Parse(exp) | ||
| 243 | + if err != nil { | ||
| 244 | + t.Error(err) | ||
| 245 | + } | ||
| 246 | + if r != 0 { | ||
| 247 | + | ||
| 248 | + } | ||
| 249 | + } | ||
| 250 | + } | ||
| 251 | +} | ||
| 252 | + | ||
| 253 | +func Parse(s string) (r float64, err error) { | ||
| 254 | + toks, err := ParseToken(s) | ||
| 255 | + if err != nil { | ||
| 256 | + return 0, err | ||
| 257 | + } | ||
| 258 | + ast := NewAST(toks, s) | ||
| 259 | + if ast.Err != nil { | ||
| 260 | + return 0, ast.Err | ||
| 261 | + } | ||
| 262 | + ar := ast.ParseExpression() | ||
| 263 | + if ast.Err != nil { | ||
| 264 | + return 0, ast.Err | ||
| 265 | + } | ||
| 266 | + defer func() { | ||
| 267 | + if e := recover(); e != nil { | ||
| 268 | + err = e.(error) | ||
| 269 | + } | ||
| 270 | + }() | ||
| 271 | + buf := bytes.NewBuffer(nil) | ||
| 272 | + enc := json.NewEncoder(buf) | ||
| 273 | + enc.SetEscapeHTML(false) | ||
| 274 | + enc.Encode(ar) | ||
| 275 | + // if ar != nil { | ||
| 276 | + // fmt.Printf("ExprAST: %+v\n", ar) | ||
| 277 | + // } | ||
| 278 | + fmt.Println(buf.String()) | ||
| 279 | + return 0, err | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +// call engine | ||
| 283 | +func exec(exp string) { | ||
| 284 | + // input text -> []token | ||
| 285 | + toks, err := ParseToken(exp) | ||
| 286 | + if err != nil { | ||
| 287 | + fmt.Println("ERROR: " + err.Error()) | ||
| 288 | + return | ||
| 289 | + } | ||
| 290 | + | ||
| 291 | + // []token -> AST Tree | ||
| 292 | + ast := NewAST(toks, exp) | ||
| 293 | + if ast.Err != nil { | ||
| 294 | + fmt.Println("ERROR: " + ast.Err.Error()) | ||
| 295 | + return | ||
| 296 | + } | ||
| 297 | + // AST builder | ||
| 298 | + ar := ast.ParseExpression() | ||
| 299 | + if ast.Err != nil { | ||
| 300 | + fmt.Println("ERROR: " + ast.Err.Error()) | ||
| 301 | + return | ||
| 302 | + } | ||
| 303 | + fmt.Printf("ExprAST: %+v\n", ar) | ||
| 304 | + // catch runtime errors | ||
| 305 | + defer func() { | ||
| 306 | + if e := recover(); e != nil { | ||
| 307 | + fmt.Println("ERROR: ", e) | ||
| 308 | + } | ||
| 309 | + }() | ||
| 310 | + // AST traversal -> result | ||
| 311 | + r := ExprASTResult(ar) | ||
| 312 | + fmt.Println("progressing ...\t", r) | ||
| 313 | + fmt.Printf("%s = %v\n", exp, r) | ||
| 314 | +} |
pkg/domain/astexpr/ast_util.go
0 → 100644
| 1 | +package astexpr | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "errors" | ||
| 5 | + "fmt" | ||
| 6 | + "math" | ||
| 7 | + "math/big" | ||
| 8 | + "strconv" | ||
| 9 | + "strings" | ||
| 10 | +) | ||
| 11 | + | ||
| 12 | +// Top level function | ||
| 13 | +// Analytical expression and execution | ||
| 14 | +// err is not nil if an error occurs (including arithmetic runtime errors) | ||
| 15 | +func ParseAndExec(s string) (r float64, err error) { | ||
| 16 | + toks, err := ParseToken(s) | ||
| 17 | + if err != nil { | ||
| 18 | + return 0, err | ||
| 19 | + } | ||
| 20 | + ast := NewAST(toks, s) | ||
| 21 | + if ast.Err != nil { | ||
| 22 | + return 0, ast.Err | ||
| 23 | + } | ||
| 24 | + ar := ast.ParseExpression() | ||
| 25 | + if ast.Err != nil { | ||
| 26 | + return 0, ast.Err | ||
| 27 | + } | ||
| 28 | + defer func() { | ||
| 29 | + if e := recover(); e != nil { | ||
| 30 | + err = e.(error) | ||
| 31 | + } | ||
| 32 | + }() | ||
| 33 | + return ExprASTResult(ar), err | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +func ErrPos(s string, pos int) string { | ||
| 37 | + r := strings.Repeat("-", len(s)) + "\n" | ||
| 38 | + s += "\n" | ||
| 39 | + for i := 0; i < pos; i++ { | ||
| 40 | + s += " " | ||
| 41 | + } | ||
| 42 | + s += "^\n" | ||
| 43 | + return r + s + r | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +// the integer power of a number | ||
| 47 | +func Pow(x float64, n float64) float64 { | ||
| 48 | + return math.Pow(x, n) | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +func expr2Radian(expr ExprAST) float64 { | ||
| 52 | + r := ExprASTResult(expr) | ||
| 53 | + if TrigonometricMode == AngleMode { | ||
| 54 | + r = r / 180 * math.Pi | ||
| 55 | + } | ||
| 56 | + return r | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +// Float64ToStr float64 -> string | ||
| 60 | +func Float64ToStr(f float64) string { | ||
| 61 | + return strconv.FormatFloat(f, 'f', -1, 64) | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +// RegFunction is Top level function | ||
| 65 | +// register a new function to use in expressions | ||
| 66 | +// name: be register function name. the same function name only needs to be registered once. | ||
| 67 | +// argc: this is a number of parameter signatures. should be -1, 0, or a positive integer | ||
| 68 | +// | ||
| 69 | +// -1 variable-length argument; >=0 fixed numbers argument | ||
| 70 | +// | ||
| 71 | +// fun: function handler | ||
| 72 | +func RegFunction(name string, argc int, fun func(...ExprAST) float64) error { | ||
| 73 | + if len(name) == 0 { | ||
| 74 | + return errors.New("RegFunction name is not empty") | ||
| 75 | + } | ||
| 76 | + if argc < -1 { | ||
| 77 | + return errors.New("RegFunction argc should be -1, 0, or a positive integer") | ||
| 78 | + } | ||
| 79 | + if _, ok := defFunc[name]; ok { | ||
| 80 | + return errors.New("RegFunction name is already exist") | ||
| 81 | + } | ||
| 82 | + defFunc[name] = defS{argc, fun} | ||
| 83 | + return nil | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +// ExprASTResult is a Top level function | ||
| 87 | +// AST traversal | ||
| 88 | +// if an arithmetic runtime error occurs, a panic exception is thrown | ||
| 89 | +func ExprASTResult(expr ExprAST) float64 { | ||
| 90 | + var l, r float64 | ||
| 91 | + switch expr.(type) { | ||
| 92 | + case BinaryExprAST: | ||
| 93 | + ast := expr.(BinaryExprAST) | ||
| 94 | + l = ExprASTResult(ast.Lhs) | ||
| 95 | + r = ExprASTResult(ast.Rhs) | ||
| 96 | + switch ast.Op { | ||
| 97 | + case "+": | ||
| 98 | + lh, _ := new(big.Float).SetString(Float64ToStr(l)) | ||
| 99 | + rh, _ := new(big.Float).SetString(Float64ToStr(r)) | ||
| 100 | + f, _ := new(big.Float).Add(lh, rh).Float64() | ||
| 101 | + return f | ||
| 102 | + case "-": | ||
| 103 | + lh, _ := new(big.Float).SetString(Float64ToStr(l)) | ||
| 104 | + rh, _ := new(big.Float).SetString(Float64ToStr(r)) | ||
| 105 | + f, _ := new(big.Float).Sub(lh, rh).Float64() | ||
| 106 | + return f | ||
| 107 | + case "*": | ||
| 108 | + f, _ := new(big.Float).Mul(new(big.Float).SetFloat64(l), new(big.Float).SetFloat64(r)).Float64() | ||
| 109 | + return f | ||
| 110 | + case "/": | ||
| 111 | + if r == 0 { | ||
| 112 | + panic(errors.New( | ||
| 113 | + fmt.Sprintf("violation of arithmetic specification: a division by zero in ExprASTResult: [%g/%g]", | ||
| 114 | + l, | ||
| 115 | + r))) | ||
| 116 | + } | ||
| 117 | + f, _ := new(big.Float).Quo(new(big.Float).SetFloat64(l), new(big.Float).SetFloat64(r)).Float64() | ||
| 118 | + return f | ||
| 119 | + case "%": | ||
| 120 | + if r == 0 { | ||
| 121 | + panic(errors.New( | ||
| 122 | + fmt.Sprintf("violation of arithmetic specification: a division by zero in ExprASTResult: [%g%%%g]", | ||
| 123 | + l, | ||
| 124 | + r))) | ||
| 125 | + } | ||
| 126 | + return float64(int(l) % int(r)) | ||
| 127 | + case "^": | ||
| 128 | + return Pow(l, r) | ||
| 129 | + default: | ||
| 130 | + | ||
| 131 | + } | ||
| 132 | + case NumberExprAST: | ||
| 133 | + return expr.(NumberExprAST).Val | ||
| 134 | + case FunCallerExprAST: | ||
| 135 | + f := expr.(FunCallerExprAST) | ||
| 136 | + def := defFunc[f.Name] | ||
| 137 | + return def.fun(f.Args...) | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + return 0.0 | ||
| 141 | +} |
pkg/domain/astexpr/ast_util_test.go
0 → 100644
| 1 | +package astexpr | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "math/rand" | ||
| 5 | + "testing" | ||
| 6 | + "time" | ||
| 7 | +) | ||
| 8 | + | ||
| 9 | +func TestParseAndExecSimple(t *testing.T) { | ||
| 10 | + type U struct { | ||
| 11 | + Expr string | ||
| 12 | + R float64 | ||
| 13 | + } | ||
| 14 | + exprs := []U{ | ||
| 15 | + {"1", 1}, | ||
| 16 | + {"--1", 1}, | ||
| 17 | + {"1+2", 3}, | ||
| 18 | + {"-1+2", 1}, | ||
| 19 | + {"-(1+2)", -3}, | ||
| 20 | + {"-(1+2)*5", -15}, | ||
| 21 | + {"-(1+2)*5/3", -5}, | ||
| 22 | + {"1+(-(1+2)*5/3)", -4}, | ||
| 23 | + {"3^4", 81}, | ||
| 24 | + {"3^4.5", 140.29611541307906}, | ||
| 25 | + {"3.5^4.5", 280.7412308013823}, | ||
| 26 | + {"8%2", 0}, | ||
| 27 | + {"8%3", 2}, | ||
| 28 | + {"8%3.5", 2}, | ||
| 29 | + {"1e2", 100}, | ||
| 30 | + {"1e+2", 100}, | ||
| 31 | + {"1e-2", 0.01}, | ||
| 32 | + {"1e-2+1e2", 100.01}, | ||
| 33 | + {"1e-2+1e2*6/3", 200.01}, | ||
| 34 | + {"(1e-2+1e2)*6/3", 200.02}, | ||
| 35 | + {"(88*8)+(1+1+1+1)+(6/1.5)-(99%9*(2^4))", 712}, | ||
| 36 | + {"1/3*3", 1}, | ||
| 37 | + {"123_456_789", 123456789}, | ||
| 38 | + {"123_456_789___", 123456789}, | ||
| 39 | + {"pi", 3.141592653589793}, | ||
| 40 | + {"abs(1)", 1}, | ||
| 41 | + {"abs(-1)", 1}, | ||
| 42 | + {"ceil(90.2)", 91}, | ||
| 43 | + {"ceil(90.8)", 91}, | ||
| 44 | + {"ceil(90.0)", 90}, | ||
| 45 | + {"floor(90.2)", 90}, | ||
| 46 | + {"floor(90.8)", 90}, | ||
| 47 | + {"floor(90.0)", 90}, | ||
| 48 | + {"round(90.0)", 90}, | ||
| 49 | + {"round(90.4)", 90}, | ||
| 50 | + {"round(90.5)", 91}, | ||
| 51 | + {"round(90.9)", 91}, | ||
| 52 | + {"sqrt(4)", 2}, | ||
| 53 | + {"cbrt(27)", 3}, | ||
| 54 | + {"sqrt(4) + cbrt(27)", 5}, | ||
| 55 | + {"sqrt(2^2) + cbrt(3^3)", 5}, | ||
| 56 | + {"127^2+5/2-sqrt(2^2) + cbrt(3^3)", 16132.5}, | ||
| 57 | + {"max(2)", 2}, | ||
| 58 | + {"max(abs(1)+10)", 11}, | ||
| 59 | + {"max(abs(1)+10)*2-1", 21}, | ||
| 60 | + {"max(2,3.5)", 3.5}, | ||
| 61 | + {"max(2^3,3+abs(-1)*6)", 9}, | ||
| 62 | + {"max(2^3,3+abs(-1)*6, 20)", 20}, | ||
| 63 | + {"max(2^3,3+abs(-1)*6,ceil(9.4))", 10}, | ||
| 64 | + {"max(1,2,3,4,5,6,10,7,4,5,6,9.8)", 10}, | ||
| 65 | + {"min(3.5)", 3.5}, | ||
| 66 | + {"min(ceil(1.2))", 2}, | ||
| 67 | + {"min(2,3.5)", 2}, | ||
| 68 | + {"min(2^3,3+abs(-1)*6)", 8}, | ||
| 69 | + {"min(2^3,3+abs(-1)*6,1^10)", 1}, | ||
| 70 | + {"min(99.1,0.2,3,4,5,6,10,7,4,5,6,9.8)", 0.2}, | ||
| 71 | + {"max(2^3,3^2)", 9}, | ||
| 72 | + {"min(2^3,3^2)", 8}, | ||
| 73 | + {"noerr(1/0)", 0}, | ||
| 74 | + {"noerr(1/(1-1))", 0}, | ||
| 75 | + {"0.1+0.2", 0.3}, | ||
| 76 | + {"0.3-0.1", 0.2}, | ||
| 77 | + {"10^-1", 0.1}, | ||
| 78 | + {"10^-2", 0.01}, | ||
| 79 | + {"10^-1*100", 10}, | ||
| 80 | + {"10%0", 0}, | ||
| 81 | + } | ||
| 82 | + for _, e := range exprs { | ||
| 83 | + r, _ := ParseAndExec(e.Expr) | ||
| 84 | + if r != e.R { | ||
| 85 | + t.Error(e, " ParseAndExec:", r) | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +func TestParseAndExecTrigonometric(t *testing.T) { | ||
| 91 | + type U struct { | ||
| 92 | + Expr string | ||
| 93 | + RadianMode float64 | ||
| 94 | + AngleMode float64 | ||
| 95 | + } | ||
| 96 | + exprs := []U{ | ||
| 97 | + {"sin(pi/2)", 1, 0.027412133592044294}, | ||
| 98 | + {"csc(pi/2)", 1, 36.48019577324057}, | ||
| 99 | + {"cos(0)", 1, 1}, | ||
| 100 | + {"sec(0)", 1, 1}, | ||
| 101 | + {"tan(pi/4)", 1, 0.013708642534394057}, | ||
| 102 | + {"cot(pi/4)", 1, 72.94668290394674}, | ||
| 103 | + | ||
| 104 | + {"sin(90)", 0.893996663600558, 1}, | ||
| 105 | + {"csc(90)", 1.1185724071637082, 1}, | ||
| 106 | + {"cos(0)", 1, 1}, | ||
| 107 | + {"sec(0)", 1, 1}, | ||
| 108 | + {"tan(45)", 1.6197751905438615, 1}, | ||
| 109 | + {"cot(45)", 0.6173696237835551, 1}, | ||
| 110 | + } | ||
| 111 | + for _, e := range exprs { | ||
| 112 | + TrigonometricMode = RadianMode | ||
| 113 | + r, _ := ParseAndExec(e.Expr) | ||
| 114 | + if r != e.RadianMode { | ||
| 115 | + t.Error(e, " ParseAndExec RadianMode:", r) | ||
| 116 | + } | ||
| 117 | + TrigonometricMode = AngleMode | ||
| 118 | + r, _ = ParseAndExec(e.Expr) | ||
| 119 | + if r != e.AngleMode { | ||
| 120 | + t.Error(e, " ParseAndExec AngleMode:", r) | ||
| 121 | + } | ||
| 122 | + } | ||
| 123 | +} | ||
| 124 | + | ||
| 125 | +func TestRegFunction(t *testing.T) { | ||
| 126 | + funs := []struct { | ||
| 127 | + Name string | ||
| 128 | + Argc int | ||
| 129 | + Fun func(expr ...ExprAST) float64 | ||
| 130 | + Exp string | ||
| 131 | + R float64 | ||
| 132 | + }{ | ||
| 133 | + { | ||
| 134 | + "double", | ||
| 135 | + 1, | ||
| 136 | + func(expr ...ExprAST) float64 { | ||
| 137 | + return ExprASTResult(expr[0]) * 2 | ||
| 138 | + }, | ||
| 139 | + "double(6)", | ||
| 140 | + 12, | ||
| 141 | + }, | ||
| 142 | + { | ||
| 143 | + "percentage50", | ||
| 144 | + 1, | ||
| 145 | + func(expr ...ExprAST) float64 { | ||
| 146 | + return ExprASTResult(expr[0]) / 2 | ||
| 147 | + }, | ||
| 148 | + "percentage50(6)", | ||
| 149 | + 3, | ||
| 150 | + }, | ||
| 151 | + { | ||
| 152 | + "range", | ||
| 153 | + 0, | ||
| 154 | + func(expr ...ExprAST) float64 { | ||
| 155 | + return 10.0 | ||
| 156 | + }, | ||
| 157 | + "range()", | ||
| 158 | + 10, | ||
| 159 | + }, | ||
| 160 | + { | ||
| 161 | + "choice", | ||
| 162 | + -1, | ||
| 163 | + func(expr ...ExprAST) float64 { | ||
| 164 | + rand.Seed(time.Now().UnixNano()) | ||
| 165 | + return ExprASTResult(expr[rand.Intn(len(expr))]) | ||
| 166 | + }, | ||
| 167 | + "choice(1.1, 9.8, 2.5, 100)", | ||
| 168 | + 10, | ||
| 169 | + }, | ||
| 170 | + } | ||
| 171 | + for _, f := range funs { | ||
| 172 | + _ = RegFunction(f.Name, f.Argc, f.Fun) | ||
| 173 | + r, err := ParseAndExec(f.Exp) | ||
| 174 | + if f.Name == "choice" { | ||
| 175 | + if !inSlices(r, []float64{1.1, 9.8, 2.5, 100}) { | ||
| 176 | + t.Error(err, "RegFunction errors when register new function: ", f.Name) | ||
| 177 | + } | ||
| 178 | + continue | ||
| 179 | + } else if r != f.R { | ||
| 180 | + t.Error(err, "RegFunction errors when register new function: ", f.Name) | ||
| 181 | + } | ||
| 182 | + } | ||
| 183 | + | ||
| 184 | +} | ||
| 185 | + | ||
| 186 | +func TestParseAndExecError(t *testing.T) { | ||
| 187 | + exprs := []string{ | ||
| 188 | + "(", | ||
| 189 | + "((((((", | ||
| 190 | + "((xscdfddff", | ||
| 191 | + "(1", | ||
| 192 | + "(1+", | ||
| 193 | + "1+", | ||
| 194 | + "1*", | ||
| 195 | + "+2344", | ||
| 196 | + "3+(", | ||
| 197 | + "4+(90-", | ||
| 198 | + "3-(4*7-2)+", | ||
| 199 | + "3-(4*7-2)+98*", | ||
| 200 | + "1#1", | ||
| 201 | + "_123_456_789___", | ||
| 202 | + "1ee3+3", | ||
| 203 | + "sin()", | ||
| 204 | + "sin", | ||
| 205 | + "pi(", | ||
| 206 | + "sin(1, 50)", | ||
| 207 | + "max", | ||
| 208 | + "max()", | ||
| 209 | + "max(1,)", | ||
| 210 | + "max(1,4,6,7,5,)", | ||
| 211 | + "min", | ||
| 212 | + "min(,)", | ||
| 213 | + "min()", | ||
| 214 | + "min(1,)", | ||
| 215 | + "min(1,998,4,23,234,2,)", | ||
| 216 | + "min(1,998,4,23,234,2,,,)", | ||
| 217 | + "1/0", | ||
| 218 | + "99.9 / (2-1-1)", | ||
| 219 | + "(1+2)3", | ||
| 220 | + "1+1 111", | ||
| 221 | + "1+1 111+2", | ||
| 222 | + "1 3", | ||
| 223 | + "1 3-", | ||
| 224 | + } | ||
| 225 | + for _, e := range exprs { | ||
| 226 | + _, err := ParseAndExec(e) | ||
| 227 | + if err == nil { | ||
| 228 | + t.Error(e, " this is error expr!") | ||
| 229 | + } | ||
| 230 | + } | ||
| 231 | +} | ||
| 232 | + | ||
| 233 | +func inSlices(target float64, s []float64) bool { | ||
| 234 | + for _, v := range s { | ||
| 235 | + if v == target { | ||
| 236 | + return true | ||
| 237 | + } | ||
| 238 | + } | ||
| 239 | + return false | ||
| 240 | +} |
| @@ -160,7 +160,7 @@ func (t TableType) ToString() string { | @@ -160,7 +160,7 @@ func (t TableType) ToString() string { | ||
| 160 | } | 160 | } |
| 161 | 161 | ||
| 162 | func (t TableType) TableStatusEditable() bool { | 162 | func (t TableType) TableStatusEditable() bool { |
| 163 | - return t == SchemaTable || t == CalculateItem || t == CalculateTable || t == CalculateSet | 163 | + return t == SchemaTable || t == CalculateItem || t == CalculateSet |
| 164 | } | 164 | } |
| 165 | 165 | ||
| 166 | func (t TableType) TableHasGroup() bool { | 166 | func (t TableType) TableHasGroup() bool { |
| @@ -28,6 +28,16 @@ type Field struct { | @@ -28,6 +28,16 @@ type Field struct { | ||
| 28 | Order string `json:"order,omitempty"` | 28 | Order string `json:"order,omitempty"` |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | +func (f *Field) SqlTypeEqual(compare *Field) bool { | ||
| 32 | + if f.SQLType == compare.SQLType { | ||
| 33 | + return true | ||
| 34 | + } | ||
| 35 | + if SQLType(f.SQLType).IsString() == SQLType(compare.SQLType).IsString() { | ||
| 36 | + return true | ||
| 37 | + } | ||
| 38 | + return false | ||
| 39 | +} | ||
| 40 | + | ||
| 31 | func (f *Field) Valid() error { | 41 | func (f *Field) Valid() error { |
| 32 | if _, ok := SQLTypeMap[strings.ToUpper(f.SQLType)]; !ok { | 42 | if _, ok := SQLTypeMap[strings.ToUpper(f.SQLType)]; !ok { |
| 33 | return fmt.Errorf("unknown sql type:%v", f.SQLType) | 43 | return fmt.Errorf("unknown sql type:%v", f.SQLType) |
| @@ -133,6 +133,25 @@ func (querySet *QuerySet) GetDependencyTables(queryComponents []*QueryComponent) | @@ -133,6 +133,25 @@ func (querySet *QuerySet) GetDependencyTables(queryComponents []*QueryComponent) | ||
| 133 | return res | 133 | return res |
| 134 | } | 134 | } |
| 135 | 135 | ||
| 136 | +func (querySet *QuerySet) Valid(queryComponents []*QueryComponent) error { | ||
| 137 | + switch querySet.Type { | ||
| 138 | + case CalculateTable.ToString(): | ||
| 139 | + if len(queryComponents) == 0 { | ||
| 140 | + return fmt.Errorf("行、值不能同时为空") | ||
| 141 | + } | ||
| 142 | + qc := queryComponents[0] | ||
| 143 | + set := collection.NewSet() | ||
| 144 | + for _, f := range qc.Aggregation.AggregationFields() { | ||
| 145 | + if !set.Contains(f.DisplayName) { | ||
| 146 | + set.AddStr(f.DisplayName) | ||
| 147 | + } else { | ||
| 148 | + return fmt.Errorf("字段'%s'存在重名,请进行重命名", f.DisplayName) | ||
| 149 | + } | ||
| 150 | + } | ||
| 151 | + } | ||
| 152 | + return nil | ||
| 153 | +} | ||
| 154 | + | ||
| 136 | type QuerySets []*QuerySet | 155 | type QuerySets []*QuerySet |
| 137 | 156 | ||
| 138 | func (querySets QuerySets) ToMap() map[int]*QuerySet { | 157 | func (querySets QuerySets) ToMap() map[int]*QuerySet { |
| @@ -67,7 +67,7 @@ func (table *Table) TableIdString() string { | @@ -67,7 +67,7 @@ func (table *Table) TableIdString() string { | ||
| 67 | 67 | ||
| 68 | func (table *Table) WithContext(ctx *Context) *Table { | 68 | func (table *Table) WithContext(ctx *Context) *Table { |
| 69 | rand.Seed(time.Now().Unix()) | 69 | rand.Seed(time.Now().Unix()) |
| 70 | - table.SQLName = fmt.Sprintf("%v_t%v_c%v", limitStringLen(table.SQLName, 40), rand.Intn(1000000), limitStringLen(fmt.Sprintf("%d", ctx.CompanyId), 8)) | 70 | + table.SQLName = fmt.Sprintf("%v_t%v_c%v", limitStringLen(table.SQLName, 30), rand.Intn(1000000), limitStringLen(fmt.Sprintf("%d", ctx.CompanyId), 8)) |
| 71 | table.Context = ctx | 71 | table.Context = ctx |
| 72 | return table | 72 | return table |
| 73 | } | 73 | } |
| @@ -188,7 +188,7 @@ func NewTableAppendRequest(param domain.ReqAppendData) TableAppendRequest { | @@ -188,7 +188,7 @@ func NewTableAppendRequest(param domain.ReqAppendData) TableAppendRequest { | ||
| 188 | OriginalTableId: intToString(param.FileId), | 188 | OriginalTableId: intToString(param.FileId), |
| 189 | CheckoutTableFileUrl: param.FileUrl, | 189 | CheckoutTableFileUrl: param.FileUrl, |
| 190 | DatabaseTableName: param.Table.SQLName, | 190 | DatabaseTableName: param.Table.SQLName, |
| 191 | - ColumnSchemas: DomainFieldsToColumnSchemas(param.ExcelTable.DataFields), | 191 | + ColumnSchemas: DomainFieldsToColumnSchemas(param.From), //param.ExcelTable.DataFields |
| 192 | FieldSchemas: ToFieldSchemas(param.Table.DataFields), | 192 | FieldSchemas: ToFieldSchemas(param.Table.DataFields), |
| 193 | SchemaMap: make(map[string]domain.ColumnSchema), | 193 | SchemaMap: make(map[string]domain.ColumnSchema), |
| 194 | } | 194 | } |
| @@ -118,6 +118,9 @@ func (d *FilePreviewDto) Load(fileId int, m *domain.DataLoadDataTable, file *red | @@ -118,6 +118,9 @@ func (d *FilePreviewDto) Load(fileId int, m *domain.DataLoadDataTable, file *red | ||
| 118 | if s == "<NA>" { | 118 | if s == "<NA>" { |
| 119 | return "" | 119 | return "" |
| 120 | } | 120 | } |
| 121 | + if s == "nan" { | ||
| 122 | + return "" | ||
| 123 | + } | ||
| 121 | return s | 124 | return s |
| 122 | }), true) | 125 | }), true) |
| 123 | d.Data = domain.GripData(mapData, int64(m.Total)) | 126 | d.Data = domain.GripData(mapData, int64(m.Total)) |
| @@ -8,6 +8,7 @@ import ( | @@ -8,6 +8,7 @@ import ( | ||
| 8 | "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/redis" | 8 | "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/redis" |
| 9 | "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/repository" | 9 | "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/repository" |
| 10 | "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/starrocks" | 10 | "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/starrocks" |
| 11 | + "reflect" | ||
| 11 | "strings" | 12 | "strings" |
| 12 | "time" | 13 | "time" |
| 13 | ) | 14 | ) |
| @@ -273,6 +274,9 @@ func (ptr *QuerySetService) PreviewPrepare(ctx *domain.Context, querySetId int, | @@ -273,6 +274,9 @@ func (ptr *QuerySetService) PreviewPrepare(ctx *domain.Context, querySetId int, | ||
| 273 | if err != nil { | 274 | if err != nil { |
| 274 | return nil, err | 275 | return nil, err |
| 275 | } | 276 | } |
| 277 | + if err = querySet.Valid(queryComponents); err != nil { | ||
| 278 | + return nil, err | ||
| 279 | + } | ||
| 276 | if !queryComponentsHasEdit(ctx, querySet, queryComponents) && querySet.QuerySetInfo.BindTableId > 0 { | 280 | if !queryComponentsHasEdit(ctx, querySet, queryComponents) && querySet.QuerySetInfo.BindTableId > 0 { |
| 277 | if t, _ := tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": querySet.QuerySetInfo.BindTableId}); t != nil { | 281 | if t, _ := tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": querySet.QuerySetInfo.BindTableId}); t != nil { |
| 278 | return t, nil | 282 | return t, nil |
| @@ -655,6 +659,29 @@ func aggregationEditLog(ctx *domain.Context, querySet *domain.QuerySet, queryCom | @@ -655,6 +659,29 @@ func aggregationEditLog(ctx *domain.Context, querySet *domain.QuerySet, queryCom | ||
| 655 | return res | 659 | return res |
| 656 | } | 660 | } |
| 657 | 661 | ||
| 662 | +func aggregationHasEdit(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) bool { | ||
| 663 | + if len(queryComponents) > 0 && len(querySet.QueryComponents) > 0 { | ||
| 664 | + oldQC := querySet.QueryComponents[0] | ||
| 665 | + newQC := queryComponents[0] | ||
| 666 | + if oldQC.Aggregation == nil || newQC.Aggregation == nil { | ||
| 667 | + return false | ||
| 668 | + } | ||
| 669 | + | ||
| 670 | + c1 := make([]string, 0) | ||
| 671 | + for _, f := range oldQC.Aggregation.AggregationFields() { | ||
| 672 | + c1 = append(c1, f.Expr.ExprSql) | ||
| 673 | + } | ||
| 674 | + c2 := make([]string, 0) | ||
| 675 | + for _, f := range newQC.Aggregation.AggregationFields() { | ||
| 676 | + c2 = append(c2, f.Expr.ExprSql) | ||
| 677 | + } | ||
| 678 | + if !reflect.DeepEqual(c1, c2) { | ||
| 679 | + return true | ||
| 680 | + } | ||
| 681 | + } | ||
| 682 | + return false | ||
| 683 | +} | ||
| 684 | + | ||
| 658 | func queryComponentsHasEdit(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) bool { | 685 | func queryComponentsHasEdit(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) bool { |
| 659 | logs := selectsEditLog(ctx, querySet, queryComponents) | 686 | logs := selectsEditLog(ctx, querySet, queryComponents) |
| 660 | if len(logs) > 0 { | 687 | if len(logs) > 0 { |
| @@ -668,6 +695,9 @@ func queryComponentsHasEdit(ctx *domain.Context, querySet *domain.QuerySet, quer | @@ -668,6 +695,9 @@ func queryComponentsHasEdit(ctx *domain.Context, querySet *domain.QuerySet, quer | ||
| 668 | if len(logs) > 0 { | 695 | if len(logs) > 0 { |
| 669 | return true | 696 | return true |
| 670 | } | 697 | } |
| 698 | + if aggregationHasEdit(ctx, querySet, queryComponents) { | ||
| 699 | + return true | ||
| 700 | + } | ||
| 671 | for _, item := range queryComponents { | 701 | for _, item := range queryComponents { |
| 672 | if len(item.Id) == 0 { | 702 | if len(item.Id) == 0 { |
| 673 | return true | 703 | return true |
| @@ -757,6 +787,9 @@ func (ptr *QuerySetService) CreateOrUpdateCalculateTable(ctx *domain.Context, qu | @@ -757,6 +787,9 @@ func (ptr *QuerySetService) CreateOrUpdateCalculateTable(ctx *domain.Context, qu | ||
| 757 | err error | 787 | err error |
| 758 | foundMasterTable *domain.Table | 788 | foundMasterTable *domain.Table |
| 759 | ) | 789 | ) |
| 790 | + if err = querySet.Valid(queryComponents); err != nil { | ||
| 791 | + return nil, err | ||
| 792 | + } | ||
| 760 | dependencyTables := querySet.GetDependencyTables(queryComponents) | 793 | dependencyTables := querySet.GetDependencyTables(queryComponents) |
| 761 | tableRepository, _ := repository.NewTableRepository(ptr.transactionContext) | 794 | tableRepository, _ := repository.NewTableRepository(ptr.transactionContext) |
| 762 | foundMasterTable, err = tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": masterTable.TableId}) | 795 | foundMasterTable, err = tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": masterTable.TableId}) |
| @@ -79,6 +79,13 @@ func (ptr *AppendDataToTableService) AppendData(ctx *domain.Context, fileId int, | @@ -79,6 +79,13 @@ func (ptr *AppendDataToTableService) AppendData(ctx *domain.Context, fileId int, | ||
| 79 | continue | 79 | continue |
| 80 | } | 80 | } |
| 81 | } | 81 | } |
| 82 | + if fromField.SQLType != "" && !toField.SqlTypeEqual(fromField) { | ||
| 83 | + //return nil, fmt.Errorf("字段【%s】的类型与导入数据表的类型不匹配", toField.Name) | ||
| 84 | + return map[string]interface{}{ | ||
| 85 | + "result": fmt.Sprintf("字段【%s】的类型与导入数据表的类型不匹配", toField.Name), | ||
| 86 | + }, nil | ||
| 87 | + } | ||
| 88 | + fromField.SQLType = toField.SQLType // 兼容 INT BIGINT | ||
| 82 | requestData.To = append(requestData.To, toField) | 89 | requestData.To = append(requestData.To, toField) |
| 83 | requestData.From = append(requestData.From, fromField) | 90 | requestData.From = append(requestData.From, fromField) |
| 84 | } | 91 | } |
-
请 注册 或 登录 后发表评论