1// SPDX-License-Identifier: MIT
2
3package expr
4
5import (
6 "errors"
7 "strconv"
8 "strings"
9 "unicode"
10)
11
12// builtinFunc implements a built-in function. It runs inside the evaluator so
13// that it can charge the output budget and watch the context, and it receives
14// the call, not the evaluated arguments: like the runner's functions
15// (Sdk/Functions/*.cs) it evaluates only the arguments it needs, in its own
16// order, so an argument that is never reached cannot fail.
17type builtinFunc func(e *evaluator, c *FunctionCall) (Value, error)
18
19// builtinImpls are the implementations of the built-in functions, keyed by
20// upper-cased name (see foldKey). Arity is validated by the parser, so each
21// may assume its documented argument count.
22var builtinImpls map[string]builtinFunc
23
24// The map is filled in init: the implementations call back into the evaluator,
25// which would make a package-level initializer cyclic.
26func init() {
27 builtinImpls = map[string]builtinFunc{
28 "CASE": funcCase,
29 "CONTAINS": funcContains,
30 "ENDSWITH": funcEndsWith,
31 "FORMAT": funcFormat,
32 "FROMJSON": funcFromJSON,
33 "JOIN": funcJoin,
34 "STARTSWITH": funcStartsWith,
35 "TOJSON": funcToJSON,
36 }
37}
38
39// funcCase is case(pred1, val1, pred2, val2, ..., default): the predicates are
40// evaluated in order up to the first true one, then only its value (Case.cs).
41// A predicate that is not a boolean is an error. The JavaScript reference
42// ships case behind an experimental feature flag that only affects editor
43// completion; the parser always accepts it, and so does the runner.
44func funcCase(e *evaluator, c *FunctionCall) (Value, error) {
45 n := len(c.Args)
46 if n%2 == 0 {
47 return nil, evalErrorf("case requires an odd number of arguments")
48 }
49 for i := 0; i < n-1; i += 2 {
50 p, err := e.eval(c.Args[i])
51 if err != nil {
52 return nil, err
53 }
54 pred, ok := normalize(p).(Boolean)
55 if !ok {
56 return nil, evalErrorf("case predicate must evaluate to a boolean value")
57 }
58 if pred {
59 return e.eval(c.Args[i+1])
60 }
61 }
62 return e.eval(c.Args[n-1])
63}
64
65// funcContains is contains(search, item) (Contains.cs): when search is a
66// primitive and so is item, a case-insensitive (OrdinalIgnoreCase) substring
67// test on their string forms; when search is a non-empty array, == against each
68// element; false otherwise. item is only evaluated when it can matter.
69func funcContains(e *evaluator, c *FunctionCall) (Value, error) {
70 l, err := e.eval(c.Args[0])
71 if err != nil {
72 return nil, err
73 }
74 left := normalize(l)
75 switch {
76 case isPrimitive(left):
77 r, err := e.eval(c.Args[1])
78 if err != nil {
79 return nil, err
80 }
81 if right := normalize(r); isPrimitive(right) {
82 return Boolean(jsIncludes(ordinalFold(CoerceString(left)), ordinalFold(CoerceString(right)))), nil
83 }
84 case left.Kind() == KindArray && len(left.(*Array).items) > 0:
85 right, err := e.eval(c.Args[1])
86 if err != nil {
87 return nil, err
88 }
89 for _, v := range left.(*Array).items {
90 if err := e.tick(); err != nil {
91 return nil, err
92 }
93 if equals(right, v) {
94 return Boolean(true), nil
95 }
96 }
97 }
98 return Boolean(false), nil
99}
100
101func funcStartsWith(e *evaluator, c *FunctionCall) (Value, error) {
102 ls, rs, ok, err := e.primitivePair(c)
103 return Boolean(ok && jsHasPrefix(ordinalFold(ls), ordinalFold(rs))), err
104}
105
106func funcEndsWith(e *evaluator, c *FunctionCall) (Value, error) {
107 ls, rs, ok, err := e.primitivePair(c)
108 return Boolean(ok && jsHasSuffix(ordinalFold(ls), ordinalFold(rs))), err
109}
110
111// primitivePair evaluates the two arguments, the second only if the first is
112// primitive, and returns their string forms; ok is false when either is an
113// array or a dictionary.
114func (e *evaluator) primitivePair(c *FunctionCall) (ls, rs string, ok bool, err error) {
115 l, err := e.eval(c.Args[0])
116 if err != nil || !isPrimitive(l) {
117 return "", "", false, err
118 }
119 r, err := e.eval(c.Args[1])
120 if err != nil || !isPrimitive(r) {
121 return "", "", false, err
122 }
123 return CoerceString(l), CoerceString(r), true, nil
124}
125
126// funcJoin is join(array, separator) (Join.cs). A primitive first argument is
127// just converted to a string, ignoring the separator; a dictionary or an empty
128// array gives "". The separator is evaluated only when there are at least two
129// items to separate, and a separator that is not primitive means ",".
130func funcJoin(e *evaluator, c *FunctionCall) (Value, error) {
131 f, err := e.eval(c.Args[0])
132 if err != nil {
133 return nil, err
134 }
135 first := normalize(f)
136 if isPrimitive(first) {
137 return String(CoerceString(first)), nil
138 }
139 arr, ok := first.(*Array)
140 if !ok || len(arr.items) == 0 {
141 return String(""), nil
142 }
143 sep := ","
144 if len(arr.items) > 1 && len(c.Args) > 1 {
145 sv, err := e.eval(c.Args[1])
146 if err != nil {
147 return nil, err
148 }
149 if isPrimitive(sv) {
150 sep = CoerceString(sv)
151 }
152 }
153 var out strings.Builder
154 for i, it := range arr.items {
155 if err := e.tick(); err != nil {
156 return nil, err
157 }
158 part := CoerceString(it)
159 n := len(part)
160 if i > 0 {
161 n += len(sep)
162 }
163 if err := e.charge(n); err != nil {
164 return nil, err
165 }
166 if i > 0 {
167 out.WriteString(sep)
168 }
169 out.WriteString(part)
170 }
171 return String(out.String()), nil
172}
173
174// funcToJSON is toJSON(value). The output counts against the budget and is
175// cut off as soon as it exceeds what is left.
176func funcToJSON(e *evaluator, c *FunctionCall) (Value, error) {
177 v, err := e.eval(c.Args[0])
178 if err != nil {
179 return nil, err
180 }
181 max := -1
182 if e.limit >= 0 {
183 max = e.limit - e.used
184 }
185 out, err := EncodeJSON(v, max)
186 switch {
187 case errors.Is(err, errJSONTooBig):
188 return nil, e.budgetError()
189 case err != nil:
190 return nil, &EvalError{Message: "toJSON: " + err.Error(), Err: err}
191 }
192 if err := e.charge(len(out)); err != nil {
193 return nil, err
194 }
195 return String(out), nil
196}
197
198// funcFromJSON is fromJSON(value). Anything that Json.NET cannot read, blank
199// input included, fails with the message of its JsonReaderException (a
200// [*JSONError]), which is what the runner reports.
201func funcFromJSON(e *evaluator, c *FunctionCall) (Value, error) {
202 a, err := e.eval(c.Args[0])
203 if err != nil {
204 return nil, err
205 }
206 v, err := FromJSON([]byte(CoerceString(a)))
207 if err != nil {
208 return nil, &EvalError{Message: err.Error(), Err: err}
209 }
210 return v, nil
211}
212
213// formatPiece is a segment of the result of format(): literal text, or the
214// index of an argument (evaluated when the result is assembled) with the
215// format specifiers written after the colon.
216type formatPiece struct {
217 text string
218 arg int
219 specs string
220 isArg bool
221}
222
223// funcFormat is format(fmt, args...) (Format.cs): "{N}" inserts argument N,
224// "{{" and "}}" are literal braces. The format string is scanned completely
225// first (so a syntax error or a reference to a missing argument is reported
226// before any argument is evaluated), then the arguments are evaluated, once
227// each, in the order they appear. N is at most 255 (a byte). Format specifiers
228// ("{0:x}") are not supported for any value: they are an error.
229func funcFormat(e *evaluator, c *FunctionCall) (Value, error) {
230 f, err := e.eval(c.Args[0])
231 if err != nil {
232 return nil, err
233 }
234 fs := CoerceString(f)
235 var pieces []formatPiece
236 text := func(t string) error {
237 if t == "" {
238 return nil
239 }
240 if err := e.charge(utf16Len(t)); err != nil {
241 return err
242 }
243 pieces = append(pieces, formatPiece{text: t})
244 return nil
245 }
246 index := 0
247 for index < len(fs) {
248 lbrace := indexByteFrom(fs, '{', index)
249 rbrace := indexByteFrom(fs, '}', index)
250 switch {
251 case lbrace >= 0 && (rbrace < 0 || rbrace > lbrace):
252 // Escaped left brace.
253 if charAt(fs, lbrace+1) == '{' {
254 if err := text(fs[index : lbrace+1]); err != nil {
255 return nil, err
256 }
257 index = lbrace + 2
258 continue
259 }
260 // Left brace, number, optional format specifiers, right brace.
261 if rbrace > lbrace+1 {
262 if argIndex, end, ok := readArgIndex(fs, lbrace+1); ok {
263 if specs, closing, ok := readFormatSpecifiers(fs, end+1); ok {
264 if argIndex > len(c.Args)-2 {
265 return nil, evalErrorf("The following format string references more arguments than were supplied: %s", fs)
266 }
267 if err := text(fs[index:lbrace]); err != nil {
268 return nil, err
269 }
270 pieces = append(pieces, formatPiece{arg: argIndex, specs: specs, isArg: true})
271 index = closing + 1
272 continue
273 }
274 }
275 }
276 return nil, evalErrorf("The following format string is invalid: %s", fs)
277 case rbrace >= 0:
278 // Escaped right brace.
279 if charAt(fs, rbrace+1) != '}' {
280 return nil, evalErrorf("The following format string is invalid: %s", fs)
281 }
282 if err := text(fs[index : rbrace+1]); err != nil {
283 return nil, err
284 }
285 index = rbrace + 2
286 default:
287 if err := text(fs[index:]); err != nil {
288 return nil, err
289 }
290 index = len(fs)
291 }
292 }
293 // Assemble: arguments are evaluated lazily, once each, in the order of
294 // their first appearance.
295 type argValue struct {
296 str string
297 kind Kind
298 done bool
299 }
300 cache := make([]argValue, len(c.Args)-1)
301 var out strings.Builder
302 for _, p := range pieces {
303 if !p.isArg {
304 out.WriteString(p.text)
305 continue
306 }
307 av := &cache[p.arg]
308 if !av.done {
309 v, err := e.eval(c.Args[1+p.arg])
310 if err != nil {
311 return nil, err
312 }
313 *av = argValue{str: CoerceString(v), kind: normalize(v).Kind(), done: true}
314 }
315 if p.specs != "" {
316 return nil, evalErrorf("The format specifiers '%s' are not valid for objects of type '%s'", p.specs, av.kind)
317 }
318 if av.str != "" {
319 if err := e.charge(utf16Len(av.str)); err != nil {
320 return nil, err
321 }
322 }
323 out.WriteString(av.str)
324 }
325 return String(out.String()), nil
326}
327
328func indexByteFrom(s string, c byte, from int) int {
329 if from >= len(s) {
330 return -1
331 }
332 i := strings.IndexByte(s[from:], c)
333 if i < 0 {
334 return -1
335 }
336 return from + i
337}
338
339// charAt returns the byte at i, or 0 past the end (safeCharAt).
340func charAt(s string, i int) byte {
341 if i < len(s) {
342 return s[i]
343 }
344 return 0
345}
346
347// readArgIndex reads the digits at s[start:] as the index of a format
348// argument: a byte, so at most 255 (Byte.TryParse). It returns the index of the
349// last digit. Digits are Unicode decimal digits for the scan but only ASCII
350// ones parse, so any other digit makes the format string invalid.
351func readArgIndex(s string, start int) (value, end int, ok bool) {
352 n := 0
353 for i := start; i < len(s); {
354 r, w := decodeRune(s[i:])
355 if !unicode.IsDigit(r) {
356 break
357 }
358 i += w
359 n = i - start
360 }
361 if n == 0 {
362 return 0, 0, false
363 }
364 v, err := strconv.ParseUint(s[start:start+n], 10, 8)
365 if err != nil || !isASCII(s[start:start+n]) {
366 return 0, 0, false
367 }
368 return int(v), start + n - 1, true
369}
370
371// readFormatSpecifiers reads what follows the digits of "{N": either the
372// closing brace, or ":" and specifiers up to a closing brace, where "}}" is a
373// literal brace inside the specifiers. It returns the specifiers and the index
374// of the closing brace.
375func readFormatSpecifiers(s string, start int) (specs string, closing int, ok bool) {
376 switch charAt(s, start) {
377 case '}':
378 return "", start, true
379 case ':':
380 default:
381 return "", 0, false
382 }
383 var b strings.Builder
384 for i := start + 1; i < len(s); {
385 if s[i] != '}' {
386 b.WriteByte(s[i])
387 i++
388 } else if charAt(s, i+1) == '}' {
389 b.WriteByte('}')
390 i += 2
391 } else {
392 return b.String(), i, true
393 }
394 }
395 return "", 0, false
396}
397
398// utf16Len is the length of s in UTF-16 code units, the length of a .NET string.
399func utf16Len(s string) int {
400 n := 0
401 for _, r := range s {
402 n++
403 if r >= 0x10000 {
404 n++
405 }
406 }
407 return n
408}