Understand the interpreter mode

principle

Define grammar rules for the new "language" and define an interpreter to interpret grammar

Implementation core

Split the operations of different operators into separate operator expression classes to avoid large and complete parsing classes

Best Practices 1

Demand background

Suppose we define a new "language" for addition, subtraction, multiplication and division, and the syntax rules are as follows:
Operators only include addition, subtraction, multiplication and division, and have no concept of priority;
In the expression (that is, the "sentence" mentioned earlier), write numbers first, then operators, separated by spaces;
According to the sequence, take out the calculation results of two numbers and one operator, put the results back into the head of the number, and cycle the above process until there is only one number left, which is the final calculation result of the expression.

For example, the expression "8 3 2 4 - + *" is processed according to the above syntax rules. Take out the number "8 3" and "-" operators and calculate 5, so the expression becomes "5 2 4 + *". Then, we take out the "5 2" and "+" operators and calculate 7, and the expression becomes "7 4 *". Finally, we take out the "7, 4" and "*" operators, and the final result is 28.

Implementation version 1

type ExpressionInterpreter struct{}

func (i *ExpressionInterpreter) Interpreter(s string) int64 {
	strList := strings.Split(s, " ")
	numberQueue := queue.New()
	for i := 0; i < (len(strList)+1)/2; i++ {
		num, _ := strconv.ParseInt(strList[i], 10, 64)
		numberQueue.PushBack(num)
	}
	for i := (len(strList) + 1) / 2; i < len(strList); i++ {
		var result int64
		num1 := numberQueue.PopFront().(int64)
		num2 := numberQueue.PopFront().(int64)
		sign := strList[i]
		if sign == "+" {
			result = num1 + num2
		} else if sign == "-" {
			result = num1 - num2
		} else if sign == "*" {
			result = num1 * num2
		} else if sign == "/" {
			result = num1 / num2
		} else {
			panic("illegal sign")
		}
		numberQueue.PushFront(result)
	}
	if numberQueue.Len() != 1 {
		panic("expression illegal")
	}
	return numberQueue.PopFront().(int64)
}

Here, the logic of each operator is relatively simple, so there is no problem in putting it in a class. However, if the logic of each operator is very complex, it will lead to a large class and reduced readability. Therefore, we can split the logic of each operator into separate classes

Implementation version 2

type Expression interface {
	Interpreter() int64
}

type NumExpression struct {
	num int64
}

func NewNumExpression(num int64) *NumExpression {
	return &NumExpression{
		num: num,
	}
}

func (e *NumExpression) Interpreter() int64 {
	return e.num
}

type AddExpression struct {
	leftExp  Expression
	rightExp Expression
}

func NewAddExpression(leftExp Expression, rightExp Expression) *AddExpression {
	return &AddExpression{
		leftExp:  leftExp,
		rightExp: rightExp,
	}
}

func (e *AddExpression) Interpreter() int64 {
	return e.leftExp.Interpreter() + e.rightExp.Interpreter()
}

type SubtractExpression struct {
	leftExp  Expression
	rightExp Expression
}

func NewSubtractExpression(leftExp Expression, rightExp Expression) *SubtractExpression {
	return &SubtractExpression{
		leftExp:  leftExp,
		rightExp: rightExp,
	}
}

func (e *SubtractExpression) Interpreter() int64 {
	return e.leftExp.Interpreter() - e.rightExp.Interpreter()
}

type MulExpression struct {
	leftExp  Expression
	rightExp Expression
}

func NewMulExpression(leftExp Expression, rightExp Expression) *MulExpression {
	return &MulExpression{
		leftExp:  leftExp,
		rightExp: rightExp,
	}
}

func (e *MulExpression) Interpreter() int64 {
	return e.leftExp.Interpreter() * e.rightExp.Interpreter()
}

type DivisionExpression struct {
	leftExp  Expression
	rightExp Expression
}

func NewDivisionExpression(leftExp Expression, rightExp Expression) *DivisionExpression {
	return &DivisionExpression{
		leftExp:  leftExp,
		rightExp: rightExp,
	}
}

func (e *DivisionExpression) Interpreter() int64 {
	return e.leftExp.Interpreter() / e.rightExp.Interpreter()
}

func (i *ExpressionInterpreter) InterpreterV2(s string) int64 {
	strList := strings.Split(s, " ")
	numberQueue := queue.New()
	for i := 0; i < (len(strList)+1)/2; i++ {
		num, _ := strconv.ParseInt(strList[i], 10, 64)
		numberQueue.PushBack(num)
	}
	for i := (len(strList) + 1) / 2; i < len(strList); i++ {
		var result int64
		num1 := NewNumExpression(numberQueue.PopFront().(int64))
		num2 := NewNumExpression(numberQueue.PopFront().(int64))
		sign := strList[i]
		var expression Expression
		if sign == "+" {
			expression = NewAddExpression(num1, num2)
		} else if sign == "-" {
			expression = NewSubtractExpression(num1, num2)
		} else if sign == "*" {
			expression = NewMulExpression(num1, num2)
		} else if sign == "/" {
			expression = NewDivisionExpression(num1, num2)
		} else {
			panic("illegal sign")
		}
		result = expression.Interpreter()
		numberQueue.PushFront(result)
	}
	if numberQueue.Len() != 1 {
		panic("expression illegal")
	}
	return numberQueue.PopFront().(int64)
}

Best practices 2

Demand background

In our usual project development, the monitoring system is very important. It can monitor the operation of the business system at all times and report exceptions to the developers in time. For example, if the number of interface errors per minute exceeds 100, the monitoring system will send an alarm to the developer through SMS, wechat, e-mail, etc.
Generally speaking, the monitoring system supports developers to customize alarm rules. For example, we can use the following expression to represent an alarm rule, which means:
When the total number of API errors per minute exceeds 100 or the total number of API calls per minute exceeds 10000, an alarm is triggered.
api_error_per_minute > 100 || api_count_per_minute > 10000

We assume that the custom alarm rule only contains five operators "|, & &, >, <,", among which ">, <," operators have higher priority than "|, & &" operators, and "& &" operators have higher priority than "|". In an expression, any element needs to be separated by a space. In addition, users can customize the key s to be monitored, such as the previous api_error_per_minute,api_count_per_minute.

code implementation

type AlterExpression interface {
	Interpreter(stats map[string]int) bool
}

type GreaterExpression struct {
	key   string
	value int
}

func NewGreaterExpression(key string, value int) *GreaterExpression {
	return &GreaterExpression{
		key:   key,
		value: value,
	}
}

func (e *GreaterExpression) Interpreter(stats map[string]int) bool {
	v, ok := stats[e.key]
	if ok == false {
		return false
	}
	return v > e.value
}

type LessExpression struct {
	key   string
	value int
}

func NewLessExpression(key string, value int) *LessExpression {
	return &LessExpression{
		key:   key,
		value: value,
	}
}

func (e *LessExpression) Interpreter(stats map[string]int) bool {
	v, ok := stats[e.key]
	if ok == false {
		return false
	}
	return v < e.value
}

type EqualExpression struct {
	key   string
	value int
}

func NewEqualExpression(key string, value int) *EqualExpression {
	return &EqualExpression{
		key:   key,
		value: value,
	}
}

func (e *EqualExpression) Interpreter(stats map[string]int) bool {
	v, ok := stats[e.key]
	if ok == false {
		return false
	}
	return e.value == v
}

type AndExpression struct {
	expressionList []AlterExpression
}

func NewAndExpression(rule string) *AndExpression {
	strList := strings.Split(rule, `&&`)
	expList := make([]AlterExpression, 0, len(strList))
	for i := range strList {
		str := strings.Trim(strList[i], " ")
		opStrList := strings.Split(str, " ")
		sign := opStrList[1]
		key := opStrList[0]
		valueStr := opStrList[2]
		value, _ := strconv.ParseInt(valueStr, 10, 64)
		if sign == ">" {
			expList = append(expList, NewGreaterExpression(key, int(value)))
		} else if sign == "<" {
			expList = append(expList, NewLessExpression(key, int(value)))
		} else if sign == "" {
			expList = append(expList, NewEqualExpression(key, int(value)))
		} else {
			panic("illegal op")
		}
	}
	return &AndExpression{
		expressionList: expList,
	}
}

func (e *AndExpression) Interpreter(stats map[string]int) bool {
	for i := range e.expressionList {
		expression := e.expressionList[i]
		if expression.Interpreter(stats) == false {
			return false
		}
	}
	return true
}

type OrExpression struct {
	expressionList []AlterExpression
}

func NewOrExpression(rule string) *OrExpression {
	strList := strings.Split(rule, `||`)
	expList := make([]AlterExpression, 0, len(strList))
	for i := range strList {
		str := strings.Trim(strList[i], " ")
		expList = append(expList, NewAndExpression(str))
	}
	return &OrExpression{
		expressionList: expList,
	}
}

func (e *OrExpression) Interpreter(stats map[string]int) bool {
	for i := range e.expressionList {
		expression := e.expressionList[i]
		if expression.Interpreter(stats) {
			return true
		}
	}
	return false
}

type AlterRuleInterpreter struct {
	expression AlterExpression
}

func NewAlterRuleInterpreter(ruleExpression string) *AlterRuleInterpreter {
	return &AlterRuleInterpreter{
		expression: NewOrExpression(ruleExpression),
	}
}

func (i *AlterRuleInterpreter) Interpreter(stats map[string]int) bool {
	return i.expression.Interpreter(stats)
}

func ApplicationTest() {
	rule := "key1 > 100 && key2 < 30 || key3 < 100 || key4 == 88"
	alterRuleInterpreter := NewAlterRuleInterpreter(rule)
	stats := map[string]int{
		"key1": 101,
		"key2": 18,
		"key3": 121,
		"key4": 88,
	}
	alter := alterRuleInterpreter.Interpreter(stats)
	if alter {
		println("call the police!!!")
	}
}

Keywords: Go Design Pattern

Added by dc2_jspec on Sat, 18 Dec 2021 04:05:41 +0200