bare-script 3.0.5 → 3.0.7

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/runtime.js CHANGED
@@ -3,8 +3,8 @@
3
3
 
4
4
  /** @module lib/runtime */
5
5
 
6
+ import {ValueArgsError, valueBoolean, valueCompare, valueString} from './value.js';
6
7
  import {defaultMaxStatements, expressionFunctions, scriptFunctions} from './library.js';
7
- import {valueBoolean, valueCompare, valueString} from './value.js';
8
8
 
9
9
 
10
10
  /**
@@ -211,6 +211,9 @@ export function evaluateExpression(expr, options = null, locals = null, builtins
211
211
  if (options !== null && 'logFn' in options && options.debug) {
212
212
  options.logFn(`BareScript: Function "${funcName}" failed with error: ${error.message}`);
213
213
  }
214
+ if (error instanceof ValueArgsError) {
215
+ return error.returnValue;
216
+ }
214
217
  return null;
215
218
  }
216
219
  }
@@ -5,8 +5,8 @@
5
5
 
6
6
  import {BareScriptParserError, parseScript} from './parser.js';
7
7
  import {BareScriptRuntimeError, evaluateExpression, scriptFunction} from './runtime.js';
8
+ import {ValueArgsError, valueBoolean, valueCompare, valueString} from './value.js';
8
9
  import {defaultMaxStatements, expressionFunctions, scriptFunctions} from './library.js';
9
- import {valueBoolean, valueCompare, valueString} from './value.js';
10
10
  import {lintScript} from './model.js';
11
11
  import {urlFileRelative} from './options.js';
12
12
 
@@ -281,6 +281,9 @@ export async function evaluateExpressionAsync(expr, options = null, locals = nul
281
281
  if (options !== null && 'logFn' in options && options.debug) {
282
282
  options.logFn(`BareScript: Function "${funcName}" failed with error: ${error.message}`);
283
283
  }
284
+ if (error instanceof ValueArgsError) {
285
+ return error.returnValue;
286
+ }
284
287
  return null;
285
288
  }
286
289
  }
package/lib/value.js CHANGED
@@ -1,6 +1,9 @@
1
1
  // Licensed under the MIT License
2
2
  // https://github.com/craigahobbs/bare-script/blob/main/LICENSE
3
3
 
4
+ import {parseSchemaMarkdown} from 'schema-markdown/lib/parser.js';
5
+ import {validateType} from 'schema-markdown/lib/schema.js';
6
+
4
7
 
5
8
  /**
6
9
  * Get a value's type string
@@ -205,6 +208,221 @@ export function valueCompare(left, right) {
205
208
  }
206
209
 
207
210
 
211
+ //
212
+ // Function arguments validation
213
+ //
214
+
215
+
216
+ /**
217
+ * Validate a function's arguments
218
+ *
219
+ * @param {Object[]} fnArgs - The function arguments model
220
+ * @param {Array} args - The function arguments
221
+ * @param {*} [errorReturnValue = null] - The function's return value on error
222
+ * @returns {Array} The validated function arguments
223
+ * @ignore
224
+ */
225
+ export function valueArgsValidate(fnArgs, args, errorReturnValue = null) {
226
+ const fnArgsLength = fnArgs.length;
227
+ for (let ix = 0; ix < fnArgsLength; ix++) {
228
+ const fnArg = fnArgs[ix];
229
+ const {'type': argType = null, 'default': argDefault = null, lastArgArray = false} = fnArg;
230
+
231
+ // Missing argument?
232
+ if (ix >= args.length) {
233
+ // Last argument array?
234
+ if (lastArgArray) {
235
+ args.push([]);
236
+ continue;
237
+ }
238
+
239
+ // Argument default?
240
+ if (argDefault !== null) {
241
+ args.push(argDefault);
242
+ continue;
243
+ }
244
+
245
+ // Boolean argument?
246
+ if (argType === 'boolean') {
247
+ args.push(false);
248
+ continue;
249
+ }
250
+
251
+ // Argument nullable?
252
+ if (argType === null || fnArg.nullable) {
253
+ args.push(null);
254
+ continue;
255
+ }
256
+
257
+ // Invalid null value...
258
+ throw new ValueArgsError(fnArg.name, null, errorReturnValue);
259
+ }
260
+
261
+ // Last arg array?
262
+ if (lastArgArray) {
263
+ args.push(args.splice(ix));
264
+ continue;
265
+ }
266
+
267
+ // Any type OK?
268
+ if (argType === null) {
269
+ continue;
270
+ }
271
+
272
+ // Boolean argument?
273
+ const argValue = args[ix];
274
+ if (argType === 'boolean') {
275
+ args[ix] = valueBoolean(argValue);
276
+ continue;
277
+ }
278
+
279
+ // Null value?
280
+ const argValueType = typeof argValue;
281
+ if (argValue === null || argValueType === 'undefined') {
282
+ // Argument nullable?
283
+ if (!fnArg.nullable) {
284
+ throw new ValueArgsError(fnArg.name, argValue, errorReturnValue);
285
+ }
286
+ continue;
287
+ }
288
+
289
+ // Invalid value?
290
+ if ((argType === 'number' && argValueType !== 'number') ||
291
+ (argType === 'string' && argValueType !== 'string') ||
292
+ (argType === 'array' && !Array.isArray(argValue)) ||
293
+ (argType === 'object' && !(argValueType === 'object' && Object.getPrototypeOf(argValue) === Object.prototype)) ||
294
+ (argType === 'datetime' && !(argValue instanceof Date)) ||
295
+ (argType === 'regex' && !(argValue instanceof RegExp)) ||
296
+ (argType === 'function' && argValueType !== 'function')
297
+ ) {
298
+ throw new ValueArgsError(fnArg.name, argValue, errorReturnValue);
299
+ }
300
+
301
+ // Number constraints
302
+ if (argType === 'number') {
303
+ const {integer = false, lt = null, lte = null, gt = null, gte = null} = fnArg;
304
+ if ((integer && Math.floor(argValue) !== argValue) ||
305
+ (lt !== null && !(argValue < lt)) ||
306
+ (lte !== null && !(argValue <= lte)) ||
307
+ (gt !== null && !(argValue > gt)) ||
308
+ (gte !== null && !(argValue >= gte))) {
309
+ throw new ValueArgsError(fnArg.name, argValue, errorReturnValue);
310
+ }
311
+ }
312
+ }
313
+
314
+ // Extra arguments?
315
+ if (args.length > fnArgsLength) {
316
+ args.splice(fnArgsLength);
317
+ }
318
+
319
+ return args;
320
+ }
321
+
322
+
323
+ /**
324
+ * A function arguments validation error
325
+ *
326
+ * @extends {Error}
327
+ * @property {*} returnValue - The function's error return value
328
+ * @ignore
329
+ */
330
+ export class ValueArgsError extends Error {
331
+ /**
332
+ * Create a BareScript runtime error
333
+ *
334
+ * @param {string} argName - The function argument name
335
+ * @param {*} argValue - The function argument value
336
+ * @param {*} [returnValue = null] - The function's error return value
337
+ */
338
+ constructor(argName, argValue, returnValue = null) {
339
+ const message = `Invalid "${argName}" argument value, ${valueJSON(argValue)}`;
340
+ super(message);
341
+ this.name = this.constructor.name;
342
+ this.returnValue = returnValue;
343
+ }
344
+ }
345
+
346
+
347
+ /**
348
+ * Validate a function arguments model
349
+ *
350
+ * @param {Object[]} fnArgs - The function arguments model
351
+ * @returns {Object[]} The validated function arguments model
352
+ * @ignore
353
+ */
354
+ export function valueArgsModel(fnArgs) {
355
+ validateType(valueArgsTypes, 'FunctionArguments', fnArgs);
356
+
357
+ // Use nullable instead of default-null
358
+ for (const fnArg of fnArgs) {
359
+ if (fnArg.default === null) {
360
+ throw Error(`Argument "${fnArg.name}" has default value of null - use nullable instead`);
361
+ }
362
+ }
363
+
364
+ return fnArgs;
365
+ }
366
+
367
+
368
+ // Function arguments type model
369
+ const valueArgsTypes = parseSchemaMarkdown(`\
370
+ # A function arguments model
371
+ typedef FunctionArgument[len > 0] FunctionArguments
372
+
373
+
374
+ # A function argument model
375
+ struct FunctionArgument
376
+
377
+ # The argument name
378
+ string name
379
+
380
+ # The argument type
381
+ optional FunctionArgumentType type
382
+
383
+ # If true, the argument may be null
384
+ optional bool nullable
385
+
386
+ # The default argument value
387
+ optional object default
388
+
389
+ # If true, this argument is the array of remaining arguments
390
+ optional object lastArgArray
391
+
392
+ # If true, the number argument must be an integer
393
+ optional bool integer
394
+
395
+ # The number argument must be less-than
396
+ optional object lt
397
+
398
+ # The number argument must be less-than-or-equal-to
399
+ optional object lte
400
+
401
+ # The number argument must be greater-than
402
+ optional object gt
403
+
404
+ # The number argument must be greater-than-or-equal-to
405
+ optional object gte
406
+
407
+
408
+ # The function argument types
409
+ enum FunctionArgumentType
410
+ array
411
+ boolean
412
+ datetime
413
+ function
414
+ number
415
+ object
416
+ regex
417
+ string
418
+ `);
419
+
420
+
421
+ //
422
+ // Number value functions
423
+ //
424
+
425
+
208
426
  /**
209
427
  * Round a number
210
428
  *
@@ -256,6 +474,11 @@ export function valueParseInteger(text, radix = 10) {
256
474
  const rInteger = /^\s*[-+]?\d+\s*$/;
257
475
 
258
476
 
477
+ //
478
+ // Datetime value functions
479
+ //
480
+
481
+
259
482
  /**
260
483
  * Parse a datetime string
261
484
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "bare-script",
4
- "version": "3.0.5",
4
+ "version": "3.0.7",
5
5
  "description": "BareScript; a lightweight scripting and expression language",
6
6
  "keywords": [
7
7
  "expression",