walker.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. package ast
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/opencost/opencost/pkg/filter21/util"
  6. "golang.org/x/text/cases"
  7. "golang.org/x/text/language"
  8. )
  9. // used to apply a title to pipeline
  10. var titleCaser cases.Caser = cases.Title(language.Und, cases.NoLower)
  11. var lowerCaser cases.Caser = cases.Lower(language.Und)
  12. // TraversalState represents the state of the current leaf node in a traversal
  13. // of the filter Any grouping ops will include an Enter on their first
  14. // occurence, and an Exit when leaving the op state.
  15. type TraversalState int
  16. const (
  17. // TraversalStateNone is used whenever a binary op leaf node is traversed.
  18. TraversalStateNone TraversalState = iota
  19. // TraversalStateEnter is used when a group op leaf node is traversed (and, or, not)
  20. TraversalStateEnter
  21. // TraversalStateExit is used wwhen a group op leaf node is popped (and, or, not).
  22. TraversalStateExit
  23. )
  24. // TransformLeaves produces a new tree, leaving non-leaf nodes (e.g. And, Or)
  25. // intact and replacing leaf nodes (e.g. Equals, Contains) with the result of
  26. // calling leafTransformer(node).
  27. func TransformLeaves(node FilterNode, transformer func(FilterNode) FilterNode) FilterNode {
  28. if node == nil {
  29. return nil
  30. }
  31. // For group ops, we need to execute the callback with an Enter,
  32. // recursively call traverse, then execute the callback with an Exit.
  33. switch n := node.(type) {
  34. case *NotOp:
  35. return &NotOp{
  36. Operand: TransformLeaves(n.Operand, transformer),
  37. }
  38. case *AndOp:
  39. var newOperands []FilterNode
  40. for _, o := range n.Operands {
  41. newOperands = append(newOperands, TransformLeaves(o, transformer))
  42. }
  43. return &AndOp{
  44. Operands: newOperands,
  45. }
  46. case *OrOp:
  47. var newOperands []FilterNode
  48. for _, o := range n.Operands {
  49. newOperands = append(newOperands, TransformLeaves(o, transformer))
  50. }
  51. return &OrOp{
  52. Operands: newOperands,
  53. }
  54. // Remaining nodes are assumed to be leaves
  55. default:
  56. return transformer(node)
  57. }
  58. }
  59. // PreOrderTraversal accepts a root `FilterNode` and calls the f callback on
  60. // each leaf node it traverses. When entering "group" leaf nodes (leaf nodes
  61. // which contain other leaf nodes), a TraversalStateEnter/Exit will be includes
  62. // to denote each depth. In short, the callback will be executed twice for each
  63. // "group" op, once before entering, and once bofore exiting.
  64. func PreOrderTraversal(node FilterNode, f func(FilterNode, TraversalState)) {
  65. if node == nil {
  66. return
  67. }
  68. // For group ops, we need to execute the callback with an Enter,
  69. // recursively call traverse, then execute the callback with an Exit.
  70. switch n := node.(type) {
  71. case *NotOp:
  72. f(node, TraversalStateEnter)
  73. PreOrderTraversal(n.Operand, f)
  74. f(node, TraversalStateExit)
  75. case *AndOp:
  76. f(node, TraversalStateEnter)
  77. for _, o := range n.Operands {
  78. PreOrderTraversal(o, f)
  79. }
  80. f(node, TraversalStateExit)
  81. case *OrOp:
  82. f(node, TraversalStateEnter)
  83. for _, o := range n.Operands {
  84. PreOrderTraversal(o, f)
  85. }
  86. f(node, TraversalStateExit)
  87. // Otherwise, we just linearly traverse
  88. default:
  89. f(node, TraversalStateNone)
  90. }
  91. }
  92. // ToPreOrderString runs a PreOrderTraversal and generates an indented tree structure string
  93. // format for the provided tree root.
  94. func ToPreOrderString(node FilterNode) string {
  95. var sb strings.Builder
  96. indent := 0
  97. printNode := func(n FilterNode, action TraversalState) {
  98. if action == TraversalStateEnter {
  99. sb.WriteString(OpStringFor(n, action, indent))
  100. indent++
  101. } else if action == TraversalStateExit {
  102. indent--
  103. sb.WriteString(OpStringFor(n, action, indent))
  104. } else {
  105. sb.WriteString(OpStringFor(n, action, indent))
  106. }
  107. }
  108. PreOrderTraversal(node, printNode)
  109. return sb.String()
  110. }
  111. // ToPreOrderShortString runs a PreOrderTraversal and generates a condensed tree structure string
  112. // format for the provided tree root.
  113. func ToPreOrderShortString(node FilterNode) string {
  114. var sb strings.Builder
  115. printNode := func(n FilterNode, action TraversalState) {
  116. if action == TraversalStateEnter {
  117. sb.WriteString(ShortOpStringFor(n, action))
  118. } else if action == TraversalStateExit {
  119. sb.WriteString(ShortOpStringFor(n, action))
  120. } else {
  121. sb.WriteString(ShortOpStringFor(n, action))
  122. }
  123. }
  124. PreOrderTraversal(node, printNode)
  125. return sb.String()
  126. }
  127. // OpStringFor returns a string for the provided leaf node, traversal state, and current
  128. // depth.
  129. func OpStringFor(node FilterNode, traversalState TraversalState, depth int) string {
  130. prefix := indent(depth)
  131. if traversalState == TraversalStateExit {
  132. return prefix + "}\n"
  133. }
  134. if traversalState == TraversalStateEnter {
  135. return prefix + titleCaser.String(string(node.Op())) + " {\n"
  136. }
  137. open := prefix + titleCaser.String(string(node.Op())) + " { "
  138. switch n := node.(type) {
  139. case *VoidOp:
  140. open += ")"
  141. case *EqualOp:
  142. open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
  143. case *ContainsOp:
  144. open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
  145. case *ContainsPrefixOp:
  146. open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
  147. case *ContainsSuffixOp:
  148. open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
  149. default:
  150. open += "}\n"
  151. }
  152. return open
  153. }
  154. // ShortOpStringFor returns a condensed string for the provided leaf node, traversal state, and current
  155. // depth.
  156. func ShortOpStringFor(node FilterNode, traversalState TraversalState) string {
  157. if traversalState == TraversalStateExit {
  158. return ")"
  159. }
  160. if traversalState == TraversalStateEnter {
  161. return lowerCaser.String(string(node.Op())) + "("
  162. }
  163. open := lowerCaser.String(string(node.Op())) + "("
  164. switch n := node.(type) {
  165. case *VoidOp:
  166. open += ")"
  167. case *EqualOp:
  168. open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
  169. case *ContainsOp:
  170. open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
  171. case *ContainsPrefixOp:
  172. open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
  173. case *ContainsSuffixOp:
  174. open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
  175. default:
  176. open += ")"
  177. }
  178. return open
  179. }
  180. // condenses an identifier string
  181. func condenseIdent(ident Identifier) string {
  182. s := condense(ident.Field.Name)
  183. if ident.Key != "" {
  184. s += "[" + ident.Key + "]"
  185. }
  186. return s
  187. }
  188. func condense(s string) string {
  189. lc := lowerCaser.String(s)
  190. if len(lc) > 2 {
  191. return lc[:2]
  192. }
  193. return lc
  194. }
  195. // Clone deep copies and returns the AST parameter.
  196. func Clone(filter FilterNode) FilterNode {
  197. var result FilterNode = &VoidOp{}
  198. var currentOps *util.Stack[FilterGroup] = util.NewStack[FilterGroup]()
  199. PreOrderTraversal(filter, func(fn FilterNode, state TraversalState) {
  200. switch n := fn.(type) {
  201. case *AndOp:
  202. if state == TraversalStateEnter {
  203. currentOps.Push(&AndOp{})
  204. } else if state == TraversalStateExit {
  205. if currentOps.Length() > 1 {
  206. current := currentOps.Pop()
  207. currentOps.Top().Add(current)
  208. } else {
  209. result = currentOps.Pop()
  210. }
  211. }
  212. case *OrOp:
  213. if state == TraversalStateEnter {
  214. currentOps.Push(&OrOp{})
  215. } else if state == TraversalStateExit {
  216. if currentOps.Length() > 1 {
  217. current := currentOps.Pop()
  218. currentOps.Top().Add(current)
  219. } else {
  220. result = currentOps.Pop()
  221. }
  222. }
  223. case *NotOp:
  224. if state == TraversalStateEnter {
  225. currentOps.Push(&NotOp{})
  226. } else if state == TraversalStateExit {
  227. if currentOps.Length() > 1 {
  228. current := currentOps.Pop()
  229. currentOps.Top().Add(current)
  230. } else {
  231. result = currentOps.Pop()
  232. }
  233. }
  234. case *ContradictionOp:
  235. if currentOps.Length() == 0 {
  236. result = &ContradictionOp{}
  237. } else {
  238. currentOps.Top().Add(&ContradictionOp{})
  239. }
  240. case *EqualOp:
  241. var field Field = *n.Left.Field
  242. sm := &EqualOp{
  243. Left: Identifier{
  244. Field: &field,
  245. Key: n.Left.Key,
  246. },
  247. Right: n.Right,
  248. }
  249. if currentOps.Length() == 0 {
  250. result = sm
  251. } else {
  252. currentOps.Top().Add(sm)
  253. }
  254. case *ContainsOp:
  255. var field Field = *n.Left.Field
  256. sm := &ContainsOp{
  257. Left: Identifier{
  258. Field: &field,
  259. Key: n.Left.Key,
  260. },
  261. Right: n.Right,
  262. }
  263. if currentOps.Length() == 0 {
  264. result = sm
  265. } else {
  266. currentOps.Top().Add(sm)
  267. }
  268. case *ContainsPrefixOp:
  269. var field Field = *n.Left.Field
  270. sm := &ContainsPrefixOp{
  271. Left: Identifier{
  272. Field: &field,
  273. Key: n.Left.Key,
  274. },
  275. Right: n.Right,
  276. }
  277. if currentOps.Length() == 0 {
  278. result = sm
  279. } else {
  280. currentOps.Top().Add(sm)
  281. }
  282. case *ContainsSuffixOp:
  283. var field Field = *n.Left.Field
  284. sm := &ContainsSuffixOp{
  285. Left: Identifier{
  286. Field: &field,
  287. Key: n.Left.Key,
  288. },
  289. Right: n.Right,
  290. }
  291. if currentOps.Length() == 0 {
  292. result = sm
  293. } else {
  294. currentOps.Top().Add(sm)
  295. }
  296. }
  297. })
  298. return result
  299. }
  300. // returns an 2-space indention for each depth
  301. func indent(depth int) string {
  302. if depth <= 0 {
  303. return ""
  304. }
  305. return strings.Repeat(" ", depth)
  306. }