正在显示
10 个修改的文件
包含
1356 行增加
和
9 行删除
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[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[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.Name = name | ||
| 121 | + f.Args = exprs | ||
| 122 | + return f | ||
| 123 | + } | ||
| 124 | + // call const | ||
| 125 | + if v, ok := defConst[name]; ok { | ||
| 126 | + return NumberExprAST{ | ||
| 127 | + Val: v, | ||
| 128 | + Str: strconv.FormatFloat(v, 'f', 0, 64), | ||
| 129 | + } | ||
| 130 | + } else { | ||
| 131 | + if strings.Contains(name, ".") { | ||
| 132 | + return FieldExprAST{ | ||
| 133 | + Str: name, | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | + a.Err = errors.New( | ||
| 137 | + fmt.Sprintf("const `%s` is undefined\n%s", | ||
| 138 | + name, | ||
| 139 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 140 | + return NumberExprAST{} | ||
| 141 | + } | ||
| 142 | +} | ||
| 143 | + | ||
| 144 | +func (a *AST) parsePrimary() ExprAST { | ||
| 145 | + switch a.currTok.Type { | ||
| 146 | + case Identifier: | ||
| 147 | + return a.parseFunCallerOrConst() | ||
| 148 | + case Literal: | ||
| 149 | + return a.parseNumber() | ||
| 150 | + case StringArgs: | ||
| 151 | + e := ValueExprAST{ | ||
| 152 | + Str: a.currTok.Tok, | ||
| 153 | + } | ||
| 154 | + a.getNextToken() | ||
| 155 | + return e | ||
| 156 | + case Operator: | ||
| 157 | + if a.currTok.Tok == "(" { | ||
| 158 | + t := a.getNextToken() | ||
| 159 | + if t == nil { | ||
| 160 | + a.Err = errors.New( | ||
| 161 | + fmt.Sprintf("want '(' or '0-9' but get EOF\n%s", | ||
| 162 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 163 | + return nil | ||
| 164 | + } | ||
| 165 | + e := a.ParseExpression() | ||
| 166 | + if e == nil { | ||
| 167 | + return nil | ||
| 168 | + } | ||
| 169 | + if a.currTok.Tok != ")" { | ||
| 170 | + a.Err = errors.New( | ||
| 171 | + fmt.Sprintf("want ')' but get %s\n%s", | ||
| 172 | + a.currTok.Tok, | ||
| 173 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 174 | + return nil | ||
| 175 | + } | ||
| 176 | + a.getNextToken() | ||
| 177 | + return e | ||
| 178 | + } else if a.currTok.Tok == "-" { | ||
| 179 | + if a.getNextToken() == nil { | ||
| 180 | + a.Err = errors.New( | ||
| 181 | + fmt.Sprintf("want '0-9' but get '-'\n%s", | ||
| 182 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 183 | + return nil | ||
| 184 | + } | ||
| 185 | + bin := BinaryExprAST{ | ||
| 186 | + Op: "-", | ||
| 187 | + Lhs: NumberExprAST{}, | ||
| 188 | + Rhs: a.parsePrimary(), | ||
| 189 | + } | ||
| 190 | + return bin | ||
| 191 | + } else { | ||
| 192 | + return a.parseNumber() | ||
| 193 | + } | ||
| 194 | + case COMMA: | ||
| 195 | + a.Err = errors.New( | ||
| 196 | + fmt.Sprintf("want '(' or '0-9' but get %s\n%s", | ||
| 197 | + a.currTok.Tok, | ||
| 198 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 199 | + return nil | ||
| 200 | + default: | ||
| 201 | + return nil | ||
| 202 | + } | ||
| 203 | +} | ||
| 204 | + | ||
| 205 | +func (a *AST) parseBinOpRHS(execPrec int, lhs ExprAST) ExprAST { | ||
| 206 | + for { | ||
| 207 | + tokPrec := a.getTokPrecedence() | ||
| 208 | + if tokPrec < execPrec { | ||
| 209 | + return lhs | ||
| 210 | + } | ||
| 211 | + binOp := a.currTok.Tok | ||
| 212 | + if a.getNextToken() == nil { | ||
| 213 | + a.Err = errors.New( | ||
| 214 | + fmt.Sprintf("want '(' or '0-9' but get EOF\n%s", | ||
| 215 | + ErrPos(a.source, a.currTok.Offset))) | ||
| 216 | + return nil | ||
| 217 | + } | ||
| 218 | + rhs := a.parsePrimary() | ||
| 219 | + if rhs == nil { | ||
| 220 | + return nil | ||
| 221 | + } | ||
| 222 | + nextPrec := a.getTokPrecedence() | ||
| 223 | + if tokPrec < nextPrec { | ||
| 224 | + rhs = a.parseBinOpRHS(tokPrec+1, rhs) | ||
| 225 | + if rhs == nil { | ||
| 226 | + return nil | ||
| 227 | + } | ||
| 228 | + } | ||
| 229 | + lhs = BinaryExprAST{ | ||
| 230 | + Op: binOp, | ||
| 231 | + Lhs: lhs, | ||
| 232 | + Rhs: rhs, | ||
| 233 | + } | ||
| 234 | + } | ||
| 235 | +} |
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 | + "countif": {-1, defNone}, | ||
| 61 | + //"&": {-1, defNone}, | ||
| 62 | + "concat": {-1, defNone}, | ||
| 63 | + } | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +// sin(pi/2) = 1 | ||
| 67 | +func defSin(expr ...ExprAST) float64 { | ||
| 68 | + return math.Sin(expr2Radian(expr[0])) | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +// cos(0) = 1 | ||
| 72 | +func defCos(expr ...ExprAST) float64 { | ||
| 73 | + return math.Cos(expr2Radian(expr[0])) | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +// tan(pi/4) = 1 | ||
| 77 | +func defTan(expr ...ExprAST) float64 { | ||
| 78 | + return math.Tan(expr2Radian(expr[0])) | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +// cot(pi/4) = 1 | ||
| 82 | +func defCot(expr ...ExprAST) float64 { | ||
| 83 | + return 1 / defTan(expr...) | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +// sec(0) = 1 | ||
| 87 | +func defSec(expr ...ExprAST) float64 { | ||
| 88 | + return 1 / defCos(expr...) | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +// csc(pi/2) = 1 | ||
| 92 | +func defCsc(expr ...ExprAST) float64 { | ||
| 93 | + return 1 / defSin(expr...) | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +// abs(-2) = 2 | ||
| 97 | +func defAbs(expr ...ExprAST) float64 { | ||
| 98 | + return math.Abs(ExprASTResult(expr[0])) | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +// ceil(4.2) = ceil(4.8) = 5 | ||
| 102 | +func defCeil(expr ...ExprAST) float64 { | ||
| 103 | + return math.Ceil(ExprASTResult(expr[0])) | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +// floor(4.2) = floor(4.8) = 4 | ||
| 107 | +func defFloor(expr ...ExprAST) float64 { | ||
| 108 | + return math.Floor(ExprASTResult(expr[0])) | ||
| 109 | +} | ||
| 110 | + | ||
| 111 | +// round(4.2) = 4 | ||
| 112 | +// round(4.6) = 5 | ||
| 113 | +func defRound(expr ...ExprAST) float64 { | ||
| 114 | + return math.Round(ExprASTResult(expr[0])) | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +// sqrt(4) = 2 | ||
| 118 | +// sqrt(4) = abs(sqrt(4)) | ||
| 119 | +// returns only the absolute value of the result | ||
| 120 | +func defSqrt(expr ...ExprAST) float64 { | ||
| 121 | + return math.Sqrt(ExprASTResult(expr[0])) | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +// cbrt(27) = 3 | ||
| 125 | +func defCbrt(expr ...ExprAST) float64 { | ||
| 126 | + return math.Cbrt(ExprASTResult(expr[0])) | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +// max(2) = 2 | ||
| 130 | +// max(2, 3) = 3 | ||
| 131 | +// max(2, 3, 1) = 3 | ||
| 132 | +func defMax(expr ...ExprAST) float64 { | ||
| 133 | + if len(expr) == 0 { | ||
| 134 | + panic(errors.New("calling function `max` must have at least one parameter.")) | ||
| 135 | + } | ||
| 136 | + if len(expr) == 1 { | ||
| 137 | + return ExprASTResult(expr[0]) | ||
| 138 | + } | ||
| 139 | + maxV := ExprASTResult(expr[0]) | ||
| 140 | + for i := 1; i < len(expr); i++ { | ||
| 141 | + v := ExprASTResult(expr[i]) | ||
| 142 | + maxV = math.Max(maxV, v) | ||
| 143 | + } | ||
| 144 | + return maxV | ||
| 145 | +} | ||
| 146 | + | ||
| 147 | +// min(2) = 2 | ||
| 148 | +// min(2, 3) = 2 | ||
| 149 | +// min(2, 3, 1) = 1 | ||
| 150 | +func defMin(expr ...ExprAST) float64 { | ||
| 151 | + if len(expr) == 0 { | ||
| 152 | + panic(errors.New("calling function `min` must have at least one parameter.")) | ||
| 153 | + } | ||
| 154 | + if len(expr) == 1 { | ||
| 155 | + return ExprASTResult(expr[0]) | ||
| 156 | + } | ||
| 157 | + maxV := ExprASTResult(expr[0]) | ||
| 158 | + for i := 1; i < len(expr); i++ { | ||
| 159 | + v := ExprASTResult(expr[i]) | ||
| 160 | + maxV = math.Min(maxV, v) | ||
| 161 | + } | ||
| 162 | + return maxV | ||
| 163 | +} | ||
| 164 | + | ||
| 165 | +// noerr(1/0) = 0 | ||
| 166 | +// noerr(2.5/(1-1)) = 0 | ||
| 167 | +func defNoErr(expr ...ExprAST) (r float64) { | ||
| 168 | + defer func() { | ||
| 169 | + if e := recover(); e != nil { | ||
| 170 | + r = 0 | ||
| 171 | + } | ||
| 172 | + }() | ||
| 173 | + return ExprASTResult(expr[0]) | ||
| 174 | +} | ||
| 175 | + | ||
| 176 | +func defNone(expr ...ExprAST) (r float64) { | ||
| 177 | + return 0 | ||
| 178 | +} |
| 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 ( |
| @@ -16,6 +17,11 @@ type ExprAST interface { | @@ -16,6 +17,11 @@ type ExprAST interface { | ||
| 16 | toStr() string | 17 | toStr() string |
| 17 | } | 18 | } |
| 18 | 19 | ||
| 20 | +type NumberExprAST struct { | ||
| 21 | + Val float64 | ||
| 22 | + Str string | ||
| 23 | +} | ||
| 24 | + | ||
| 19 | type ValueExprAST struct { | 25 | type ValueExprAST struct { |
| 20 | ExprType string `json:"exprType"` | 26 | ExprType string `json:"exprType"` |
| 21 | Val string `json:"val"` | 27 | Val string `json:"val"` |
| @@ -25,7 +31,7 @@ type ValueExprAST struct { | @@ -25,7 +31,7 @@ type ValueExprAST struct { | ||
| 25 | type FieldExprAST struct { | 31 | type FieldExprAST struct { |
| 26 | ExprType string `json:"exprType"` | 32 | ExprType string `json:"exprType"` |
| 27 | Str string `json:"str"` | 33 | Str string `json:"str"` |
| 28 | - Field *TableField `json:"field"` | 34 | + Field *domain.TableField `json:"field"` |
| 29 | } | 35 | } |
| 30 | 36 | ||
| 31 | type BinaryExprAST struct { | 37 | type BinaryExprAST struct { |
| @@ -42,6 +48,13 @@ type FunCallerExprAST struct { | @@ -42,6 +48,13 @@ type FunCallerExprAST struct { | ||
| 42 | Args []ExprAST `json:"args"` | 48 | Args []ExprAST `json:"args"` |
| 43 | } | 49 | } |
| 44 | 50 | ||
| 51 | +func (n NumberExprAST) toStr() string { | ||
| 52 | + return fmt.Sprintf( | ||
| 53 | + "NumberExprAST:%s", | ||
| 54 | + n.Str, | ||
| 55 | + ) | ||
| 56 | +} | ||
| 57 | + | ||
| 45 | func (n ValueExprAST) toStr() string { | 58 | func (n ValueExprAST) toStr() string { |
| 46 | return fmt.Sprintf( | 59 | return fmt.Sprintf( |
| 47 | "ValueExprAST:%s", | 60 | "ValueExprAST:%s", |
| @@ -84,7 +97,7 @@ type CloneBinaryExprAST struct { | @@ -84,7 +97,7 @@ type CloneBinaryExprAST struct { | ||
| 84 | Rhs json.RawMessage `json:"rhs"` | 97 | Rhs json.RawMessage `json:"rhs"` |
| 85 | } | 98 | } |
| 86 | 99 | ||
| 87 | -func AstExprUnmarshalJSON(data []byte) (interface{}, error) { | 100 | +func ExprUnmarshal(data []byte) (interface{}, error) { |
| 88 | var m = make(map[string]interface{}) | 101 | var m = make(map[string]interface{}) |
| 89 | if err := json.Unmarshal(data, &m); err != nil { | 102 | if err := json.Unmarshal(data, &m); err != nil { |
| 90 | return nil, err | 103 | return nil, err |
| @@ -108,7 +121,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | @@ -108,7 +121,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | ||
| 108 | } | 121 | } |
| 109 | exprReturn := &FunCallerExprAST{Name: expr.Name, ExprType: TypeFunCallerExprAST} | 122 | exprReturn := &FunCallerExprAST{Name: expr.Name, ExprType: TypeFunCallerExprAST} |
| 110 | for i := range expr.Arg { | 123 | for i := range expr.Arg { |
| 111 | - subExpr, err := AstExprUnmarshalJSON(expr.Arg[i]) | 124 | + subExpr, err := ExprUnmarshal(expr.Arg[i]) |
| 112 | if err != nil { | 125 | if err != nil { |
| 113 | return nil, err | 126 | return nil, err |
| 114 | } | 127 | } |
| @@ -126,7 +139,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | @@ -126,7 +139,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | ||
| 126 | } | 139 | } |
| 127 | exprReturn := &BinaryExprAST{Op: expr.Op, ExprType: TypeBinaryExprAST} | 140 | exprReturn := &BinaryExprAST{Op: expr.Op, ExprType: TypeBinaryExprAST} |
| 128 | if len(expr.Lhs) > 0 { | 141 | if len(expr.Lhs) > 0 { |
| 129 | - subExpr, err := AstExprUnmarshalJSON(expr.Lhs) | 142 | + subExpr, err := ExprUnmarshal(expr.Lhs) |
| 130 | if err != nil { | 143 | if err != nil { |
| 131 | return nil, err | 144 | return nil, err |
| 132 | } | 145 | } |
| @@ -135,7 +148,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | @@ -135,7 +148,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | ||
| 135 | } | 148 | } |
| 136 | } | 149 | } |
| 137 | if len(expr.Rhs) > 0 { | 150 | if len(expr.Rhs) > 0 { |
| 138 | - subExpr, err := AstExprUnmarshalJSON(expr.Rhs) | 151 | + subExpr, err := ExprUnmarshal(expr.Rhs) |
| 139 | if err != nil { | 152 | if err != nil { |
| 140 | return nil, err | 153 | return nil, err |
| 141 | } | 154 | } |
| 1 | -package domain | 1 | +package astexpr |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "github.com/linmadan/egglib-go/utils/json" | 4 | "github.com/linmadan/egglib-go/utils/json" |
| 5 | "github.com/stretchr/testify/assert" | 5 | "github.com/stretchr/testify/assert" |
| 6 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" | ||
| 6 | "testing" | 7 | "testing" |
| 7 | ) | 8 | ) |
| 8 | 9 | ||
| @@ -57,7 +58,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | @@ -57,7 +58,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | ||
| 57 | &FieldExprAST{ | 58 | &FieldExprAST{ |
| 58 | ExprType: TypeFieldExprAST, | 59 | ExprType: TypeFieldExprAST, |
| 59 | Str: "业绩1", | 60 | Str: "业绩1", |
| 60 | - Field: &TableField{ | 61 | + Field: &domain.TableField{ |
| 61 | TableId: 1, | 62 | TableId: 1, |
| 62 | TableName: "测试ABC", | 63 | TableName: "测试ABC", |
| 63 | TableSqlName: "table_abc_test", | 64 | TableSqlName: "table_abc_test", |
| @@ -83,7 +84,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | @@ -83,7 +84,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | ||
| 83 | if err != nil { | 84 | if err != nil { |
| 84 | t.Fatal(err) | 85 | t.Fatal(err) |
| 85 | } | 86 | } |
| 86 | - v, err := AstExprUnmarshalJSON(data) | 87 | + v, err := ExprUnmarshal(data) |
| 87 | if err != nil { | 88 | if err != nil { |
| 88 | t.Fatal(err) | 89 | t.Fatal(err) |
| 89 | } | 90 | } |
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 | + | ||
| 42 | + ch byte | ||
| 43 | + offset int | ||
| 44 | + | ||
| 45 | + err error | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +func ParseToken(s string) ([]*Token, error) { | ||
| 49 | + p := &Parser{ | ||
| 50 | + Source: s, | ||
| 51 | + err: nil, | ||
| 52 | + ch: s[0], | ||
| 53 | + } | ||
| 54 | + toks := p.parse() | ||
| 55 | + if p.err != nil { | ||
| 56 | + return nil, p.err | ||
| 57 | + } | ||
| 58 | + return toks, nil | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +func (p *Parser) parse() []*Token { | ||
| 62 | + toks := make([]*Token, 0) | ||
| 63 | + for { | ||
| 64 | + tok := p.nextTok() | ||
| 65 | + if tok == nil { | ||
| 66 | + break | ||
| 67 | + } | ||
| 68 | + toks = append(toks, tok) | ||
| 69 | + } | ||
| 70 | + return toks | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +func (p *Parser) nextTok() *Token { | ||
| 74 | + if p.offset >= len(p.Source) || p.err != nil { | ||
| 75 | + return nil | ||
| 76 | + } | ||
| 77 | + var err error | ||
| 78 | + for p.isWhitespace(p.ch) && err == nil { | ||
| 79 | + err = p.nextCh() | ||
| 80 | + } | ||
| 81 | + start := p.offset | ||
| 82 | + var tok *Token | ||
| 83 | + switch p.ch { | ||
| 84 | + case | ||
| 85 | + '(', | ||
| 86 | + ')', | ||
| 87 | + '+', | ||
| 88 | + '-', | ||
| 89 | + '*', | ||
| 90 | + '/', | ||
| 91 | + '^', | ||
| 92 | + '%', | ||
| 93 | + '&': | ||
| 94 | + tok = &Token{ | ||
| 95 | + Tok: string(p.ch), | ||
| 96 | + Type: Operator, | ||
| 97 | + } | ||
| 98 | + tok.Offset = start | ||
| 99 | + err = p.nextCh() | ||
| 100 | + case | ||
| 101 | + '>', | ||
| 102 | + '<', | ||
| 103 | + '=': | ||
| 104 | + if p.isCompareWordChar(p.ch) { | ||
| 105 | + for p.isCompareWordChar(p.ch) && p.nextCh() == nil { | ||
| 106 | + } | ||
| 107 | + tok = &Token{ | ||
| 108 | + Tok: p.Source[start:p.offset], | ||
| 109 | + Type: CompareOperator, | ||
| 110 | + } | ||
| 111 | + tok.Offset = start | ||
| 112 | + } else if p.ch != ' ' { | ||
| 113 | + s := fmt.Sprintf("symbol error: unknown '%v', pos [%v:]\n%s", | ||
| 114 | + string(p.ch), | ||
| 115 | + start, | ||
| 116 | + ErrPos(p.Source, start)) | ||
| 117 | + p.err = errors.New(s) | ||
| 118 | + } | ||
| 119 | + case | ||
| 120 | + '0', | ||
| 121 | + '1', | ||
| 122 | + '2', | ||
| 123 | + '3', | ||
| 124 | + '4', | ||
| 125 | + '5', | ||
| 126 | + '6', | ||
| 127 | + '7', | ||
| 128 | + '8', | ||
| 129 | + '9': | ||
| 130 | + for p.isDigitNum(p.ch) && p.nextCh() == nil { | ||
| 131 | + if (p.ch == '-' || p.ch == '+') && p.Source[p.offset-1] != 'e' { | ||
| 132 | + break | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + tok = &Token{ | ||
| 136 | + Tok: strings.ReplaceAll(p.Source[start:p.offset], "_", ""), | ||
| 137 | + Type: Literal, | ||
| 138 | + } | ||
| 139 | + tok.Offset = start | ||
| 140 | + case '"': | ||
| 141 | + for (p.isDigitNum(p.ch) || p.isChar(p.ch)) && p.nextCh() == nil { | ||
| 142 | + if p.ch == '"' { | ||
| 143 | + break | ||
| 144 | + } | ||
| 145 | + } | ||
| 146 | + err = p.nextCh() | ||
| 147 | + tok = &Token{ | ||
| 148 | + Tok: p.Source[start:p.offset], | ||
| 149 | + Type: StringArgs, | ||
| 150 | + } | ||
| 151 | + tok.Offset = start | ||
| 152 | + case ',': | ||
| 153 | + tok = &Token{ | ||
| 154 | + Tok: string(p.ch), | ||
| 155 | + Type: COMMA, | ||
| 156 | + } | ||
| 157 | + tok.Offset = start | ||
| 158 | + err = p.nextCh() | ||
| 159 | + | ||
| 160 | + default: | ||
| 161 | + if p.isChar(p.ch) { | ||
| 162 | + for p.isWordChar(p.ch) && p.nextCh() == nil { | ||
| 163 | + } | ||
| 164 | + tok = &Token{ | ||
| 165 | + Tok: p.Source[start:p.offset], | ||
| 166 | + Type: Identifier, | ||
| 167 | + } | ||
| 168 | + tok.Offset = start | ||
| 169 | + } else if p.ch != ' ' { | ||
| 170 | + s := fmt.Sprintf("symbol error: unknown '%v', pos [%v:]\n%s", | ||
| 171 | + string(p.ch), | ||
| 172 | + start, | ||
| 173 | + ErrPos(p.Source, start)) | ||
| 174 | + p.err = errors.New(s) | ||
| 175 | + } | ||
| 176 | + } | ||
| 177 | + return tok | ||
| 178 | +} | ||
| 179 | + | ||
| 180 | +func (p *Parser) nextCh() error { | ||
| 181 | + p.offset++ | ||
| 182 | + if p.offset < len(p.Source) { | ||
| 183 | + p.ch = p.Source[p.offset] | ||
| 184 | + return nil | ||
| 185 | + } | ||
| 186 | + return errors.New("EOF") | ||
| 187 | +} | ||
| 188 | + | ||
| 189 | +func (p *Parser) isWhitespace(c byte) bool { | ||
| 190 | + return c == ' ' || | ||
| 191 | + c == '\t' || | ||
| 192 | + c == '\n' || | ||
| 193 | + c == '\v' || | ||
| 194 | + c == '\f' || | ||
| 195 | + c == '\r' | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +func (p *Parser) isDigitNum(c byte) bool { | ||
| 199 | + return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+' | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +func (p *Parser) isChar(c byte) bool { | ||
| 203 | + return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '.' || c == '"' | ||
| 204 | +} | ||
| 205 | + | ||
| 206 | +func (p *Parser) isWordChar(c byte) bool { | ||
| 207 | + return p.isChar(c) || '0' <= c && c <= '9' | ||
| 208 | +} | ||
| 209 | + | ||
| 210 | +func (p *Parser) isCompareWordChar(c byte) bool { | ||
| 211 | + return c == '=' || c == '<' || c == '>' | ||
| 212 | +} |
pkg/domain/astexpr/ast_test.go
0 → 100644
| 1 | +package astexpr | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "encoding/json" | ||
| 5 | + "fmt" | ||
| 6 | + "testing" | ||
| 7 | +) | ||
| 8 | + | ||
| 9 | +func TestExecA(t *testing.T) { | ||
| 10 | + exp := "1+2" | ||
| 11 | + exec(exp) | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +func TestExecB(t *testing.T) { | ||
| 15 | + exp := "1+2-4" | ||
| 16 | + exec(exp) | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +func TestExecC(t *testing.T) { | ||
| 20 | + exp := "1+2-4*3-8" | ||
| 21 | + exec(exp) | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +func TestExecD(t *testing.T) { | ||
| 25 | + exp := "1+2-(4*3-8)" | ||
| 26 | + exec(exp) | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +func TestExecE(t *testing.T) { | ||
| 30 | + exp := "1+2-(4*3+(1-8))" | ||
| 31 | + exec(exp) | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +func TestExecF(t *testing.T) { | ||
| 35 | + exp := "1+(2-(4*3+(1-8)))" | ||
| 36 | + exec(exp) | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +func TestExecG(t *testing.T) { | ||
| 40 | + exp := "((1-2)*(3-8))*((((9+2222))))" | ||
| 41 | + exec(exp) | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +func TestExecH(t *testing.T) { | ||
| 45 | + exp := "0.8888-0.1 * 444 -0.2" | ||
| 46 | + exec(exp) | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +func TestExecI(t *testing.T) { | ||
| 50 | + exp := "0.8888-0.1 * (444 -0.2)" | ||
| 51 | + exec(exp) | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +func TestExecJ(t *testing.T) { | ||
| 55 | + exp := "1_234_567*2-3" | ||
| 56 | + exec(exp) | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +func TestExecK(t *testing.T) { | ||
| 60 | + exp := "2.3e4*4/3" | ||
| 61 | + exec(exp) | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +func TestExecL(t *testing.T) { | ||
| 65 | + exp := "-1+9-88" | ||
| 66 | + exec(exp) | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +func TestExecM(t *testing.T) { | ||
| 70 | + exp := "-1+9-88+(88)" | ||
| 71 | + exec(exp) | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +func TestExecN(t *testing.T) { | ||
| 75 | + exp := "-1+9-88+(-88)*666-1" | ||
| 76 | + exec(exp) | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | +func TestExecO(t *testing.T) { | ||
| 80 | + exp := "-(1)+(3)-(-3)*7-((-3))" | ||
| 81 | + exec(exp) | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +func TestExecP(t *testing.T) { | ||
| 85 | + exp := "-(-9+3)" | ||
| 86 | + exec(exp) | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +func TestExecQ(t *testing.T) { | ||
| 90 | + exp := "2e-3*2+2e2+1" | ||
| 91 | + exec(exp) | ||
| 92 | +} | ||
| 93 | + | ||
| 94 | +func TestExecR(t *testing.T) { | ||
| 95 | + exp := "3.8 - 56 / (1-1) - 4" | ||
| 96 | + exec(exp) | ||
| 97 | +} | ||
| 98 | + | ||
| 99 | +func TestExecS(t *testing.T) { | ||
| 100 | + exp := "noerr(3.8 - 56 / (1-1) - 4)" | ||
| 101 | + exec(exp) | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +func TestFunCaller(t *testing.T) { | ||
| 105 | + funs := []struct { | ||
| 106 | + Name string | ||
| 107 | + Argc int | ||
| 108 | + Fun func(expr ...ExprAST) float64 | ||
| 109 | + Exp string | ||
| 110 | + R float64 | ||
| 111 | + }{ | ||
| 112 | + //{ | ||
| 113 | + // "double", | ||
| 114 | + // 1, | ||
| 115 | + // func(expr ...engine.ExprAST) float64 { | ||
| 116 | + // return engine.ExprASTResult(expr[0]) * 2 | ||
| 117 | + // }, | ||
| 118 | + // "double(6)", | ||
| 119 | + // 12, | ||
| 120 | + //}, | ||
| 121 | + { | ||
| 122 | + "sum", | ||
| 123 | + -1, | ||
| 124 | + nil, | ||
| 125 | + "sum(if(100+10,table.a,20))", | ||
| 126 | + 10, | ||
| 127 | + }, | ||
| 128 | + { | ||
| 129 | + "sum", | ||
| 130 | + -1, | ||
| 131 | + nil, | ||
| 132 | + "sum(if(100<10,table.a,20))", | ||
| 133 | + 10, | ||
| 134 | + }, | ||
| 135 | + { | ||
| 136 | + "sum", | ||
| 137 | + -1, | ||
| 138 | + nil, | ||
| 139 | + "sum(if(100<10,table.a,20+30))", | ||
| 140 | + 10, | ||
| 141 | + }, | ||
| 142 | + { | ||
| 143 | + "sum", | ||
| 144 | + -1, | ||
| 145 | + nil, | ||
| 146 | + "sum(if(table.a<table.b,table.a,20+30))", | ||
| 147 | + 10, | ||
| 148 | + }, | ||
| 149 | + { | ||
| 150 | + "sum", | ||
| 151 | + -1, | ||
| 152 | + nil, | ||
| 153 | + "sum(if(table.a<=table.b,table.a,20+30))", | ||
| 154 | + 10, | ||
| 155 | + }, | ||
| 156 | + } | ||
| 157 | + for _, f := range funs { | ||
| 158 | + if f.Fun != nil { | ||
| 159 | + _ = RegFunction(f.Name, f.Argc, f.Fun) | ||
| 160 | + } | ||
| 161 | + r, err := Parse(f.Exp) | ||
| 162 | + if err != nil { | ||
| 163 | + | ||
| 164 | + } | ||
| 165 | + if r != 0 { | ||
| 166 | + | ||
| 167 | + } | ||
| 168 | + } | ||
| 169 | +} | ||
| 170 | + | ||
| 171 | +func TestFunCaller2(t *testing.T) { | ||
| 172 | + funs := []struct { | ||
| 173 | + Name string | ||
| 174 | + Exp []string | ||
| 175 | + }{ | ||
| 176 | + { | ||
| 177 | + "sum", | ||
| 178 | + []string{"sum(table.a)"}, | ||
| 179 | + }, | ||
| 180 | + { | ||
| 181 | + "sumif", | ||
| 182 | + []string{"sumif(table.month,10,table.count)"}, | ||
| 183 | + }, | ||
| 184 | + { | ||
| 185 | + "if", | ||
| 186 | + []string{"if(table.month>10,table.count1,table.count2)"}, | ||
| 187 | + }, | ||
| 188 | + { | ||
| 189 | + "and", | ||
| 190 | + []string{"and(table.year=2011,table.month=6)"}, | ||
| 191 | + }, | ||
| 192 | + { | ||
| 193 | + "or", | ||
| 194 | + []string{"or(table.year=2011,table.year=2012)"}, | ||
| 195 | + }, | ||
| 196 | + { | ||
| 197 | + "month", | ||
| 198 | + []string{"month(\"1991-1-1\")"}, | ||
| 199 | + }, | ||
| 200 | + { | ||
| 201 | + "year", | ||
| 202 | + []string{"year(\"1991-1-1\")"}, | ||
| 203 | + }, | ||
| 204 | + { | ||
| 205 | + "round", | ||
| 206 | + []string{ | ||
| 207 | + "round(1.56)", | ||
| 208 | + "round(table.a)", | ||
| 209 | + }, | ||
| 210 | + }, | ||
| 211 | + { | ||
| 212 | + "rounddown", | ||
| 213 | + []string{ | ||
| 214 | + "rounddown(1.56)", | ||
| 215 | + "rounddown(table.a)", | ||
| 216 | + }, | ||
| 217 | + }, | ||
| 218 | + { | ||
| 219 | + "roundup", | ||
| 220 | + []string{ | ||
| 221 | + "roundup(1.56)", | ||
| 222 | + "roundup(table.a)", | ||
| 223 | + }, | ||
| 224 | + }, | ||
| 225 | + { | ||
| 226 | + "count", | ||
| 227 | + []string{ | ||
| 228 | + "count(1.56)", | ||
| 229 | + "count(table.a)", | ||
| 230 | + }, | ||
| 231 | + }, | ||
| 232 | + { | ||
| 233 | + "&", | ||
| 234 | + []string{ | ||
| 235 | + "table.a&table.b", | ||
| 236 | + }, | ||
| 237 | + }, | ||
| 238 | + } | ||
| 239 | + for _, f := range funs { | ||
| 240 | + for _, exp := range f.Exp { | ||
| 241 | + r, err := Parse(exp) | ||
| 242 | + if err != nil { | ||
| 243 | + t.Error(err) | ||
| 244 | + } | ||
| 245 | + if r != 0 { | ||
| 246 | + | ||
| 247 | + } | ||
| 248 | + } | ||
| 249 | + } | ||
| 250 | +} | ||
| 251 | + | ||
| 252 | +func Parse(s string) (r float64, err error) { | ||
| 253 | + toks, err := ParseToken(s) | ||
| 254 | + if err != nil { | ||
| 255 | + return 0, err | ||
| 256 | + } | ||
| 257 | + ast := NewAST(toks, s) | ||
| 258 | + if ast.Err != nil { | ||
| 259 | + return 0, ast.Err | ||
| 260 | + } | ||
| 261 | + ar := ast.ParseExpression() | ||
| 262 | + if ast.Err != nil { | ||
| 263 | + return 0, ast.Err | ||
| 264 | + } | ||
| 265 | + defer func() { | ||
| 266 | + if e := recover(); e != nil { | ||
| 267 | + err = e.(error) | ||
| 268 | + } | ||
| 269 | + }() | ||
| 270 | + if ar != nil { | ||
| 271 | + fmt.Printf("ExprAST: %+v\n", ar) | ||
| 272 | + } | ||
| 273 | + arData, _ := json.Marshal(ar) | ||
| 274 | + fmt.Printf("%s\n", string(arData)) | ||
| 275 | + | ||
| 276 | + return 0, err | ||
| 277 | +} | ||
| 278 | + | ||
| 279 | +// call engine | ||
| 280 | +func exec(exp string) { | ||
| 281 | + // input text -> []token | ||
| 282 | + toks, err := ParseToken(exp) | ||
| 283 | + if err != nil { | ||
| 284 | + fmt.Println("ERROR: " + err.Error()) | ||
| 285 | + return | ||
| 286 | + } | ||
| 287 | + | ||
| 288 | + // []token -> AST Tree | ||
| 289 | + ast := NewAST(toks, exp) | ||
| 290 | + if ast.Err != nil { | ||
| 291 | + fmt.Println("ERROR: " + ast.Err.Error()) | ||
| 292 | + return | ||
| 293 | + } | ||
| 294 | + // AST builder | ||
| 295 | + ar := ast.ParseExpression() | ||
| 296 | + if ast.Err != nil { | ||
| 297 | + fmt.Println("ERROR: " + ast.Err.Error()) | ||
| 298 | + return | ||
| 299 | + } | ||
| 300 | + fmt.Printf("ExprAST: %+v\n", ar) | ||
| 301 | + // catch runtime errors | ||
| 302 | + defer func() { | ||
| 303 | + if e := recover(); e != nil { | ||
| 304 | + fmt.Println("ERROR: ", e) | ||
| 305 | + } | ||
| 306 | + }() | ||
| 307 | + // AST traversal -> result | ||
| 308 | + r := ExprASTResult(ar) | ||
| 309 | + fmt.Println("progressing ...\t", r) | ||
| 310 | + fmt.Printf("%s = %v\n", exp, r) | ||
| 311 | +} |
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 | +} |
| @@ -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) |
| @@ -79,6 +79,12 @@ func (ptr *AppendDataToTableService) AppendData(ctx *domain.Context, fileId int, | @@ -79,6 +79,12 @@ 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 | + } | ||
| 82 | requestData.To = append(requestData.To, toField) | 88 | requestData.To = append(requestData.To, toField) |
| 83 | requestData.From = append(requestData.From, fromField) | 89 | requestData.From = append(requestData.From, fromField) |
| 84 | } | 90 | } |
-
请 注册 或 登录 后发表评论