github.com/viant/toolbox@v0.34.5/data/parser.go (about) 1 package data 2 3 import ( 4 "bytes" 5 "github.com/viant/toolbox" 6 "math" 7 "strings" 8 ) 9 10 const ( 11 eofToken = -1 12 invalidToken = iota 13 beforeVarToken 14 varToken 15 incToken 16 decrementToken 17 shiftToken 18 enclosedVarToken 19 callToken 20 idToken 21 arrayIndexToken 22 unmatchedToken 23 keyIndexToken 24 whitespace 25 groupingToken 26 operatorTojeb 27 doubleQuoteEnclosedToken 28 comaToken 29 ) 30 31 var matchers = map[int]toolbox.Matcher{ 32 beforeVarToken: toolbox.NewTerminatorMatcher("$"), 33 varToken: toolbox.NewCharactersMatcher("$"), 34 comaToken: toolbox.NewTerminatorMatcher(","), 35 idToken: toolbox.NewCustomIdMatcher("_"), 36 incToken: toolbox.NewKeywordsMatcher(true, "++"), 37 decrementToken: toolbox.NewKeywordsMatcher(true, "--"), 38 shiftToken: toolbox.NewKeywordsMatcher(true, "<-"), 39 arrayIndexToken: toolbox.NewBodyMatcher("[", "]"), 40 callToken: toolbox.NewBodyMatcher("(", ")"), 41 enclosedVarToken: toolbox.NewBodyMatcher("{", "}"), 42 doubleQuoteEnclosedToken: toolbox.NewBodyMatcher(`"`, `"`), 43 keyIndexToken: toolbox.NewCustomIdMatcher("."), 44 unmatchedToken: toolbox.NewRemainingSequenceMatcher(), 45 groupingToken: toolbox.NewBodyMatcher("(", ")"), 46 operatorTojeb: toolbox.NewTerminatorMatcher("+", "-", "*", "/", "^", "%"), 47 whitespace: toolbox.NewCharactersMatcher(" \t\n\r"), 48 } 49 50 //Parse parses expression 51 func Parse(expression string, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool)) interface{} { 52 tokenizer := toolbox.NewTokenizer(expression, invalidToken, eofToken, matchers) 53 var value interface{} 54 var result = fragments{} 55 var ok bool 56 done := false 57 for tokenizer.Index < len(expression) && !done { 58 match := tokenizer.Nexts(beforeVarToken, varToken, unmatchedToken, eofToken) 59 switch match.Token { 60 case unmatchedToken: 61 result.Append(match.Matched) 62 done = true 63 continue 64 case eofToken: 65 break 66 case beforeVarToken: 67 result.Append(match.Matched) 68 continue 69 70 case varToken: 71 variable := "$" 72 match = tokenizer.Nexts(idToken, enclosedVarToken, incToken, decrementToken, shiftToken) 73 switch match.Token { 74 case eofToken: 75 result.Append(variable) 76 continue 77 case enclosedVarToken: 78 79 expanded := expandEnclosed(match.Matched, handler) 80 if toolbox.IsFloat(expanded) || toolbox.IsInt(expanded) { 81 value = expanded 82 result.Append(value) 83 continue 84 } 85 expandedText := toolbox.AsString(expanded) 86 if strings.HasSuffix(expandedText, ")") { 87 value = Parse("$"+expandedText, handler) 88 if textValue, ok := value.(string); ok && textValue == "$"+expandedText { 89 value = "${" + expandedText + "}" 90 } 91 result.Append(value) 92 continue 93 } 94 95 variable := "${" + expandedText + "}" 96 if value, ok = handler(variable, false, ""); !ok { 97 value = variable 98 } 99 result.Append(value) 100 continue 101 102 case incToken, decrementToken, shiftToken: 103 variable += match.Matched 104 match = tokenizer.Nexts(idToken) //enclosedVarToken, idToken ? 105 if match.Token != idToken { 106 result.Append(variable) 107 continue 108 } 109 fallthrough 110 111 case idToken: 112 113 variable += match.Matched 114 variable = expandVariable(tokenizer, variable, handler) 115 match = tokenizer.Nexts(callToken, incToken, decrementToken, beforeVarToken, unmatchedToken, eofToken) 116 switch match.Token { 117 118 case callToken: 119 arguments := string(match.Matched[1 : len(match.Matched)-1]) 120 if value, ok = handler(variable, true, arguments); !ok { 121 value = variable + match.Matched 122 } 123 result.Append(value) 124 continue 125 case incToken, decrementToken: 126 variable += match.Matched 127 match.Matched = "" 128 fallthrough 129 130 case beforeVarToken, unmatchedToken, eofToken, invalidToken: 131 if value, ok = handler(variable, false, ""); !ok { 132 value = variable 133 } 134 result.Append(value) 135 result.Append(match.Matched) 136 continue 137 } 138 139 default: 140 result.Append(variable) 141 } 142 } 143 } 144 return result.Get() 145 } 146 147 func expandVariable(tokenizer *toolbox.Tokenizer, variable string, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool)) string { 148 match := tokenizer.Nexts(keyIndexToken, arrayIndexToken) 149 switch match.Token { 150 case keyIndexToken: 151 variable = expandSubKey(variable, match, tokenizer, handler) 152 case arrayIndexToken: 153 variable = expandIndex(variable, match, handler, tokenizer) 154 } 155 return variable 156 } 157 158 func expandIndex(variable string, match *toolbox.Token, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool), tokenizer *toolbox.Tokenizer) string { 159 variable += toolbox.AsString(Parse(match.Matched, handler)) 160 match = tokenizer.Nexts(arrayIndexToken, keyIndexToken) 161 switch match.Token { 162 case keyIndexToken: 163 variable = expandSubKey(variable, match, tokenizer, handler) 164 case arrayIndexToken: 165 variable += toolbox.AsString(Parse(match.Matched, handler)) 166 } 167 return variable 168 } 169 170 func expandSubKey(variable string, match *toolbox.Token, tokenizer *toolbox.Tokenizer, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool)) string { 171 variable += match.Matched 172 match = tokenizer.Nexts(idToken, enclosedVarToken, arrayIndexToken) 173 switch match.Token { 174 case idToken: 175 variable += match.Matched 176 variable = expandVariable(tokenizer, variable, handler) 177 case enclosedVarToken: 178 expanded := expandEnclosed(match.Matched, handler) 179 variable += toolbox.AsString(expanded) 180 case arrayIndexToken: 181 variable = expandIndex(variable, match, handler, tokenizer) 182 } 183 return variable 184 } 185 186 func expandEnclosed(expr string, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool)) interface{} { 187 if strings.HasPrefix(expr, "{") && strings.HasSuffix(expr, "}") { 188 expr = string(expr[1 : len(expr)-1]) 189 190 } 191 tokenizer := toolbox.NewTokenizer(expr, invalidToken, eofToken, matchers) 192 match, err := toolbox.ExpectTokenOptionallyFollowedBy(tokenizer, whitespace, "expected operatorTojeb", groupingToken, operatorTojeb) 193 if err != nil { 194 return Parse(expr, handler) 195 } 196 switch match.Token { 197 case groupingToken: 198 groupExpr := string(match.Matched[1 : len(match.Matched)-1]) 199 result := expandEnclosed(groupExpr, handler) 200 if !(toolbox.IsInt(result) || toolbox.IsFloat(result)) { 201 return Parse(expr, handler) 202 } 203 expandedGroup := toolbox.AsString(result) + string(expr[tokenizer.Index:]) 204 return expandEnclosed(expandedGroup, handler) 205 case operatorTojeb: 206 leftOperand, leftOk := tryNumericOperand(match.Matched, handler).(float64) 207 operator := string(expr[tokenizer.Index : tokenizer.Index+1]) 208 rightOperand, rightOk := tryNumericOperand(string(expr[tokenizer.Index+1:]), handler).(float64) 209 if !leftOk || !rightOk { 210 return Parse(expr, handler) 211 } 212 var floatResult float64 213 switch operator { 214 case "+": 215 floatResult = leftOperand + rightOperand 216 case "-": 217 floatResult = leftOperand - rightOperand 218 case "/": 219 if rightOperand == 0 { //division by zero issue 220 return Parse(expr, handler) 221 } 222 floatResult = leftOperand / rightOperand 223 case "*": 224 floatResult = leftOperand * rightOperand 225 case "^": 226 floatResult = math.Pow(leftOperand, rightOperand) 227 case "%": 228 floatResult = float64(int(leftOperand) % int(rightOperand)) 229 default: 230 return Parse(expr, handler) 231 } 232 intResult := int(floatResult) 233 if floatResult == float64(intResult) { 234 return intResult 235 } 236 return floatResult 237 } 238 return Parse(expr, handler) 239 } 240 241 func tryNumericOperand(expression string, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool)) interface{} { 242 expression = strings.TrimSpace(expression) 243 if result, err := toolbox.ToFloat(expression); err == nil { 244 return result 245 } 246 left := expandEnclosed(expression, handler) 247 if result, err := toolbox.ToFloat(left); err == nil { 248 return result 249 } 250 251 left = Parse("$"+expression, handler) 252 if result, err := toolbox.ToFloat(left); err == nil { 253 return result 254 } 255 return expression 256 } 257 258 func asExpandedText(source interface{}) string { 259 if source != nil && (toolbox.IsSlice(source) || toolbox.IsMap(source)) { 260 buf := new(bytes.Buffer) 261 err := toolbox.NewJSONEncoderFactory().Create(buf).Encode(source) 262 if err == nil { 263 return buf.String() 264 } 265 } 266 return toolbox.AsString(source) 267 } 268 269 type fragments []interface{} 270 271 func (f *fragments) Append(item interface{}) { 272 if text, ok := item.(string); ok { 273 if text == "" { 274 return 275 } 276 } 277 *f = append(*f, item) 278 } 279 280 func (f fragments) Get() interface{} { 281 count := len(f) 282 if count == 0 { 283 return "" 284 } 285 var emptyCount = 0 286 var result interface{} 287 for _, item := range f { 288 if text, ok := item.(string); ok && strings.TrimSpace(text) == "" { 289 emptyCount++ 290 } else { 291 result = item 292 } 293 } 294 if emptyCount == count-1 { 295 return result 296 } 297 var textResult = "" 298 for _, item := range f { 299 textResult += asExpandedText(item) 300 } 301 return textResult 302 }