walker.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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. // PreOrderTraversal accepts a root `FilterNode` and calls the f callback on each leaf node it traverses.
  25. // When entering "group" leaf nodes (leaf nodes which contain other leaf nodes), a TraversalStateEnter/Exit
  26. // will be includes to denote each depth. In short, the callback will be executed twice for each "group" op,
  27. // once before entering, and once bofore exiting.
  28. func PreOrderTraversal(node FilterNode, f func(FilterNode, TraversalState)) {
  29. if node == nil {
  30. return
  31. }
  32. // For group ops, we need to execute the callback with an Enter,
  33. // recursively call traverse, then execute the callback with an Exit.
  34. switch n := node.(type) {
  35. case *NotOp:
  36. f(node, TraversalStateEnter)
  37. PreOrderTraversal(n.Operand, f)
  38. f(node, TraversalStateExit)
  39. case *AndOp:
  40. f(node, TraversalStateEnter)
  41. for _, o := range n.Operands {
  42. PreOrderTraversal(o, f)
  43. }
  44. f(node, TraversalStateExit)
  45. case *OrOp:
  46. f(node, TraversalStateEnter)
  47. for _, o := range n.Operands {
  48. PreOrderTraversal(o, f)
  49. }
  50. f(node, TraversalStateExit)
  51. // Otherwise, we just linearly traverse
  52. default:
  53. f(node, TraversalStateNone)
  54. }
  55. }
  56. // ToPreOrderString runs a PreOrderTraversal and generates an indented tree structure string
  57. // format for the provided tree root.
  58. func ToPreOrderString(node FilterNode) string {
  59. var sb strings.Builder
  60. indent := 0
  61. printNode := func(n FilterNode, action TraversalState) {
  62. if action == TraversalStateEnter {
  63. sb.WriteString(OpStringFor(n, action, indent))
  64. indent++
  65. } else if action == TraversalStateExit {
  66. indent--
  67. sb.WriteString(OpStringFor(n, action, indent))
  68. } else {
  69. sb.WriteString(OpStringFor(n, action, indent))
  70. }
  71. }
  72. PreOrderTraversal(node, printNode)
  73. return sb.String()
  74. }
  75. // ToPreOrderShortString runs a PreOrderTraversal and generates a condensed tree structure string
  76. // format for the provided tree root.
  77. func ToPreOrderShortString(node FilterNode) string {
  78. var sb strings.Builder
  79. printNode := func(n FilterNode, action TraversalState) {
  80. if action == TraversalStateEnter {
  81. sb.WriteString(ShortOpStringFor(n, action))
  82. } else if action == TraversalStateExit {
  83. sb.WriteString(ShortOpStringFor(n, action))
  84. } else {
  85. sb.WriteString(ShortOpStringFor(n, action))
  86. }
  87. }
  88. PreOrderTraversal(node, printNode)
  89. return sb.String()
  90. }
  91. // OpStringFor returns a string for the provided leaf node, traversal state, and current
  92. // depth.
  93. func OpStringFor(node FilterNode, traversalState TraversalState, depth int) string {
  94. prefix := indent(depth)
  95. if traversalState == TraversalStateExit {
  96. return prefix + "}\n"
  97. }
  98. if traversalState == TraversalStateEnter {
  99. return prefix + titleCaser.String(string(node.Op())) + " {\n"
  100. }
  101. open := prefix + titleCaser.String(string(node.Op())) + " { "
  102. switch n := node.(type) {
  103. case *VoidOp:
  104. open += ")"
  105. case *EqualOp:
  106. open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
  107. case *ContainsOp:
  108. open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
  109. case *ContainsPrefixOp:
  110. open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
  111. case *ContainsSuffixOp:
  112. open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
  113. default:
  114. open += "}\n"
  115. }
  116. return open
  117. }
  118. // ShortOpStringFor returns a condensed string for the provided leaf node, traversal state, and current
  119. // depth.
  120. func ShortOpStringFor(node FilterNode, traversalState TraversalState) string {
  121. if traversalState == TraversalStateExit {
  122. return ")"
  123. }
  124. if traversalState == TraversalStateEnter {
  125. return lowerCaser.String(string(node.Op())) + "("
  126. }
  127. open := lowerCaser.String(string(node.Op())) + "("
  128. switch n := node.(type) {
  129. case *VoidOp:
  130. open += ")"
  131. case *EqualOp:
  132. open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
  133. case *ContainsOp:
  134. open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
  135. case *ContainsPrefixOp:
  136. open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
  137. case *ContainsSuffixOp:
  138. open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
  139. default:
  140. open += ")"
  141. }
  142. return open
  143. }
  144. // condenses an identifier string
  145. func condenseIdent(ident Identifier) string {
  146. s := condense(ident.Field.Name)
  147. if ident.Key != "" {
  148. s += "[" + ident.Key + "]"
  149. }
  150. return s
  151. }
  152. func condense(s string) string {
  153. lc := lowerCaser.String(s)
  154. if len(lc) > 2 {
  155. return lc[:2]
  156. }
  157. return lc
  158. }
  159. // Clone deep copies and returns the AST parameter.
  160. func Clone(filter FilterNode) FilterNode {
  161. var result FilterNode = &VoidOp{}
  162. var currentOps *util.Stack[FilterGroup] = util.NewStack[FilterGroup]()
  163. PreOrderTraversal(filter, func(fn FilterNode, state TraversalState) {
  164. switch n := fn.(type) {
  165. case *AndOp:
  166. if state == TraversalStateEnter {
  167. currentOps.Push(&AndOp{})
  168. } else if state == TraversalStateExit {
  169. if currentOps.Length() > 1 {
  170. current := currentOps.Pop()
  171. currentOps.Top().Add(current)
  172. } else {
  173. result = currentOps.Pop()
  174. }
  175. }
  176. case *OrOp:
  177. if state == TraversalStateEnter {
  178. currentOps.Push(&OrOp{})
  179. } else if state == TraversalStateExit {
  180. if currentOps.Length() > 1 {
  181. current := currentOps.Pop()
  182. currentOps.Top().Add(current)
  183. } else {
  184. result = currentOps.Pop()
  185. }
  186. }
  187. case *NotOp:
  188. if state == TraversalStateEnter {
  189. currentOps.Push(&NotOp{})
  190. } else if state == TraversalStateExit {
  191. if currentOps.Length() > 1 {
  192. current := currentOps.Pop()
  193. currentOps.Top().Add(current)
  194. } else {
  195. result = currentOps.Pop()
  196. }
  197. }
  198. case *EqualOp:
  199. var field Field = *n.Left.Field
  200. sm := &EqualOp{
  201. Left: Identifier{
  202. Field: &field,
  203. Key: n.Left.Key,
  204. },
  205. Right: n.Right,
  206. }
  207. if currentOps.Length() == 0 {
  208. result = sm
  209. } else {
  210. currentOps.Top().Add(sm)
  211. }
  212. case *ContainsOp:
  213. var field Field = *n.Left.Field
  214. sm := &ContainsOp{
  215. Left: Identifier{
  216. Field: &field,
  217. Key: n.Left.Key,
  218. },
  219. Right: n.Right,
  220. }
  221. if currentOps.Length() == 0 {
  222. result = sm
  223. } else {
  224. currentOps.Top().Add(sm)
  225. }
  226. case *ContainsPrefixOp:
  227. var field Field = *n.Left.Field
  228. sm := &ContainsPrefixOp{
  229. Left: Identifier{
  230. Field: &field,
  231. Key: n.Left.Key,
  232. },
  233. Right: n.Right,
  234. }
  235. if currentOps.Length() == 0 {
  236. result = sm
  237. } else {
  238. currentOps.Top().Add(sm)
  239. }
  240. case *ContainsSuffixOp:
  241. var field Field = *n.Left.Field
  242. sm := &ContainsSuffixOp{
  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. }
  255. })
  256. return result
  257. }
  258. // returns an 2-space indention for each depth
  259. func indent(depth int) string {
  260. if depth <= 0 {
  261. return ""
  262. }
  263. return strings.Repeat(" ", depth)
  264. }