| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- package ast
- import (
- "fmt"
- "sort"
- "strings"
- "github.com/opencost/opencost/pkg/filter21/util"
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
- )
- // used to apply a title to pipeline
- var titleCaser cases.Caser = cases.Title(language.Und, cases.NoLower)
- var lowerCaser cases.Caser = cases.Lower(language.Und)
- // TraversalState represents the state of the current leaf node in a traversal
- // of the filter Any grouping ops will include an Enter on their first
- // occurence, and an Exit when leaving the op state.
- type TraversalState int
- const (
- // TraversalStateNone is used whenever a binary op leaf node is traversed.
- TraversalStateNone TraversalState = iota
- // TraversalStateEnter is used when a group op leaf node is traversed (and, or, not)
- TraversalStateEnter
- // TraversalStateExit is used wwhen a group op leaf node is popped (and, or, not).
- TraversalStateExit
- )
- // TransformLeaves produces a new tree, leaving non-leaf nodes (e.g. And, Or)
- // intact and replacing leaf nodes (e.g. Equals, Contains) with the result of
- // calling leafTransformer(node).
- func TransformLeaves(node FilterNode, transformer func(FilterNode) FilterNode) FilterNode {
- if node == nil {
- return nil
- }
- // For group ops, we need to execute the callback with an Enter,
- // recursively call traverse, then execute the callback with an Exit.
- switch n := node.(type) {
- case *NotOp:
- return &NotOp{
- Operand: TransformLeaves(n.Operand, transformer),
- }
- case *AndOp:
- var newOperands []FilterNode
- for _, o := range n.Operands {
- newOperands = append(newOperands, TransformLeaves(o, transformer))
- }
- return &AndOp{
- Operands: newOperands,
- }
- case *OrOp:
- var newOperands []FilterNode
- for _, o := range n.Operands {
- newOperands = append(newOperands, TransformLeaves(o, transformer))
- }
- return &OrOp{
- Operands: newOperands,
- }
- // Remaining nodes are assumed to be leaves
- default:
- return transformer(node)
- }
- }
- // PreOrderTraversal accepts a root `FilterNode` and calls the f callback on
- // each leaf node it traverses. When entering "group" leaf nodes (leaf nodes
- // which contain other leaf nodes), a TraversalStateEnter/Exit will be includes
- // to denote each depth. In short, the callback will be executed twice for each
- // "group" op, once before entering, and once bofore exiting.
- func PreOrderTraversal(node FilterNode, f func(FilterNode, TraversalState)) {
- if node == nil {
- return
- }
- // For group ops, we need to execute the callback with an Enter,
- // recursively call traverse, then execute the callback with an Exit.
- switch n := node.(type) {
- case *NotOp:
- f(node, TraversalStateEnter)
- PreOrderTraversal(n.Operand, f)
- f(node, TraversalStateExit)
- case *AndOp:
- f(node, TraversalStateEnter)
- for _, o := range n.Operands {
- PreOrderTraversal(o, f)
- }
- f(node, TraversalStateExit)
- case *OrOp:
- f(node, TraversalStateEnter)
- for _, o := range n.Operands {
- PreOrderTraversal(o, f)
- }
- f(node, TraversalStateExit)
- // Otherwise, we just linearly traverse
- default:
- f(node, TraversalStateNone)
- }
- }
- // ToPreOrderString runs a PreOrderTraversal and generates an indented tree structure string
- // format for the provided tree root.
- func ToPreOrderString(node FilterNode) string {
- var sb strings.Builder
- indent := 0
- printNode := func(n FilterNode, action TraversalState) {
- if action == TraversalStateEnter {
- sb.WriteString(OpStringFor(n, action, indent))
- indent++
- } else if action == TraversalStateExit {
- indent--
- sb.WriteString(OpStringFor(n, action, indent))
- } else {
- sb.WriteString(OpStringFor(n, action, indent))
- }
- }
- PreOrderTraversal(node, printNode)
- return sb.String()
- }
- // ToPreOrderShortString runs a PreOrderTraversal and generates a condensed tree structure string
- // format for the provided tree root.
- func ToPreOrderShortString(node FilterNode) string {
- var sb strings.Builder
- printNode := func(n FilterNode, action TraversalState) {
- if action == TraversalStateEnter {
- sb.WriteString(ShortOpStringFor(n, action))
- } else if action == TraversalStateExit {
- sb.WriteString(ShortOpStringFor(n, action))
- } else {
- sb.WriteString(ShortOpStringFor(n, action))
- }
- }
- PreOrderTraversal(node, printNode)
- return sb.String()
- }
- // OpStringFor returns a string for the provided leaf node, traversal state, and current
- // depth.
- func OpStringFor(node FilterNode, traversalState TraversalState, depth int) string {
- prefix := indent(depth)
- if traversalState == TraversalStateExit {
- return prefix + "}\n"
- }
- if traversalState == TraversalStateEnter {
- return prefix + titleCaser.String(string(node.Op())) + " {\n"
- }
- open := prefix + titleCaser.String(string(node.Op())) + " { "
- switch n := node.(type) {
- case *VoidOp:
- open += ")"
- case *EqualOp:
- open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
- case *ContainsOp:
- open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
- case *ContainsPrefixOp:
- open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
- case *ContainsSuffixOp:
- open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
- default:
- open += "}\n"
- }
- return open
- }
- // ShortOpStringFor returns a condensed string for the provided leaf node, traversal state, and current
- // depth.
- func ShortOpStringFor(node FilterNode, traversalState TraversalState) string {
- if traversalState == TraversalStateExit {
- return ")"
- }
- if traversalState == TraversalStateEnter {
- return lowerCaser.String(string(node.Op())) + "("
- }
- open := lowerCaser.String(string(node.Op())) + "("
- switch n := node.(type) {
- case *VoidOp:
- open += ")"
- case *EqualOp:
- open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
- case *ContainsOp:
- open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
- case *ContainsPrefixOp:
- open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
- case *ContainsSuffixOp:
- open += fmt.Sprintf("%s,%s)", condenseIdent(n.Left), n.Right)
- default:
- open += ")"
- }
- return open
- }
- // condenses an identifier string
- func condenseIdent(ident Identifier) string {
- s := condense(ident.Field.Name)
- if ident.Key != "" {
- s += "[" + ident.Key + "]"
- }
- return s
- }
- func condense(s string) string {
- lc := lowerCaser.String(s)
- if len(lc) > 2 {
- return lc[:2]
- }
- return lc
- }
- // Clone deep copies and returns the AST parameter.
- func Clone(filter FilterNode) FilterNode {
- var result FilterNode = &VoidOp{}
- var currentOps *util.Stack[FilterGroup] = util.NewStack[FilterGroup]()
- PreOrderTraversal(filter, func(fn FilterNode, state TraversalState) {
- if fn == nil {
- return
- }
- switch n := fn.(type) {
- case *AndOp:
- if state == TraversalStateEnter {
- currentOps.Push(&AndOp{})
- } else if state == TraversalStateExit {
- if currentOps.Length() > 1 {
- current := currentOps.Pop()
- currentOps.Top().Add(current)
- } else {
- result = currentOps.Pop()
- }
- }
- case *OrOp:
- if state == TraversalStateEnter {
- currentOps.Push(&OrOp{})
- } else if state == TraversalStateExit {
- if currentOps.Length() > 1 {
- current := currentOps.Pop()
- currentOps.Top().Add(current)
- } else {
- result = currentOps.Pop()
- }
- }
- case *NotOp:
- if state == TraversalStateEnter {
- currentOps.Push(&NotOp{})
- } else if state == TraversalStateExit {
- if currentOps.Length() > 1 {
- current := currentOps.Pop()
- currentOps.Top().Add(current)
- } else {
- result = currentOps.Pop()
- }
- }
- case *ContradictionOp:
- if currentOps.Length() == 0 {
- result = &ContradictionOp{}
- } else {
- currentOps.Top().Add(&ContradictionOp{})
- }
- case *EqualOp:
- var field Field
- if n.Left.Field != nil {
- field = *n.Left.Field
- }
- sm := &EqualOp{
- Left: Identifier{
- Field: &field,
- Key: n.Left.Key,
- },
- Right: n.Right,
- }
- if currentOps.Length() == 0 {
- result = sm
- } else {
- currentOps.Top().Add(sm)
- }
- case *ContainsOp:
- var field Field
- if n.Left.Field != nil {
- field = *n.Left.Field
- }
- sm := &ContainsOp{
- Left: Identifier{
- Field: &field,
- Key: n.Left.Key,
- },
- Right: n.Right,
- }
- if currentOps.Length() == 0 {
- result = sm
- } else {
- currentOps.Top().Add(sm)
- }
- case *ContainsPrefixOp:
- var field Field
- if n.Left.Field != nil {
- field = *n.Left.Field
- }
- sm := &ContainsPrefixOp{
- Left: Identifier{
- Field: &field,
- Key: n.Left.Key,
- },
- Right: n.Right,
- }
- if currentOps.Length() == 0 {
- result = sm
- } else {
- currentOps.Top().Add(sm)
- }
- case *ContainsSuffixOp:
- var field Field
- if n.Left.Field != nil {
- field = *n.Left.Field
- }
- sm := &ContainsSuffixOp{
- Left: Identifier{
- Field: &field,
- Key: n.Left.Key,
- },
- Right: n.Right,
- }
- if currentOps.Length() == 0 {
- result = sm
- } else {
- currentOps.Top().Add(sm)
- }
- }
- })
- return result
- }
- // returns an 2-space indention for each depth
- func indent(depth int) string {
- if depth <= 0 {
- return ""
- }
- return strings.Repeat(" ", depth)
- }
- func Fields(filter FilterNode) []Field {
- fields := map[Field]bool{}
- PreOrderTraversal(filter, func(fn FilterNode, state TraversalState) {
- if fn == nil {
- return
- }
- switch n := fn.(type) {
- case *EqualOp:
- if n.Left.Field != nil {
- fields[*n.Left.Field] = true
- }
- case *ContainsOp:
- if n.Left.Field != nil {
- fields[*n.Left.Field] = true
- }
- case *ContainsPrefixOp:
- if n.Left.Field != nil {
- fields[*n.Left.Field] = true
- }
- case *ContainsSuffixOp:
- if n.Left.Field != nil {
- fields[*n.Left.Field] = true
- }
- }
- })
- response := make([]Field, 0, len(fields))
- for field := range fields {
- response = append(response, field)
- }
- sort.Slice(response, func(i, j int) bool {
- return response[i].Name < response[j].Name
- })
- return response
- }
- func ToString(filter FilterNode) string {
- // TODO make this return a parseable filter string, if possible...
- return ToPreOrderShortString(filter)
- }
|