-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroll.go
More file actions
143 lines (122 loc) · 3.81 KB
/
Copy pathroll.go
File metadata and controls
143 lines (122 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package main
import (
"context"
"regexp"
"strconv"
"strings"
"time"
"github.com/armon/go-metrics"
"github.com/bwmarrin/discordgo"
"github.com/travis-g/dice/math"
"go.uber.org/zap"
)
// commentFieldFunc returns whether a rune is a comment character. Used for
// splitting roll inputs that contain comments/labels.
func commentFieldFunc(r rune) bool {
return r == '\\' || r == '#' || r == delim
}
var (
mentionRegexp = regexp.MustCompile(`<@.+?>`)
multirollSplitRegexp = regexp.MustCompile(`[\n;]`)
)
// NewRollInputFromString returns a new RollInput based off an input string with
// optional comment (i.e. label).
//
// TODO: fuzzy-parse rolls from inputs, ex. "roll 3d6 + 4 damage" => "3d6 + 4"
func NewRollInputFromString(input string) *NamedRollInput {
// strip any mentions and Discord-specific tag things
input = mentionRegexp.ReplaceAllString(input, "")
input = strings.TrimPrefix(input, "/roll")
// strip any leading/trailing whitespace
input = strings.TrimSpace(input)
// if empty input or notation starts with a comment, short-circuit
if input == "" || commentFieldFunc([]rune(input)[0]) {
return new(NamedRollInput)
}
data := new(NamedRollInput)
code := input
// check if there's a roll notation or expression
reCode := regexp.MustCompile("`(.+?)`")
matches := reCode.FindStringSubmatch(code)
if len(matches) > 0 {
data = NewRollInputFromString(matches[1])
code = matches[1]
}
reLabel := regexp.MustCompile(`_(.+?)_`)
matches = reLabel.FindStringSubmatch(input)
if len(matches) > 0 {
data.Label = matches[1]
}
// split at label/comment
parts := strings.FieldsFunc(code, commentFieldFunc)
data.Expression = strings.TrimSpace(parts[0])
if len(parts) > 1 {
// remove everything prior to the first split loc carefully. first rune
// after cutting off the expression will be a comment char
data.Label = strings.TrimSpace(strings.TrimPrefix(input, parts[0])[1:])
}
return data
}
// NewRollInputFromMessage parses and returns a RollInput from the content
// of a Discord message.
func NewRollInputFromMessage(m *discordgo.Message) (data *NamedRollInput) {
return NewRollInputFromString(m.Content)
}
// EvaluateRollInputWithContext takes a RollInput and executes it, returning a
// Response and any errors encountered.
func EvaluateRollInputWithContext(ctx context.Context, rollInput *NamedRollInput) (res *Response, err error) {
defer recover()
s, i, m := FromContext(ctx)
if s == nil || (i == nil && m == nil) {
panic("context data missing")
}
res = &Response{
Expression: rollInput.Expression,
Label: rollInput.Label,
}
var (
cid string
id string
)
switch {
case i != nil:
id = i.ID
cid = i.ChannelID
case m != nil:
id = m.ID
cid = m.ChannelID
}
logger.Info("rolling",
zap.String("expression", res.Expression),
zap.String("label", res.Label),
zap.String("channel", cid),
zap.String("id", id),
zap.Int("shard", s.ShardID),
)
res.ExpressionResult, err = evaluateRoll(ctx, res.Expression)
if err != nil {
logger.Error("evaluation error",
zap.String("expression", res.Expression),
zap.Error(err),
)
if strings.Contains(err.Error(), "transition token types") {
err = ErrTokenTransition
}
return
}
logger.Debug("evaluated roll", zap.Any("response", res))
go trackRollFromContext(ctx)
res.Rolled = res.ExpressionResult.Rolled
res.Result = strconv.FormatFloat(res.ExpressionResult.Result, 'f', -1, 64)
return
}
// evaluateRoll executes the given roll string and emits metrics.
func evaluateRoll(ctx context.Context, roll string) (*math.ExpressionResult, error) {
defer metrics.MeasureSince([]string{"roll", "evaluate"}, time.Now())
ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond)
defer cancel()
return math.EvaluateExpression(ctx, roll)
}
func splitMultirollString(s string) []string {
return multirollSplitRegexp.Split(s, -1)
}