1// Use the top level Evaluator or StreamEvaluator to evaluate expressions and return matches.
2//
3package yqlib
4
5import (
6	"bytes"
7	"container/list"
8	"fmt"
9
10	logging "gopkg.in/op/go-logging.v1"
11	yaml "gopkg.in/yaml.v3"
12)
13
14var log = logging.MustGetLogger("yq-lib")
15
16type operationType struct {
17	Type       string
18	NumArgs    uint // number of arguments to the op
19	Precedence uint
20	Handler    operatorHandler
21}
22
23// operators TODO:
24// - mergeEmpty (sets only if the document is empty, do I do that now?)
25
26var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator}
27var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator}
28var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 35, Handler: reduceOperator}
29
30var blockOpType = &operationType{Type: "BLOCK", Precedence: 10, NumArgs: 2, Handler: emptyOperator}
31
32var unionOpType = &operationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: unionOperator}
33
34var pipeOpType = &operationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handler: pipeOperator}
35
36var assignOpType = &operationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: assignUpdateOperator}
37var addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: addAssignOperator}
38var subtractAssignOpType = &operationType{Type: "SUBTRACT_ASSIGN", NumArgs: 2, Precedence: 40, Handler: subtractAssignOperator}
39
40var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator}
41var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator}
42var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: assignVariableOperator}
43var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
44var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator}
45var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
46var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: assignAliasOperator}
47
48var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
49var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
50var subtractOpType = &operationType{Type: "SUBTRACT", NumArgs: 2, Precedence: 42, Handler: subtractOperator}
51var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
52
53var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
54var notEqualsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator}
55
56//createmap needs to be above union, as we use union to build the components of the objects
57var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 15, Handler: createMapOperator}
58
59var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: pipeOperator}
60
61var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
62var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator}
63var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator}
64var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator}
65var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
66var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
67var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
68var getAnchorOpType = &operationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: getAnchorOperator}
69var getAliasOptype = &operationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: getAliasOperator}
70var getDocumentIndexOpType = &operationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: getDocumentIndexOperator}
71var getFilenameOpType = &operationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: getFilenameOperator}
72var getFileIndexOpType = &operationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: getFileIndexOperator}
73var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: getPathOperator}
74
75var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
76var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
77var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
78var subStringOpType = &operationType{Type: "SUBSTR", NumArgs: 1, Precedence: 50, Handler: substituteStringOperator}
79var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
80
81var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
82
83var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator}
84var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 55, Handler: traversePathOperator}
85var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Precedence: 50, Handler: traverseArrayOperator}
86
87var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 55, Handler: selfOperator}
88var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
89var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
90var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
91var emptyOpType = &operationType{Type: "EMPTY", Precedence: 50, Handler: emptyOperator}
92
93var recursiveDescentOpType = &operationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator}
94
95var selectOpType = &operationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: selectOperator}
96var hasOpType = &operationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: hasOperator}
97var deleteChildOpType = &operationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: deleteChildOperator}
98var deleteImmediateChildOpType = &operationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: deleteImmediateChildOperator}
99
100type Operation struct {
101	OperationType *operationType
102	Value         interface{}
103	StringValue   string
104	CandidateNode *CandidateNode // used for Value Path elements
105	Preferences   interface{}
106	UpdateAssign  bool // used for assign ops, when true it means we evaluate the rhs given the lhs
107}
108
109func createValueOperation(value interface{}, stringValue string) *Operation {
110	var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
111	node.Value = stringValue
112
113	switch value.(type) {
114	case float32, float64:
115		node.Tag = "!!float"
116	case int, int64, int32:
117		node.Tag = "!!int"
118	case bool:
119		node.Tag = "!!bool"
120	case string:
121		node.Tag = "!!str"
122	case nil:
123		node.Tag = "!!null"
124	}
125
126	return &Operation{
127		OperationType: valueOpType,
128		Value:         value,
129		StringValue:   stringValue,
130		CandidateNode: &CandidateNode{Node: &node},
131	}
132}
133
134// debugging purposes only
135func (p *Operation) toString() string {
136	if p.OperationType == traversePathOpType {
137		return fmt.Sprintf("%v", p.Value)
138	} else if p.OperationType == selfReferenceOpType {
139		return "SELF"
140	} else if p.OperationType == valueOpType {
141		return fmt.Sprintf("%v (%T)", p.Value, p.Value)
142	} else {
143		return fmt.Sprintf("%v", p.OperationType.Type)
144	}
145}
146
147//use for debugging only
148func NodesToString(collection *list.List) string {
149	if !log.IsEnabledFor(logging.DEBUG) {
150		return ""
151	}
152
153	result := ""
154	for el := collection.Front(); el != nil; el = el.Next() {
155		result = result + "\n" + NodeToString(el.Value.(*CandidateNode))
156	}
157	return result
158}
159
160func NodeToString(node *CandidateNode) string {
161	if !log.IsEnabledFor(logging.DEBUG) {
162		return ""
163	}
164	value := node.Node
165	if value == nil {
166		return "-- nil --"
167	}
168	buf := new(bytes.Buffer)
169	encoder := yaml.NewEncoder(buf)
170	errorEncoding := encoder.Encode(value)
171	if errorEncoding != nil {
172		log.Error("Error debugging node, %v", errorEncoding.Error())
173	}
174	encoder.Close()
175	tag := value.Tag
176	if value.Kind == yaml.DocumentNode {
177		tag = "doc"
178	} else if value.Kind == yaml.AliasNode {
179		tag = "alias"
180	}
181	return fmt.Sprintf(`D%v, P%v, (%v)::%v`, node.Document, node.Path, tag, buf.String())
182}
183
184func KindString(kind yaml.Kind) string {
185	switch kind {
186	case yaml.ScalarNode:
187		return "ScalarNode"
188	case yaml.SequenceNode:
189		return "SequenceNode"
190	case yaml.MappingNode:
191		return "MappingNode"
192	case yaml.DocumentNode:
193		return "DocumentNode"
194	case yaml.AliasNode:
195		return "AliasNode"
196	default:
197		return "unknown!"
198	}
199}
200