bare-script 3.0.4 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/library.js CHANGED
@@ -1,13 +1,14 @@
1
1
  // Licensed under the MIT License
2
2
  // https://github.com/craigahobbs/bare-script/blob/main/LICENSE
3
3
 
4
+ import {
5
+ ValueArgsError, valueArgsModel, valueArgsValidate, valueBoolean, valueCompare, valueIs, valueJSON,
6
+ valueParseDatetime, valueParseInteger, valueParseNumber, valueRoundNumber, valueString, valueType
7
+ } from './value.js';
4
8
  import {
5
9
  addCalculatedField, aggregateData, filterData, joinData, parseCSV, sortData, topData, validateData
6
10
  } from './data.js';
7
11
  import {validateType, validateTypeModel} from 'schema-markdown/lib/schema.js';
8
- import {
9
- valueBoolean, valueCompare, valueIs, valueJSON, valueParseDatetime, valueParseInteger, valueParseNumber, valueString, valueType
10
- } from './value.js';
11
12
  import {parseSchemaMarkdown} from 'schema-markdown/lib/parser.js';
12
13
  import {typeModel} from 'schema-markdown/lib/typeModel.js';
13
14
 
@@ -29,14 +30,15 @@ export const defaultMaxStatements = 1e9;
29
30
  // $doc: Create a copy of an array
30
31
  // $arg array: The array to copy
31
32
  // $return: The array copy
32
- function arrayCopy([array = null]) {
33
- if (valueType(array) !== 'array') {
34
- return null;
35
- }
36
-
33
+ function arrayCopy(args) {
34
+ const [array] = valueArgsValidate(arrayCopyArgs, args);
37
35
  return [...array];
38
36
  }
39
37
 
38
+ const arrayCopyArgs = valueArgsModel([
39
+ {'name': 'array', 'type': 'array'}
40
+ ]);
41
+
40
42
 
41
43
  // $function: arrayExtend
42
44
  // $group: Array
@@ -44,15 +46,17 @@ function arrayCopy([array = null]) {
44
46
  // $arg array: The array to extend
45
47
  // $arg array2: The array to extend with
46
48
  // $return: The extended array
47
- function arrayExtend([array = null, array2 = null]) {
48
- if (valueType(array) !== 'array' || valueType(array2) !== 'array') {
49
- return null;
50
- }
51
-
49
+ function arrayExtend(args) {
50
+ const [array, array2] = valueArgsValidate(arrayExtendArgs, args);
52
51
  array.push(...array2);
53
52
  return array;
54
53
  }
55
54
 
55
+ const arrayExtendArgs = valueArgsModel([
56
+ {'name': 'array', 'type': 'array'},
57
+ {'name': 'array2', 'type': 'array'}
58
+ ]);
59
+
56
60
 
57
61
  // $function: arrayGet
58
62
  // $group: Array
@@ -60,15 +64,20 @@ function arrayExtend([array = null, array2 = null]) {
60
64
  // $arg array: The array
61
65
  // $arg index: The array element's index
62
66
  // $return: The array element
63
- function arrayGet([array = null, index = null]) {
64
- if (valueType(array) !== 'array' ||
65
- valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= array.length) {
66
- return null;
67
+ function arrayGet(args) {
68
+ const [array, index] = valueArgsValidate(arrayGetArgs, args);
69
+ if (index >= array.length) {
70
+ throw new ValueArgsError('index', index);
67
71
  }
68
72
 
69
73
  return array[index];
70
74
  }
71
75
 
76
+ const arrayGetArgs = valueArgsModel([
77
+ {'name': 'array', 'type': 'array'},
78
+ {'name': 'index', 'type': 'number', 'integer': true, 'gte': 0}
79
+ ]);
80
+
72
81
 
73
82
  // $function: arrayIndexOf
74
83
  // $group: Array
@@ -77,23 +86,31 @@ function arrayGet([array = null, index = null]) {
77
86
  // $arg value: The value to find in the array, or a match function, f(value) -> bool
78
87
  // $arg index: Optional (default is 0). The index at which to start the search.
79
88
  // $return: The first index of the value in the array; -1 if not found.
80
- function arrayIndexOf([array = null, value = null, index = 0], options) {
81
- if (valueType(array) !== 'array' ||
82
- valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= array.length) {
83
- return -1;
89
+ function arrayIndexOf(args, options) {
90
+ const [array, value, index] = valueArgsValidate(arrayIndexOfArgs, args, -1);
91
+ if (index >= array.length) {
92
+ throw new ValueArgsError('index', index, -1);
84
93
  }
85
94
 
95
+ // Value function?
86
96
  if (valueType(value) === 'function') {
87
97
  for (let ix = index; ix < array.length; ix += 1) {
88
98
  if (valueBoolean(value([array[ix]], options))) {
89
99
  return ix;
90
100
  }
91
101
  }
102
+ return -1;
92
103
  }
93
104
 
94
105
  return array.indexOf(value, index);
95
106
  }
96
107
 
108
+ const arrayIndexOfArgs = valueArgsModel([
109
+ {'name': 'array', 'type': 'array'},
110
+ {'name': 'value'},
111
+ {'name': 'index', 'type': 'number', 'default': 0, 'integer': true, 'gte': 0}
112
+ ]);
113
+
97
114
 
98
115
  // $function: arrayJoin
99
116
  // $group: Array
@@ -101,14 +118,16 @@ function arrayIndexOf([array = null, value = null, index = 0], options) {
101
118
  // $arg array: The array
102
119
  // $arg separator: The separator string
103
120
  // $return: The joined string
104
- function arrayJoin([array = null, separator = null]) {
105
- if (valueType(array) !== 'array' || valueType(separator) !== 'string') {
106
- return null;
107
- }
108
-
121
+ function arrayJoin(args) {
122
+ const [array , separator] = valueArgsValidate(arrayJoinArgs, args);
109
123
  return array.map((value) => valueString(value)).join(separator);
110
124
  }
111
125
 
126
+ const arrayJoinArgs = valueArgsModel([
127
+ {'name': 'array', 'type': 'array'},
128
+ {'name': 'separator', 'type': 'string'}
129
+ ]);
130
+
112
131
 
113
132
  // $function: arrayLastIndexOf
114
133
  // $group: Array
@@ -117,41 +136,50 @@ function arrayJoin([array = null, separator = null]) {
117
136
  // $arg value: The value to find in the array, or a match function, f(value) -> bool
118
137
  // $arg index: Optional (default is the end of the array). The index at which to start the search.
119
138
  // $return: The last index of the value in the array; -1 if not found.
120
- function arrayLastIndexOf([array = null, value = null, indexArg = null], options) {
139
+ function arrayLastIndexOf(args, options) {
140
+ const [array, value, indexArg] = valueArgsValidate(arrayLastIndexOfArgs, args, -1);
121
141
  let index = indexArg;
122
- if (valueType(array) === 'array' && index === null) {
142
+ if (index === null) {
123
143
  index = array.length - 1;
124
144
  }
125
- if (valueType(array) !== 'array' ||
126
- valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= array.length) {
127
- return -1;
145
+ if (index >= array.length) {
146
+ throw new ValueArgsError('index', index, -1);
128
147
  }
129
148
 
149
+ // Value function?
130
150
  if (valueType(value) === 'function') {
131
151
  for (let ix = index; ix >= 0; ix -= 1) {
132
152
  if (valueBoolean(value([array[ix]], options))) {
133
153
  return ix;
134
154
  }
135
155
  }
156
+ return -1;
136
157
  }
137
158
 
138
159
  return array.lastIndexOf(value, index);
139
160
  }
140
161
 
162
+ const arrayLastIndexOfArgs = valueArgsModel([
163
+ {'name': 'array', 'type': 'array'},
164
+ {'name': 'value'},
165
+ {'name': 'index', 'type': 'number', 'nullable': true, 'integer': true, 'gte': 0}
166
+ ]);
167
+
141
168
 
142
169
  // $function: arrayLength
143
170
  // $group: Array
144
171
  // $doc: Get the length of an array
145
172
  // $arg array: The array
146
173
  // $return: The array's length; zero if not an array
147
- function arrayLength([array = null]) {
148
- if (valueType(array) !== 'array') {
149
- return 0;
150
- }
151
-
174
+ function arrayLength(args) {
175
+ const [array] = valueArgsValidate(arrayLengthArgs, args, 0);
152
176
  return array.length;
153
177
  }
154
178
 
179
+ const arrayLengthArgs = valueArgsModel([
180
+ {'name': 'array', 'type': 'array'}
181
+ ]);
182
+
155
183
 
156
184
  // $function: arrayNew
157
185
  // $group: Array
@@ -169,28 +197,34 @@ function arrayNew(values) {
169
197
  // $arg size: Optional (default is 0). The new array's size.
170
198
  // $arg value: Optional (default is 0). The value with which to fill the new array.
171
199
  // $return: The new array
172
- function arrayNewSize([size = 0, value = 0]) {
173
- if (valueType(size) !== 'number' || Math.floor(size) !== size || size < 0) {
174
- return null;
175
- }
176
-
200
+ function arrayNewSize(args) {
201
+ const [size, value] = valueArgsValidate(arrayNewSizeArgs, args);
177
202
  return new Array(size).fill(value);
178
203
  }
179
204
 
205
+ const arrayNewSizeArgs = valueArgsModel([
206
+ {'name': 'size', 'type': 'number', 'default': 0, 'integer': true, 'gte': 0},
207
+ {'name': 'value', 'default': 0}
208
+ ]);
209
+
180
210
 
181
211
  // $function: arrayPop
182
212
  // $group: Array
183
213
  // $doc: Remove the last element of the array and return it
184
214
  // $arg array: The array
185
215
  // $return: The last element of the array; null if the array is empty.
186
- function arrayPop([array = null]) {
187
- if (valueType(array) !== 'array' || array.length === 0) {
188
- return null;
216
+ function arrayPop(args) {
217
+ const [array] = valueArgsValidate(arrayPopArgs, args);
218
+ if (array.length === 0) {
219
+ throw new ValueArgsError('array', array);
189
220
  }
190
-
191
221
  return array.pop();
192
222
  }
193
223
 
224
+ const arrayPopArgs = valueArgsModel([
225
+ {'name': 'array', 'type': 'array'}
226
+ ]);
227
+
194
228
 
195
229
  // $function: arrayPush
196
230
  // $group: Array
@@ -198,15 +232,17 @@ function arrayPop([array = null]) {
198
232
  // $arg array: The array
199
233
  // $arg values...: The values to add to the end of the array
200
234
  // $return: The array
201
- function arrayPush([array = null, ...values]) {
202
- if (valueType(array) !== 'array') {
203
- return null;
204
- }
205
-
235
+ function arrayPush(args) {
236
+ const [array, values] = valueArgsValidate(arrayPushArgs, args);
206
237
  array.push(...values);
207
238
  return array;
208
239
  }
209
240
 
241
+ const arrayPushArgs = valueArgsModel([
242
+ {'name': 'array', 'type': 'array'},
243
+ {'name': 'values', 'lastArgArray': true}
244
+ ]);
245
+
210
246
 
211
247
  // $function: arraySet
212
248
  // $group: Array
@@ -215,30 +251,40 @@ function arrayPush([array = null, ...values]) {
215
251
  // $arg index: The index of the element to set
216
252
  // $arg value: The value to set
217
253
  // $return: The value
218
- function arraySet([array = null, index = null, value = null]) {
219
- if (valueType(array) !== 'array' ||
220
- valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= array.length) {
221
- return null;
254
+ function arraySet(args) {
255
+ const [array, index, value] = valueArgsValidate(arraySetArgs, args);
256
+ if (index >= array.length) {
257
+ throw new ValueArgsError('index', index);
222
258
  }
223
259
 
224
260
  array[index] = value;
225
261
  return value;
226
262
  }
227
263
 
264
+ const arraySetArgs = valueArgsModel([
265
+ {'name': 'array', 'type': 'array'},
266
+ {'name': 'index', 'type': 'number', 'integer': true, 'gte': 0},
267
+ {'name': 'value'}
268
+ ]);
269
+
228
270
 
229
271
  // $function: arrayShift
230
272
  // $group: Array
231
273
  // $doc: Remove the first element of the array and return it
232
274
  // $arg array: The array
233
275
  // $return: The first element of the array; null if the array is empty.
234
- function arrayShift([array = null]) {
235
- if (valueType(array) !== 'array' || array.length === 0) {
236
- return null;
276
+ function arrayShift(args) {
277
+ const [array] = valueArgsValidate(arrayShiftArgs, args);
278
+ if (array.length === 0) {
279
+ throw new ValueArgsError('array', array);
237
280
  }
238
-
239
281
  return array.shift();
240
282
  }
241
283
 
284
+ const arrayShiftArgs = valueArgsModel([
285
+ {'name': 'array', 'type': 'array'}
286
+ ]);
287
+
242
288
 
243
289
  // $function: arraySlice
244
290
  // $group: Array
@@ -247,20 +293,25 @@ function arrayShift([array = null]) {
247
293
  // $arg start: Optional (default is 0). The start index of the slice.
248
294
  // $arg end: Optional (default is the end of the array). The end index of the slice.
249
295
  // $return: The new array slice
250
- function arraySlice([array = null, start = 0, endArg = null]) {
251
- let end = endArg;
252
- if (valueType(array) === 'array' && end === null) {
253
- end = array.length;
296
+ function arraySlice(args) {
297
+ const [array, start, endArg] = valueArgsValidate(arraySliceArgs, args);
298
+ const end = endArg !== null ? endArg : array.length;
299
+ if (start > array.length) {
300
+ throw new ValueArgsError('start', start);
254
301
  }
255
- if (valueType(array) !== 'array' ||
256
- valueType(start) !== 'number' || Math.floor(start) !== start || start < 0 || start > array.length ||
257
- valueType(end) !== 'number' || Math.floor(end) !== end || end < 0 || end > array.length) {
258
- return null;
302
+ if (end > array.length) {
303
+ throw new ValueArgsError('end', end);
259
304
  }
260
305
 
261
306
  return array.slice(start, end);
262
307
  }
263
308
 
309
+ const arraySliceArgs = valueArgsModel([
310
+ {'name': 'array', 'type': 'array'},
311
+ {'name': 'start', 'type': 'number', 'default': 0, 'integer': true, 'gte': 0},
312
+ {'name': 'end', 'type': 'number', 'nullable': true, 'integer': true, 'gte': 0}
313
+ ]);
314
+
264
315
 
265
316
  // $function: arraySort
266
317
  // $group: Array
@@ -268,17 +319,19 @@ function arraySlice([array = null, start = 0, endArg = null]) {
268
319
  // $arg array: The array
269
320
  // $arg compareFn: Optional (default is null). The comparison function.
270
321
  // $return: The sorted array
271
- function arraySort([array = null, compareFn = null], options) {
272
- if (valueType(array) !== 'array' || (compareFn !== null && valueType(compareFn) !== 'function')) {
273
- return null;
274
- }
275
-
322
+ function arraySort(args, options) {
323
+ const [array, compareFn] = valueArgsValidate(arraySortArgs, args);
276
324
  if (compareFn === null) {
277
325
  return array.sort(valueCompare);
278
326
  }
279
- return array.sort((...args) => compareFn(args, options));
327
+ return array.sort((...sortArgs) => compareFn(sortArgs, options));
280
328
  }
281
329
 
330
+ const arraySortArgs = valueArgsModel([
331
+ {'name': 'array', 'type': 'array'},
332
+ {'name': 'compareFn', 'type': 'function', 'nullable': true}
333
+ ]);
334
+
282
335
 
283
336
  //
284
337
  // Data functions
@@ -291,14 +344,16 @@ function arraySort([array = null, compareFn = null], options) {
291
344
  // $arg data: The data array
292
345
  // $arg aggregation: The [aggregation model](model.html#var.vName='Aggregation')
293
346
  // $return: The aggregated data array
294
- function dataAggregate([data = null, aggregation = null]) {
295
- if (valueType(data) !== 'array' || (aggregation !== null && valueType(aggregation) !== 'object')) {
296
- return null;
297
- }
298
-
347
+ function dataAggregate(args) {
348
+ const [data, aggregation] = valueArgsValidate(dataAggregateArgs, args);
299
349
  return aggregateData(data, aggregation);
300
350
  }
301
351
 
352
+ const dataAggregateArgs = valueArgsModel([
353
+ {'name': 'data', 'type': 'array'},
354
+ {'name': 'aggregation', 'type': 'object'}
355
+ ]);
356
+
302
357
 
303
358
  // $function: dataCalculatedField
304
359
  // $group: Data
@@ -308,15 +363,19 @@ function dataAggregate([data = null, aggregation = null]) {
308
363
  // $arg expr: The calculated field expression
309
364
  // $arg variables: Optional (default is null). A variables object the expression evaluation.
310
365
  // $return: The updated data array
311
- function dataCalculatedField([data = null, fieldName = null, expr = null, variables = null], options) {
312
- if (valueType(data) !== 'array' || valueType(fieldName) !== 'string' || valueType(expr) !== 'string' ||
313
- (variables !== null && valueType(variables) !== 'object')) {
314
- return null;
315
- }
316
-
366
+ function dataCalculatedField(args, options) {
367
+ const [data, fieldName, expr, variables] = valueArgsValidate(dataCalculatedFieldArgs, args);
317
368
  return addCalculatedField(data, fieldName, expr, variables, options);
318
369
  }
319
370
 
371
+ const dataCalculatedFieldArgs = valueArgsModel([
372
+ {'name': 'data', 'type': 'array'},
373
+ {'name': 'fieldName', 'type': 'string'},
374
+ {'name': 'expr', 'type': 'string'},
375
+ {'name': 'variables', 'type': 'object', 'nullable': true}
376
+ ]);
377
+
378
+
320
379
  // $function: dataFilter
321
380
  // $group: Data
322
381
  // $doc: Filter a data array
@@ -324,14 +383,17 @@ function dataCalculatedField([data = null, fieldName = null, expr = null, variab
324
383
  // $arg expr: The filter expression
325
384
  // $arg variables: Optional (default is null). A variables object the expression evaluation.
326
385
  // $return: The filtered data array
327
- function dataFilter([data = null, expr = null, variables = null], options) {
328
- if (valueType(data) !== 'array' || valueType(expr) !== 'string' || (variables !== null && valueType(variables) !== 'object')) {
329
- return null;
330
- }
331
-
386
+ function dataFilter(args, options) {
387
+ const [data, expr, variables] = valueArgsValidate(dataFilterArgs, args);
332
388
  return filterData(data, expr, variables, options);
333
389
  }
334
390
 
391
+ const dataFilterArgs = valueArgsModel([
392
+ {'name': 'data', 'type': 'array'},
393
+ {'name': 'expr', 'type': 'string'},
394
+ {'name': 'variables', 'type': 'object', 'nullable': true}
395
+ ]);
396
+
335
397
 
336
398
  // $function: dataJoin
337
399
  // $group: Data
@@ -344,15 +406,20 @@ function dataFilter([data = null, expr = null, variables = null], options) {
344
406
  // $arg isLeftJoin: Optional (default is false). If true, perform a left join (always include left row).
345
407
  // $arg variables: Optional (default is null). A variables object for join expression evaluation.
346
408
  // $return: The joined data array
347
- function dataJoin([leftData = null, rightData = null, joinExpr = null, rightExpr = null, isLeftJoin = false, variables = null], options) {
348
- if (valueType(leftData) !== 'array' || valueType(rightData) !== 'array' || valueType(joinExpr) !== 'string' ||
349
- (rightExpr !== null && valueType(rightExpr) !== 'string') || (variables !== null && valueType(variables) !== 'object')) {
350
- return null;
351
- }
352
-
409
+ function dataJoin(args, options) {
410
+ const [leftData, rightData, joinExpr, rightExpr, isLeftJoin, variables] = valueArgsValidate(dataJoinArgs, args);
353
411
  return joinData(leftData, rightData, joinExpr, rightExpr, isLeftJoin, variables, options);
354
412
  }
355
413
 
414
+ const dataJoinArgs = valueArgsModel([
415
+ {'name': 'leftData', 'type': 'array'},
416
+ {'name': 'rightData', 'type': 'array'},
417
+ {'name': 'joinExpr', 'type': 'string'},
418
+ {'name': 'rightExpr', 'type': 'string', 'nullable': true},
419
+ {'name': 'isLeftJoin', 'type': 'boolean', 'default': false},
420
+ {'name': 'variables', 'type': 'object', 'nullable': true}
421
+ ]);
422
+
356
423
 
357
424
  // $function: dataParseCSV
358
425
  // $group: Data
@@ -367,7 +434,7 @@ function dataParseCSV(args) {
367
434
  continue;
368
435
  }
369
436
  if (valueType(arg) !== 'string') {
370
- return null;
437
+ throw new ValueArgsError('text', arg);
371
438
  }
372
439
  lines.push(arg);
373
440
  }
@@ -384,14 +451,16 @@ function dataParseCSV(args) {
384
451
  // $arg data: The data array
385
452
  // $arg sorts: The sort field-name/descending-sort tuples
386
453
  // $return: The sorted data array
387
- function dataSort([data = null, sorts = null]) {
388
- if (valueType(data) !== 'array' || valueType(sorts) !== 'array') {
389
- return null;
390
- }
391
-
454
+ function dataSort(args) {
455
+ const [data, sorts] = valueArgsValidate(dataSortArgs, args);
392
456
  return sortData(data, sorts);
393
457
  }
394
458
 
459
+ const dataSortArgs = valueArgsModel([
460
+ {'name': 'data', 'type': 'array'},
461
+ {'name': 'sorts', 'type': 'array'}
462
+ ]);
463
+
395
464
 
396
465
  // $function: dataTop
397
466
  // $group: Data
@@ -400,16 +469,17 @@ function dataSort([data = null, sorts = null]) {
400
469
  // $arg count: The number of rows to keep (default is 1)
401
470
  // $arg categoryFields: Optional (default is null). The category fields.
402
471
  // $return: The top data array
403
- function dataTop([data = null, count = null, categoryFields = null]) {
404
- if (valueType(data) !== 'array' ||
405
- valueType(count) !== 'number' || Math.floor(count) !== count || count < 1 ||
406
- (categoryFields !== null && valueType(categoryFields) !== 'array')) {
407
- return null;
408
- }
409
-
472
+ function dataTop(args) {
473
+ const [data, count, categoryFields] = valueArgsValidate(dataTopArgs, args);
410
474
  return topData(data, count, categoryFields);
411
475
  }
412
476
 
477
+ const dataTopArgs = valueArgsModel([
478
+ {'name': 'data', 'type': 'array'},
479
+ {'name': 'count', 'type': 'number', 'integer': true, 'gte': 1},
480
+ {'name': 'categoryFields', 'type': 'array', 'nullable': true}
481
+ ]);
482
+
413
483
 
414
484
  // $function: dataValidate
415
485
  // $group: Data
@@ -417,15 +487,17 @@ function dataTop([data = null, count = null, categoryFields = null]) {
417
487
  // $arg data: The data array
418
488
  // $arg csv: Optional (default is false). If true, parse value strings.
419
489
  // $return: The validated data array
420
- function dataValidate([data = null, csv = false]) {
421
- if (valueType(data) !== 'array') {
422
- return null;
423
- }
424
-
425
- validateData(data, valueBoolean(csv));
490
+ function dataValidate(args) {
491
+ const [data, csv] = valueArgsValidate(dataValidateArgs, args);
492
+ validateData(data, csv);
426
493
  return data;
427
494
  }
428
495
 
496
+ const dataValidateArgs = valueArgsModel([
497
+ {'name': 'data', 'type': 'array'},
498
+ {'name': 'csv', 'type': 'boolean', 'default': false}
499
+ ]);
500
+
429
501
 
430
502
  //
431
503
  // Datetime functions
@@ -437,28 +509,30 @@ function dataValidate([data = null, csv = false]) {
437
509
  // $doc: Get the day of the month of a datetime
438
510
  // $arg datetime: The datetime
439
511
  // $return: The day of the month
440
- function datetimeDay([datetime = null]) {
441
- if (valueType(datetime) !== 'datetime') {
442
- return null;
443
- }
444
-
512
+ function datetimeDay(args) {
513
+ const [datetime] = valueArgsValidate(datetimeDayArgs, args);
445
514
  return datetime.getDate();
446
515
  }
447
516
 
517
+ const datetimeDayArgs = valueArgsModel([
518
+ {'name': 'datetime', 'type': 'datetime'}
519
+ ]);
520
+
448
521
 
449
522
  // $function: datetimeHour
450
523
  // $group: Datetime
451
524
  // $doc: Get the hour of a datetime
452
525
  // $arg datetime: The datetime
453
526
  // $return: The hour
454
- function datetimeHour([datetime = null]) {
455
- if (valueType(datetime) !== 'datetime') {
456
- return null;
457
- }
458
-
527
+ function datetimeHour(args) {
528
+ const [datetime] = valueArgsValidate(datetimeHourArgs, args);
459
529
  return datetime.getHours();
460
530
  }
461
531
 
532
+ const datetimeHourArgs = valueArgsModel([
533
+ {'name': 'datetime', 'type': 'datetime'}
534
+ ]);
535
+
462
536
 
463
537
  // $function: datetimeISOFormat
464
538
  // $group: Datetime
@@ -466,12 +540,10 @@ function datetimeHour([datetime = null]) {
466
540
  // $arg datetime: The datetime
467
541
  // $arg isDate: If true, format the datetime as an ISO date
468
542
  // $return: The formatted datetime string
469
- function datetimeISOFormat([datetime = null, isDate = false]) {
470
- if (valueType(datetime) !== 'datetime') {
471
- return null;
472
- }
543
+ function datetimeISOFormat(args) {
544
+ const [datetime, isDate] = valueArgsValidate(datetimeISOFormatArgs, args);
473
545
 
474
- if (valueBoolean(isDate)) {
546
+ if (isDate) {
475
547
  const year = String(datetime.getFullYear()).padStart(4, '0');
476
548
  const month = String(datetime.getMonth() + 1).padStart(2, '0');
477
549
  const day = String(datetime.getDate()).padStart(2, '0');
@@ -481,62 +553,71 @@ function datetimeISOFormat([datetime = null, isDate = false]) {
481
553
  return valueString(datetime);
482
554
  }
483
555
 
556
+ const datetimeISOFormatArgs = valueArgsModel([
557
+ {'name': 'datetime', 'type': 'datetime'},
558
+ {'name': 'isDate', 'type': 'boolean', 'default': false}
559
+ ]);
560
+
484
561
 
485
562
  // $function: datetimeISOParse
486
563
  // $group: Datetime
487
564
  // $doc: Parse an ISO date/time string
488
565
  // $arg string: The ISO date/time string
489
566
  // $return: The datetime, or null if parsing fails
490
- function datetimeISOParse([string]) {
491
- if (valueType(string) !== 'string') {
492
- return null;
493
- }
494
-
567
+ function datetimeISOParse(args) {
568
+ const [string] = valueArgsValidate(datetimeISOParseArgs, args);
495
569
  return valueParseDatetime(string);
496
570
  }
497
571
 
572
+ const datetimeISOParseArgs = valueArgsModel([
573
+ {'name': 'string', 'type': 'string'}
574
+ ]);
575
+
498
576
 
499
577
  // $function: datetimeMillisecond
500
578
  // $group: Datetime
501
579
  // $doc: Get the millisecond of a datetime
502
580
  // $arg datetime: The datetime
503
581
  // $return: The millisecond
504
- function datetimeMillisecond([datetime = null]) {
505
- if (valueType(datetime) !== 'datetime') {
506
- return null;
507
- }
508
-
582
+ function datetimeMillisecond(args) {
583
+ const [datetime] = valueArgsValidate(datetimeMillisecondArgs, args);
509
584
  return datetime.getMilliseconds();
510
585
  }
511
586
 
587
+ const datetimeMillisecondArgs = valueArgsModel([
588
+ {'name': 'datetime', 'type': 'datetime'}
589
+ ]);
590
+
512
591
 
513
592
  // $function: datetimeMinute
514
593
  // $group: Datetime
515
594
  // $doc: Get the minute of a datetime
516
595
  // $arg datetime: The datetime
517
596
  // $return: The minute
518
- function datetimeMinute([datetime = null]) {
519
- if (valueType(datetime) !== 'datetime') {
520
- return null;
521
- }
522
-
597
+ function datetimeMinute(args) {
598
+ const [datetime] = valueArgsValidate(datetimeMinuteArgs, args);
523
599
  return datetime.getMinutes();
524
600
  }
525
601
 
602
+ const datetimeMinuteArgs = valueArgsModel([
603
+ {'name': 'datetime', 'type': 'datetime'}
604
+ ]);
605
+
526
606
 
527
607
  // $function: datetimeMonth
528
608
  // $group: Datetime
529
609
  // $doc: Get the month (1-12) of a datetime
530
610
  // $arg datetime: The datetime
531
611
  // $return: The month
532
- function datetimeMonth([datetime = null]) {
533
- if (valueType(datetime) !== 'datetime') {
534
- return null;
535
- }
536
-
612
+ function datetimeMonth(args) {
613
+ const [datetime] = valueArgsValidate(datetimeMonthArgs, args);
537
614
  return datetime.getMonth() + 1;
538
615
  }
539
616
 
617
+ const datetimeMonthArgs = valueArgsModel([
618
+ {'name': 'datetime', 'type': 'datetime'}
619
+ ]);
620
+
540
621
 
541
622
  // $function: datetimeNew
542
623
  // $group: Datetime
@@ -549,20 +630,21 @@ function datetimeMonth([datetime = null]) {
549
630
  // $arg second: Optional (default is 0). The second.
550
631
  // $arg millisecond: Optional (default is 0). The millisecond.
551
632
  // $return: The new datetime
552
- function datetimeNew([year, month, day, hour = 0, minute = 0, second = 0, millisecond = 0]) {
553
- if (valueType(year) !== 'number' || Math.floor(year) !== year || year < 100 ||
554
- valueType(month) !== 'number' || Math.floor(month) !== month ||
555
- valueType(day) !== 'number' || Math.floor(day) !== day || day < -10000 || day > 10000 ||
556
- valueType(hour) !== 'number' || Math.floor(hour) !== hour ||
557
- valueType(minute) !== 'number' || Math.floor(minute) !== minute ||
558
- valueType(second) !== 'number' || Math.floor(second) !== second ||
559
- valueType(millisecond) !== 'number' || Math.floor(millisecond) !== millisecond) {
560
- return null;
561
- }
562
-
633
+ function datetimeNew(args) {
634
+ const [year, month, day, hour, minute, second, millisecond] = valueArgsValidate(datetimeNewArgs, args);
563
635
  return new Date(year, month - 1, day, hour, minute, second, millisecond);
564
636
  }
565
637
 
638
+ const datetimeNewArgs = valueArgsModel([
639
+ {'name': 'year', 'type': 'number', 'integer': true, 'gte': 100},
640
+ {'name': 'month', 'type': 'number', 'integer': true},
641
+ {'name': 'day', 'type': 'number', 'integer': true, 'gte': -10000, 'lte': 10000},
642
+ {'name': 'hour', 'type': 'number', 'default': 0, 'integer': true},
643
+ {'name': 'minute', 'type': 'number', 'default': 0, 'integer': true},
644
+ {'name': 'second', 'type': 'number', 'default': 0, 'integer': true},
645
+ {'name': 'millisecond', 'type': 'number', 'default': 0, 'integer': true}
646
+ ]);
647
+
566
648
 
567
649
  // $function: datetimeNow
568
650
  // $group: Datetime
@@ -578,14 +660,15 @@ function datetimeNow() {
578
660
  // $doc: Get the second of a datetime
579
661
  // $arg datetime: The datetime
580
662
  // $return: The second
581
- function datetimeSecond([datetime = null]) {
582
- if (valueType(datetime) !== 'datetime') {
583
- return null;
584
- }
585
-
663
+ function datetimeSecond(args) {
664
+ const [datetime] = valueArgsValidate(datetimeSecondArgs, args);
586
665
  return datetime.getSeconds();
587
666
  }
588
667
 
668
+ const datetimeSecondArgs = valueArgsModel([
669
+ {'name': 'datetime', 'type': 'datetime'}
670
+ ]);
671
+
589
672
 
590
673
  // $function: datetimeToday
591
674
  // $group: Datetime
@@ -602,14 +685,15 @@ function datetimeToday() {
602
685
  // $doc: Get the full year of a datetime
603
686
  // $arg datetime: The datetime
604
687
  // $return: The full year
605
- function datetimeYear([datetime = null]) {
606
- if (valueType(datetime) !== 'datetime') {
607
- return null;
608
- }
609
-
688
+ function datetimeYear(args) {
689
+ const [datetime] = valueArgsValidate(datetimeYearArgs, args);
610
690
  return datetime.getFullYear();
611
691
  }
612
692
 
693
+ const datetimeYearArgs = valueArgsModel([
694
+ {'name': 'datetime', 'type': 'datetime'}
695
+ ]);
696
+
613
697
 
614
698
  //
615
699
  // JSON functions
@@ -621,14 +705,15 @@ function datetimeYear([datetime = null]) {
621
705
  // $doc: Convert a JSON string to an object
622
706
  // $arg string: The JSON string
623
707
  // $return: The object
624
- function jsonParse([string = null]) {
625
- if (valueType(string) !== 'string') {
626
- return null;
627
- }
628
-
708
+ function jsonParse(args) {
709
+ const [string] = valueArgsValidate(jsonParseArgs, args);
629
710
  return JSON.parse(string);
630
711
  }
631
712
 
713
+ const jsonParseArgs = valueArgsModel([
714
+ {'name': 'string', 'type': 'string'}
715
+ ]);
716
+
632
717
 
633
718
  // $function: jsonStringify
634
719
  // $group: JSON
@@ -636,74 +721,81 @@ function jsonParse([string = null]) {
636
721
  // $arg value: The object
637
722
  // $arg indent: Optional (default is null). The indentation number.
638
723
  // $return: The JSON string
639
- function jsonStringify([value = null, indent = null]) {
640
- if (indent !== null && (valueType(indent) !== 'number' || Math.floor(indent) !== indent || indent < 1)) {
641
- return null;
642
- }
643
-
724
+ function jsonStringify(args) {
725
+ const [value, indent] = valueArgsValidate(jsonStringifyArgs, args);
644
726
  return valueJSON(value, indent);
645
727
  }
646
728
 
729
+ const jsonStringifyArgs = valueArgsModel([
730
+ {'name': 'value'},
731
+ {'name': 'indent', 'type': 'number', 'nullable': true, 'integer': true, 'gte': 1}
732
+ ]);
733
+
647
734
 
648
735
  //
649
736
  // Math functions
650
737
  //
651
738
 
739
+
652
740
  // $function: mathAbs
653
741
  // $group: Math
654
742
  // $doc: Compute the absolute value of a number
655
743
  // $arg x: The number
656
744
  // $return: The absolute value of the number
657
- function mathAbs([x = null]) {
658
- if (valueType(x) !== 'number') {
659
- return null;
660
- }
661
-
745
+ function mathAbs(args) {
746
+ const [x] = valueArgsValidate(mathAbsArgs, args);
662
747
  return Math.abs(x);
663
748
  }
664
749
 
750
+ const mathAbsArgs = valueArgsModel([
751
+ {'name': 'x', 'type': 'number'}
752
+ ]);
753
+
665
754
 
666
755
  // $function: mathAcos
667
756
  // $group: Math
668
757
  // $doc: Compute the arccosine, in radians, of a number
669
758
  // $arg x: The number
670
759
  // $return: The arccosine, in radians, of the number
671
- function mathAcos([x = null]) {
672
- if (valueType(x) !== 'number') {
673
- return null;
674
- }
675
-
760
+ function mathAcos(args) {
761
+ const [x] = valueArgsValidate(mathAcosArgs, args);
676
762
  return Math.acos(x);
677
763
  }
678
764
 
765
+ const mathAcosArgs = valueArgsModel([
766
+ {'name': 'x', 'type': 'number'}
767
+ ]);
768
+
679
769
 
680
770
  // $function: mathAsin
681
771
  // $group: Math
682
772
  // $doc: Compute the arcsine, in radians, of a number
683
773
  // $arg x: The number
684
774
  // $return: The arcsine, in radians, of the number
685
- function mathAsin([x = null]) {
686
- if (valueType(x) !== 'number') {
687
- return null;
688
- }
689
-
775
+ function mathAsin(args) {
776
+ const [x] = valueArgsValidate(mathAsinArgs, args);
690
777
  return Math.asin(x);
691
778
  }
692
779
 
780
+ const mathAsinArgs = valueArgsModel([
781
+ {'name': 'x', 'type': 'number'}
782
+ ]);
783
+
693
784
 
694
785
  // $function: mathAtan
695
786
  // $group: Math
696
787
  // $doc: Compute the arctangent, in radians, of a number
697
788
  // $arg x: The number
698
789
  // $return: The arctangent, in radians, of the number
699
- function mathAtan([x = null]) {
700
- if (valueType(x) !== 'number') {
701
- return null;
702
- }
703
-
790
+ function mathAtan(args) {
791
+ const [x] = valueArgsValidate(mathAtanArgs, args);
704
792
  return Math.atan(x);
705
793
  }
706
794
 
795
+ const mathAtanArgs = valueArgsModel([
796
+ {'name': 'x', 'type': 'number'}
797
+ ]);
798
+
707
799
 
708
800
  // $function: mathAtan2
709
801
  // $group: Math
@@ -711,70 +803,76 @@ function mathAtan([x = null]) {
711
803
  // $arg y: The Y-coordinate of the point
712
804
  // $arg x: The X-coordinate of the point
713
805
  // $return: The angle, in radians
714
- function mathAtan2([y = null, x = null]) {
715
- if (valueType(y) !== 'number' || valueType(x) !== 'number') {
716
- return null;
717
- }
718
-
806
+ function mathAtan2(args) {
807
+ const [y, x] = valueArgsValidate(mathAtan2Args, args);
719
808
  return Math.atan2(y, x);
720
809
  }
721
810
 
811
+ const mathAtan2Args = valueArgsModel([
812
+ {'name': 'y', 'type': 'number'},
813
+ {'name': 'x', 'type': 'number'}
814
+ ]);
815
+
722
816
 
723
817
  // $function: mathCeil
724
818
  // $group: Math
725
819
  // $doc: Compute the ceiling of a number (round up to the next highest integer)
726
820
  // $arg x: The number
727
821
  // $return: The ceiling of the number
728
- function mathCeil([x = null]) {
729
- if (valueType(x) !== 'number') {
730
- return null;
731
- }
732
-
822
+ function mathCeil(args) {
823
+ const [x] = valueArgsValidate(mathCeilArgs, args);
733
824
  return Math.ceil(x);
734
825
  }
735
826
 
827
+ const mathCeilArgs = valueArgsModel([
828
+ {'name': 'x', 'type': 'number'}
829
+ ]);
830
+
736
831
 
737
832
  // $function: mathCos
738
833
  // $group: Math
739
834
  // $doc: Compute the cosine of an angle, in radians
740
835
  // $arg x: The angle, in radians
741
836
  // $return: The cosine of the angle
742
- function mathCos([x = null]) {
743
- if (valueType(x) !== 'number') {
744
- return null;
745
- }
746
-
837
+ function mathCos(args) {
838
+ const [x] = valueArgsValidate(mathCosArgs, args);
747
839
  return Math.cos(x);
748
840
  }
749
841
 
842
+ const mathCosArgs = valueArgsModel([
843
+ {'name': 'x', 'type': 'number'}
844
+ ]);
845
+
750
846
 
751
847
  // $function: mathFloor
752
848
  // $group: Math
753
849
  // $doc: Compute the floor of a number (round down to the next lowest integer)
754
850
  // $arg x: The number
755
851
  // $return: The floor of the number
756
- function mathFloor([x = null]) {
757
- if (valueType(x) !== 'number') {
758
- return null;
759
- }
760
-
852
+ function mathFloor(args) {
853
+ const [x] = valueArgsValidate(mathFloorArgs, args);
761
854
  return Math.floor(x);
762
855
  }
763
856
 
857
+ const mathFloorArgs = valueArgsModel([
858
+ {'name': 'x', 'type': 'number'}
859
+ ]);
860
+
764
861
 
765
862
  // $function: mathLn
766
863
  // $group: Math
767
864
  // $doc: Compute the natural logarithm (base e) of a number
768
865
  // $arg x: The number
769
866
  // $return: The natural logarithm of the number
770
- function mathLn([x = null]) {
771
- if (valueType(x) !== 'number' || x <= 0) {
772
- return null;
773
- }
774
-
867
+ function mathLn(args) {
868
+ const [x] = valueArgsValidate(mathLnArgs, args);
775
869
  return Math.log(x);
776
870
  }
777
871
 
872
+ const mathLnArgs = valueArgsModel([
873
+ {'name': 'x', 'type': 'number', 'gt': 0}
874
+ ]);
875
+
778
876
 
779
877
  // $function: mathLog
780
878
  // $group: Math
@@ -782,14 +880,20 @@ function mathLn([x = null]) {
782
880
  // $arg x: The number
783
881
  // $arg base: Optional (default is 10). The logarithm base.
784
882
  // $return: The logarithm of the number
785
- function mathLog([x = null, base = 10]) {
786
- if (valueType(x) !== 'number' || x <= 0 || valueType(base) !== 'number' || base <= 0 || base === 1) {
787
- return null;
883
+ function mathLog(args) {
884
+ const [x, base] = valueArgsValidate(mathLogArgs, args);
885
+ if (base === 1) {
886
+ throw new ValueArgsError('base', base);
788
887
  }
789
888
 
790
889
  return Math.log(x) / Math.log(base);
791
890
  }
792
891
 
892
+ const mathLogArgs = valueArgsModel([
893
+ {'name': 'x', 'type': 'number', 'gt': 0},
894
+ {'name': 'base', 'type': 'number', 'default': 10, 'gt': 0}
895
+ ]);
896
+
793
897
 
794
898
  // $function: mathMax
795
899
  // $group: Math
@@ -797,11 +901,13 @@ function mathLog([x = null, base = 10]) {
797
901
  // $arg values...: The values
798
902
  // $return: The maximum value
799
903
  function mathMax(values) {
800
- if (values.some((value) => valueType(value) !== 'number')) {
801
- return null;
904
+ let result;
905
+ for (const value of values) {
906
+ if (typeof result === 'undefined' || valueCompare(value, result) > 0) {
907
+ result = value;
908
+ }
802
909
  }
803
-
804
- return Math.max(...values);
910
+ return result ?? null;
805
911
  }
806
912
 
807
913
 
@@ -811,11 +917,13 @@ function mathMax(values) {
811
917
  // $arg values...: The values
812
918
  // $return: The minimum value
813
919
  function mathMin(values) {
814
- if (values.some((value) => valueType(value) !== 'number')) {
815
- return null;
920
+ let result;
921
+ for (const value of values) {
922
+ if (typeof result === 'undefined' || valueCompare(value, result) < 0) {
923
+ result = value;
924
+ }
816
925
  }
817
-
818
- return Math.min(...values);
926
+ return result ?? null;
819
927
  }
820
928
 
821
929
 
@@ -843,71 +951,76 @@ function mathRandom() {
843
951
  // $arg x: The number
844
952
  // $arg digits: Optional (default is 0). The number of decimal digits to round to.
845
953
  // $return: The rounded number
846
- function mathRound([x = null, digits = 0]) {
847
- if (valueType(x) !== 'number' || valueType(digits) !== 'number' || Math.floor(digits) !== digits || digits < 0) {
848
- return null;
849
- }
850
-
851
- const multiplier = 10 ** digits;
852
- return Math.round(x * multiplier) / multiplier;
954
+ function mathRound(args) {
955
+ const [x, digits] = valueArgsValidate(mathRoundArgs, args);
956
+ return valueRoundNumber(x, digits);
853
957
  }
854
958
 
959
+ const mathRoundArgs = valueArgsModel([
960
+ {'name': 'x', 'type': 'number'},
961
+ {'name': 'digits', 'type': 'number', 'default': 0, 'integer': true, 'gte': 0}
962
+ ]);
963
+
855
964
 
856
965
  // $function: mathSign
857
966
  // $group: Math
858
967
  // $doc: Compute the sign of a number
859
968
  // $arg x: The number
860
969
  // $return: -1 for a negative number, 1 for a positive number, and 0 for zero
861
- function mathSign([x = null]) {
862
- if (valueType(x) !== 'number') {
863
- return null;
864
- }
865
-
970
+ function mathSign(args) {
971
+ const [x] = valueArgsValidate(mathSignArgs, args);
866
972
  return Math.sign(x);
867
973
  }
868
974
 
975
+ const mathSignArgs = valueArgsModel([
976
+ {'name': 'x', 'type': 'number'}
977
+ ]);
978
+
869
979
 
870
980
  // $function: mathSin
871
981
  // $group: Math
872
982
  // $doc: Compute the sine of an angle, in radians
873
983
  // $arg x: The angle, in radians
874
984
  // $return: The sine of the angle
875
- function mathSin([x = null]) {
876
- if (valueType(x) !== 'number') {
877
- return null;
878
- }
879
-
985
+ function mathSin(args) {
986
+ const [x] = valueArgsValidate(mathSinArgs, args);
880
987
  return Math.sin(x);
881
988
  }
882
989
 
990
+ const mathSinArgs = valueArgsModel([
991
+ {'name': 'x', 'type': 'number'}
992
+ ]);
993
+
883
994
 
884
995
  // $function: mathSqrt
885
996
  // $group: Math
886
997
  // $doc: Compute the square root of a number
887
998
  // $arg x: The number
888
999
  // $return: The square root of the number
889
- function mathSqrt([x = null]) {
890
- if (valueType(x) !== 'number' || x < 0) {
891
- return null;
892
- }
893
-
1000
+ function mathSqrt(args) {
1001
+ const [x] = valueArgsValidate(mathSqrtArgs, args);
894
1002
  return Math.sqrt(x);
895
1003
  }
896
1004
 
1005
+ const mathSqrtArgs = valueArgsModel([
1006
+ {'name': 'x', 'type': 'number', 'gte': 0}
1007
+ ]);
1008
+
897
1009
 
898
1010
  // $function: mathTan
899
1011
  // $group: Math
900
1012
  // $doc: Compute the tangent of an angle, in radians
901
1013
  // $arg x: The angle, in radians
902
1014
  // $return: The tangent of the angle
903
- function mathTan([x = null]) {
904
- if (valueType(x) !== 'number') {
905
- return null;
906
- }
907
-
1015
+ function mathTan(args) {
1016
+ const [x] = valueArgsValidate(mathTanArgs, args);
908
1017
  return Math.tan(x);
909
1018
  }
910
1019
 
1020
+ const mathTanArgs = valueArgsModel([
1021
+ {'name': 'x', 'type': 'number'}
1022
+ ]);
1023
+
911
1024
 
912
1025
  //
913
1026
  // Number functions
@@ -919,14 +1032,15 @@ function mathTan([x = null]) {
919
1032
  // $doc: Parse a string as a floating point number
920
1033
  // $arg string: The string
921
1034
  // $return: The number
922
- function numberParseFloat([string = null]) {
923
- if (valueType(string) !== 'string') {
924
- return null;
925
- }
926
-
1035
+ function numberParseFloat(args) {
1036
+ const [string] = valueArgsValidate(numberParseFloatArgs, args);
927
1037
  return valueParseNumber(string);
928
1038
  }
929
1039
 
1040
+ const numberParseFloatArgs = valueArgsModel([
1041
+ {'name': 'string', 'type': 'string'}
1042
+ ]);
1043
+
930
1044
 
931
1045
  // $function: numberParseInt
932
1046
  // $group: Number
@@ -934,14 +1048,16 @@ function numberParseFloat([string = null]) {
934
1048
  // $arg string: The string
935
1049
  // $arg radix: Optional (default is 10). The number base.
936
1050
  // $return: The integer
937
- function numberParseInt([string = null, radix = 10]) {
938
- if (valueType(string) !== 'string' || valueType(radix) !== 'number' || Math.floor(radix) !== radix || radix < 2 || radix > 36) {
939
- return null;
940
- }
941
-
1051
+ function numberParseInt(args) {
1052
+ const [string, radix] = valueArgsValidate(numberParseIntArgs, args);
942
1053
  return valueParseInteger(string, radix);
943
1054
  }
944
1055
 
1056
+ const numberParseIntArgs = valueArgsModel([
1057
+ {'name': 'string', 'type': 'string'},
1058
+ {'name': 'radix', 'type': 'number', 'default': 10, 'integer': true, 'gte': 2, 'lte': 36}
1059
+ ]);
1060
+
945
1061
 
946
1062
  // $function: numberToFixed
947
1063
  // $group: Number
@@ -950,18 +1066,21 @@ function numberParseInt([string = null, radix = 10]) {
950
1066
  // $arg digits: Optional (default is 2). The number of digits to appear after the decimal point.
951
1067
  // $arg trim: Optional (default is false). If true, trim trailing zeroes and decimal point.
952
1068
  // $return: The fixed-point notation string
953
- function numberToFixed([x = null, digits = 2, trim = false]) {
954
- if (valueType(x) !== 'number' || valueType(digits) !== 'number' || Math.floor(digits) !== digits || digits < 0) {
955
- return null;
956
- }
957
-
1069
+ function numberToFixed(args) {
1070
+ const [x, digits, trim] = valueArgsValidate(numberToFixedArgs, args);
958
1071
  let result = x.toFixed(digits);
959
- if (valueBoolean(trim)) {
1072
+ if (trim) {
960
1073
  result = result.replace(rNumberCleanup, '');
961
1074
  }
962
1075
  return result;
963
1076
  }
964
1077
 
1078
+ const numberToFixedArgs = valueArgsModel([
1079
+ {'name': 'x', 'type': 'number'},
1080
+ {'name': 'digits', 'type': 'number', 'default': 2, 'integer': true, 'gte': 0},
1081
+ {'name': 'trim', 'type': 'boolean', 'default': false}
1082
+ ]);
1083
+
965
1084
  const rNumberCleanup = /\.0*$/;
966
1085
 
967
1086
 
@@ -976,44 +1095,48 @@ const rNumberCleanup = /\.0*$/;
976
1095
  // $arg object: The object to assign to
977
1096
  // $arg object2: The object to assign
978
1097
  // $return: The updated object
979
- function objectAssign([object = null, object2 = null]) {
980
- if (valueType(object) !== 'object' || valueType(object2) !== 'object') {
981
- return null;
982
- }
983
-
1098
+ function objectAssign(args) {
1099
+ const [object, object2] = valueArgsValidate(objectAssignArgs, args);
984
1100
  Object.assign(object, object2);
985
1101
  return object;
986
1102
  }
987
1103
 
1104
+ const objectAssignArgs = valueArgsModel([
1105
+ {'name': 'object', 'type': 'object'},
1106
+ {'name': 'object2', 'type': 'object'}
1107
+ ]);
1108
+
988
1109
 
989
1110
  // $function: objectCopy
990
1111
  // $group: Object
991
1112
  // $doc: Create a copy of an object
992
1113
  // $arg object: The object to copy
993
1114
  // $return: The object copy
994
- function objectCopy([object = null]) {
995
- if (valueType(object) !== 'object') {
996
- return null;
997
- }
998
-
1115
+ function objectCopy(args) {
1116
+ const [object] = valueArgsValidate(objectCopyArgs, args);
999
1117
  return {...object};
1000
1118
  }
1001
1119
 
1120
+ const objectCopyArgs = valueArgsModel([
1121
+ {'name': 'object', 'type': 'object'}
1122
+ ]);
1123
+
1002
1124
 
1003
1125
  // $function: objectDelete
1004
1126
  // $group: Object
1005
1127
  // $doc: Delete an object key
1006
1128
  // $arg object: The object
1007
1129
  // $arg key: The key to delete
1008
- function objectDelete([object = null, key = null]) {
1009
- if (valueType(object) !== 'object' || valueType(key) !== 'string') {
1010
- return null;
1011
- }
1012
-
1130
+ function objectDelete(args) {
1131
+ const [object, key] = valueArgsValidate(objectDeleteArgs, args);
1013
1132
  delete object[key];
1014
- return null;
1015
1133
  }
1016
1134
 
1135
+ const objectDeleteArgs = valueArgsModel([
1136
+ {'name': 'object', 'type': 'object'},
1137
+ {'name': 'key', 'type': 'string'}
1138
+ ]);
1139
+
1017
1140
 
1018
1141
  // $function: objectGet
1019
1142
  // $group: Object
@@ -1022,14 +1145,18 @@ function objectDelete([object = null, key = null]) {
1022
1145
  // $arg key: The key
1023
1146
  // $arg defaultValue: The default value (optional)
1024
1147
  // $return: The value or null if the key does not exist
1025
- function objectGet([object = null, key = null, defaultValue = null]) {
1026
- if (valueType(object) !== 'object' || valueType(key) !== 'string') {
1027
- return defaultValue;
1028
- }
1029
-
1148
+ function objectGet(args) {
1149
+ const [,,defaultValueArg = null] = args;
1150
+ const [object, key, defaultValue] = valueArgsValidate(objectGetArgs, args, defaultValueArg);
1030
1151
  return object[key] ?? defaultValue;
1031
1152
  }
1032
1153
 
1154
+ const objectGetArgs = valueArgsModel([
1155
+ {'name': 'object', 'type': 'object'},
1156
+ {'name': 'key', 'type': 'string'},
1157
+ {'name': 'defaultValue'}
1158
+ ]);
1159
+
1033
1160
 
1034
1161
  // $function: objectHas
1035
1162
  // $group: Object
@@ -1037,41 +1164,44 @@ function objectGet([object = null, key = null, defaultValue = null]) {
1037
1164
  // $arg object: The object
1038
1165
  // $arg key: The key
1039
1166
  // $return: true if the object contains the key, false otherwise
1040
- function objectHas([object = null, key = null]) {
1041
- if (valueType(object) !== 'object' || valueType(key) !== 'string') {
1042
- return false;
1043
- }
1044
-
1167
+ function objectHas(args) {
1168
+ const [object, key] = valueArgsValidate(objectHasArgs, args, false);
1045
1169
  return key in object;
1046
1170
  }
1047
1171
 
1172
+ const objectHasArgs = valueArgsModel([
1173
+ {'name': 'object', 'type': 'object'},
1174
+ {'name': 'key', 'type': 'string'}
1175
+ ]);
1176
+
1048
1177
 
1049
1178
  // $function: objectKeys
1050
1179
  // $group: Object
1051
1180
  // $doc: Get an object's keys
1052
1181
  // $arg object: The object
1053
1182
  // $return: The array of keys
1054
- function objectKeys([object = null]) {
1055
- if (valueType(object) !== 'object') {
1056
- return null;
1057
- }
1058
-
1183
+ function objectKeys(args) {
1184
+ const [object] = valueArgsValidate(objectKeysArgs, args);
1059
1185
  return Object.keys(object);
1060
1186
  }
1061
1187
 
1188
+ const objectKeysArgs = valueArgsModel([
1189
+ {'name': 'object', 'type': 'object'}
1190
+ ]);
1191
+
1062
1192
 
1063
1193
  // $function: objectNew
1064
1194
  // $group: Object
1065
1195
  // $doc: Create a new object
1066
1196
  // $arg keyValues...: The object's initial key and value pairs
1067
1197
  // $return: The new object
1068
- function objectNew(keyValues = null) {
1198
+ function objectNew(keyValues) {
1069
1199
  const object = {};
1070
1200
  for (let ix = 0; ix < keyValues.length; ix += 2) {
1071
1201
  const key = keyValues[ix];
1072
1202
  const value = ix + 1 < keyValues.length ? keyValues[ix + 1] : null;
1073
1203
  if (valueType(key) !== 'string') {
1074
- return null;
1204
+ throw new ValueArgsError('keyValues', key);
1075
1205
  }
1076
1206
  object[key] = value;
1077
1207
  }
@@ -1086,15 +1216,18 @@ function objectNew(keyValues = null) {
1086
1216
  // $arg key: The key
1087
1217
  // $arg value: The value to set
1088
1218
  // $return: The value to set
1089
- function objectSet([object = null, key = null, value = null]) {
1090
- if (valueType(object) !== 'object' || valueType(key) !== 'string') {
1091
- return null;
1092
- }
1093
-
1219
+ function objectSet(args) {
1220
+ const [object, key, value] = valueArgsValidate(objectSetArgs, args);
1094
1221
  object[key] = value;
1095
1222
  return value;
1096
1223
  }
1097
1224
 
1225
+ const objectSetArgs = valueArgsModel([
1226
+ {'name': 'object', 'type': 'object'},
1227
+ {'name': 'key', 'type': 'string'},
1228
+ {'name': 'value'}
1229
+ ]);
1230
+
1098
1231
 
1099
1232
  //
1100
1233
  // Regex functions
@@ -1106,14 +1239,15 @@ function objectSet([object = null, key = null, value = null]) {
1106
1239
  // $doc: Escape a string for use in a regular expression
1107
1240
  // $arg string: The string to escape
1108
1241
  // $return: The escaped string
1109
- function regexEscape([string = null]) {
1110
- if (valueType(string) !== 'string') {
1111
- return null;
1112
- }
1113
-
1242
+ function regexEscape(args) {
1243
+ const [string] = valueArgsValidate(regexEscapeArgs, args);
1114
1244
  return string.replace(rRegexEscape, '\\$&');
1115
1245
  }
1116
1246
 
1247
+ const regexEscapeArgs = valueArgsModel([
1248
+ {'name': 'string', 'type': 'string'}
1249
+ ]);
1250
+
1117
1251
  const rRegexEscape = /[.*+?^${}()|[\]\\]/g;
1118
1252
 
1119
1253
 
@@ -1123,20 +1257,17 @@ const rRegexEscape = /[.*+?^${}()|[\]\\]/g;
1123
1257
  // $arg regex: The regular expression
1124
1258
  // $arg string: The string
1125
1259
  // $return: The [match object](model.html#var.vName='RegexMatch'), or null if no matches are found
1126
- function regexMatch([regex = null, string = null]) {
1127
- if (valueType(regex) !== 'regex' || valueType(string) !== 'string') {
1128
- return null;
1129
- }
1130
-
1131
- // Match?
1260
+ function regexMatch(args) {
1261
+ const [regex, string] = valueArgsValidate(regexMatchArgs, args);
1132
1262
  const match = string.match(regex);
1133
- if (match === null) {
1134
- return null;
1135
- }
1136
-
1137
- return regexMatchGroups(match);
1263
+ return match !== null ? regexMatchGroups(match) : null;
1138
1264
  }
1139
1265
 
1266
+ const regexMatchArgs = valueArgsModel([
1267
+ {'name': 'regex', 'type': 'regex'},
1268
+ {'name': 'string', 'type': 'string'}
1269
+ ]);
1270
+
1140
1271
 
1141
1272
  // $function: regexMatchAll
1142
1273
  // $group: Regex
@@ -1144,10 +1275,8 @@ function regexMatch([regex = null, string = null]) {
1144
1275
  // $arg regex: The regular expression
1145
1276
  // $arg string: The string
1146
1277
  // $return: The array of [match objects](model.html#var.vName='RegexMatch')
1147
- function regexMatchAll([regex = null, string = null]) {
1148
- if (valueType(regex) !== 'regex' || valueType(string) !== 'string') {
1149
- return null;
1150
- }
1278
+ function regexMatchAll(args) {
1279
+ const [regex, string] = valueArgsValidate(regexMatchAllArgs, args);
1151
1280
 
1152
1281
  // Re-compile the regex with the "g" flag, if necessary
1153
1282
  const regexGlobal = (regex.flags.indexOf('g') !== -1 ? regex : new RegExp(regex.source, `${regex.flags}g`));
@@ -1155,6 +1284,11 @@ function regexMatchAll([regex = null, string = null]) {
1155
1284
  return Array.from(string.matchAll(regexGlobal)).map((match) => regexMatchGroups(match));
1156
1285
  }
1157
1286
 
1287
+ const regexMatchAllArgs = valueArgsModel([
1288
+ {'name': 'regex', 'type': 'regex'},
1289
+ {'name': 'string', 'type': 'string'}
1290
+ ]);
1291
+
1158
1292
 
1159
1293
  // Helper function to create a match model from a metch object
1160
1294
  function regexMatchGroups(match) {
@@ -1204,10 +1338,8 @@ struct RegexMatch
1204
1338
  // $arg flags: - **m** - multi-line search - "^" and "$" matches next to newline characters
1205
1339
  // $arg flags: - **s** - "." matches newline characters
1206
1340
  // $return: The regular expression or null if the pattern is invalid
1207
- function regexNew([pattern = null, flags = null]) {
1208
- if (valueType(pattern) !== 'string' || (flags !== null && valueType(flags) !== 'string')) {
1209
- return null;
1210
- }
1341
+ function regexNew(args) {
1342
+ const [pattern, flags] = valueArgsValidate(regexNewArgs, args);
1211
1343
 
1212
1344
  // Valid flags mask?
1213
1345
  if (flags !== null) {
@@ -1221,6 +1353,11 @@ function regexNew([pattern = null, flags = null]) {
1221
1353
  return flags !== null ? new RegExp(pattern, flags) : new RegExp(pattern);
1222
1354
  }
1223
1355
 
1356
+ const regexNewArgs = valueArgsModel([
1357
+ {'name': 'pattern', 'type': 'string'},
1358
+ {'name': 'flags', 'type': 'string', 'nullable': true}
1359
+ ]);
1360
+
1224
1361
 
1225
1362
  // $function: regexReplace
1226
1363
  // $group: Regex
@@ -1229,10 +1366,8 @@ function regexNew([pattern = null, flags = null]) {
1229
1366
  // $arg string: The string
1230
1367
  // $arg substr: The replacement string
1231
1368
  // $return: The updated string
1232
- function regexReplace([regex = null, string = null, substr = null]) {
1233
- if (valueType(regex) !== 'regex' || valueType(string) !== 'string' || valueType(substr) !== 'string') {
1234
- return null;
1235
- }
1369
+ function regexReplace(args) {
1370
+ const [regex, string, substr] = valueArgsValidate(regexReplaceArgs, args);
1236
1371
 
1237
1372
  // Re-compile the regex with the "g" flag, if necessary
1238
1373
  const regexGlobal = (regex.flags.indexOf('g') !== -1 ? regex : new RegExp(regex.source, `${regex.flags}g`));
@@ -1240,6 +1375,12 @@ function regexReplace([regex = null, string = null, substr = null]) {
1240
1375
  return string.replaceAll(regexGlobal, substr);
1241
1376
  }
1242
1377
 
1378
+ const regexReplaceArgs = valueArgsModel([
1379
+ {'name': 'regex', 'type': 'regex'},
1380
+ {'name': 'string', 'type': 'string'},
1381
+ {'name': 'substr', 'type': 'string'}
1382
+ ]);
1383
+
1243
1384
 
1244
1385
  // $function: regexSplit
1245
1386
  // $group: Regex
@@ -1247,14 +1388,16 @@ function regexReplace([regex = null, string = null, substr = null]) {
1247
1388
  // $arg regex: The regular expression
1248
1389
  // $arg string: The string
1249
1390
  // $return: The array of split parts
1250
- function regexSplit([regex = null, string = null]) {
1251
- if (valueType(regex) !== 'regex' || valueType(string) !== 'string') {
1252
- return null;
1253
- }
1254
-
1391
+ function regexSplit(args) {
1392
+ const [regex, string] = valueArgsValidate(regexSplitArgs, args);
1255
1393
  return string.split(regex);
1256
1394
  }
1257
1395
 
1396
+ const regexSplitArgs = valueArgsModel([
1397
+ {'name': 'regex', 'type': 'regex'},
1398
+ {'name': 'string', 'type': 'string'}
1399
+ ]);
1400
+
1258
1401
 
1259
1402
  //
1260
1403
  // Schema functions
@@ -1280,15 +1423,23 @@ function schemaParse(lines) {
1280
1423
  // $arg types: Optional. The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types').
1281
1424
  // $arg filename: Optional (default is ""). The file name.
1282
1425
  // $return: The schema's [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
1283
- function schemaParseEx([lines = null, types = {}, filename = '']) {
1284
- if (!(valueType(lines) === 'array' || valueType(lines) === 'string') ||
1285
- valueType(types) !== 'object' || valueType(filename) !== 'string') {
1286
- return null;
1426
+ function schemaParseEx(args) {
1427
+ const [lines, typesArg, filename] = valueArgsValidate(schemaParseExArgs, args);
1428
+ const linesType = valueType(lines);
1429
+ const types = typesArg !== null ? typesArg : {};
1430
+ if (linesType !== 'array' && linesType !== 'string') {
1431
+ throw new ValueArgsError('lines', lines);
1287
1432
  }
1288
1433
 
1289
1434
  return parseSchemaMarkdown(lines, {types, filename});
1290
1435
  }
1291
1436
 
1437
+ const schemaParseExArgs = valueArgsModel([
1438
+ {'name': 'lines'},
1439
+ {'name': 'types', 'type': 'object', 'nullable': true},
1440
+ {'name': 'filename', 'type': 'string', 'default': ''}
1441
+ ]);
1442
+
1292
1443
 
1293
1444
  // $function: schemaTypeModel
1294
1445
  // $group: Schema
@@ -1306,29 +1457,33 @@ function schemaTypeModel() {
1306
1457
  // $arg typeName: The type name
1307
1458
  // $arg value: The object to validate
1308
1459
  // $return: The validated object or null if validation fails
1309
- function schemaValidate([types = null, typeName = null, value = null]) {
1310
- if (valueType(types) !== 'object' || valueType(typeName) !== 'string') {
1311
- return null;
1312
- }
1313
-
1460
+ function schemaValidate(args) {
1461
+ const [types, typeName, value] = valueArgsValidate(schemaValidateArgs, args);
1314
1462
  validateTypeModel(types);
1315
1463
  return validateType(types, typeName, value);
1316
1464
  }
1317
1465
 
1466
+ const schemaValidateArgs = valueArgsModel([
1467
+ {'name': 'types', 'type': 'object'},
1468
+ {'name': 'typeName', 'type': 'string'},
1469
+ {'name': 'value'}
1470
+ ]);
1471
+
1318
1472
 
1319
1473
  // $function: schemaValidateTypeModel
1320
1474
  // $group: Schema
1321
1475
  // $doc: Validate a [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
1322
1476
  // $arg types: The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types') to validate
1323
1477
  // $return: The validated [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
1324
- function schemaValidateTypeModel([types = null]) {
1325
- if (valueType(types) !== 'object') {
1326
- return null;
1327
- }
1328
-
1478
+ function schemaValidateTypeModel(args) {
1479
+ const [types] = valueArgsValidate(schemaValidateTypeModelArgs, args);
1329
1480
  return validateTypeModel(types);
1330
1481
  }
1331
1482
 
1483
+ const schemaValidateTypeModelArgs = valueArgsModel([
1484
+ {'name': 'types', 'type': 'object'}
1485
+ ]);
1486
+
1332
1487
 
1333
1488
  //
1334
1489
  // String functions
@@ -1341,15 +1496,20 @@ function schemaValidateTypeModel([types = null]) {
1341
1496
  // $arg string: The string
1342
1497
  // $arg index: The character index
1343
1498
  // $return: The character code
1344
- function stringCharCodeAt([string = null, index = null]) {
1345
- if (valueType(string) !== 'string' ||
1346
- valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= string.length) {
1347
- return null;
1499
+ function stringCharCodeAt(args) {
1500
+ const [string, index] = valueArgsValidate(stringCharCodeAtArgs, args);
1501
+ if (index >= string.length) {
1502
+ throw new ValueArgsError('index', index);
1348
1503
  }
1349
1504
 
1350
1505
  return string.charCodeAt(index);
1351
1506
  }
1352
1507
 
1508
+ const stringCharCodeAtArgs = valueArgsModel([
1509
+ {'name': 'string', 'type': 'string'},
1510
+ {'name': 'index', 'type': 'number', 'integer': true, 'gte': 0}
1511
+ ]);
1512
+
1353
1513
 
1354
1514
  // $function: stringEndsWith
1355
1515
  // $group: String
@@ -1357,23 +1517,27 @@ function stringCharCodeAt([string = null, index = null]) {
1357
1517
  // $arg string: The string
1358
1518
  // $arg search: The search string
1359
1519
  // $return: true if the string ends with the search string, false otherwise
1360
- function stringEndsWith([string = null, search = null]) {
1361
- if (valueType(string) !== 'string' || valueType(search) !== 'string') {
1362
- return null;
1363
- }
1364
-
1520
+ function stringEndsWith(args) {
1521
+ const [string, search] = valueArgsValidate(stringEndsWithArgs, args);
1365
1522
  return string.endsWith(search);
1366
1523
  }
1367
1524
 
1525
+ const stringEndsWithArgs = valueArgsModel([
1526
+ {'name': 'string', 'type': 'string'},
1527
+ {'name': 'search', 'type': 'string'}
1528
+ ]);
1529
+
1368
1530
 
1369
1531
  // $function: stringFromCharCode
1370
1532
  // $group: String
1371
1533
  // $doc: Create a string of characters from character codes
1372
1534
  // $arg charCodes...: The character codes
1373
1535
  // $return: The string of characters
1374
- function stringFromCharCode(charCodes = null) {
1375
- if (charCodes.some((code) => valueType(code) !== 'number' || Math.floor(code) !== code || code < 0)) {
1376
- return null;
1536
+ function stringFromCharCode(charCodes) {
1537
+ for (const code of charCodes) {
1538
+ if (valueType(code) !== 'number' || Math.floor(code) !== code || code < 0) {
1539
+ throw new ValueArgsError('charCodes', code);
1540
+ }
1377
1541
  }
1378
1542
 
1379
1543
  return String.fromCharCode(...charCodes);
@@ -1387,15 +1551,21 @@ function stringFromCharCode(charCodes = null) {
1387
1551
  // $arg search: The search string
1388
1552
  // $arg index: Optional (default is 0). The index at which to start the search.
1389
1553
  // $return: The first index of the search string; -1 if not found.
1390
- function stringIndexOf([string = null, search = null, index = 0]) {
1391
- if (valueType(string) !== 'string' || valueType(search) !== 'string' ||
1392
- valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= string.length) {
1393
- return -1;
1554
+ function stringIndexOf(args) {
1555
+ const [string, search, index] = valueArgsValidate(stringIndexOfArgs, args, -1);
1556
+ if (index >= string.length) {
1557
+ throw new ValueArgsError('index', index, -1);
1394
1558
  }
1395
1559
 
1396
1560
  return string.indexOf(search, index);
1397
1561
  }
1398
1562
 
1563
+ const stringIndexOfArgs = valueArgsModel([
1564
+ {'name': 'string', 'type': 'string'},
1565
+ {'name': 'search', 'type': 'string'},
1566
+ {'name': 'index', 'type': 'number', 'default': 0, 'integer': true, 'gte': 0}
1567
+ ]);
1568
+
1399
1569
 
1400
1570
  // $function: stringLastIndexOf
1401
1571
  // $group: String
@@ -1404,47 +1574,52 @@ function stringIndexOf([string = null, search = null, index = 0]) {
1404
1574
  // $arg search: The search string
1405
1575
  // $arg index: Optional (default is the end of the string). The index at which to start the search.
1406
1576
  // $return: The last index of the search string; -1 if not found.
1407
- function stringLastIndexOf([string = null, search = null, indexArg = null]) {
1408
- let index = indexArg;
1409
- if (index === null && valueType(string) === 'string') {
1410
- index = string.length - 1;
1411
- }
1412
- if (valueType(string) !== 'string' || valueType(search) !== 'string' ||
1413
- valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= string.length) {
1414
- return -1;
1577
+ function stringLastIndexOf(args) {
1578
+ const [string, search, indexArg] = valueArgsValidate(stringLastIndexOfArgs, args, -1);
1579
+ const index = indexArg !== null ? indexArg : string.length - 1;
1580
+ if (index >= string.length) {
1581
+ throw new ValueArgsError('index', index, -1);
1415
1582
  }
1416
1583
 
1417
1584
  return string.lastIndexOf(search, index);
1418
1585
  }
1419
1586
 
1587
+ const stringLastIndexOfArgs = valueArgsModel([
1588
+ {'name': 'string', 'type': 'string'},
1589
+ {'name': 'search', 'type': 'string'},
1590
+ {'name': 'index', 'type': 'number', 'nullable': true, 'integer': true, 'gte': 0}
1591
+ ]);
1592
+
1420
1593
 
1421
1594
  // $function: stringLength
1422
1595
  // $group: String
1423
1596
  // $doc: Get the length of a string
1424
1597
  // $arg string: The string
1425
1598
  // $return: The string's length; zero if not a string
1426
- function stringLength([string = null]) {
1427
- if (valueType(string) !== 'string') {
1428
- return 0;
1429
- }
1430
-
1599
+ function stringLength(args) {
1600
+ const [string] = valueArgsValidate(stringLengthArgs, args, 0);
1431
1601
  return string.length;
1432
1602
  }
1433
1603
 
1604
+ const stringLengthArgs = valueArgsModel([
1605
+ {'name': 'string', 'type': 'string'}
1606
+ ]);
1607
+
1434
1608
 
1435
1609
  // $function: stringLower
1436
1610
  // $group: String
1437
1611
  // $doc: Convert a string to lower-case
1438
1612
  // $arg string: The string
1439
1613
  // $return: The lower-case string
1440
- function stringLower([string = null]) {
1441
- if (valueType(string) !== 'string') {
1442
- return null;
1443
- }
1444
-
1614
+ function stringLower(args) {
1615
+ const [string] = valueArgsValidate(stringLowerArgs, args);
1445
1616
  return string.toLowerCase();
1446
1617
  }
1447
1618
 
1619
+ const stringLowerArgs = valueArgsModel([
1620
+ {'name': 'string', 'type': 'string'}
1621
+ ]);
1622
+
1448
1623
 
1449
1624
  // $function: stringNew
1450
1625
  // $group: String
@@ -1462,14 +1637,16 @@ function stringNew([value = null]) {
1462
1637
  // $arg string: The string to repeat
1463
1638
  // $arg count: The number of times to repeat the string
1464
1639
  // $return: The repeated string
1465
- function stringRepeat([string = null, count = null]) {
1466
- if (valueType(string) !== 'string' || valueType(count) !== 'number' || Math.floor(count) !== count || count < 0) {
1467
- return null;
1468
- }
1469
-
1640
+ function stringRepeat(args) {
1641
+ const [string, count] = valueArgsValidate(stringRepeatArgs, args);
1470
1642
  return string.repeat(count);
1471
1643
  }
1472
1644
 
1645
+ const stringRepeatArgs = valueArgsModel([
1646
+ {'name': 'string', 'type': 'string'},
1647
+ {'name': 'count', 'type': 'number', 'integer': true, 'gte': 0}
1648
+ ]);
1649
+
1473
1650
 
1474
1651
  // $function: stringReplace
1475
1652
  // $group: String
@@ -1478,14 +1655,17 @@ function stringRepeat([string = null, count = null]) {
1478
1655
  // $arg substr: The string to replace
1479
1656
  // $arg newSubstr: The replacement string
1480
1657
  // $return: The updated string
1481
- function stringReplace([string = null, substr = null, newSubstr = null]) {
1482
- if (valueType(string) !== 'string' || valueType(substr) !== 'string' || valueType(newSubstr) !== 'string') {
1483
- return null;
1484
- }
1485
-
1658
+ function stringReplace(args) {
1659
+ const [string, substr, newSubstr] = valueArgsValidate(stringReplaceArgs, args);
1486
1660
  return string.replaceAll(substr, newSubstr);
1487
1661
  }
1488
1662
 
1663
+ const stringReplaceArgs = valueArgsModel([
1664
+ {'name': 'string', 'type': 'string'},
1665
+ {'name': 'substr', 'type': 'string'},
1666
+ {'name': 'newSubstr', 'type': 'string'}
1667
+ ]);
1668
+
1489
1669
 
1490
1670
  // $function: stringSlice
1491
1671
  // $group: String
@@ -1494,20 +1674,25 @@ function stringReplace([string = null, substr = null, newSubstr = null]) {
1494
1674
  // $arg start: The start index of the slice
1495
1675
  // $arg end: Optional (default is the end of the string). The end index of the slice.
1496
1676
  // $return: The new string slice
1497
- function stringSlice([string = null, begin = null, endArg = null]) {
1498
- let end = endArg;
1499
- if (end === null && valueType(string) === 'string') {
1500
- end = string.length;
1677
+ function stringSlice(args) {
1678
+ const [string, start, endArg] = valueArgsValidate(stringSliceArgs, args);
1679
+ const end = endArg !== null ? endArg : string.length;
1680
+ if (start > string.length) {
1681
+ throw new ValueArgsError('start', start);
1501
1682
  }
1502
- if (valueType(string) !== 'string' ||
1503
- valueType(begin) !== 'number' || Math.floor(begin) !== begin || begin < 0 || begin > string.length ||
1504
- valueType(end) !== 'number' || Math.floor(end) !== end || end < 0 || end > string.length) {
1505
- return null;
1683
+ if (end > string.length) {
1684
+ throw new ValueArgsError('end', end);
1506
1685
  }
1507
1686
 
1508
- return string.slice(begin, end);
1687
+ return string.slice(start, end);
1509
1688
  }
1510
1689
 
1690
+ const stringSliceArgs = valueArgsModel([
1691
+ {'name': 'string', 'type': 'string'},
1692
+ {'name': 'start', 'type': 'number', 'integer': true, 'gte': 0},
1693
+ {'name': 'end', 'type': 'number', 'nullable': true, 'integer': true, 'gte': 0}
1694
+ ]);
1695
+
1511
1696
 
1512
1697
  // $function: stringSplit
1513
1698
  // $group: String
@@ -1515,14 +1700,16 @@ function stringSlice([string = null, begin = null, endArg = null]) {
1515
1700
  // $arg string: The string to split
1516
1701
  // $arg separator: The separator string
1517
1702
  // $return: The array of split-out strings
1518
- function stringSplit([string = null, separator = null]) {
1519
- if (valueType(string) !== 'string' || valueType(separator) !== 'string') {
1520
- return null;
1521
- }
1522
-
1703
+ function stringSplit(args) {
1704
+ const [string, separator] = valueArgsValidate(stringSplitArgs, args);
1523
1705
  return string.split(separator);
1524
1706
  }
1525
1707
 
1708
+ const stringSplitArgs = valueArgsModel([
1709
+ {'name': 'string', 'type': 'string'},
1710
+ {'name': 'separator', 'type': 'string'}
1711
+ ]);
1712
+
1526
1713
 
1527
1714
  // $function: stringStartsWith
1528
1715
  // $group: String
@@ -1530,42 +1717,46 @@ function stringSplit([string = null, separator = null]) {
1530
1717
  // $arg string: The string
1531
1718
  // $arg search: The search string
1532
1719
  // $return: true if the string starts with the search string, false otherwise
1533
- function stringStartsWith([string = null, search = null]) {
1534
- if (valueType(string) !== 'string' || valueType(search) !== 'string') {
1535
- return null;
1536
- }
1537
-
1720
+ function stringStartsWith(args) {
1721
+ const [string, search] = valueArgsValidate(stringStartsWithArgs, args);
1538
1722
  return string.startsWith(search);
1539
1723
  }
1540
1724
 
1725
+ const stringStartsWithArgs = valueArgsModel([
1726
+ {'name': 'string', 'type': 'string'},
1727
+ {'name': 'search', 'type': 'string'}
1728
+ ]);
1729
+
1541
1730
 
1542
1731
  // $function: stringTrim
1543
1732
  // $group: String
1544
1733
  // $doc: Trim the whitespace from the beginning and end of a string
1545
1734
  // $arg string: The string
1546
1735
  // $return: The trimmed string
1547
- function stringTrim([string = null]) {
1548
- if (valueType(string) !== 'string') {
1549
- return null;
1550
- }
1551
-
1736
+ function stringTrim(args) {
1737
+ const [string] = valueArgsValidate(stringTrimArgs, args);
1552
1738
  return string.trim();
1553
1739
  }
1554
1740
 
1741
+ const stringTrimArgs = valueArgsModel([
1742
+ {'name': 'string', 'type': 'string'}
1743
+ ]);
1744
+
1555
1745
 
1556
1746
  // $function: stringUpper
1557
1747
  // $group: String
1558
1748
  // $doc: Convert a string to upper-case
1559
1749
  // $arg string: The string
1560
1750
  // $return: The upper-case string
1561
- function stringUpper([string = null]) {
1562
- if (valueType(string) !== 'string') {
1563
- return null;
1564
- }
1565
-
1751
+ function stringUpper(args) {
1752
+ const [string] = valueArgsValidate(stringUpperArgs, args);
1566
1753
  return string.toUpperCase();
1567
1754
  }
1568
1755
 
1756
+ const stringUpperArgs = valueArgsModel([
1757
+ {'name': 'string', 'type': 'string'}
1758
+ ]);
1759
+
1569
1760
 
1570
1761
  //
1571
1762
  // System functions
@@ -1608,11 +1799,12 @@ async function systemFetch([url = null], options) {
1608
1799
  // Validate the URL argument
1609
1800
  const requests = [];
1610
1801
  let isResponseArray = false;
1611
- if (valueType(url) === 'string') {
1802
+ const urlType = valueType(url);
1803
+ if (urlType === 'string') {
1612
1804
  requests.push({'url': url});
1613
- } else if (valueType(url) === 'object') {
1805
+ } else if (urlType === 'object') {
1614
1806
  requests.push(validateType(systemFetchTypes, 'SystemFetchRequest', url));
1615
- } else if (valueType(url) === 'array') {
1807
+ } else if (urlType === 'array') {
1616
1808
  isResponseArray = true;
1617
1809
  for (const urlItem of url) {
1618
1810
  if (valueType(urlItem) === 'string') {
@@ -1622,7 +1814,7 @@ async function systemFetch([url = null], options) {
1622
1814
  }
1623
1815
  }
1624
1816
  } else {
1625
- return null;
1817
+ throw new ValueArgsError('url', url);
1626
1818
  }
1627
1819
 
1628
1820
  // Fetch in parallel
@@ -1687,15 +1879,17 @@ struct SystemFetchRequest
1687
1879
  // $arg name: The global variable name
1688
1880
  // $arg defaultValue: The default value (optional)
1689
1881
  // $return: The global variable's value or null if it does not exist
1690
- function systemGlobalGet([name = null, defaultValue = null], options) {
1691
- if (valueType(name) !== 'string') {
1692
- return defaultValue;
1693
- }
1694
-
1882
+ function systemGlobalGet(args, options) {
1883
+ const [name, defaultValue] = valueArgsValidate(systemGlobalGetArgs, args);
1695
1884
  const globals = (options !== null ? (options.globals ?? null) : null);
1696
1885
  return globals !== null ? (globals[name] ?? defaultValue) : defaultValue;
1697
1886
  }
1698
1887
 
1888
+ const systemGlobalGetArgs = valueArgsModel([
1889
+ {'name': 'name', 'type': 'string'},
1890
+ {'name': 'defaultValue'}
1891
+ ]);
1892
+
1699
1893
 
1700
1894
  // $function: systemGlobalSet
1701
1895
  // $group: System
@@ -1703,11 +1897,8 @@ function systemGlobalGet([name = null, defaultValue = null], options) {
1703
1897
  // $arg name: The global variable name
1704
1898
  // $arg value: The global variable's value
1705
1899
  // $return: The global variable's value
1706
- function systemGlobalSet([name = null, value = null], options) {
1707
- if (valueType(name) !== 'string') {
1708
- return null;
1709
- }
1710
-
1900
+ function systemGlobalSet(args, options) {
1901
+ const [name, value] = valueArgsValidate(systemGlobalSetArgs, args);
1711
1902
  const globals = (options !== null ? (options.globals ?? null) : null);
1712
1903
  if (globals !== null) {
1713
1904
  globals[name] = value;
@@ -1715,6 +1906,11 @@ function systemGlobalSet([name = null, value = null], options) {
1715
1906
  return value;
1716
1907
  }
1717
1908
 
1909
+ const systemGlobalSetArgs = valueArgsModel([
1910
+ {'name': 'name', 'type': 'string'},
1911
+ {'name': 'value'}
1912
+ ]);
1913
+
1718
1914
 
1719
1915
  // $function: systemIs
1720
1916
  // $group: System
@@ -1756,14 +1952,20 @@ function systemLogDebug([message = null], options) {
1756
1952
  // $arg func: The function
1757
1953
  // $arg args...: The function arguments
1758
1954
  // $return: The new function called with "args"
1759
- function systemPartial([func = null, ...args]) {
1760
- if (valueType(func) !== 'function' || args.length < 1) {
1761
- return null;
1955
+ function systemPartial(args) {
1956
+ const [func, funcArgs] = valueArgsValidate(systemPartialArgs, args);
1957
+ if (funcArgs.length < 1) {
1958
+ throw new ValueArgsError('args', funcArgs);
1762
1959
  }
1763
1960
 
1764
- return (argsExtra, options) => func([...args, ...argsExtra], options);
1961
+ return (argsExtra, options) => func([...funcArgs, ...argsExtra], options);
1765
1962
  }
1766
1963
 
1964
+ const systemPartialArgs = valueArgsModel([
1965
+ {'name': 'func', 'type': 'function'},
1966
+ {'name': 'args', 'lastArgArray': true}
1967
+ ]);
1968
+
1767
1969
 
1768
1970
  // $function: systemType
1769
1971
  // $group: System
@@ -1787,19 +1989,21 @@ function systemType([value = null]) {
1787
1989
  // $arg url: The URL string
1788
1990
  // $arg extra: Optional (default is true). If true, encode extra characters for wider compatibility.
1789
1991
  // $return: The encoded URL string
1790
- function urlEncode([url = null, extra = true]) {
1791
- if (valueType(url) !== 'string') {
1792
- return null;
1793
- }
1794
-
1992
+ function urlEncode(args) {
1993
+ const [url, extra] = valueArgsValidate(urlEncodeArgs, args);
1795
1994
  let urlEncoded = encodeURI(url);
1796
- if (valueBoolean(extra)) {
1995
+ if (extra) {
1797
1996
  // Replace ')' with '%29' for Markdown links
1798
1997
  urlEncoded = urlEncoded.replaceAll(')', '%29');
1799
1998
  }
1800
1999
  return urlEncoded;
1801
2000
  }
1802
2001
 
2002
+ const urlEncodeArgs = valueArgsModel([
2003
+ {'name': 'url', 'type': 'string'},
2004
+ {'name': 'extra', 'type': 'boolean', 'default': true}
2005
+ ]);
2006
+
1803
2007
 
1804
2008
  // $function: urlEncodeComponent
1805
2009
  // $group: URL
@@ -1807,19 +2011,21 @@ function urlEncode([url = null, extra = true]) {
1807
2011
  // $arg url: The URL component string
1808
2012
  // $arg extra: Optional (default is true). If true, encode extra characters for wider compatibility.
1809
2013
  // $return: The encoded URL component string
1810
- function urlEncodeComponent([url = null, extra = true]) {
1811
- if (valueType(url) !== 'string') {
1812
- return null;
1813
- }
1814
-
2014
+ function urlEncodeComponent(args) {
2015
+ const [url, extra] = valueArgsValidate(urlEncodeComponentArgs, args);
1815
2016
  let urlEncoded = encodeURIComponent(url);
1816
- if (valueBoolean(extra)) {
2017
+ if (extra) {
1817
2018
  // Replace ')' with '%29' for Markdown links
1818
2019
  urlEncoded = urlEncoded.replaceAll(')', '%29');
1819
2020
  }
1820
2021
  return urlEncoded;
1821
2022
  }
1822
2023
 
2024
+ const urlEncodeComponentArgs = valueArgsModel([
2025
+ {'name': 'url', 'type': 'string'},
2026
+ {'name': 'extra', 'type': 'boolean', 'default': true}
2027
+ ]);
2028
+
1823
2029
 
1824
2030
  // The built-in script functions
1825
2031
  export const scriptFunctions = {