From 6c05518146934e13765276fcb32b6548f58f3d2f Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Tue, 25 Aug 2020 16:53:51 -0800 Subject: [PATCH 1/9] Add rpn command --- djpianalto.com/goff/exts/fun.go | 12 ++- djpianalto.com/goff/exts/init.go | 9 ++ djpianalto.com/goff/utils/rpn.go | 156 +++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 djpianalto.com/goff/utils/rpn.go diff --git a/djpianalto.com/goff/exts/fun.go b/djpianalto.com/goff/exts/fun.go index 3471b09..f1abd04 100644 --- a/djpianalto.com/goff/exts/fun.go +++ b/djpianalto.com/goff/exts/fun.go @@ -2,8 +2,10 @@ package exts import ( "fmt" - "github.com/dustinpianalto/disgoman" "strconv" + + "djpianalto.com/goff/djpianalto.com/goff/utils" + "github.com/dustinpianalto/disgoman" ) func interleave(ctx disgoman.Context, args []string) { @@ -47,3 +49,11 @@ func deinterleave(ctx disgoman.Context, args []string) { ctx.Send(fmt.Sprintf("(%v, %v)", x, y)) } } + +func rpn(ctx disgoman.Context, args []string) { + rpn, err := utils.GenerateRPN(args) + if err != nil { + ctx.Send(err.Error()) + } + ctx.Send(rpn) +} diff --git a/djpianalto.com/goff/exts/init.go b/djpianalto.com/goff/exts/init.go index 9d8bacd..1031a96 100644 --- a/djpianalto.com/goff/exts/init.go +++ b/djpianalto.com/goff/exts/init.go @@ -186,4 +186,13 @@ func AddCommandHandlers(h *disgoman.CommandManager) { RequiredPermissions: 0, Invoke: deinterleave, }) + _ = h.AddCommand(&disgoman.Command{ + Name: "rpn", + Aliases: []string{"d"}, + Description: "Convert infix to rpn", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: rpn, + }) } diff --git a/djpianalto.com/goff/utils/rpn.go b/djpianalto.com/goff/utils/rpn.go new file mode 100644 index 0000000..db39f5d --- /dev/null +++ b/djpianalto.com/goff/utils/rpn.go @@ -0,0 +1,156 @@ +package utils + +import ( + "fmt" + "strconv" + "strings" +) + +type Operator struct { + Token string + Precedence int + Association string +} + +func (o Operator) HasHigherPrecedence(t Operator) bool { + return o.Precedence < t.Precedence // lower number is higher precedence +} + +func (o Operator) HasEqualPrecedence(t Operator) bool { + return o.Precedence == t.Precedence +} + +func (o Operator) IsLeftAssociative() bool { + return o.Association == "left" +} + +var operators = map[string]Operator{ + "+": Operator{ + Token: "+", + Precedence: 4, + Association: "left", + }, + "-": Operator{ + Token: "-", + Precedence: 4, + Association: "left", + }, + "*": Operator{ + Token: "*", + Precedence: 3, + Association: "left", + }, + "/": Operator{ + Token: "/", + Precedence: 3, + Association: "left", + }, + "%": Operator{ + Token: "%", + Precedence: 3, + Association: "left", + }, + "(": Operator{ + Token: "(", + Precedence: 1, + Association: "left", + }, + ")": Operator{ + Token: ")", + Precedence: 1, + Association: "left", + }, +} + +type Stack []Operator + +func (s *Stack) IsEmpty() bool { + return len(*s) == 0 +} + +func (s *Stack) Push(op Operator) { + *s = append(*s, op) +} + +func (s *Stack) Pop() (Operator, bool) { + if s.IsEmpty() { + return Operator{}, false + } + index := len(*s) - 1 + element := (*s)[index] + *s = (*s)[:index] + return element, true +} + +func (s *Stack) Top() Operator { + if s.IsEmpty() { + return Operator{} + } + return (*s)[len(*s)-1] +} + +func GenerateRPN(tokens []string) (string, error) { + output := "" + s := Stack{} + for _, token := range tokens { + err := processToken(token, &s, &output) + if err != nil { + return "", err + } + } + for !s.IsEmpty() { + ele, _ := s.Pop() + output += " " + ele.Token + } + + return strings.TrimSpace(output), nil +} + +func processToken(t string, s *Stack, o *string) error { + if _, err := strconv.Atoi(t); err == nil { + *o += " " + t + return nil + } else if op, ok := operators[t]; ok { + if op.Token == "(" { + s.Push(op) + } else if op.Token == ")" { + if s.IsEmpty() { + return fmt.Errorf("mismatched parentheses") + } + for s.Top().Token != "(" { + if ele, ok := s.Pop(); ok { + *o += " " + ele.Token + } else { + return fmt.Errorf("mismatched parentheses") + } + if s.IsEmpty() { + break + } + } + s.Pop() // Pop and discard the ( + } else if !s.IsEmpty() { + for { + if (s.Top().HasHigherPrecedence(op) || + (s.Top().HasEqualPrecedence(op) && + op.IsLeftAssociative())) && + s.Top().Token != "(" { + if ele, ok := s.Pop(); ok { + *o += " " + ele.Token + if s.IsEmpty() { + break + } + continue + } else { + break + } + } + break + } + s.Push(op) + } else { + s.Push(op) + } + return nil + } + return fmt.Errorf("invalid character %s", t) +} From d66a25a52510a9357aa24682402de37ef911d9de Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Tue, 25 Aug 2020 17:23:28 -0800 Subject: [PATCH 2/9] Add rpn command --- djpianalto.com/goff/exts/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djpianalto.com/goff/exts/init.go b/djpianalto.com/goff/exts/init.go index 1031a96..919d2a2 100644 --- a/djpianalto.com/goff/exts/init.go +++ b/djpianalto.com/goff/exts/init.go @@ -188,7 +188,7 @@ func AddCommandHandlers(h *disgoman.CommandManager) { }) _ = h.AddCommand(&disgoman.Command{ Name: "rpn", - Aliases: []string{"d"}, + Aliases: []string{}, Description: "Convert infix to rpn", OwnerOnly: false, Hidden: false, From 1ddc4d4c3643b118b7919b5fb3bd0b2762d518f9 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Tue, 25 Aug 2020 21:03:50 -0800 Subject: [PATCH 3/9] Add rpn parser and infix solver --- djpianalto.com/goff/exts/fun.go | 27 +++++++- djpianalto.com/goff/exts/init.go | 25 ++++++- djpianalto.com/goff/utils/rpnParser.go | 95 ++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 djpianalto.com/goff/utils/rpnParser.go diff --git a/djpianalto.com/goff/exts/fun.go b/djpianalto.com/goff/exts/fun.go index f1abd04..c133a88 100644 --- a/djpianalto.com/goff/exts/fun.go +++ b/djpianalto.com/goff/exts/fun.go @@ -3,6 +3,7 @@ package exts import ( "fmt" "strconv" + "strings" "djpianalto.com/goff/djpianalto.com/goff/utils" "github.com/dustinpianalto/disgoman" @@ -50,10 +51,34 @@ func deinterleave(ctx disgoman.Context, args []string) { } } -func rpn(ctx disgoman.Context, args []string) { +func generateRPNCommand(ctx disgoman.Context, args []string) { rpn, err := utils.GenerateRPN(args) if err != nil { ctx.Send(err.Error()) + return } ctx.Send(rpn) } + +func parseRPNCommand(ctx disgoman.Context, args []string) { + res, err := utils.ParseRPN(args) + if err != nil { + ctx.Send(err.Error()) + return + } + ctx.Send(fmt.Sprintf("The result is: %v", res)) +} + +func solveCommand(ctx disgoman.Context, args []string) { + rpn, err := utils.GenerateRPN(args) + if err != nil { + ctx.Send(err.Error()) + return + } + res, err := utils.ParseRPN(strings.Split(rpn, " ")) + if err != nil { + ctx.Send(err.Error()) + return + } + ctx.Send(fmt.Sprintf("The result is: %v", res)) +} diff --git a/djpianalto.com/goff/exts/init.go b/djpianalto.com/goff/exts/init.go index 919d2a2..bc11dcc 100644 --- a/djpianalto.com/goff/exts/init.go +++ b/djpianalto.com/goff/exts/init.go @@ -187,12 +187,31 @@ func AddCommandHandlers(h *disgoman.CommandManager) { Invoke: deinterleave, }) _ = h.AddCommand(&disgoman.Command{ - Name: "rpn", - Aliases: []string{}, + Name: "RPN", + Aliases: []string{"rpn"}, Description: "Convert infix to rpn", OwnerOnly: false, Hidden: false, RequiredPermissions: 0, - Invoke: rpn, + Invoke: generateRPNCommand, }) + _ = h.AddCommand(&disgoman.Command{ + Name: "ParseRPN", + Aliases: []string{'PRPN'}, + Description: "Parse RPN string and return the result", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: parseRPNCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "solve", + Aliases: []string{"math"}, + Description: "Solve infix equation and return the result", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: solveCommand, + }) + } diff --git a/djpianalto.com/goff/utils/rpnParser.go b/djpianalto.com/goff/utils/rpnParser.go new file mode 100644 index 0000000..778a5e7 --- /dev/null +++ b/djpianalto.com/goff/utils/rpnParser.go @@ -0,0 +1,95 @@ +package utils + +import ( + "errors" + "fmt" + "math" + "strconv" +) + +type Stack []float64 + +func (s *Stack) IsEmpty() bool { + return len(*s) == 0 +} + +func (s *Stack) Push(op float64) { + *s = append(*s, op) +} + +func (s *Stack) Pop() (float64, bool) { + if s.IsEmpty() { + return 0, false + } + index := len(*s) - 1 + element := (*s)[index] + *s = (*s)[:index] + return element, true +} + +func (s *Stack) PopTwo() (float64, float64, bool) { + if s.IsEmpty() || len(*s) < 2 { + return 0, 0, false + } + index := len(*s) - 1 + b := (*s)[index] + a := (*s)[index-1] + *s = (*s)[:index-1] + return a, b, true + +} + +func (s *Stack) Top() float64 { + if s.IsEmpty() { + return 0 + } + return (*s)[len(*s)-1] +} + +func ParseRPN(args []string) (float64, error) { + s := Stack{} + for _, token := range args { + switch token { + case "+": + if a, b, ok := s.PopTwo(); ok { + s.Push(a + b) + } else { + return 0, fmt.Errorf("not enough operands on stack for +: %v", s) + } + case "-": + if a, b, ok := s.PopTwo(); ok { + s.Push(a - b) + } else { + return 0, fmt.Errorf("not enough operands on stack for -: %v", s) + } + case "*": + if a, b, ok := s.PopTwo(); ok { + s.Push(a * b) + } else { + return 0, fmt.Errorf("not enough operands on stack for *: %v", s) + } + case "/": + if a, b, ok := s.PopTwo(); ok { + s.Push(a / b) + } else { + return 0, fmt.Errorf("not enough operands on stack for /: %v", s) + } + case "%": + if a, b, ok := s.PopTwo(); ok { + s.Push(math.Mod(a, b)) + } else { + return 0, fmt.Errorf("not enough operands on stack for %: %v", s) + } + default: + f, err := strconv.ParseFloat(token, 64) + if err != nil { + return 0, err + } + s.Push(f) + } + } + if res, ok := s.Pop(); ok { + return res, nil + } + return 0, errors.New("no result") +} From c3d2073b2b941b8dfc35a4922895bb47dac74850 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Tue, 25 Aug 2020 21:06:33 -0800 Subject: [PATCH 4/9] Fix bug due to redefining Stack --- djpianalto.com/goff/utils/rpnParser.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/djpianalto.com/goff/utils/rpnParser.go b/djpianalto.com/goff/utils/rpnParser.go index 778a5e7..7f9dff9 100644 --- a/djpianalto.com/goff/utils/rpnParser.go +++ b/djpianalto.com/goff/utils/rpnParser.go @@ -7,17 +7,17 @@ import ( "strconv" ) -type Stack []float64 +type FStack []float64 -func (s *Stack) IsEmpty() bool { +func (s *FStack) IsEmpty() bool { return len(*s) == 0 } -func (s *Stack) Push(op float64) { +func (s *FStack) Push(op float64) { *s = append(*s, op) } -func (s *Stack) Pop() (float64, bool) { +func (s *FStack) Pop() (float64, bool) { if s.IsEmpty() { return 0, false } @@ -27,7 +27,7 @@ func (s *Stack) Pop() (float64, bool) { return element, true } -func (s *Stack) PopTwo() (float64, float64, bool) { +func (s *FStack) PopTwo() (float64, float64, bool) { if s.IsEmpty() || len(*s) < 2 { return 0, 0, false } @@ -39,7 +39,7 @@ func (s *Stack) PopTwo() (float64, float64, bool) { } -func (s *Stack) Top() float64 { +func (s *FStack) Top() float64 { if s.IsEmpty() { return 0 } @@ -47,7 +47,7 @@ func (s *Stack) Top() float64 { } func ParseRPN(args []string) (float64, error) { - s := Stack{} + s := FStack{} for _, token := range args { switch token { case "+": From 4955f4bf8896509d724993c05058d61edd405993 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Tue, 25 Aug 2020 21:07:36 -0800 Subject: [PATCH 5/9] Fix bug due to wrong quotes --- djpianalto.com/goff/exts/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djpianalto.com/goff/exts/init.go b/djpianalto.com/goff/exts/init.go index bc11dcc..1c99737 100644 --- a/djpianalto.com/goff/exts/init.go +++ b/djpianalto.com/goff/exts/init.go @@ -197,7 +197,7 @@ func AddCommandHandlers(h *disgoman.CommandManager) { }) _ = h.AddCommand(&disgoman.Command{ Name: "ParseRPN", - Aliases: []string{'PRPN'}, + Aliases: []string{"PRPN"}, Description: "Parse RPN string and return the result", OwnerOnly: false, Hidden: false, From d16521e5e31e735645d0d1793bb49cdb1d40fbfb Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Tue, 25 Aug 2020 21:10:32 -0800 Subject: [PATCH 6/9] Add prpn alias --- djpianalto.com/goff/exts/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djpianalto.com/goff/exts/init.go b/djpianalto.com/goff/exts/init.go index 1c99737..c8e6adc 100644 --- a/djpianalto.com/goff/exts/init.go +++ b/djpianalto.com/goff/exts/init.go @@ -197,7 +197,7 @@ func AddCommandHandlers(h *disgoman.CommandManager) { }) _ = h.AddCommand(&disgoman.Command{ Name: "ParseRPN", - Aliases: []string{"PRPN"}, + Aliases: []string{"PRPN", "prpn"}, Description: "Parse RPN string and return the result", OwnerOnly: false, Hidden: false, From ac46753546cf6de91cf78612491295c7113b803d Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Tue, 25 Aug 2020 21:12:08 -0800 Subject: [PATCH 7/9] Add infix alias --- djpianalto.com/goff/exts/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djpianalto.com/goff/exts/init.go b/djpianalto.com/goff/exts/init.go index c8e6adc..4fc48bc 100644 --- a/djpianalto.com/goff/exts/init.go +++ b/djpianalto.com/goff/exts/init.go @@ -206,7 +206,7 @@ func AddCommandHandlers(h *disgoman.CommandManager) { }) _ = h.AddCommand(&disgoman.Command{ Name: "solve", - Aliases: []string{"math"}, + Aliases: []string{"math", "infix"}, Description: "Solve infix equation and return the result", OwnerOnly: false, Hidden: false, From b8ee6a1505911e45fbbf56460debec68faccb7a0 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Tue, 25 Aug 2020 22:22:39 -0800 Subject: [PATCH 8/9] Move rpn logic to separate package --- djpianalto.com/goff/exts/fun.go | 10 +++++----- go.mod | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/djpianalto.com/goff/exts/fun.go b/djpianalto.com/goff/exts/fun.go index c133a88..5084128 100644 --- a/djpianalto.com/goff/exts/fun.go +++ b/djpianalto.com/goff/exts/fun.go @@ -5,8 +5,8 @@ import ( "strconv" "strings" - "djpianalto.com/goff/djpianalto.com/goff/utils" "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/rpnparse" ) func interleave(ctx disgoman.Context, args []string) { @@ -52,7 +52,7 @@ func deinterleave(ctx disgoman.Context, args []string) { } func generateRPNCommand(ctx disgoman.Context, args []string) { - rpn, err := utils.GenerateRPN(args) + rpn, err := rpnparse.GenerateRPN(args) if err != nil { ctx.Send(err.Error()) return @@ -61,7 +61,7 @@ func generateRPNCommand(ctx disgoman.Context, args []string) { } func parseRPNCommand(ctx disgoman.Context, args []string) { - res, err := utils.ParseRPN(args) + res, err := rpnparse.ParseRPN(args) if err != nil { ctx.Send(err.Error()) return @@ -70,12 +70,12 @@ func parseRPNCommand(ctx disgoman.Context, args []string) { } func solveCommand(ctx disgoman.Context, args []string) { - rpn, err := utils.GenerateRPN(args) + rpn, err := rpnparse.GenerateRPN(args) if err != nil { ctx.Send(err.Error()) return } - res, err := utils.ParseRPN(strings.Split(rpn, " ")) + res, err := rpnparse.ParseRPN(strings.Split(rpn, " ")) if err != nil { ctx.Send(err.Error()) return diff --git a/go.mod b/go.mod index 05b3072..427dafe 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( github.com/bwmarrin/discordgo v0.20.3-0.20200525154655-ca64123b05de github.com/dustinpianalto/disgoman v0.0.10 + github.com/dustinpianalto/rpnparse v1.0.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/lib/pq v1.3.0 github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254 From e9adc055368db822be2e575a9d8753b8c50b1b01 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Tue, 25 Aug 2020 22:27:15 -0800 Subject: [PATCH 9/9] Bump rpnparse version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 427dafe..4f02677 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/bwmarrin/discordgo v0.20.3-0.20200525154655-ca64123b05de github.com/dustinpianalto/disgoman v0.0.10 - github.com/dustinpianalto/rpnparse v1.0.0 + github.com/dustinpianalto/rpnparse v1.0.1 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/lib/pq v1.3.0 github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254