1// SPDX-License-Identifier: MIT
2
3package expr
4
5import "math"
6
7// Kind is the type of a [Value].
8type Kind int
9
10// The kinds of value an expression can produce.
11const (
12 KindNull Kind = iota
13 KindBoolean
14 KindNumber
15 KindString
16 KindArray
17 KindDictionary
18)
19
20// String returns the name GitHub uses for the kind when a non-primitive
21// value is converted to a string: "Array" and "Object" for the two compound
22// kinds.
23func (k Kind) String() string {
24 switch k {
25 case KindNull:
26 return "Null"
27 case KindBoolean:
28 return "Boolean"
29 case KindNumber:
30 return "Number"
31 case KindString:
32 return "String"
33 case KindArray:
34 return "Array"
35 case KindDictionary:
36 return "Object"
37 }
38 return "unknown"
39}
40
41// Value is the result of an expression or an element of a context: one of
42// [Null], [Boolean], [Number], [String], [*Array] and [*Dictionary]. The set
43// is closed. A nil Value, or a nil *Array or *Dictionary, behaves as [Null].
44type Value interface {
45 // Kind reports the type of the value.
46 Kind() Kind
47 isValue()
48}
49
50// Null is the null value.
51type Null struct{}
52
53// Boolean is a true or false value.
54type Boolean bool
55
56// Number is a double-precision floating-point number. NaN and the infinities
57// are legal values.
58type Number float64
59
60// String is a text value.
61type String string
62
63func (Null) Kind() Kind { return KindNull }
64func (Boolean) Kind() Kind { return KindBoolean }
65func (Number) Kind() Kind { return KindNumber }
66func (String) Kind() Kind { return KindString }
67func (*Array) Kind() Kind { return KindArray }
68func (*Dictionary) Kind() Kind { return KindDictionary }
69
70func (Null) isValue() {}
71func (Boolean) isValue() {}
72func (Number) isValue() {}
73func (String) isValue() {}
74func (*Array) isValue() {}
75func (*Dictionary) isValue() {}
76
77// Array is an ordered list of values. Two arrays are equal in an expression
78// only if they are the same *Array.
79type Array struct {
80 items []Value
81 // filtered is set on arrays produced by the * filter ("a.*", "a[*]"),
82 // which flatten one level when indexed again ("a.*.b").
83 filtered bool
84}
85
86// NewArray returns an array holding items.
87func NewArray(items ...Value) *Array {
88 return &Array{items: append([]Value(nil), items...)}
89}
90
91// Len returns the number of elements.
92func (a *Array) Len() int {
93 if a == nil {
94 return 0
95 }
96 return len(a.items)
97}
98
99// At returns the element at index i, or [Null] when i is out of range.
100func (a *Array) At(i int) Value {
101 if a == nil || i < 0 || i >= len(a.items) {
102 return Null{}
103 }
104 return normalize(a.items[i])
105}
106
107// Items returns a copy of the elements.
108func (a *Array) Items() []Value {
109 if a == nil {
110 return nil
111 }
112 out := make([]Value, len(a.items))
113 for i, v := range a.items {
114 out[i] = normalize(v)
115 }
116 return out
117}
118
119// Append adds v at the end of the array.
120func (a *Array) Append(v Value) { a.items = append(a.items, v) }
121
122// Filtered reports whether the array was produced by a * filter.
123func (a *Array) Filtered() bool { return a != nil && a.filtered }
124
125// Pair is a key and its value in a [Dictionary].
126type Pair struct {
127 Key string
128 Value Value
129}
130
131// Dictionary is an ordered map from string keys to values. Keys are looked up
132// case-insensitively and keep the spelling and position of their first
133// insertion. Two dictionaries are equal in an expression only if they are
134// the same *Dictionary.
135type Dictionary struct {
136 keys []string
137 vals []Value
138 index map[string]int
139}
140
141// NewDictionary returns an empty dictionary.
142func NewDictionary() *Dictionary {
143 return &Dictionary{index: map[string]int{}}
144}
145
146// Add inserts key with value v and reports whether it did. When a key that
147// differs only by case is already present it does nothing and returns false:
148// the first insertion wins.
149func (d *Dictionary) Add(key string, v Value) bool {
150 if d.index == nil {
151 d.index = map[string]int{}
152 }
153 folded := foldKey(key)
154 if _, ok := d.index[folded]; ok {
155 return false
156 }
157 d.index[folded] = len(d.vals)
158 d.keys = append(d.keys, key)
159 d.vals = append(d.vals, v)
160 return true
161}
162
163// set stores v under key: a key that differs only by case from an existing one
164// replaces its value and keeps its spelling and position.
165func (d *Dictionary) set(key string, v Value) {
166 if d.index == nil {
167 d.index = map[string]int{}
168 }
169 if i, ok := d.index[foldKey(key)]; ok {
170 d.vals[i] = v
171 return
172 }
173 d.Add(key, v)
174}
175
176// Get returns the value stored under key, compared case-insensitively.
177func (d *Dictionary) Get(key string) (Value, bool) {
178 if d == nil {
179 return nil, false
180 }
181 i, ok := d.index[foldKey(key)]
182 if !ok {
183 return nil, false
184 }
185 return normalize(d.vals[i]), true
186}
187
188// Len returns the number of entries.
189func (d *Dictionary) Len() int {
190 if d == nil {
191 return 0
192 }
193 return len(d.vals)
194}
195
196// Keys returns the keys in insertion order.
197func (d *Dictionary) Keys() []string {
198 if d == nil {
199 return nil
200 }
201 return append([]string(nil), d.keys...)
202}
203
204// Values returns the values in insertion order.
205func (d *Dictionary) Values() []Value {
206 if d == nil {
207 return nil
208 }
209 out := make([]Value, len(d.vals))
210 for i, v := range d.vals {
211 out[i] = normalize(v)
212 }
213 return out
214}
215
216// Pairs returns the entries in insertion order.
217func (d *Dictionary) Pairs() []Pair {
218 if d == nil {
219 return nil
220 }
221 out := make([]Pair, len(d.keys))
222 for i, k := range d.keys {
223 out[i] = Pair{Key: k, Value: normalize(d.vals[i])}
224 }
225 return out
226}
227
228// normalize maps nil values to Null so that callers can switch on Kind.
229func normalize(v Value) Value {
230 switch x := v.(type) {
231 case nil:
232 return Null{}
233 case *Array:
234 if x == nil {
235 return Null{}
236 }
237 case *Dictionary:
238 if x == nil {
239 return Null{}
240 }
241 }
242 return v
243}
244
245// isPrimitive reports whether v is a Null, Boolean, Number or String.
246func isPrimitive(v Value) bool {
247 switch normalize(v).Kind() {
248 case KindNull, KindBoolean, KindNumber, KindString:
249 return true
250 }
251 return false
252}
253
254// toNumber converts v to a number the way GitHub does: null is 0, booleans
255// are 1 and 0, strings are parsed like JavaScript's Number(), and arrays and
256// dictionaries are NaN.
257func toNumber(v Value) float64 {
258 switch x := normalize(v).(type) {
259 case Null:
260 return 0
261 case Boolean:
262 if x {
263 return 1
264 }
265 return 0
266 case Number:
267 return float64(x)
268 case String:
269 return parseNumber(string(x))
270 }
271 return nan
272}
273
274// CoerceString converts v to the string GitHub shows for it: null is "", booleans
275// are "true" and "false", numbers are formatted with at most 15 decimals
276// ("0.1" for 0.1+0.2) and arrays and dictionaries are "Array" and "Object".
277func CoerceString(v Value) string {
278 switch x := normalize(v).(type) {
279 case Null:
280 return ""
281 case Boolean:
282 if x {
283 return "true"
284 }
285 return "false"
286 case Number:
287 return numberToCoercedString(float64(x))
288 case String:
289 return string(x)
290 default:
291 return x.Kind().String()
292 }
293}
294
295// Truthy reports whether v is true when used as a condition. Null, false, 0,
296// NaN and the empty string are falsy; everything else, including every array
297// and dictionary, is truthy.
298func Truthy(v Value) bool {
299 switch x := normalize(v).(type) {
300 case Null:
301 return false
302 case Boolean:
303 return bool(x)
304 case Number:
305 return x != 0 && !math.IsNaN(float64(x))
306 case String:
307 return len(x) != 0
308 }
309 return true
310}