-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathfunc.go
More file actions
151 lines (134 loc) · 4.04 KB
/
func.go
File metadata and controls
151 lines (134 loc) · 4.04 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
144
145
146
147
148
149
150
151
package dsl
import (
"encoding/binary"
"fmt"
"hash/fnv"
"math"
"strconv"
"strings"
"github.com/Knetic/govaluate"
)
type dslFunction struct {
IsCacheable bool
Name string
// if numberOfArgs is defined the signature is automatically generated
NumberOfArgs int
Signatures []string
ExpressionFunction govaluate.ExpressionFunction
}
func (d dslFunction) GetSignatures() []string {
// fixed number of args implies a static signature
if d.NumberOfArgs > 0 {
args := make([]string, 0, d.NumberOfArgs)
for i := 1; i <= d.NumberOfArgs; i++ {
args = append(args, "arg"+strconv.Itoa(i))
}
argsPart := fmt.Sprintf("(%s interface{}) interface{}", strings.Join(args, ", "))
signature := d.Name + argsPart
return []string{signature}
}
// multi signatures
var signatures []string
for _, signature := range d.Signatures {
signatures = append(signatures, d.Name+signature)
}
return signatures
}
func (d dslFunction) Exec(args ...interface{}) (interface{}, error) {
// fixed number of args implies the possibility to perform matching between the expected number of args and the ones provided
if d.NumberOfArgs > 0 {
if len(args) != d.NumberOfArgs {
signatures := d.GetSignatures()
if len(signatures) > 0 {
return nil, fmt.Errorf("%w. correct method signature %q", ErrInvalidDslFunction, signatures[0])
}
return nil, ErrInvalidDslFunction
}
}
if !d.IsCacheable {
return d.ExpressionFunction(args...)
}
functionHash := d.hash(args...)
if result, err := resultCache.Get(functionHash); err == nil {
return result, nil
}
result, err := d.ExpressionFunction(args...)
if err == nil {
_ = resultCache.Set(functionHash, result)
}
return result, err
}
func (d dslFunction) hash(args ...interface{}) string {
hasher := fnv.New64a()
_, _ = hasher.Write([]byte(d.Name))
_, _ = hasher.Write([]byte{'-'})
// NOTE(dwisiswant0): Using a single byte slice for binary conversions of
// numeric types avoids repeated mallocs within the loop, improving perf.
var b [8]byte
for i, arg := range args {
switch v := arg.(type) {
case string:
_, _ = hasher.Write([]byte(v))
case []byte:
_, _ = hasher.Write(v)
// Booleans
case bool:
if v {
_, _ = hasher.Write([]byte{1})
} else {
_, _ = hasher.Write([]byte{0})
}
// Integers
// NOTE(dwisiswant0): TIL! By writing the raw binary representation of
// numeric types directly to the hasher, we avoid the significant perf
// and memory overhead of converting em to strings (ex. via `fmt.Sprintf`
// or `strconv`). This is MUCH faster & reduces GC pressure.
// We use `binary.LittleEndian` to ensure the byte order is consistent
// across all machine archs, which is critical for generating
// deterministic hashes.
// Proof: `go test -benchmem -run=^$ -bench ^BenchmarkDSLFunctionHash$ .`
case int:
binary.LittleEndian.PutUint64(b[:], uint64(v))
_, _ = hasher.Write(b[:])
case int8:
_, _ = hasher.Write([]byte{byte(v)})
case int16:
binary.LittleEndian.PutUint16(b[:2], uint16(v))
_, _ = hasher.Write(b[:2])
case int32:
binary.LittleEndian.PutUint32(b[:4], uint32(v))
_, _ = hasher.Write(b[:4])
case int64:
binary.LittleEndian.PutUint64(b[:], uint64(v))
_, _ = hasher.Write(b[:])
// Unsigned Integers
case uint:
binary.LittleEndian.PutUint64(b[:], uint64(v))
_, _ = hasher.Write(b[:])
case uint8: // same as byte
_, _ = hasher.Write([]byte{v})
case uint16:
binary.LittleEndian.PutUint16(b[:2], v)
_, _ = hasher.Write(b[:2])
case uint32: // same as rune
binary.LittleEndian.PutUint32(b[:4], v)
_, _ = hasher.Write(b[:4])
case uint64:
binary.LittleEndian.PutUint64(b[:], v)
_, _ = hasher.Write(b[:])
// Floats
case float32:
binary.LittleEndian.PutUint32(b[:4], math.Float32bits(v))
_, _ = hasher.Write(b[:4])
case float64:
binary.LittleEndian.PutUint64(b[:], math.Float64bits(v))
_, _ = hasher.Write(b[:])
default:
_, _ = fmt.Fprintf(hasher, "%v", v)
}
if i < len(args)-1 {
_, _ = hasher.Write([]byte{','})
}
}
return strconv.FormatUint(hasher.Sum64(), 10)
}