正在显示
10 个修改的文件
包含
1358 行增加
和
11 行删除
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"` |
@@ -23,9 +29,9 @@ type ValueExprAST struct { | @@ -23,9 +29,9 @@ type ValueExprAST struct { | ||
23 | } | 29 | } |
24 | 30 | ||
25 | type FieldExprAST struct { | 31 | type FieldExprAST struct { |
26 | - ExprType string `json:"exprType"` | ||
27 | - Str string `json:"str"` | ||
28 | - Field *TableField `json:"field"` | 32 | + ExprType string `json:"exprType"` |
33 | + Str string `json:"str"` | ||
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 | } |
-
请 注册 或 登录 后发表评论