-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathexpr.go
152 lines (136 loc) · 3.66 KB
/
expr.go
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
152
package exprel
import (
"bytes"
"context"
"errors"
)
// Expression is an user-defined expression that can be evaluated.
type Expression struct {
node node
}
// Parse returned a new, executable expression from s. The syntax of s is
// outlined in the package documentation.
//
// Upon success, expression and nil are returned. Upon failure, nil and error
// are returned.
func Parse(s string) (*Expression, error) {
// simple expression; nothing to parse
if len(s) == 0 || s[0] != '=' {
return &Expression{
node: stringNode(s),
}, nil
}
n, err := parseString(s[1:])
if err != nil {
return nil, err
}
return &Expression{
node: n,
}, nil
}
// Evaluate is a wrapper around EvaluateContext that uses the background context.
func (e *Expression) Evaluate(s Source) (val interface{}, err error) {
return e.EvaluateContext(context.Background(), s)
}
// EvaluateContext evaluates the expression with the given source.
//
// Upon success, value and nil are returned. Upon failure, nil and error are
// returned.
func (e *Expression) EvaluateContext(ctx context.Context, s Source) (val interface{}, err error) {
defer func() {
if rec := recover(); rec != nil {
if runtimeErr, ok := rec.(*RuntimeError); ok {
err = runtimeErr
return
}
panic(rec)
}
}()
return e.node.Evaluate(ctx, s), nil
}
// MarshalText implements encoding.TextMarshaler.
func (e *Expression) MarshalText() ([]byte, error) {
if e.node == nil {
return nil, errors.New("empty expression")
}
var b bytes.Buffer
b.WriteByte('=')
e.node.Encode(&b)
return b.Bytes(), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (e *Expression) UnmarshalText(text []byte) error {
expr, err := Parse(string(text))
if err != nil {
return err
}
*e = *expr
return nil
}
// Evaluate is a wrapper around EvaluateContext that uses the background context.
func Evaluate(s string, source ...Source) (val interface{}, err error) {
return EvaluateContext(context.Background(), s, source...)
}
// EvaluateContext parses the given string and evaluates it with the given sources
// (Base is automatically included).
//
// Upon success, value and nil are returned. Upon failure, nil and error are
// returned.
func EvaluateContext(ctx context.Context, s string, source ...Source) (val interface{}, err error) {
expr, err := Parse(s)
if err != nil {
return nil, err
}
data := make(Sources, len(source)+1)
data[0] = Base
copy(data[1:], source)
result, err := expr.EvaluateContext(ctx, data)
if err != nil {
return nil, err
}
return result, nil
}
// String ensures that an evaluated expression's return type is string.
func String(val interface{}, err error) (string, error) {
if err != nil {
return "", err
}
casted, ok := val.(string)
if !ok {
return "", errors.New("exprel: invalid return type (string expected, got " + typename(val) + ")")
}
return casted, nil
}
// Number ensures that an evaluated expression's return type is float64.
func Number(val interface{}, err error) (float64, error) {
if err != nil {
return 0, err
}
casted, ok := val.(float64)
if !ok {
return 0, errors.New("exprel: invalid return type (number expected, got " + typename(val) + ")")
}
return casted, nil
}
// Boolean ensures that an evaluated expression's return type is bool.
func Boolean(val interface{}, err error) (bool, error) {
if err != nil {
return false, err
}
casted, ok := val.(bool)
if !ok {
return false, errors.New("exprel: invalid return type (boolean expected, got " + typename(val) + ")")
}
return casted, nil
}
func typename(val interface{}) string {
switch val.(type) {
case string:
return "string"
case bool:
return "boolean"
case float64:
return "number"
}
return ""
}