yq/pkg/yqlib/lexer.go

160 lines
5.1 KiB
Go
Raw Permalink Normal View History

2022-07-13 04:45:28 +00:00
package yqlib
import (
"fmt"
"regexp"
"strconv"
)
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 ".["
} else {
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 = strconv.ParseInt(matches[1], 10, 32)
if errParsingInt != nil {
return 0, errParsingInt
}
return int(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 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 index != len(tokens)-1 && currentToken.AssignOperation != nil &&
tokens[index+1].TokenType == operationToken &&
tokens[index+1].Operation.OperationType == 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 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 &&
((tokens[index+1].TokenType == operationToken && (tokens[index+1].Operation.OperationType == 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 traverArray because next is opencollect")
op := &Operation{OperationType: traverseArrayOpType}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
return postProcessedTokens, skipNextToken
}