Skip to content

Commit

Permalink
#12 Refactor factory with the operator precedence. Reverse Polish Not…
Browse files Browse the repository at this point in the history
…ation in progress
  • Loading branch information
thegodenage committed Mar 25, 2024
1 parent 8ff4ff8 commit 9e97dff
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 161 deletions.
196 changes: 44 additions & 152 deletions internal/rule/factory.go
Original file line number Diff line number Diff line change
@@ -1,180 +1,72 @@
package rule

import (
"errors"
"fmt"
"reflect"

"github.com/emirpasic/gods/stacks/arraystack"
)
import "github.com/emirpasic/gods/stacks/arraystack"

var (
matches = []match{}

adjustableNodes = []reflect.Type{
reflect.TypeOf(gt{}),
}

higherOrderNodes = []reflect.Type{
reflect.TypeOf(and{}),
reflect.TypeOf(or{}),
// operatorPrecedence is a slice of operators in the precedence order.
// The most important ones are on the top, the lest important ones are on the bottom.
// Also if there are two operators: A and B, and if those are in the same row,
// those are equal in terms of precedence.
operatorPrecedence = [][]string{
// Postfix
{
tokenLParen,
tokenRParen,
tokenDot,
},
// Relational
{
tokenMoreThan,
tokenLessThan,
},
// Equality
{
tokenEqual,
tokenNotEqual,
},
// AND
{
tokenDoubleAmpersand,
},
// OR
{
tokenOr,
},
// Dot
{
tokenDot,
},
}
)

type NodeAdjuster interface {
AdjustNode(nd node, tokensMatch []Token) (node, error)
}

type expressionTreeFactory struct {
nodeAdjuster NodeAdjuster
}

var _ ExpressionTreeFactory = (*expressionTreeFactory)(nil)

func (e *expressionTreeFactory) CreateExpressionTree(tokens []Token) (expressionTree, error) {
var (
nodes []node
tokenMatch []Token
)

for _, token := range tokens {
tokenMatch = append(tokenMatch, token)

for _, m := range matches {
if m.isMatching(tokenMatch) {
nd, err := e.adaptNode(m.node, tokenMatch)
if err != nil {
return nil, fmt.Errorf("adapt node: %w", err)
}

nodes = append(nodes, nd)

tokenMatch = []Token{}
}
}
}

eTree, err := buildExpressionTree(nodes)
if err != nil {
return nil, fmt.Errorf("build expression tree: %w", err)
}

return eTree, nil
}

Check failure on line 49 in internal/rule/factory.go

View workflow job for this annotation

GitHub Actions / build

missing return

func (e *expressionTreeFactory) adaptNode(base node, tokenMatch []Token) (node, error) {
if !isAdjustableNode(base) {
return base, nil
}

nd, err := e.nodeAdjuster.AdjustNode(base, tokenMatch)
if err != nil {
return nil, fmt.Errorf("adjust node %s: %w", reflect.TypeOf(base).Name(), err)
}

return nd, nil
}

type match struct {
tokens []Token
node node
}
func reversePolishNotationSort(tokens []Token) []Token {
var (
operatorStack arraystack.Stack

Check failure on line 53 in internal/rule/factory.go

View workflow job for this annotation

GitHub Actions / build

operatorStack declared and not used
outputTokens []Token

Check failure on line 54 in internal/rule/factory.go

View workflow job for this annotation

GitHub Actions / build

outputTokens declared and not used
)

func (m *match) isMatching(tokens []Token) bool {
if len(m.tokens) != len(tokens) {
return false
}
for _, token := range tokens {
if isOperator(token) {

for i, token := range m.tokens {
if tokens[i].Name != token.Name {
return false
}
}

return true
}

Check failure on line 62 in internal/rule/factory.go

View workflow job for this annotation

GitHub Actions / build

missing return

func isAdjustableNode(nd node) bool {
nodeType := reflect.TypeOf(nd)

for _, adjustableNode := range adjustableNodes {
if nodeType.Name() == adjustableNode.Name() {
func isOperator(tkn Token) bool {
for _, tknOperator := range tokensOperators {
if tkn.Name == tknOperator {
return true
}
}

return false
}

func buildExpressionTree(nodes []node) (expressionTree, error) {
stack := arraystack.Stack{}

// todo: we need to add additional check if less than 2 then we need to validate if NEXT node is not a higherOrderNode
// and then we need to build based on that
for i := 0; i < len(nodes); i++ {
switch nodes[i].(type) {
case and:
// after nth iteration it could be an and, we want then continue
// in order to add additional elements to the stack
if stack.Size() < 2 {
continue
}

if stack.Size() > 2 {
return nil, errors.New("expression is invalid, and can only evaluate 2 predicates")
}

nd := and{}

var childrenNodes []node

for j := 0; j < 2; j++ {
v, _ := stack.Pop()
vt, _ := v.(node)
childrenNodes = append(childrenNodes, vt)
}

// nodes are popped from the stack, therefore we need to change order of the children
nd.SetChild(childrenNodes[1], childrenNodes[0])

stack.Clear()

stack.Push(nd)
case or:
if stack.Size() < 2 {
continue
}

if stack.Size() > 2 {
return nil, errors.New("expression is invalid, and can only evaluate 2 predicates")
}

nd := or{}

var childrenNodes []node

for j := 0; j < 2; j++ {
v, _ := stack.Pop()
vt, _ := v.(node)
childrenNodes = append(childrenNodes, vt)
}

nd.SetChild(childrenNodes[1], childrenNodes[0])

stack.Clear()

stack.Push(nd)
default:
stack.Push(nodes[i])
}
}

v, ok := stack.Pop()
if !ok {
return nil, fmt.Errorf("empty expression stack")
}

vt, _ := v.(node)

return expressionTree(vt), nil
}
13 changes: 4 additions & 9 deletions internal/rule/tokenizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ var (
tokenLessThan = "<"
tokenSingleApostrophe = "'"
tokenOr = "||"
specialCharacters = []string{
tokenEqual = "=="
tokenNotEqual = "!="
tokensOperators = []string{
tokenLParen,
tokenRParen,
tokenDot,
Expand All @@ -31,13 +33,6 @@ var (
tokenOr,
}

mathematicalOperators = []string{
tokenDoubleAmpersand,
tokenMoreThan,
tokenLessThan,
tokenOr,
}

methodLen = "LEN"
methodFormat = "FORMAT"
methods = []string{
Expand Down Expand Up @@ -164,7 +159,7 @@ func isVariable(match []rune, variable string) bool {
func getSpecialCharacter(match []rune) (string, bool) {
strMatch := string(match)

for _, sch := range specialCharacters {
for _, sch := range tokensOperators {
if sch == strMatch {
return sch, true
}
Expand Down

0 comments on commit 9e97dff

Please sign in to comment.