bare-script 2.3.2 → 3.0.0
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/README.md +22 -4
- package/bin/bare.js +3 -16
- package/bin/baredoc.js +3 -16
- package/lib/bare.js +87 -84
- package/lib/baredoc.js +133 -59
- package/lib/data.js +155 -158
- package/lib/library.js +794 -291
- package/lib/model.js +3 -3
- package/lib/options.js +72 -0
- package/lib/optionsNode.js +70 -0
- package/lib/parser.js +72 -70
- package/lib/runtime.js +107 -88
- package/lib/runtimeAsync.js +112 -87
- package/lib/value.js +268 -0
- package/package.json +2 -2
package/lib/runtimeAsync.js
CHANGED
|
@@ -4,25 +4,23 @@
|
|
|
4
4
|
/** @module lib/runtimeAsync */
|
|
5
5
|
|
|
6
6
|
import {BareScriptParserError, parseScript} from './parser.js';
|
|
7
|
-
import {BareScriptRuntimeError, evaluateExpression,
|
|
7
|
+
import {BareScriptRuntimeError, evaluateExpression, scriptFunction} from './runtime.js';
|
|
8
8
|
import {defaultMaxStatements, expressionFunctions, scriptFunctions} from './library.js';
|
|
9
|
+
import {valueBoolean, valueCompare, valueString} from './value.js';
|
|
9
10
|
import {lintScript} from './model.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/* eslint-disable no-await-in-loop, require-await */
|
|
11
|
+
import {urlFileRelative} from './options.js';
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Execute a BareScript model asynchronously.
|
|
17
16
|
* Use this form of the function if you have any global asynchronous functions.
|
|
18
17
|
*
|
|
19
|
-
* @
|
|
20
|
-
* @param {Object}
|
|
21
|
-
* @param {Object} [options = {}] - The [script execution options]{@link module:lib/runtime~ExecuteScriptOptions}
|
|
18
|
+
* @param {Object} script - The [BareScript model](./model/#var.vName='BareScript')
|
|
19
|
+
* @param {Object} [options = {}] - The [script execution options]{@link module:lib/options~ExecuteScriptOptions}
|
|
22
20
|
* @returns The script result
|
|
23
21
|
* @throws [BareScriptRuntimeError]{@link module:lib/runtime.BareScriptRuntimeError}
|
|
24
22
|
*/
|
|
25
|
-
export
|
|
23
|
+
export function executeScriptAsync(script, options = {}) {
|
|
26
24
|
// Create the global variable object, if necessary
|
|
27
25
|
let {globals = null} = options;
|
|
28
26
|
if (globals === null) {
|
|
@@ -54,8 +52,9 @@ async function executeScriptHelperAsync(statements, options, locals) {
|
|
|
54
52
|
const [statementKey] = Object.keys(statement);
|
|
55
53
|
|
|
56
54
|
// Increment the statement counter
|
|
55
|
+
options.statementCount += 1;
|
|
57
56
|
const maxStatements = options.maxStatements ?? defaultMaxStatements;
|
|
58
|
-
if (maxStatements > 0 &&
|
|
57
|
+
if (maxStatements > 0 && options.statementCount > maxStatements) {
|
|
59
58
|
throw new BareScriptRuntimeError(`Exceeded maximum script statements (${maxStatements})`);
|
|
60
59
|
}
|
|
61
60
|
|
|
@@ -100,51 +99,20 @@ async function executeScriptHelperAsync(statements, options, locals) {
|
|
|
100
99
|
// Function?
|
|
101
100
|
} else if (statementKey === 'function') {
|
|
102
101
|
if (statement.function.async) {
|
|
103
|
-
globals[statement.function.name] =
|
|
104
|
-
const funcLocals = {};
|
|
105
|
-
if ('args' in statement.function) {
|
|
106
|
-
const argsLength = args.length;
|
|
107
|
-
const funcArgsLength = statement.function.args.length;
|
|
108
|
-
const ixArgLast = (statement.function.lastArgArray ?? null) && (funcArgsLength - 1);
|
|
109
|
-
for (let ixArg = 0; ixArg < funcArgsLength; ixArg++) {
|
|
110
|
-
const argName = statement.function.args[ixArg];
|
|
111
|
-
if (ixArg < argsLength) {
|
|
112
|
-
funcLocals[argName] = (ixArg === ixArgLast ? args.slice(ixArg) : args[ixArg]);
|
|
113
|
-
} else {
|
|
114
|
-
funcLocals[argName] = (ixArg === ixArgLast ? [] : null);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return executeScriptHelperAsync(statement.function.statements, fnOptions, funcLocals);
|
|
119
|
-
};
|
|
102
|
+
globals[statement.function.name] = (args, fnOptions) => scriptFunctionAsync(statement.function, args, fnOptions);
|
|
120
103
|
} else {
|
|
121
|
-
globals[statement.function.name] = (args, fnOptions) =>
|
|
122
|
-
const funcLocals = {};
|
|
123
|
-
if ('args' in statement.function) {
|
|
124
|
-
const argsLength = args.length;
|
|
125
|
-
const funcArgsLength = statement.function.args.length;
|
|
126
|
-
const ixArgLast = (statement.function.lastArgArray ?? null) && (funcArgsLength - 1);
|
|
127
|
-
for (let ixArg = 0; ixArg < funcArgsLength; ixArg++) {
|
|
128
|
-
const argName = statement.function.args[ixArg];
|
|
129
|
-
if (ixArg < argsLength) {
|
|
130
|
-
funcLocals[argName] = (ixArg === ixArgLast ? args.slice(ixArg) : args[ixArg]);
|
|
131
|
-
} else {
|
|
132
|
-
funcLocals[argName] = (ixArg === ixArgLast ? [] : null);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return executeScriptHelper(statement.function.statements, fnOptions, funcLocals);
|
|
137
|
-
};
|
|
104
|
+
globals[statement.function.name] = (args, fnOptions) => scriptFunction(statement.function, args, fnOptions);
|
|
138
105
|
}
|
|
139
106
|
|
|
140
107
|
// Include?
|
|
141
108
|
} else if (statementKey === 'include') {
|
|
142
109
|
// Fetch the include script text
|
|
110
|
+
const urlFn = options.urlFn ?? null;
|
|
143
111
|
const includeURLs = statement.include.includes.map(({url, system = false}) => {
|
|
144
|
-
if (system && 'systemPrefix' in options
|
|
145
|
-
return
|
|
112
|
+
if (system && 'systemPrefix' in options) {
|
|
113
|
+
return urlFileRelative(options.systemPrefix, url);
|
|
146
114
|
}
|
|
147
|
-
return
|
|
115
|
+
return urlFn !== null ? urlFn(url) : url;
|
|
148
116
|
});
|
|
149
117
|
const responses = await Promise.all(includeURLs.map(async (url) => {
|
|
150
118
|
try {
|
|
@@ -194,7 +162,7 @@ async function executeScriptHelperAsync(statements, options, locals) {
|
|
|
194
162
|
|
|
195
163
|
// Execute the include script
|
|
196
164
|
const includeOptions = {...options};
|
|
197
|
-
includeOptions.urlFn = (url) => (
|
|
165
|
+
includeOptions.urlFn = (url) => urlFileRelative(includeURL, url);
|
|
198
166
|
await executeScriptHelperAsync(scriptModel.statements, includeOptions, null);
|
|
199
167
|
}
|
|
200
168
|
}
|
|
@@ -204,17 +172,23 @@ async function executeScriptHelperAsync(statements, options, locals) {
|
|
|
204
172
|
}
|
|
205
173
|
|
|
206
174
|
|
|
207
|
-
//
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
175
|
+
// Runtime script async function implementation
|
|
176
|
+
function scriptFunctionAsync(function_, args, options) {
|
|
177
|
+
const funcLocals = {};
|
|
178
|
+
if ('args' in function_) {
|
|
179
|
+
const argsLength = args.length;
|
|
180
|
+
const funcArgsLength = function_.args.length;
|
|
181
|
+
const ixArgLast = (function_.lastArgArray ?? null) && (funcArgsLength - 1);
|
|
182
|
+
for (let ixArg = 0; ixArg < funcArgsLength; ixArg++) {
|
|
183
|
+
const argName = function_.args[ixArg];
|
|
184
|
+
if (ixArg < argsLength) {
|
|
185
|
+
funcLocals[argName] = (ixArg === ixArgLast ? args.slice(ixArg) : args[ixArg]);
|
|
186
|
+
} else {
|
|
187
|
+
funcLocals[argName] = (ixArg === ixArgLast ? [] : null);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return executeScriptHelperAsync(function_.statements, options, funcLocals);
|
|
218
192
|
}
|
|
219
193
|
|
|
220
194
|
|
|
@@ -223,11 +197,10 @@ function getBaseURL(url) {
|
|
|
223
197
|
* Use this form of the function if you have any asynchronous functions.
|
|
224
198
|
*
|
|
225
199
|
* @async
|
|
226
|
-
* @param {Object} expr - The [expression model]
|
|
227
|
-
* @param {?Object} [options = null] - The [script execution options]{@link module:lib/
|
|
200
|
+
* @param {Object} expr - The [expression model](./model/#var.vName='Expression')
|
|
201
|
+
* @param {?Object} [options = null] - The [script execution options]{@link module:lib/options~ExecuteScriptOptions}
|
|
228
202
|
* @param {?Object} [locals = null] - The local variables
|
|
229
|
-
* @param {boolean} [builtins = true] - If true, include the
|
|
230
|
-
* [built-in expression functions]{@link https://craigahobbs.github.io/bare-script/library/expression.html}
|
|
203
|
+
* @param {boolean} [builtins = true] - If true, include the [built-in expression functions](./library/expression.html)
|
|
231
204
|
* @returns The expression result
|
|
232
205
|
* @throws [BareScriptRuntimeError]{@link module:lib/runtime.BareScriptRuntimeError}
|
|
233
206
|
*/
|
|
@@ -289,7 +262,6 @@ export async function evaluateExpressionAsync(expr, options = null, locals = nul
|
|
|
289
262
|
// Global/local function?
|
|
290
263
|
let funcValue = (locals !== null ? locals[funcName] : undefined);
|
|
291
264
|
if (typeof funcValue === 'undefined') {
|
|
292
|
-
/* c8 ignore next */
|
|
293
265
|
funcValue = (globals !== null ? globals[funcName] : undefined);
|
|
294
266
|
if (typeof funcValue === 'undefined') {
|
|
295
267
|
funcValue = (builtins ? expressionFunctions[funcName] : null) ?? null;
|
|
@@ -321,40 +293,90 @@ export async function evaluateExpressionAsync(expr, options = null, locals = nul
|
|
|
321
293
|
const binOp = expr.binary.op;
|
|
322
294
|
const leftValue = await evaluateExpressionAsync(expr.binary.left, options, locals, builtins);
|
|
323
295
|
|
|
324
|
-
// Short-circuiting binary
|
|
296
|
+
// Short-circuiting "and" binary operator
|
|
325
297
|
if (binOp === '&&') {
|
|
326
|
-
|
|
298
|
+
if (!valueBoolean(leftValue)) {
|
|
299
|
+
return leftValue;
|
|
300
|
+
}
|
|
301
|
+
return evaluateExpressionAsync(expr.binary.right, options, locals, builtins);
|
|
302
|
+
|
|
303
|
+
// Short-circuiting "or" binary operator
|
|
327
304
|
} else if (binOp === '||') {
|
|
328
|
-
|
|
305
|
+
if (valueBoolean(leftValue)) {
|
|
306
|
+
return leftValue;
|
|
307
|
+
}
|
|
308
|
+
return evaluateExpressionAsync (expr.binary.right, options, locals, builtins);
|
|
329
309
|
}
|
|
330
310
|
|
|
331
311
|
// Non-short-circuiting binary operators
|
|
332
312
|
const rightValue = await evaluateExpressionAsync(expr.binary.right, options, locals, builtins);
|
|
333
|
-
if (binOp === '
|
|
334
|
-
|
|
313
|
+
if (binOp === '+') {
|
|
314
|
+
// number + number
|
|
315
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
316
|
+
return leftValue + rightValue;
|
|
317
|
+
|
|
318
|
+
// string + string
|
|
319
|
+
} else if (typeof leftValue === 'string' && typeof rightValue === 'string') {
|
|
320
|
+
return leftValue + rightValue;
|
|
321
|
+
|
|
322
|
+
// string + <any>
|
|
323
|
+
} else if (typeof leftValue === 'string') {
|
|
324
|
+
return leftValue + valueString(rightValue);
|
|
325
|
+
} else if (typeof rightValue === 'string') {
|
|
326
|
+
return valueString(leftValue) + rightValue;
|
|
327
|
+
|
|
328
|
+
// datetime + number
|
|
329
|
+
} else if (leftValue instanceof Date && typeof rightValue === 'number') {
|
|
330
|
+
return new Date(leftValue.getTime() + rightValue);
|
|
331
|
+
} else if (typeof leftValue === 'number' && rightValue instanceof Date) {
|
|
332
|
+
return new Date(leftValue + rightValue.getTime());
|
|
333
|
+
}
|
|
334
|
+
} else if (binOp === '-') {
|
|
335
|
+
// number - number
|
|
336
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
337
|
+
return leftValue - rightValue;
|
|
338
|
+
|
|
339
|
+
// datetime - datetime
|
|
340
|
+
} else if (leftValue instanceof Date && rightValue instanceof Date) {
|
|
341
|
+
return leftValue - rightValue;
|
|
342
|
+
}
|
|
335
343
|
} else if (binOp === '*') {
|
|
336
|
-
|
|
344
|
+
// number * number
|
|
345
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
346
|
+
return leftValue * rightValue;
|
|
347
|
+
}
|
|
337
348
|
} else if (binOp === '/') {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
349
|
+
// number / number
|
|
350
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
351
|
+
return leftValue / rightValue;
|
|
352
|
+
}
|
|
353
|
+
} else if (binOp === '==') {
|
|
354
|
+
return valueCompare(leftValue, rightValue) === 0;
|
|
355
|
+
} else if (binOp === '!=') {
|
|
356
|
+
return valueCompare(leftValue, rightValue) !== 0;
|
|
345
357
|
} else if (binOp === '<=') {
|
|
346
|
-
return leftValue <=
|
|
358
|
+
return valueCompare(leftValue, rightValue) <= 0;
|
|
347
359
|
} else if (binOp === '<') {
|
|
348
|
-
return leftValue <
|
|
360
|
+
return valueCompare(leftValue, rightValue) < 0;
|
|
349
361
|
} else if (binOp === '>=') {
|
|
350
|
-
return leftValue >=
|
|
362
|
+
return valueCompare(leftValue, rightValue) >= 0;
|
|
351
363
|
} else if (binOp === '>') {
|
|
352
|
-
return leftValue >
|
|
353
|
-
} else if (binOp === '
|
|
354
|
-
|
|
364
|
+
return valueCompare(leftValue, rightValue) > 0;
|
|
365
|
+
} else if (binOp === '%') {
|
|
366
|
+
// number % number
|
|
367
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
368
|
+
return leftValue % rightValue;
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
// binOp === '**'
|
|
372
|
+
// number ** number
|
|
373
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
374
|
+
return leftValue ** rightValue;
|
|
375
|
+
}
|
|
355
376
|
}
|
|
356
|
-
|
|
357
|
-
|
|
377
|
+
|
|
378
|
+
// Invalid operation values
|
|
379
|
+
return null;
|
|
358
380
|
}
|
|
359
381
|
|
|
360
382
|
// Unary expression
|
|
@@ -362,10 +384,13 @@ export async function evaluateExpressionAsync(expr, options = null, locals = nul
|
|
|
362
384
|
const unaryOp = expr.unary.op;
|
|
363
385
|
const value = await evaluateExpressionAsync(expr.unary.expr, options, locals, builtins);
|
|
364
386
|
if (unaryOp === '!') {
|
|
365
|
-
return !value;
|
|
387
|
+
return !valueBoolean(value);
|
|
388
|
+
} else if (unaryOp === '-' && typeof value === 'number') {
|
|
389
|
+
return -value;
|
|
366
390
|
}
|
|
367
|
-
|
|
368
|
-
|
|
391
|
+
|
|
392
|
+
// Invalid operation value
|
|
393
|
+
return null;
|
|
369
394
|
}
|
|
370
395
|
|
|
371
396
|
// Expression group
|
package/lib/value.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Licensed under the MIT License
|
|
2
|
+
// https://github.com/craigahobbs/bare-script/blob/main/LICENSE
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get a value's type string
|
|
7
|
+
*
|
|
8
|
+
* @param value - The value
|
|
9
|
+
* @returns {string} The type string ('array', 'boolean', 'datetime', 'function', 'null', 'number', 'object', 'regex', 'string')
|
|
10
|
+
* @ignore
|
|
11
|
+
*/
|
|
12
|
+
export function valueType(value) {
|
|
13
|
+
const type = typeof value;
|
|
14
|
+
if (value === null || type === 'undefined') {
|
|
15
|
+
return 'null';
|
|
16
|
+
} else if (type === 'string') {
|
|
17
|
+
return 'string';
|
|
18
|
+
} else if (type === 'boolean') {
|
|
19
|
+
return 'boolean';
|
|
20
|
+
} else if (type === 'number') {
|
|
21
|
+
return 'number';
|
|
22
|
+
} else if (value instanceof Date) {
|
|
23
|
+
return 'datetime';
|
|
24
|
+
} else if (Array.isArray(value)) {
|
|
25
|
+
return 'array';
|
|
26
|
+
} else if (value instanceof RegExp) {
|
|
27
|
+
return 'regex';
|
|
28
|
+
} else if (type === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
|
|
29
|
+
return 'object';
|
|
30
|
+
} else if (type === 'function') {
|
|
31
|
+
return 'function';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Unknown value type
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get a value's string representation
|
|
41
|
+
*
|
|
42
|
+
* @param value - The value
|
|
43
|
+
* @returns {string} The value as a string
|
|
44
|
+
* @ignore
|
|
45
|
+
*/
|
|
46
|
+
export function valueString(value) {
|
|
47
|
+
const type = typeof value;
|
|
48
|
+
if (value === null || type === 'undefined') {
|
|
49
|
+
return 'null';
|
|
50
|
+
} else if (type === 'string') {
|
|
51
|
+
return value;
|
|
52
|
+
} else if (type === 'boolean') {
|
|
53
|
+
return value ? 'true' : 'false';
|
|
54
|
+
} else if (type === 'number') {
|
|
55
|
+
return `${value}`;
|
|
56
|
+
} else if (value instanceof Date) {
|
|
57
|
+
return value.toISOString().replaceAll(rValueStringDatetimeZulu, '+00:00');
|
|
58
|
+
} else if (Array.isArray(value)) {
|
|
59
|
+
return valueJSON(value);
|
|
60
|
+
} else if (value instanceof RegExp) {
|
|
61
|
+
return '<regex>';
|
|
62
|
+
} else if (type === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
|
|
63
|
+
return valueJSON(value);
|
|
64
|
+
} else if (type === 'function') {
|
|
65
|
+
return '<function>';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Unknown value type
|
|
69
|
+
return '<unknown>';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
const rValueStringDatetimeZulu = /(?:\.000)?Z$/g;
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get a value's JSON string representation
|
|
78
|
+
*
|
|
79
|
+
* @param value - The value
|
|
80
|
+
* @param {number} indent - The JSON indent
|
|
81
|
+
* @returns {string} The value as a JSON string
|
|
82
|
+
* @ignore
|
|
83
|
+
*/
|
|
84
|
+
export function valueJSON(value, indent = null) {
|
|
85
|
+
return JSON.stringify(valueJSONSort(value), null, indent);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
function valueJSONSort(value) {
|
|
90
|
+
const type = typeof value;
|
|
91
|
+
if (value === null || type === 'undefined' || type === 'string' || type === 'boolean' || type === 'number') {
|
|
92
|
+
return value;
|
|
93
|
+
} else if (value instanceof Date) {
|
|
94
|
+
return value.toISOString().replaceAll(rValueStringDatetimeZulu, '+00:00');
|
|
95
|
+
} else if (Array.isArray(value)) {
|
|
96
|
+
return value.map(valueJSONSort);
|
|
97
|
+
} else if (type === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
|
|
98
|
+
const valueCopy = {};
|
|
99
|
+
for (const valueKey of Object.keys(value).sort()) {
|
|
100
|
+
valueCopy[valueKey] = valueJSONSort(value[valueKey]);
|
|
101
|
+
}
|
|
102
|
+
return valueCopy;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Everything else is null
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Interpret the value as a boolean
|
|
112
|
+
*
|
|
113
|
+
* @param value - The value
|
|
114
|
+
* @returns {boolean} The value as a boolean
|
|
115
|
+
* @ignore
|
|
116
|
+
*/
|
|
117
|
+
export function valueBoolean(value) {
|
|
118
|
+
const type = typeof value;
|
|
119
|
+
if (value === null || type === 'undefined') {
|
|
120
|
+
return false;
|
|
121
|
+
} else if (type === 'string') {
|
|
122
|
+
return value !== '';
|
|
123
|
+
} else if (type === 'boolean') {
|
|
124
|
+
return value;
|
|
125
|
+
} else if (type === 'number') {
|
|
126
|
+
return value !== 0;
|
|
127
|
+
} else if (value instanceof Date) {
|
|
128
|
+
return true;
|
|
129
|
+
} else if (Array.isArray(value)) {
|
|
130
|
+
return value.length !== 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Everything else is true
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Test if one value is the same object as another
|
|
140
|
+
*
|
|
141
|
+
* @param value1 - The first value
|
|
142
|
+
* @param value2 - The second value
|
|
143
|
+
* @returns {boolean} true if values are the same object, false otherwise
|
|
144
|
+
* @ignore
|
|
145
|
+
*/
|
|
146
|
+
export function valueIs(value1, value2) {
|
|
147
|
+
if (value1 instanceof RegExp && value2 instanceof RegExp) {
|
|
148
|
+
return value1 === value2 || value1.source === value2.source;
|
|
149
|
+
}
|
|
150
|
+
return value1 === value2;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Compare two values
|
|
156
|
+
*
|
|
157
|
+
* @param left - The left value
|
|
158
|
+
* @param right - The right value
|
|
159
|
+
* @returns {number} -1 if the left value is less than the right value, 0 if equal, and 1 if greater than
|
|
160
|
+
* @ignore
|
|
161
|
+
*/
|
|
162
|
+
export function valueCompare(left, right) {
|
|
163
|
+
const leftType = typeof left;
|
|
164
|
+
const rightType = typeof right;
|
|
165
|
+
if (left === null || leftType === 'undefined') {
|
|
166
|
+
return right === null || rightType === 'undefined' ? 0 : -1;
|
|
167
|
+
} else if (right === null || rightType === 'undefined') {
|
|
168
|
+
return 1;
|
|
169
|
+
} else if (leftType === 'string' && rightType === 'string') {
|
|
170
|
+
return left < right ? -1 : (left === right ? 0 : 1);
|
|
171
|
+
} else if (leftType === 'number' && rightType === 'number') {
|
|
172
|
+
return left < right ? -1 : (left === right ? 0 : 1);
|
|
173
|
+
} else if (leftType === 'boolean' && rightType === 'boolean') {
|
|
174
|
+
return left < right ? -1 : (left === right ? 0 : 1);
|
|
175
|
+
} else if (left instanceof Date && right instanceof Date) {
|
|
176
|
+
return left < right ? -1 : (left > right ? 1 : 0);
|
|
177
|
+
} else if (Array.isArray(left) && Array.isArray(right)) {
|
|
178
|
+
const ixEnd = Math.min(left.length, right.length);
|
|
179
|
+
for (let ix = 0; ix < ixEnd; ix++) {
|
|
180
|
+
const itemCompare = valueCompare(left[ix], right[ix]);
|
|
181
|
+
if (itemCompare !== 0) {
|
|
182
|
+
return itemCompare;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return left.length < right.length ? -1 : (left.length === right.length ? 0 : 1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Invalid comparison - compare by type name
|
|
189
|
+
const leftValueType = valueType(left) ?? 'unknown';
|
|
190
|
+
const rightValueType = valueType(right) ?? 'unknown';
|
|
191
|
+
return leftValueType < rightValueType ? -1 : (leftValueType === rightValueType ? 0 : 1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Round a number
|
|
197
|
+
*
|
|
198
|
+
* @param {number} value - The number to round
|
|
199
|
+
* @param {number} digits - The number of digits of precision
|
|
200
|
+
* @returns {number} The rounded number
|
|
201
|
+
* @ignore
|
|
202
|
+
*/
|
|
203
|
+
export function valueRoundNumber(value, digits) {
|
|
204
|
+
const multiplier = 10 ** digits;
|
|
205
|
+
return Math.round(value * multiplier) / multiplier;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Parse a number string
|
|
211
|
+
*
|
|
212
|
+
* @param {string} text - The string to parse as a number
|
|
213
|
+
* @returns {number|null}: A number value or null if parsing fails
|
|
214
|
+
* @ignore
|
|
215
|
+
*/
|
|
216
|
+
export function valueParseNumber(text) {
|
|
217
|
+
if (!rNumber.test(text)) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return Number.parseFloat(text);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
const rNumber = /^\s*[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?\s*$/;
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Parse an integer string
|
|
229
|
+
*
|
|
230
|
+
* @param {string} text - The string to parse as an integer
|
|
231
|
+
* @param {number} radix - The integer's radix (2 - 36). Default is 10.
|
|
232
|
+
* @returns {number|null}: A number value or null if parsing fails
|
|
233
|
+
* @ignore
|
|
234
|
+
*/
|
|
235
|
+
export function valueParseInteger(text, radix = 10) {
|
|
236
|
+
if (!rInteger.test(text)) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
return Number.parseInt(text, radix);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
const rInteger = /^\s*[-+]?\d+\s*$/;
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Parse a datetime string
|
|
248
|
+
*
|
|
249
|
+
* @param {string} text - The string to parse as a datetime
|
|
250
|
+
* @returns {Date|null} A datetime value or null if parsing fails
|
|
251
|
+
* @ignore
|
|
252
|
+
*/
|
|
253
|
+
export function valueParseDatetime(text) {
|
|
254
|
+
const mDate = text.match(rDate);
|
|
255
|
+
if (mDate !== null) {
|
|
256
|
+
const year = Number.parseInt(mDate.groups.year, 10);
|
|
257
|
+
const month = Number.parseInt(mDate.groups.month, 10);
|
|
258
|
+
const day = Number.parseInt(mDate.groups.day, 10);
|
|
259
|
+
return new Date(year, month - 1, day);
|
|
260
|
+
} else if (rDatetime.test(text)) {
|
|
261
|
+
return new Date(text);
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
const rDate = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/;
|
|
268
|
+
const rDatetime = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "bare-script",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"description": "BareScript; a lightweight scripting and expression language",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"expression",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"c8": "~9.1",
|
|
34
|
-
"eslint": "~8.
|
|
34
|
+
"eslint": "~8.57",
|
|
35
35
|
"jsdoc": "~4.0"
|
|
36
36
|
}
|
|
37
37
|
}
|