walker.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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. if fn == nil {
  201. return
  202. }
  203. switch n := fn.(type) {
  204. case *AndOp:
  205. if state == TraversalStateEnter {
  206. currentOps.Push(&AndOp{})
  207. } else if state == TraversalStateExit {
  208. if currentOps.Length() > 1 {
  209. current := currentOps.Pop()
  210. currentOps.Top().Add(current)
  211. } else {
  212. result = currentOps.Pop()
  213. }
  214. }
  215. case *OrOp:
  216. if state == TraversalStateEnter {
  217. currentOps.Push(&OrOp{})
  218. } else if state == TraversalStateExit {
  219. if currentOps.Length() > 1 {
  220. current := currentOps.Pop()
  221. currentOps.Top().Add(current)
  222. } else {
  223. result = currentOps.Pop()
  224. }
  225. }
  226. case *NotOp:
  227. if state == TraversalStateEnter {
  228. currentOps.Push(&NotOp{})
  229. } else if state == TraversalStateExit {
  230. if currentOps.Length() > 1 {
  231. current := currentOps.Pop()
  232. currentOps.Top().Add(current)
  233. } else {
  234. result = currentOps.Pop()
  235. }
  236. }
  237. case *ContradictionOp:
  238. if currentOps.Length() == 0 {
  239. result = &ContradictionOp{}
  240. } else {
  241. currentOps.Top().Add(&ContradictionOp{})
  242. }
  243. case *EqualOp:
  244. var field Field
  245. if n.Left.Field != nil {
  246. field = *n.Left.Field
  247. }
  248. sm := &EqualOp{
  249. Left: Identifier{
  250. Field: &field,
  251. Key: n.Left.Key,
  252. },
  253. Right: n.Right,
  254. }
  255. if currentOps.Length() == 0 {
  256. result = sm
  257. } else {
  258. currentOps.Top().Add(sm)
  259. }
  260. case *ContainsOp:
  261. var field Field
  262. if n.Left.Field != nil {
  263. field = *n.Left.Field
  264. }
  265. sm := &ContainsOp{
  266. Left: Identifier{
  267. Field: &field,
  268. Key: n.Left.Key,
  269. },
  270. Right: n.Right,
  271. }
  272. if currentOps.Length() == 0 {
  273. result = sm
  274. } else {
  275. currentOps.Top().Add(sm)
  276. }
  277. case *ContainsPrefixOp:
  278. var field Field
  279. if n.Left.Field != nil {
  280. field = *n.Left.Field
  281. }
  282. sm := &ContainsPrefixOp{
  283. Left: Identifier{
  284. Field: &field,
  285. Key: n.Left.Key,
  286. },
  287. Right: n.Right,
  288. }
  289. if currentOps.Length() == 0 {
  290. result = sm
  291. } else {
  292. currentOps.Top().Add(sm)
  293. }
  294. case *ContainsSuffixOp:
  295. var field Field
  296. if n.Left.Field != nil {
  297. field = *n.Left.Field
  298. }
  299. sm := &ContainsSuffixOp{
  300. Left: Identifier{
  301. Field: &field,
  302. Key: n.Left.Key,
  303. },
  304. Right: n.Right,
  305. }
  306. if currentOps.Length() == 0 {
  307. result = sm
  308. } else {
  309. currentOps.Top().Add(sm)
  310. }
  311. }
  312. })
  313. return result
  314. }
  315. // returns an 2-space indention for each depth
  316. func indent(depth int) string {
  317. if depth <= 0 {
  318. return ""
  319. }
  320. return strings.Repeat(" ", depth)
  321. }