Skip to content

Commit

Permalink
Merge pull request #3 from Prrromanssss/unit-tests_for_parser
Browse files Browse the repository at this point in the history
Unit tests for parser
  • Loading branch information
Prrromanssss authored Apr 21, 2024
2 parents 9b62b60 + e26f277 commit 3c0bbb9
Show file tree
Hide file tree
Showing 12 changed files with 751 additions and 69 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ jobs:
uses: golangci/golangci-lint-action@v4
with:
version: v1.54
working-directory: backend
- name: Run tests
run: go test ./...
with:
working-directory: backend
1 change: 1 addition & 0 deletions backend/internal/orchestrator/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ func (o *Orchestrator) UpdateExpressionFromAgents(
return messages.ResultAndTokenMessage{},
fmt.Errorf("can't get expression by id: %v, fn: %s", err, fn)
}

resAndTokenMsg, err := parser.InsertResultToToken(
expression.ParseData,
exprMsg.Token,
Expand Down
1 change: 0 additions & 1 deletion backend/internal/orchestrator/parser/get_tokens_test.go

This file was deleted.

3 changes: 2 additions & 1 deletion backend/internal/orchestrator/parser/infix_to_postfix.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import (
"strings"
)

func infixToPostfix(expression string) (string, error) {
// InfixToPostfix translates an expression from infix record to postfix.
func InfixToPostfix(expression string) (string, error) {
var output strings.Builder
var stack []rune
for _, char := range expression {
Expand Down
147 changes: 147 additions & 0 deletions backend/internal/orchestrator/parser/infix_to_postfix_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,148 @@
package parser_test

import (
"errors"
"strings"
"testing"

"github.com/Prrromanssss/DAEE-fullstack/internal/orchestrator/parser"
)

func TestInfixToPostfix(t *testing.T) {
testCases := []struct {
name string
expression string
wantedExpression string
err error
}{
{
name: "Basic arithmetic expressions - '+'",
expression: "3+4",
wantedExpression: "3 4 +",
err: nil,
},
{
name: "Basic arithmetic expressions - '-'",
expression: "3-4",
wantedExpression: "3 4 -",
err: nil,
},
{
name: "Basic arithmetic expressions - '*'",
expression: "3*4",
wantedExpression: "3 4 *",
err: nil,
},
{
name: "Basic arithmetic expressions - '/'",
expression: "3/4",
wantedExpression: "3 4 /",
err: nil,
},
{
name: "Expression with parentheses at the beginning with plus operator",
expression: "(3+4)*5",
wantedExpression: "3 4 + 5 *",
err: nil,
},
{
name: "Expression with parentheses at the beginning with product operator",
expression: "(3*4)+5",
wantedExpression: "3 4 * 5 +",
err: nil,
},
{
name: "Expression with parentheses at the end with plus operator",
expression: "3*(4+5)",
wantedExpression: "3 4 5 + *",
err: nil,
},
{
name: "Expression with 2 parentheses at the end and at the beginning",
expression: "(3+4)*(5-6)",
wantedExpression: "3 4 + 5 6 - *",
err: nil,
},
{
name: "Complex expressions 1",
expression: "3+4*5",
wantedExpression: "3 4 5 * +",
err: nil,
},
{
name: "Complex expressions 2",
expression: "3*4+5",
wantedExpression: "3 4 * 5 +",
err: nil,
},
{
name: "Complex expressions 3",
expression: "(3+4)*5-6/2",
wantedExpression: "3 4 + 5 * 6 2 / -",
err: nil,
},
{
name: "Complex expressions 4",
expression: "3*(4+5)-6/(2+1)",
wantedExpression: "3 4 5 + * 6 2 1 + / -",
err: nil,
},
{
name: "Expression with multiple operators of the same precedence 1",
expression: "3+4-5",
wantedExpression: "3 4 + 5 -",
err: nil,
},
{
name: "Expression with multiple operators of the same precedence 2",
expression: "3*4/5",
wantedExpression: "3 4 * 5 /",
err: nil,
},
{
name: "Expression with multiple operators of the same precedence 3",
expression: "(3+4)-(5+6)",
wantedExpression: "3 4 + 5 6 + -",
err: nil,
},
{
name: "Expression with multiple operators of the same precedence 4",
expression: "(3*4)/(5*6)",
wantedExpression: "3 4 * 5 6 * /",
err: nil,
},
{
name: "Invalid expression",
expression: "5+)+3",
wantedExpression: "",
err: errors.New("invalid expression"),
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got, err := parser.InfixToPostfix(tc.expression)
if got != tc.wantedExpression {
t.Errorf(
"InfixToPostfix(%v) = %v, %v; want %v, but got %v",
tc.expression, got, err,
tc.wantedExpression, got,
)
}
if tc.err != nil && (err == nil || !strings.Contains(err.Error(), tc.err.Error())) {
t.Errorf(
"InfixToPostfix(%v) = %v, %v; expected error containing '%v', but got %v",
tc.expression, got, err,
tc.err, err,
)
} else if tc.err == nil && err != nil {
t.Errorf(
"InfixToPostfix(%v) = %v, %v; expected no error, but got %v",
tc.expression, got, err,
err,
)
}
})
}
}
145 changes: 82 additions & 63 deletions backend/internal/orchestrator/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,28 @@ import (
"unicode"
)

// ParseExpression parses the expression from the user.
func ParseExpression(expression string) (string, error) {
rawExpression := strings.ReplaceAll(expression, " ", "")
if !isValidExpression(rawExpression) {
if !IsValidExpression(rawExpression) {
return "", errors.New("invalid expression")
}
rawExpression = addBrackets(rawExpression)
result, err := infixToPostfix(rawExpression)
rawExpression = AddBrackets(rawExpression)
result, err := InfixToPostfix(rawExpression)
if err != nil {
return "", err
}
return result, nil
}

func contains(arr []rune, element rune) bool {
for _, elem := range arr {
if elem == element {
return true
}
}
return false
}

func isValidExpression(expression string) bool {
// IsValidExpression checks whether the eexpression is valid or not.
func IsValidExpression(expression string) bool {
stack := make([]rune, 0)

if expression == "" {
return false
}

for i, char := range expression {
switch char {
case '(':
Expand All @@ -42,81 +39,47 @@ func isValidExpression(expression string) bool {
}
stack = stack[:len(stack)-1]
case '*', '/':
if i == 0 {
if i == 0 || i == len(expression)-1 {
return false
}
if contains([]rune{'+', '-', '*', '/', '(', ' '}, rune(expression[i-1])) {
if contains([]rune{'+', '-', '*', '/', '(', ' '}, rune(expression[i-1])) ||
contains([]rune{'+', '-', '*', '/', ')'}, rune(expression[i+1])) {
return false
}
if i+1 < len(expression) && expression[i+1] == '0' {
return false
}
case '-', '+':
if i == len(expression)-1 {
return false
}

if i == 0 || i == 1 || expression[i-1] == '(' {
if expression[i+1] == ')' {
return false
}
continue
}

if contains([]rune{'+', '-', '*', '/', ' '}, rune(expression[i-1])) &&
contains([]rune{'+', '-', '*', '/', '(', ' '}, rune(expression[i-2])) {
return false
}

default:
if !unicode.IsDigit(char) {
return false
}
if i > 0 && expression[i-1] == '0' {
return false
}
}
}

return len(stack) == 0
}

func addZeroToUnaryPlusAndMinus(expression string) string {
var result strings.Builder
// log.Println(expression)
length := len(expression)
ind := 0
for ind < length {
if ind+1 < length && contains([]rune{'+', '-', '*', '/'}, rune(expression[ind])) && expression[ind+1] == '+' {
result.WriteRune(rune(expression[ind]))
result.WriteRune('0')
result.WriteRune('+')
ind++
} else if ind == 0 && expression[ind] == '+' {
result.WriteRune('0')
result.WriteRune('+')
} else if ind+1 < length && contains([]rune{'+', '-', '*', '/'}, rune(expression[ind])) && expression[ind+1] == '-' {
result.WriteRune(rune(expression[ind]))
result.WriteRune('0')
result.WriteRune('-')
ind++
} else if ind == 0 && expression[ind] == '-' {
result.WriteRune('0')
result.WriteRune('-')
} else {
result.WriteRune(rune(expression[ind]))
}
ind++
// log.Println(result.String())
}
return result.String()
}

func orderPlusMinus(expression string) []rune {
res := make([]rune, 0)
for _, char := range expression {
if char == '-' || char == '+' {
res = append(res, char)
}
}
return res
}

func IsNumber(s string) bool {
_, err := strconv.ParseFloat(s, 64)
return err == nil
}

func addBrackets(expression string) string {
// AddBrackets adds brackets to espression in order to parallelize some operations.
func AddBrackets(expression string) string {
var result string

parts := strings.FieldsFunc(addZeroToUnaryPlusAndMinus(expression), func(r rune) bool {
Expand Down Expand Up @@ -167,5 +130,61 @@ func addBrackets(expression string) string {
}
result = strings.ReplaceAll(result, "&", "+")
result = strings.ReplaceAll(result, "$", "-")

return result
}

func addZeroToUnaryPlusAndMinus(expression string) string {
var result strings.Builder
length := len(expression)
ind := 0
for ind < length {
if ind+1 < length && contains([]rune{'+', '-', '*', '/'}, rune(expression[ind])) && expression[ind+1] == '+' {
result.WriteRune(rune(expression[ind]))
result.WriteRune('0')
result.WriteRune('+')
ind++
} else if ind == 0 && expression[ind] == '+' {
result.WriteRune('0')
result.WriteRune('+')
} else if ind+1 < length && contains([]rune{'+', '-', '*', '/'}, rune(expression[ind])) && expression[ind+1] == '-' {
result.WriteRune(rune(expression[ind]))
result.WriteRune('0')
result.WriteRune('-')
ind++
} else if ind == 0 && expression[ind] == '-' {
result.WriteRune('0')
result.WriteRune('-')
} else {
result.WriteRune(rune(expression[ind]))
}
ind++
// log.Println(result.String())
}
return result.String()
}

func orderPlusMinus(expression string) []rune {
res := make([]rune, 0)
for _, char := range expression {
if char == '-' || char == '+' {
res = append(res, char)
}
}
return res
}

// IsNumber checks if s is a number.
func IsNumber(s string) bool {
_, err := strconv.ParseFloat(s, 64)
return err == nil
}

func contains(arr []rune, element rune) bool {
for _, elem := range arr {
if elem == element {
return true
}
}
return false
}
Loading

0 comments on commit 3c0bbb9

Please sign in to comment.