Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/proxy/plugins/logif/lang/lang.go
2501 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package lang
6
7
import (
8
"context"
9
"fmt"
10
"reflect"
11
"regexp"
12
"strconv"
13
"text/scanner"
14
"unicode"
15
16
"github.com/PaesslerAG/gval"
17
)
18
19
var Lang = gval.NewLanguage(
20
// Logic
21
gval.InfixShortCircuit("&&", func(lhs interface{}) (interface{}, bool) { return false, lhs == false }),
22
gval.InfixShortCircuit("||", func(lhs interface{}) (interface{}, bool) { return true, lhs == true }),
23
24
gval.InfixBoolOperator("&&", func(lhs, rhs bool) (interface{}, error) { return lhs && rhs, nil }),
25
gval.InfixBoolOperator("||", func(lhs, rhs bool) (interface{}, error) { return lhs || rhs, nil }),
26
27
gval.InfixBoolOperator("==", func(lhs, rhs bool) (interface{}, error) { return lhs == rhs, nil }),
28
gval.InfixBoolOperator("!=", func(lhs, rhs bool) (interface{}, error) { return lhs != rhs, nil }),
29
30
// Arithmetic
31
32
gval.InfixNumberOperator("==", func(lhs, rhs float64) (interface{}, error) { return lhs == rhs, nil }),
33
gval.InfixNumberOperator("!=", func(lhs, rhs float64) (interface{}, error) { return lhs != rhs, nil }),
34
35
gval.InfixNumberOperator("<", func(lhs, rhs float64) (interface{}, error) { return lhs < rhs, nil }),
36
gval.InfixNumberOperator("<=", func(lhs, rhs float64) (interface{}, error) { return lhs <= rhs, nil }),
37
gval.InfixNumberOperator(">", func(lhs, rhs float64) (interface{}, error) { return lhs > rhs, nil }),
38
gval.InfixNumberOperator(">=", func(lhs, rhs float64) (interface{}, error) { return lhs >= rhs, nil }),
39
40
// Text
41
42
gval.InfixEvalOperator("~~", regEx),
43
44
// Base
45
46
gval.InfixOperator("==", func(lhs, rhs interface{}) (interface{}, error) { return reflect.DeepEqual(lhs, rhs), nil }),
47
gval.InfixOperator("!=", func(lhs, rhs interface{}) (interface{}, error) { return !reflect.DeepEqual(lhs, rhs), nil }),
48
49
gval.PrefixExtension(scanner.Int, parseNumber),
50
gval.PrefixExtension(scanner.Float, parseNumber),
51
gval.PrefixExtension(scanner.RawString, parseString),
52
53
gval.Constant("true", true),
54
gval.Constant("false", false),
55
56
gval.Parentheses(),
57
58
gval.Precedence("||", 20),
59
gval.Precedence("&&", 21),
60
61
gval.Precedence("==", 40),
62
gval.Precedence("!=", 40),
63
gval.Precedence("~~", 40),
64
65
gval.Precedence("<", 40),
66
gval.Precedence("<=", 40),
67
gval.Precedence(">", 40),
68
gval.Precedence(">=", 40),
69
70
gval.PrefixMetaPrefix(scanner.Ident, parseIdent),
71
)
72
73
var Fields []string
74
75
func Compile(expression string) (gval.Evaluable, error) {
76
return Lang.NewEvaluable(expression)
77
}
78
79
func Execute(eval gval.Evaluable, data map[string]interface{}) (interface{}, error) {
80
return eval(context.Background(), data)
81
}
82
83
func parseString(c context.Context, p *gval.Parser) (gval.Evaluable, error) {
84
s, err := strconv.Unquote(p.TokenText())
85
if err != nil {
86
return nil, fmt.Errorf("could not parse string: %s", err)
87
}
88
return p.Const(s), nil
89
}
90
91
func parseNumber(c context.Context, p *gval.Parser) (gval.Evaluable, error) {
92
n, err := strconv.ParseFloat(p.TokenText(), 64)
93
if err != nil {
94
return nil, err
95
}
96
return p.Const(n), nil
97
}
98
99
func regEx(a, b gval.Evaluable) (gval.Evaluable, error) {
100
if !b.IsConst() {
101
return func(c context.Context, o interface{}) (interface{}, error) {
102
a, err := a.EvalString(c, o)
103
if err != nil {
104
return nil, err
105
}
106
b, err := b.EvalString(c, o)
107
if err != nil {
108
return nil, err
109
}
110
matched, err := regexp.MatchString(b, a)
111
return matched, err
112
}, nil
113
}
114
s, err := b.EvalString(context.TODO(), nil)
115
if err != nil {
116
return nil, err
117
}
118
regex, err := regexp.Compile(s)
119
if err != nil {
120
return nil, err
121
}
122
return func(c context.Context, v interface{}) (interface{}, error) {
123
s, err := a.EvalString(c, v)
124
if err != nil {
125
return nil, err
126
}
127
return regex.MatchString(s), nil
128
}, nil
129
}
130
131
func parseIdent(c context.Context, p *gval.Parser) (call string, alternative func() (gval.Evaluable, error), err error) {
132
token := p.TokenText()
133
return token, func() (gval.Evaluable, error) {
134
tok := token
135
for {
136
scan := p.Scan()
137
curr := p.TokenText()
138
switch scan {
139
case '-':
140
fallthrough
141
case '>':
142
// Disambiguate greater than (>) operator to obtain correct parsing
143
r := p.Peek()
144
if unicode.IsLetter(r) || r == '_' || r == '-' || r == '[' || unicode.IsDigit(r) {
145
scan = p.Scan()
146
} else {
147
p.Camouflage("variable")
148
Fields = append(Fields, tok)
149
return p.Var(p.Const(tok)), nil
150
}
151
// Continue scanning the identifier
152
switch scan {
153
case scanner.Int:
154
fallthrough
155
case scanner.Ident:
156
fallthrough
157
case '[':
158
tok += curr + p.TokenText()
159
continue
160
default:
161
return nil, p.Expected("field", scanner.Ident)
162
}
163
case scanner.Int:
164
tok += p.TokenText()
165
166
switch p.Scan() {
167
case ']':
168
tok += "]"
169
default:
170
return nil, p.Expected("array closing bracket", ']')
171
}
172
default:
173
p.Camouflage("variable", '>', '-')
174
Fields = append(Fields, tok)
175
return p.Var(p.Const(tok)), nil
176
}
177
}
178
}, nil
179
}
180
181