bare-script 2.3.2 → 3.0.1

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.
@@ -4,25 +4,23 @@
4
4
  /** @module lib/runtimeAsync */
5
5
 
6
6
  import {BareScriptParserError, parseScript} from './parser.js';
7
- import {BareScriptRuntimeError, evaluateExpression, executeScriptHelper} from './runtime.js';
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
- * @async
20
- * @param {Object} script - The [BareScript model]{@link https://craigahobbs.github.io/bare-script/model/#var.vName='BareScript'}
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 async function executeScriptAsync(script, options = {}) {
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 && ++options.statementCount > maxStatements) {
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] = async (args, fnOptions) => {
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 && isRelativeURL(url)) {
145
- return `${options.systemPrefix}${url}`;
112
+ if (system && 'systemPrefix' in options) {
113
+ return urlFileRelative(options.systemPrefix, url);
146
114
  }
147
- return 'urlFn' in options ? options.urlFn(url) : url;
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) => (isRelativeURL(url) ? `${getBaseURL(includeURL)}${url}` : 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
- // Test if a URL is relative
208
- function isRelativeURL(url) {
209
- return !rNotRelativeURL.test(url);
210
- }
211
-
212
- const rNotRelativeURL = /^(?:[a-z]+:|\/|\?|#)/;
213
-
214
-
215
- // Get a URL's base URL
216
- function getBaseURL(url) {
217
- return url.slice(0, url.lastIndexOf('/') + 1);
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]{@link https://craigahobbs.github.io/bare-script/model/#var.vName='Expression'}
227
- * @param {?Object} [options = null] - The [script execution options]{@link module:lib/runtime~ExecuteScriptOptions}
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 operators - evaluate right expression only if necessary
296
+ // Short-circuiting "and" binary operator
325
297
  if (binOp === '&&') {
326
- return leftValue && evaluateExpressionAsync(expr.binary.right, options, locals, builtins);
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
- return leftValue || evaluateExpressionAsync(expr.binary.right, options, locals, builtins);
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
- return leftValue ** rightValue;
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
- return leftValue * rightValue;
344
+ // number * number
345
+ if (typeof leftValue === 'number' && typeof rightValue === 'number') {
346
+ return leftValue * rightValue;
347
+ }
337
348
  } else if (binOp === '/') {
338
- return leftValue / rightValue;
339
- } else if (binOp === '%') {
340
- return leftValue % rightValue;
341
- } else if (binOp === '+') {
342
- return leftValue + rightValue;
343
- } else if (binOp === '-') {
344
- return leftValue - rightValue;
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 <= rightValue;
358
+ return valueCompare(leftValue, rightValue) <= 0;
347
359
  } else if (binOp === '<') {
348
- return leftValue < rightValue;
360
+ return valueCompare(leftValue, rightValue) < 0;
349
361
  } else if (binOp === '>=') {
350
- return leftValue >= rightValue;
362
+ return valueCompare(leftValue, rightValue) >= 0;
351
363
  } else if (binOp === '>') {
352
- return leftValue > rightValue;
353
- } else if (binOp === '==') {
354
- return leftValue === rightValue;
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
- // else if (binOp === '!=')
357
- return leftValue !== rightValue;
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
- // else if (unaryOp === '-')
368
- return -value;
391
+
392
+ // Invalid operation value
393
+ return null;
369
394
  }
370
395
 
371
396
  // Expression group
package/lib/value.js ADDED
@@ -0,0 +1,287 @@
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
+ const year = value.getFullYear();
58
+ const month = value.getMonth() + 1;
59
+ const monthStr = `${month < 10 ? '0' : ''}${month}`;
60
+ const day = value.getDate();
61
+ const dayStr = `${day < 10 ? '0' : ''}${day}`;
62
+ const hour = value.getHours();
63
+ const hourStr = `${hour < 10 ? '0' : ''}${hour}`;
64
+ const minute = value.getMinutes();
65
+ const minuteStr = `${minute < 10 ? '0' : ''}${minute}`;
66
+ const second = value.getSeconds();
67
+ const secondStr = `${second < 10 ? '0' : ''}${second}`;
68
+ const millisecond = value.getMilliseconds();
69
+ const millisecondStr = millisecond === 0 ? '' : `.${millisecond < 100 ? '0' : ''}${millisecond < 10 ? '0' : ''}${millisecond}`;
70
+ const tzOffset = value.getTimezoneOffset();
71
+ /* c8 ignore next */
72
+ const tzSign = tzOffset < 0 ? '-' : '+';
73
+ const tzHour = Math.floor(Math.abs(tzOffset) / 60);
74
+ /* c8 ignore next */
75
+ const tzHourStr = `${tzHour < 10 ? '0' : ''}${tzHour}`;
76
+ const tzMinute = Math.abs(tzOffset) - tzHour * 60;
77
+ /* c8 ignore next */
78
+ const tzMinuteStr = `${tzMinute < 10 ? '0' : ''}${tzMinute}`;
79
+ return `${year}-${monthStr}-${dayStr}T${hourStr}:${minuteStr}:${secondStr}${millisecondStr}${tzSign}${tzHourStr}:${tzMinuteStr}`;
80
+ } else if (Array.isArray(value)) {
81
+ return valueJSON(value);
82
+ } else if (value instanceof RegExp) {
83
+ return '<regex>';
84
+ } else if (type === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
85
+ return valueJSON(value);
86
+ } else if (type === 'function') {
87
+ return '<function>';
88
+ }
89
+
90
+ // Unknown value type
91
+ return '<unknown>';
92
+ }
93
+
94
+
95
+ /**
96
+ * Get a value's JSON string representation
97
+ *
98
+ * @param value - The value
99
+ * @param {number} indent - The JSON indent
100
+ * @returns {string} The value as a JSON string
101
+ * @ignore
102
+ */
103
+ export function valueJSON(value, indent = null) {
104
+ return JSON.stringify(valueJSONSort(value), null, indent);
105
+ }
106
+
107
+
108
+ function valueJSONSort(value) {
109
+ const type = typeof value;
110
+ if (value === null || type === 'undefined' || type === 'string' || type === 'boolean' || type === 'number') {
111
+ return value;
112
+ } else if (value instanceof Date) {
113
+ return valueString(value);
114
+ } else if (Array.isArray(value)) {
115
+ return value.map(valueJSONSort);
116
+ } else if (type === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
117
+ const valueCopy = {};
118
+ for (const valueKey of Object.keys(value).sort()) {
119
+ valueCopy[valueKey] = valueJSONSort(value[valueKey]);
120
+ }
121
+ return valueCopy;
122
+ }
123
+
124
+ // Everything else is null
125
+ return null;
126
+ }
127
+
128
+
129
+ /**
130
+ * Interpret the value as a boolean
131
+ *
132
+ * @param value - The value
133
+ * @returns {boolean} The value as a boolean
134
+ * @ignore
135
+ */
136
+ export function valueBoolean(value) {
137
+ const type = typeof value;
138
+ if (value === null || type === 'undefined') {
139
+ return false;
140
+ } else if (type === 'string') {
141
+ return value !== '';
142
+ } else if (type === 'boolean') {
143
+ return value;
144
+ } else if (type === 'number') {
145
+ return value !== 0;
146
+ } else if (value instanceof Date) {
147
+ return true;
148
+ } else if (Array.isArray(value)) {
149
+ return value.length !== 0;
150
+ }
151
+
152
+ // Everything else is true
153
+ return true;
154
+ }
155
+
156
+
157
+ /**
158
+ * Test if one value is the same object as another
159
+ *
160
+ * @param value1 - The first value
161
+ * @param value2 - The second value
162
+ * @returns {boolean} true if values are the same object, false otherwise
163
+ * @ignore
164
+ */
165
+ export function valueIs(value1, value2) {
166
+ if (value1 instanceof RegExp && value2 instanceof RegExp) {
167
+ return value1 === value2 || value1.source === value2.source;
168
+ }
169
+ return value1 === value2;
170
+ }
171
+
172
+
173
+ /**
174
+ * Compare two values
175
+ *
176
+ * @param left - The left value
177
+ * @param right - The right value
178
+ * @returns {number} -1 if the left value is less than the right value, 0 if equal, and 1 if greater than
179
+ * @ignore
180
+ */
181
+ export function valueCompare(left, right) {
182
+ const leftType = typeof left;
183
+ const rightType = typeof right;
184
+ if (left === null || leftType === 'undefined') {
185
+ return right === null || rightType === 'undefined' ? 0 : -1;
186
+ } else if (right === null || rightType === 'undefined') {
187
+ return 1;
188
+ } else if (leftType === 'string' && rightType === 'string') {
189
+ return left < right ? -1 : (left === right ? 0 : 1);
190
+ } else if (leftType === 'number' && rightType === 'number') {
191
+ return left < right ? -1 : (left === right ? 0 : 1);
192
+ } else if (leftType === 'boolean' && rightType === 'boolean') {
193
+ return left < right ? -1 : (left === right ? 0 : 1);
194
+ } else if (left instanceof Date && right instanceof Date) {
195
+ return left < right ? -1 : (left > right ? 1 : 0);
196
+ } else if (Array.isArray(left) && Array.isArray(right)) {
197
+ const ixEnd = Math.min(left.length, right.length);
198
+ for (let ix = 0; ix < ixEnd; ix++) {
199
+ const itemCompare = valueCompare(left[ix], right[ix]);
200
+ if (itemCompare !== 0) {
201
+ return itemCompare;
202
+ }
203
+ }
204
+ return left.length < right.length ? -1 : (left.length === right.length ? 0 : 1);
205
+ }
206
+
207
+ // Invalid comparison - compare by type name
208
+ const leftValueType = valueType(left) ?? 'unknown';
209
+ const rightValueType = valueType(right) ?? 'unknown';
210
+ return leftValueType < rightValueType ? -1 : (leftValueType === rightValueType ? 0 : 1);
211
+ }
212
+
213
+
214
+ /**
215
+ * Round a number
216
+ *
217
+ * @param {number} value - The number to round
218
+ * @param {number} digits - The number of digits of precision
219
+ * @returns {number} The rounded number
220
+ * @ignore
221
+ */
222
+ export function valueRoundNumber(value, digits) {
223
+ const multiplier = 10 ** digits;
224
+ return Math.round(value * multiplier) / multiplier;
225
+ }
226
+
227
+
228
+ /**
229
+ * Parse a number string
230
+ *
231
+ * @param {string} text - The string to parse as a number
232
+ * @returns {number|null}: A number value or null if parsing fails
233
+ * @ignore
234
+ */
235
+ export function valueParseNumber(text) {
236
+ if (!rNumber.test(text)) {
237
+ return null;
238
+ }
239
+ return Number.parseFloat(text);
240
+ }
241
+
242
+
243
+ const rNumber = /^\s*[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?\s*$/;
244
+
245
+
246
+ /**
247
+ * Parse an integer string
248
+ *
249
+ * @param {string} text - The string to parse as an integer
250
+ * @param {number} radix - The integer's radix (2 - 36). Default is 10.
251
+ * @returns {number|null}: A number value or null if parsing fails
252
+ * @ignore
253
+ */
254
+ export function valueParseInteger(text, radix = 10) {
255
+ if (!rInteger.test(text)) {
256
+ return null;
257
+ }
258
+ return Number.parseInt(text, radix);
259
+ }
260
+
261
+
262
+ const rInteger = /^\s*[-+]?\d+\s*$/;
263
+
264
+
265
+ /**
266
+ * Parse a datetime string
267
+ *
268
+ * @param {string} text - The string to parse as a datetime
269
+ * @returns {Date|null} A datetime value or null if parsing fails
270
+ * @ignore
271
+ */
272
+ export function valueParseDatetime(text) {
273
+ const mDate = text.match(rDate);
274
+ if (mDate !== null) {
275
+ const year = Number.parseInt(mDate.groups.year, 10);
276
+ const month = Number.parseInt(mDate.groups.month, 10);
277
+ const day = Number.parseInt(mDate.groups.day, 10);
278
+ return new Date(year, month - 1, day);
279
+ } else if (rDatetime.test(text)) {
280
+ return new Date(text);
281
+ }
282
+ return null;
283
+ }
284
+
285
+
286
+ const rDate = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/;
287
+ const rDatetime = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?: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": "2.3.2",
4
+ "version": "3.0.1",
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.56",
34
+ "eslint": "~8.57",
35
35
  "jsdoc": "~4.0"
36
36
  }
37
37
  }