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  }