yq/pkg/yqlib/operator_multiply.go

235 lines
7.7 KiB
Go

package yqlib
import (
"container/list"
"fmt"
"strconv"
"strings"
)
type multiplyPreferences struct {
AppendArrays bool
DeepMergeArrays bool
TraversePrefs traversePreferences
AssignPrefs assignPreferences
}
func createMultiplyOp(prefs interface{}) func(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
return func(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
return &ExpressionNode{Operation: &Operation{OperationType: multiplyOpType, Preferences: prefs},
LHS: lhs,
RHS: rhs}
}
}
func multiplyAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
var multiplyPrefs = expressionNode.Operation.Preferences
return compoundAssignFunction(d, context, expressionNode, createMultiplyOp(multiplyPrefs))
}
func multiplyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("MultiplyOperator")
return crossFunction(d, context, expressionNode, multiply(expressionNode.Operation.Preferences.(multiplyPreferences)), false)
}
func getComments(lhs *CandidateNode, rhs *CandidateNode) (leadingContent string, headComment string, footComment string) {
leadingContent = rhs.LeadingContent
headComment = rhs.HeadComment
footComment = rhs.FootComment
if lhs.HeadComment != "" || lhs.LeadingContent != "" {
headComment = lhs.HeadComment
leadingContent = lhs.LeadingContent
}
if lhs.FootComment != "" {
footComment = lhs.FootComment
}
return leadingContent, headComment, footComment
}
func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
// need to do this before unWrapping the potential document node
leadingContent, headComment, footComment := getComments(lhs, rhs)
log.Debugf("Multiplying LHS: %v", NodeToString(lhs))
log.Debugf("- RHS: %v", NodeToString(rhs))
if rhs.Tag == "!!null" {
return lhs.Copy(), nil
}
if (lhs.Kind == MappingNode && rhs.Kind == MappingNode) ||
(lhs.Tag == "!!null" && rhs.Kind == MappingNode) ||
(lhs.Kind == SequenceNode && rhs.Kind == SequenceNode) ||
(lhs.Tag == "!!null" && rhs.Kind == SequenceNode) {
var newBlank = lhs.Copy()
newBlank.LeadingContent = leadingContent
newBlank.HeadComment = headComment
newBlank.FootComment = footComment
return mergeObjects(d, context.WritableClone(), newBlank, rhs, preferences)
}
return multiplyScalars(lhs, rhs)
}
}
func multiplyScalars(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhsTag := lhs.Tag
rhsTag := rhs.guessTagFromCustomType()
lhsIsCustom := false
if !strings.HasPrefix(lhsTag, "!!") {
// custom tag - we have to have a guess
lhsTag = lhs.guessTagFromCustomType()
lhsIsCustom = true
}
if lhsTag == "!!int" && rhsTag == "!!int" {
return multiplyIntegers(lhs, rhs)
} else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") {
return multiplyFloats(lhs, rhs, lhsIsCustom)
} else if (lhsTag == "!!str" && rhsTag == "!!int") || (lhsTag == "!!int" && rhsTag == "!!str") {
return repeatString(lhs, rhs)
}
return nil, fmt.Errorf("cannot multiply %v with %v", lhs.Tag, rhs.Tag)
}
func multiplyFloats(lhs *CandidateNode, rhs *CandidateNode, lhsIsCustom bool) (*CandidateNode, error) {
target := lhs.CopyWithoutContent()
target.Kind = ScalarNode
target.Style = lhs.Style
if lhsIsCustom {
target.Tag = lhs.Tag
} else {
target.Tag = "!!float"
}
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
if err != nil {
return nil, err
}
rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
if err != nil {
return nil, err
}
target.Value = fmt.Sprintf("%v", lhsNum*rhsNum)
return target, nil
}
func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
target := lhs.CopyWithoutContent()
target.Kind = ScalarNode
target.Style = lhs.Style
target.Tag = lhs.Tag
format, lhsNum, err := parseInt64(lhs.Value)
if err != nil {
return nil, err
}
_, rhsNum, err := parseInt64(rhs.Value)
if err != nil {
return nil, err
}
target.Value = fmt.Sprintf(format, lhsNum*rhsNum)
return target, nil
}
func repeatString(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
var stringNode *CandidateNode
var intNode *CandidateNode
if lhs.Tag == "!!str" {
stringNode = lhs
intNode = rhs
} else {
stringNode = rhs
intNode = lhs
}
target := lhs.CopyWithoutContent()
target.UpdateAttributesFrom(stringNode, assignPreferences{})
count, err := parseInt(intNode.Value)
if err != nil {
return nil, err
} else if count < 0 {
return nil, fmt.Errorf("Cannot repeat string by a negative number (%v)", count)
}
target.Value = strings.Repeat(stringNode.Value, count)
return target, nil
}
func mergeObjects(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) (*CandidateNode, error) {
var results = list.New()
// only need to recurse the array if we are doing a deep merge
prefs := recursiveDescentPreferences{RecurseArray: preferences.DeepMergeArrays,
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}}
log.Debugf("merge - preferences.DeepMergeArrays %v", preferences.DeepMergeArrays)
log.Debugf("merge - preferences.AppendArrays %v", preferences.AppendArrays)
err := recursiveDecent(results, context.SingleChildContext(rhs), prefs)
if err != nil {
return nil, err
}
var pathIndexToStartFrom int
if results.Front() != nil {
pathIndexToStartFrom = len(results.Front().Value.(*CandidateNode).GetPath())
log.Debugf("pathIndexToStartFrom: %v", pathIndexToStartFrom)
}
for el := results.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("going to applied assignment to LHS: %v with RHS: %v", NodeToString(lhs), NodeToString(candidate))
if candidate.Tag == "!!merge" {
continue
}
err := applyAssignment(d, context, pathIndexToStartFrom, lhs, candidate, preferences)
if err != nil {
return nil, err
}
log.Debugf("applied assignment to LHS: %v", NodeToString(lhs))
}
return lhs, nil
}
func applyAssignment(d *dataTreeNavigator, context Context, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) error {
shouldAppendArrays := preferences.AppendArrays
lhsPath := rhs.GetPath()[pathIndexToStartFrom:]
log.Debugf("merge - lhsPath %v", lhsPath)
assignmentOp := &Operation{OperationType: assignAttributesOpType, Preferences: preferences.AssignPrefs}
if shouldAppendArrays && rhs.Kind == SequenceNode {
assignmentOp.OperationType = addAssignOpType
log.Debugf("merge - assignmentOp.OperationType = addAssignOpType")
} else if !preferences.DeepMergeArrays && rhs.Kind == SequenceNode ||
(rhs.Kind == ScalarNode || rhs.Kind == AliasNode) {
assignmentOp.OperationType = assignOpType
assignmentOp.UpdateAssign = false
log.Debugf("merge - rhs.Kind == SequenceNode: %v", rhs.Kind == SequenceNode)
log.Debugf("merge - rhs.Kind == ScalarNode: %v", rhs.Kind == ScalarNode)
log.Debugf("merge - rhs.Kind == AliasNode: %v", rhs.Kind == AliasNode)
log.Debugf("merge - assignmentOp.OperationType = assignOpType, no updateassign")
} else {
log.Debugf("merge - assignmentOp := &Operation{OperationType: assignAttributesOpType}")
}
rhsOp := &Operation{OperationType: referenceOpType, CandidateNode: rhs}
assignmentOpNode := &ExpressionNode{
Operation: assignmentOp,
LHS: createTraversalTree(lhsPath, preferences.TraversePrefs, rhs.IsMapKey),
RHS: &ExpressionNode{Operation: rhsOp},
}
_, err := d.GetMatchingNodes(context.SingleChildContext(lhs), assignmentOpNode)
return err
}