| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- package matcher
- import (
- "fmt"
- "github.com/opencost/opencost/pkg/filter21/ast"
- "github.com/opencost/opencost/pkg/filter21/transform"
- "github.com/opencost/opencost/pkg/filter21/util"
- )
- // FieldMapper is the adapter which can fetch actual T instance data of type U
- // leveraging the ast.Identifier definition.
- type FieldMapper[T any, U any] func(T, ast.Identifier) (U, error)
- // StringFieldMapper is the adapter which can fetch actual T instance data of type string
- // leveraging the ast.Identifier definition.
- type StringFieldMapper[T any] FieldMapper[T, string]
- // SliceFieldMapper is the adapter which can fetch actual T instance data of type []string
- // leveraging the ast.Identifier definition.
- type SliceFieldMapper[T any] FieldMapper[T, []string]
- // SliceFieldMapper is the adapter which can fetch actual T instance data of type map[string]string
- // leveraging the ast.Identifier definition.
- type MapFieldMapper[T any] FieldMapper[T, map[string]string]
- // MatchCompiler compiles an `ast.FilterNode` into a Matcher[T] implementation.
- type MatchCompiler[T any] struct {
- stringMatcher *StringMatcherFactory[T]
- sliceMatcher *StringSliceMatcherFactory[T]
- mapMatcher *StringMapMatcherFactory[T]
- passes []transform.CompilerPass
- }
- // NewMatchCompiler creates a new MatchCompiler for T instances provided the funcs which
- // can map ast.Identifier instances to a specific T field
- func NewMatchCompiler[T any](
- stringFieldMapper StringFieldMapper[T],
- sliceFieldMapper SliceFieldMapper[T],
- mapFieldMapper MapFieldMapper[T],
- passes ...transform.CompilerPass,
- ) *MatchCompiler[T] {
- return &MatchCompiler[T]{
- stringMatcher: NewStringMatcherFactory(stringFieldMapper),
- sliceMatcher: NewStringSliceMatcherFactory(sliceFieldMapper),
- mapMatcher: NewStringMapMatcherFactory(mapFieldMapper),
- passes: passes,
- }
- }
- // Compile accepts an `ast.FilterNode` tree and compiles it into a `Matcher[T]` implementation
- // which can be used to match T instances dynamically.
- func (mc *MatchCompiler[T]) Compile(filter ast.FilterNode) (Matcher[T], error) {
- // apply compiler passes on parsed ast
- var err error
- filter, err = transform.ApplyAll(filter, mc.passes)
- if err != nil {
- return nil, fmt.Errorf("applying compiler passes: %w", err)
- }
- // if the root node is a void op, return an allpass
- if _, ok := filter.(*ast.VoidOp); ok {
- return &AllPass[T]{}, nil
- }
- var result Matcher[T]
- var currentOps *util.Stack[MatcherGroup[T]] = util.NewStack[MatcherGroup[T]]()
- // handle leaf is the ast walker func. group ops get pushed onto a stack on
- // the Enter state, and popped on the Exit state. Any ops between Enter and
- // Exit are added to the group. If there are no more groups on the stack after
- // an Exit state, we set the result to the final group.
- handleLeaf := func(leaf ast.FilterNode, state ast.TraversalState) {
- switch n := leaf.(type) {
- case *ast.AndOp:
- if state == ast.TraversalStateEnter {
- currentOps.Push(&And[T]{})
- } else if state == ast.TraversalStateExit {
- if currentOps.Length() > 1 {
- current := currentOps.Pop()
- currentOps.Top().Add(current)
- } else {
- result = currentOps.Pop()
- }
- }
- case *ast.OrOp:
- if state == ast.TraversalStateEnter {
- currentOps.Push(&Or[T]{})
- } else if state == ast.TraversalStateExit {
- if currentOps.Length() > 1 {
- current := currentOps.Pop()
- currentOps.Top().Add(current)
- } else {
- result = currentOps.Pop()
- }
- }
- case *ast.NotOp:
- if state == ast.TraversalStateEnter {
- currentOps.Push(&Not[T]{})
- } else if state == ast.TraversalStateExit {
- if currentOps.Length() > 1 {
- current := currentOps.Pop()
- currentOps.Top().Add(current)
- } else {
- result = currentOps.Pop()
- }
- }
- case *ast.ContradictionOp:
- if currentOps.Length() == 0 {
- result = &AllCut[T]{}
- } else {
- currentOps.Top().Add(&AllCut[T]{})
- }
- case *ast.EqualOp:
- sm := mc.stringMatcher.NewStringMatcher(n.Op(), n.Left, n.Right)
- if currentOps.Length() == 0 {
- result = sm
- } else {
- currentOps.Top().Add(sm)
- }
- case *ast.ContainsOp:
- f := n.Left.Field
- key := n.Left.Key
- var sm Matcher[T]
- if f.IsSlice() {
- sm = mc.sliceMatcher.NewStringSliceMatcher(n.Op(), n.Left, n.Right)
- } else if f.IsMap() && key == "" {
- sm = mc.mapMatcher.NewStringMapMatcher(n.Op(), n.Left, n.Right)
- } else {
- sm = mc.stringMatcher.NewStringMatcher(n.Op(), n.Left, n.Right)
- }
- if currentOps.Length() == 0 {
- result = sm
- } else {
- currentOps.Top().Add(sm)
- }
- case *ast.ContainsPrefixOp:
- f := n.Left.Field
- key := n.Left.Key
- var sm Matcher[T]
- if f.IsSlice() {
- sm = mc.sliceMatcher.NewStringSliceMatcher(n.Op(), n.Left, n.Right)
- } else if f.IsMap() && key == "" {
- sm = mc.mapMatcher.NewStringMapMatcher(n.Op(), n.Left, n.Right)
- } else {
- sm = mc.stringMatcher.NewStringMatcher(n.Op(), n.Left, n.Right)
- }
- if currentOps.Length() == 0 {
- result = sm
- } else {
- currentOps.Top().Add(sm)
- }
- case *ast.ContainsSuffixOp:
- f := n.Left.Field
- key := n.Left.Key
- var sm Matcher[T]
- if f.IsSlice() {
- sm = mc.sliceMatcher.NewStringSliceMatcher(n.Op(), n.Left, n.Right)
- } else if f.IsMap() && key == "" {
- sm = mc.mapMatcher.NewStringMapMatcher(n.Op(), n.Left, n.Right)
- } else {
- sm = mc.stringMatcher.NewStringMatcher(n.Op(), n.Left, n.Right)
- }
- if currentOps.Length() == 0 {
- result = sm
- } else {
- currentOps.Top().Add(sm)
- }
- }
- }
- ast.PreOrderTraversal(filter, handleLeaf)
- if result == nil {
- return &AllPass[T]{}, nil
- }
- return result, nil
- }
|