mirror of
https://github.com/mikefarah/yq.git
synced 2024-11-12 05:38:04 +00:00
183 lines
6.1 KiB
Go
183 lines
6.1 KiB
Go
package yqlib
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
)
|
|
|
|
type expressionTokeniser interface {
|
|
Tokenise(expression string) ([]*token, error)
|
|
}
|
|
|
|
type tokenType uint32
|
|
|
|
const (
|
|
operationToken = 1 << iota
|
|
openBracket
|
|
closeBracket
|
|
openCollect
|
|
closeCollect
|
|
openCollectObject
|
|
closeCollectObject
|
|
traverseArrayCollect
|
|
)
|
|
|
|
type token struct {
|
|
TokenType tokenType
|
|
Operation *Operation
|
|
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
|
|
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
|
|
Match string
|
|
}
|
|
|
|
func (t *token) toString(detail bool) string {
|
|
if t.TokenType == operationToken {
|
|
if detail {
|
|
return fmt.Sprintf("%v (%v)", t.Operation.toString(), t.Operation.OperationType.Precedence)
|
|
}
|
|
return t.Operation.toString()
|
|
} else if t.TokenType == openBracket {
|
|
return "("
|
|
} else if t.TokenType == closeBracket {
|
|
return ")"
|
|
} else if t.TokenType == openCollect {
|
|
return "["
|
|
} else if t.TokenType == closeCollect {
|
|
return "]"
|
|
} else if t.TokenType == openCollectObject {
|
|
return "{"
|
|
} else if t.TokenType == closeCollectObject {
|
|
return "}"
|
|
} else if t.TokenType == traverseArrayCollect {
|
|
return ".["
|
|
|
|
}
|
|
return "NFI"
|
|
}
|
|
|
|
func unwrap(value string) string {
|
|
return value[1 : len(value)-1]
|
|
}
|
|
|
|
func extractNumberParameter(value string) (int, error) {
|
|
parameterParser := regexp.MustCompile(`.*\(([0-9]+)\)`)
|
|
matches := parameterParser.FindStringSubmatch(value)
|
|
var indent, errParsingInt = parseInt(matches[1])
|
|
if errParsingInt != nil {
|
|
return 0, errParsingInt
|
|
}
|
|
return indent, nil
|
|
}
|
|
|
|
func hasOptionParameter(value string, option string) bool {
|
|
parameterParser := regexp.MustCompile(`.*\([^\)]*\)`)
|
|
matches := parameterParser.FindStringSubmatch(value)
|
|
if len(matches) == 0 {
|
|
return false
|
|
}
|
|
parameterString := matches[0]
|
|
optionParser := regexp.MustCompile(fmt.Sprintf("\\b%v\\b", option))
|
|
return len(optionParser.FindStringSubmatch(parameterString)) > 0
|
|
}
|
|
|
|
func postProcessTokens(tokens []*token) []*token {
|
|
var postProcessedTokens = make([]*token, 0)
|
|
|
|
skipNextToken := false
|
|
|
|
for index := range tokens {
|
|
if skipNextToken {
|
|
skipNextToken = false
|
|
} else {
|
|
postProcessedTokens, skipNextToken = handleToken(tokens, index, postProcessedTokens)
|
|
}
|
|
}
|
|
|
|
return postProcessedTokens
|
|
}
|
|
|
|
func tokenIsOpType(token *token, opType *operationType) bool {
|
|
return token.TokenType == operationToken && token.Operation.OperationType == opType
|
|
}
|
|
|
|
func handleToken(tokens []*token, index int, postProcessedTokens []*token) (tokensAccum []*token, skipNextToken bool) {
|
|
skipNextToken = false
|
|
currentToken := tokens[index]
|
|
|
|
log.Debug("processing %v", currentToken.toString(true))
|
|
|
|
if currentToken.TokenType == traverseArrayCollect {
|
|
// `.[exp]`` works by creating a traversal array of [self, exp] and piping that into the traverse array operator
|
|
//need to put a traverse array then a collect currentToken
|
|
// do this by adding traverse then converting currentToken to collect
|
|
|
|
log.Debug("adding self")
|
|
op := &Operation{OperationType: selfReferenceOpType, StringValue: "SELF"}
|
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
|
|
|
log.Debug("adding traverse array")
|
|
op = &Operation{OperationType: traverseArrayOpType, StringValue: "TRAVERSE_ARRAY"}
|
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
|
|
|
currentToken = &token{TokenType: openCollect}
|
|
|
|
}
|
|
|
|
if tokenIsOpType(currentToken, createMapOpType) {
|
|
log.Debugf("tokenIsOpType: createMapOpType")
|
|
// check the previous token is '[', means we are slice, but dont have a first number
|
|
if tokens[index-1].TokenType == traverseArrayCollect {
|
|
log.Debugf("previous token is : traverseArrayOpType")
|
|
// need to put the number 0 before this token, as that is implied
|
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: createValueOperation(0, "0")})
|
|
}
|
|
}
|
|
|
|
if index != len(tokens)-1 && currentToken.AssignOperation != nil &&
|
|
tokenIsOpType(tokens[index+1], assignOpType) {
|
|
log.Debug("its an update assign")
|
|
currentToken.Operation = currentToken.AssignOperation
|
|
currentToken.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
|
|
skipNextToken = true
|
|
}
|
|
|
|
log.Debug("adding token to the fixed list")
|
|
postProcessedTokens = append(postProcessedTokens, currentToken)
|
|
|
|
if tokenIsOpType(currentToken, createMapOpType) {
|
|
log.Debugf("tokenIsOpType: createMapOpType")
|
|
// check the next token is ']', means we are slice, but dont have a second number
|
|
if index != len(tokens)-1 && tokens[index+1].TokenType == closeCollect {
|
|
log.Debugf("next token is : closeCollect")
|
|
// need to put the number 0 before this token, as that is implied
|
|
lengthOp := &Operation{OperationType: lengthOpType}
|
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: lengthOp})
|
|
}
|
|
}
|
|
|
|
if index != len(tokens)-1 &&
|
|
((currentToken.TokenType == openCollect && tokens[index+1].TokenType == closeCollect) ||
|
|
(currentToken.TokenType == openCollectObject && tokens[index+1].TokenType == closeCollectObject)) {
|
|
log.Debug("adding empty")
|
|
op := &Operation{OperationType: emptyOpType, StringValue: "EMPTY"}
|
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
|
}
|
|
|
|
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
|
|
|
|
(tokenIsOpType(tokens[index+1], traversePathOpType) ||
|
|
(tokens[index+1].TokenType == traverseArrayCollect)) {
|
|
log.Debug("adding pipe because the next thing is traverse")
|
|
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE", StringValue: "."}
|
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
|
}
|
|
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
|
|
tokens[index+1].TokenType == openCollect {
|
|
|
|
log.Debug("adding traverseArray because next is opencollect")
|
|
op := &Operation{OperationType: traverseArrayOpType}
|
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
|
}
|
|
return postProcessedTokens, skipNextToken
|
|
}
|