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.
- 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 +287 -0
- package/package.json +2 -2
package/lib/model.js
CHANGED
|
@@ -221,7 +221,7 @@ struct FunctionExpression
|
|
|
221
221
|
/**
|
|
222
222
|
* Validate a BareScript script model
|
|
223
223
|
*
|
|
224
|
-
* @param {Object} script - The [BareScript model]
|
|
224
|
+
* @param {Object} script - The [BareScript model](./model/#var.vName='BareScript')
|
|
225
225
|
* @returns {Object} The validated BareScript model
|
|
226
226
|
* @throws [ValidationError]{@link https://craigahobbs.github.io/schema-markdown-js/module-lib_schema.ValidationError.html}
|
|
227
227
|
*/
|
|
@@ -233,7 +233,7 @@ export function validateScript(script) {
|
|
|
233
233
|
/**
|
|
234
234
|
* Validate an expression model
|
|
235
235
|
*
|
|
236
|
-
* @param {Object} expr - The [expression model]
|
|
236
|
+
* @param {Object} expr - The [expression model](./model/#var.vName='Expression')
|
|
237
237
|
* @returns {Object} The validated expression model
|
|
238
238
|
* @throws [ValidationError]{@link https://craigahobbs.github.io/schema-markdown-js/module-lib_schema.ValidationError.html}
|
|
239
239
|
*/
|
|
@@ -245,7 +245,7 @@ export function validateExpression(expr) {
|
|
|
245
245
|
/**
|
|
246
246
|
* Lint a BareScript script model
|
|
247
247
|
*
|
|
248
|
-
* @param {Object} script - The [BareScript model]
|
|
248
|
+
* @param {Object} script - The [BareScript model](./model/#var.vName='BareScript')
|
|
249
249
|
* @returns {string[]} The array of lint warnings
|
|
250
250
|
*/
|
|
251
251
|
export function lintScript(script) {
|
package/lib/options.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Licensed under the MIT License
|
|
2
|
+
// https://github.com/craigahobbs/bare-script/blob/main/LICENSE
|
|
3
|
+
|
|
4
|
+
/** @module lib/options */
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The BareScript runtime options
|
|
9
|
+
*
|
|
10
|
+
* @typedef {Object} ExecuteScriptOptions
|
|
11
|
+
* @property {boolean} [debug] - If true, execute in debug mode
|
|
12
|
+
* @property {function} [fetchFn] - The [fetch function]{@link module:lib/options~FetchFn}
|
|
13
|
+
* @property {Object} [globals] - The global variables
|
|
14
|
+
* @property {function} [logFn] - The [log function]{@link module:lib/options~LogFn}
|
|
15
|
+
* @property {number} [maxStatements] - The maximum number of statements; default is 1e9; 0 for no maximum
|
|
16
|
+
* @property {number} [statementCount] - The current statement count
|
|
17
|
+
* @property {function} [urlFn] - The [URL modifier function]{@link module:lib/options~URLFn}
|
|
18
|
+
* @property {string} [systemPrefix] - The system include prefix
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The fetch function
|
|
24
|
+
*
|
|
25
|
+
* @callback FetchFn
|
|
26
|
+
* @param {string} url - The URL to fetch
|
|
27
|
+
* @param {?Object} [options] - The [fetch options]{@link https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters}
|
|
28
|
+
* @returns {Promise} The fetch [response promise]{@link https://developer.mozilla.org/en-US/docs/Web/API/Response}
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The log function
|
|
34
|
+
*
|
|
35
|
+
* @callback LogFn
|
|
36
|
+
* @param {string} text - The log text
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The URL modifier function
|
|
42
|
+
*
|
|
43
|
+
* @callback URLFn
|
|
44
|
+
* @param {string} url - The URL
|
|
45
|
+
* @returns {string} The modified URL
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A [URL modifier function]{@link module:lib/options~URLFn} implementation that fixes up file-relative paths
|
|
51
|
+
*
|
|
52
|
+
* @param {string} file - The URL or path to which relative URLs are relative
|
|
53
|
+
* @param {string} url - The URL or POSIX path to resolve
|
|
54
|
+
* @returns {string} The resolved URL
|
|
55
|
+
*/
|
|
56
|
+
export function urlFileRelative(file, url) {
|
|
57
|
+
// URL?
|
|
58
|
+
if (rURL.test(url)) {
|
|
59
|
+
return url;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Absolute POSIX path?
|
|
63
|
+
if (url.startsWith('/')) {
|
|
64
|
+
return url;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// URL is relative POSIX path
|
|
68
|
+
return `${file.slice(0, file.lastIndexOf('/') + 1)}${url}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
export const rURL = /^[a-z]+:/;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Licensed under the MIT License
|
|
2
|
+
// https://github.com/craigahobbs/bare-script/blob/main/LICENSE
|
|
3
|
+
|
|
4
|
+
/** @module lib/optionsNode */
|
|
5
|
+
|
|
6
|
+
import {readFile, writeFile} from 'node:fs/promises';
|
|
7
|
+
import {stdout} from 'node:process';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A [fetch function]{@link module:lib/options~FetchFn} implementation that fetches resources that uses HTTP GET
|
|
12
|
+
* and POST for URLs, otherwise read-only file system access
|
|
13
|
+
*/
|
|
14
|
+
export function fetchReadOnly(url, options = null, fetchFn = fetch, readFileFn = readFile) {
|
|
15
|
+
// URL fetch?
|
|
16
|
+
if (rURL.test(url)) {
|
|
17
|
+
return fetchFn(url, options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// File write?
|
|
21
|
+
if ((options ?? null) !== null && 'body' in options) {
|
|
22
|
+
return {'ok': false};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// File read
|
|
26
|
+
return {
|
|
27
|
+
'ok': true,
|
|
28
|
+
'text': () => readFileFn(url, 'utf-8')
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A [fetch function]{@link module:lib/options~FetchFn} implementation that fetches resources that uses HTTP GET
|
|
35
|
+
* and POST for URLs, otherwise read-write file system access
|
|
36
|
+
*/
|
|
37
|
+
export function fetchReadWrite(url, options, fetchFn = fetch, readFileFn = readFile, writeFileFn = writeFile) {
|
|
38
|
+
// URL fetch?
|
|
39
|
+
if (rURL.test(url)) {
|
|
40
|
+
return fetchFn(url, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// File write?
|
|
44
|
+
if ((options ?? null) !== null && 'body' in options) {
|
|
45
|
+
return {
|
|
46
|
+
'ok': true,
|
|
47
|
+
'text': async () => {
|
|
48
|
+
await writeFileFn(url, options.body);
|
|
49
|
+
return '{}';
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// File read
|
|
55
|
+
return {
|
|
56
|
+
'ok': true,
|
|
57
|
+
'text': () => readFileFn(url, 'utf-8')
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
export const rURL = /^[a-z]+:/;
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A [log function]{@link module:lib/options~LogFn} implementation that outputs to stdout
|
|
67
|
+
*/
|
|
68
|
+
export function logStdout(text, stdoutObj = stdout) {
|
|
69
|
+
stdoutObj.write(`${text}\n`);
|
|
70
|
+
}
|
package/lib/parser.js
CHANGED
|
@@ -4,40 +4,12 @@
|
|
|
4
4
|
/** @module lib/parser */
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
// BareScript regex
|
|
8
|
-
const rScriptLineSplit = /\r?\n/;
|
|
9
|
-
const rScriptContinuation = /\\\s*$/;
|
|
10
|
-
const rScriptComment = /^\s*(?:#.*)?$/;
|
|
11
|
-
const rScriptAssignment = /^\s*(?<name>[A-Za-z_]\w*)\s*=\s*(?<expr>.+)$/;
|
|
12
|
-
const rScriptFunctionBegin = new RegExp(
|
|
13
|
-
'^(?<async>\\s*async)?\\s*function\\s+(?<name>[A-Za-z_]\\w*)\\s*\\(' +
|
|
14
|
-
'\\s*(?<args>[A-Za-z_]\\w*(?:\\s*,\\s*[A-Za-z_]\\w*)*)?(?<lastArgArray>\\s*\\.\\.\\.)?\\s*\\)\\s*:\\s*$'
|
|
15
|
-
);
|
|
16
|
-
const rScriptFunctionArgSplit = /\s*,\s*/;
|
|
17
|
-
const rScriptFunctionEnd = /^\s*endfunction\s*$/;
|
|
18
|
-
const rScriptLabel = /^\s*(?<name>[A-Za-z_]\w*)\s*:\s*$/;
|
|
19
|
-
const rScriptJump = /^(?<jump>\s*(?:jump|jumpif\s*\((?<expr>.+)\)))\s+(?<name>[A-Za-z_]\w*)\s*$/;
|
|
20
|
-
const rScriptReturn = /^(?<return>\s*return(?:\s+(?<expr>.+))?)\s*$/;
|
|
21
|
-
const rScriptInclude = /^\s*include\s+(?<delim>')(?<url>(?:\\'|[^'])*)'\s*$/;
|
|
22
|
-
const rScriptIncludeSystem = /^\s*include\s+(?<delim><)(?<url>[^>]*)>\s*$/;
|
|
23
|
-
const rScriptIfBegin = /^\s*if\s+(?<expr>.+)\s*:\s*$/;
|
|
24
|
-
const rScriptIfElseIf = /^\s*elif\s+(?<expr>.+)\s*:\s*$/;
|
|
25
|
-
const rScriptIfElse = /^\s*else\s*:\s*$/;
|
|
26
|
-
const rScriptIfEnd = /^\s*endif\s*$/;
|
|
27
|
-
const rScriptForBegin = /^\s*for\s+(?<value>[A-Za-z_]\w*)(?:\s*,\s*(?<index>[A-Za-z_]\w*))?\s+in\s+(?<values>.+)\s*:\s*$/;
|
|
28
|
-
const rScriptForEnd = /^\s*endfor\s*$/;
|
|
29
|
-
const rScriptWhileBegin = /^\s*while\s+(?<expr>.+)\s*:\s*$/;
|
|
30
|
-
const rScriptWhileEnd = /^\s*endwhile\s*$/;
|
|
31
|
-
const rScriptBreak = /^\s*break\s*$/;
|
|
32
|
-
const rScriptContinue = /^\s*continue\s*$/;
|
|
33
|
-
|
|
34
|
-
|
|
35
7
|
/**
|
|
36
8
|
* Parse a BareScript script
|
|
37
9
|
*
|
|
38
|
-
* @param {string|string[]} scriptText - The [script text]
|
|
10
|
+
* @param {string|string[]} scriptText - The [script text](./language/)
|
|
39
11
|
* @param {number} [startLineNumber = 1] - The script's starting line number
|
|
40
|
-
* @returns {Object} The [BareScript model]
|
|
12
|
+
* @returns {Object} The [BareScript model](./model/#var.vName='BareScript')
|
|
41
13
|
* @throws [BareScriptParserError]{@link module:lib/parser.BareScriptParserError}
|
|
42
14
|
*/
|
|
43
15
|
export function parseScript(scriptText, startLineNumber = 1) {
|
|
@@ -319,7 +291,10 @@ export function parseScript(scriptText, startLineNumber = 1) {
|
|
|
319
291
|
// Add the for-each header statements
|
|
320
292
|
statements.push(
|
|
321
293
|
{'expr': {'name': foreach.values, 'expr': parseExpression(matchForBegin.groups.values)}},
|
|
322
|
-
{'expr': {
|
|
294
|
+
{'expr': {
|
|
295
|
+
'name': foreach.length,
|
|
296
|
+
'expr': {'function': {'name': 'arrayLength', 'args': [{'variable': foreach.values}]}}
|
|
297
|
+
}},
|
|
323
298
|
{'jump': {'label': foreach.done, 'expr': {'unary': {'op': '!', 'expr': {'variable': foreach.length}}}}},
|
|
324
299
|
{'expr': {'name': foreach.index, 'expr': {'number': 0}}},
|
|
325
300
|
{'label': foreach.loop},
|
|
@@ -470,48 +445,39 @@ export function parseScript(scriptText, startLineNumber = 1) {
|
|
|
470
445
|
}
|
|
471
446
|
|
|
472
447
|
|
|
473
|
-
// BareScript
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const
|
|
483
|
-
const
|
|
484
|
-
const
|
|
485
|
-
const
|
|
486
|
-
const
|
|
487
|
-
const
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
'<=': new Set(['==', '!=', '&&', '||']),
|
|
500
|
-
'<': new Set(['==', '!=', '&&', '||']),
|
|
501
|
-
'>=': new Set(['==', '!=', '&&', '||']),
|
|
502
|
-
'>': new Set(['==', '!=', '&&', '||']),
|
|
503
|
-
'==': new Set(['&&', '||']),
|
|
504
|
-
'!=': new Set(['&&', '||']),
|
|
505
|
-
'&&': new Set(['||']),
|
|
506
|
-
'||': new Set([])
|
|
507
|
-
};
|
|
448
|
+
// BareScript regex
|
|
449
|
+
const rScriptLineSplit = /\r?\n/;
|
|
450
|
+
const rScriptContinuation = /\\\s*$/;
|
|
451
|
+
const rScriptComment = /^\s*(?:#.*)?$/;
|
|
452
|
+
const rScriptAssignment = /^\s*(?<name>[A-Za-z_]\w*)\s*=\s*(?<expr>.+)$/;
|
|
453
|
+
const rScriptFunctionBegin = new RegExp(
|
|
454
|
+
'^(?<async>\\s*async)?\\s*function\\s+(?<name>[A-Za-z_]\\w*)\\s*\\(' +
|
|
455
|
+
'\\s*(?<args>[A-Za-z_]\\w*(?:\\s*,\\s*[A-Za-z_]\\w*)*)?(?<lastArgArray>\\s*\\.\\.\\.)?\\s*\\)\\s*:\\s*$'
|
|
456
|
+
);
|
|
457
|
+
const rScriptFunctionArgSplit = /\s*,\s*/;
|
|
458
|
+
const rScriptFunctionEnd = /^\s*endfunction\s*$/;
|
|
459
|
+
const rScriptLabel = /^\s*(?<name>[A-Za-z_]\w*)\s*:\s*$/;
|
|
460
|
+
const rScriptJump = /^(?<jump>\s*(?:jump|jumpif\s*\((?<expr>.+)\)))\s+(?<name>[A-Za-z_]\w*)\s*$/;
|
|
461
|
+
const rScriptReturn = /^(?<return>\s*return(?:\s+(?<expr>.+))?)\s*$/;
|
|
462
|
+
const rScriptInclude = /^\s*include\s+(?<delim>')(?<url>(?:\\'|[^'])*)'\s*$/;
|
|
463
|
+
const rScriptIncludeSystem = /^\s*include\s+(?<delim><)(?<url>[^>]*)>\s*$/;
|
|
464
|
+
const rScriptIfBegin = /^\s*if\s+(?<expr>.+)\s*:\s*$/;
|
|
465
|
+
const rScriptIfElseIf = /^\s*elif\s+(?<expr>.+)\s*:\s*$/;
|
|
466
|
+
const rScriptIfElse = /^\s*else\s*:\s*$/;
|
|
467
|
+
const rScriptIfEnd = /^\s*endif\s*$/;
|
|
468
|
+
const rScriptForBegin = /^\s*for\s+(?<value>[A-Za-z_]\w*)(?:\s*,\s*(?<index>[A-Za-z_]\w*))?\s+in\s+(?<values>.+)\s*:\s*$/;
|
|
469
|
+
const rScriptForEnd = /^\s*endfor\s*$/;
|
|
470
|
+
const rScriptWhileBegin = /^\s*while\s+(?<expr>.+)\s*:\s*$/;
|
|
471
|
+
const rScriptWhileEnd = /^\s*endwhile\s*$/;
|
|
472
|
+
const rScriptBreak = /^\s*break\s*$/;
|
|
473
|
+
const rScriptContinue = /^\s*continue\s*$/;
|
|
508
474
|
|
|
509
475
|
|
|
510
476
|
/**
|
|
511
477
|
* Parse a BareScript expression
|
|
512
478
|
*
|
|
513
|
-
* @param {string} exprText - The [expression text]
|
|
514
|
-
* @returns {Object} The [expression model]
|
|
479
|
+
* @param {string} exprText - The [expression text](./language/#expressions)
|
|
480
|
+
* @returns {Object} The [expression model](./model/#var.vName='Expression')
|
|
515
481
|
* @throws [BareScriptParserError]{@link module:lib/parser.BareScriptParserError}
|
|
516
482
|
*/
|
|
517
483
|
export function parseExpression(exprText) {
|
|
@@ -571,6 +537,25 @@ function parseBinaryExpression(exprText, binLeftExpr = null) {
|
|
|
571
537
|
}
|
|
572
538
|
|
|
573
539
|
|
|
540
|
+
// Binary operator re-order map
|
|
541
|
+
const binaryReorder = {
|
|
542
|
+
'**': new Set(['*', '/', '%', '+', '-', '<=', '<', '>=', '>', '==', '!=', '&&', '||']),
|
|
543
|
+
'*': new Set(['+', '-', '<=', '<', '>=', '>', '==', '!=', '&&', '||']),
|
|
544
|
+
'/': new Set(['+', '-', '<=', '<', '>=', '>', '==', '!=', '&&', '||']),
|
|
545
|
+
'%': new Set(['+', '-', '<=', '<', '>=', '>', '==', '!=', '&&', '||']),
|
|
546
|
+
'+': new Set(['<=', '<', '>=', '>', '==', '!=', '&&', '||']),
|
|
547
|
+
'-': new Set(['<=', '<', '>=', '>', '==', '!=', '&&', '||']),
|
|
548
|
+
'<=': new Set(['==', '!=', '&&', '||']),
|
|
549
|
+
'<': new Set(['==', '!=', '&&', '||']),
|
|
550
|
+
'>=': new Set(['==', '!=', '&&', '||']),
|
|
551
|
+
'>': new Set(['==', '!=', '&&', '||']),
|
|
552
|
+
'==': new Set(['&&', '||']),
|
|
553
|
+
'!=': new Set(['&&', '||']),
|
|
554
|
+
'&&': new Set(['||']),
|
|
555
|
+
'||': new Set([])
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
|
|
574
559
|
// Helper function to parse a unary expression
|
|
575
560
|
function parseUnaryExpression(exprText) {
|
|
576
561
|
// Group open?
|
|
@@ -604,8 +589,7 @@ function parseUnaryExpression(exprText) {
|
|
|
604
589
|
if (matchFunctionOpen !== null) {
|
|
605
590
|
let argText = exprText.slice(matchFunctionOpen[0].length);
|
|
606
591
|
const args = [];
|
|
607
|
-
// eslint-disable-
|
|
608
|
-
while (true) {
|
|
592
|
+
while (true) { // eslint-disable-line no-constant-condition
|
|
609
593
|
// Function close?
|
|
610
594
|
const matchFunctionClose = argText.match(rExprFunctionClose);
|
|
611
595
|
if (matchFunctionClose !== null) {
|
|
@@ -680,6 +664,24 @@ function parseUnaryExpression(exprText) {
|
|
|
680
664
|
}
|
|
681
665
|
|
|
682
666
|
|
|
667
|
+
// BareScript expression regex
|
|
668
|
+
const rExprBinaryOp = /^\s*(\*\*|\*|\/|%|\+|-|<=|<|>=|>|==|!=|&&|\|\|)/;
|
|
669
|
+
const rExprUnaryOp = /^\s*(!|-)/;
|
|
670
|
+
const rExprFunctionOpen = /^\s*([A-Za-z_]\w+)\s*\(/;
|
|
671
|
+
const rExprFunctionSeparator = /^\s*,/;
|
|
672
|
+
const rExprFunctionClose = /^\s*\)/;
|
|
673
|
+
const rExprGroupOpen = /^\s*\(/;
|
|
674
|
+
const rExprGroupClose = /^\s*\)/;
|
|
675
|
+
const rExprNumber = /^\s*([+-]?\d+(?:\.\d*)?(?:e[+-]\d+)?)/;
|
|
676
|
+
const rExprString = /^\s*'((?:\\\\|\\'|[^'])*)'/;
|
|
677
|
+
const rExprStringEscape = /\\([\\'])/g;
|
|
678
|
+
const rExprStringDouble = /^\s*"((?:\\\\|\\"|[^"])*)"/;
|
|
679
|
+
const rExprStringDoubleEscape = /\\([\\"])/g;
|
|
680
|
+
const rExprVariable = /^\s*([A-Za-z_]\w*)/;
|
|
681
|
+
const rExprVariableEx = /^\s*\[\s*((?:\\\]|[^\]])+)\s*\]/;
|
|
682
|
+
const rExprVariableExEscape = /\\([\\\]])/g;
|
|
683
|
+
|
|
684
|
+
|
|
683
685
|
/**
|
|
684
686
|
* A BareScript parser error
|
|
685
687
|
*
|
package/lib/runtime.js
CHANGED
|
@@ -4,52 +4,14 @@
|
|
|
4
4
|
/** @module lib/runtime */
|
|
5
5
|
|
|
6
6
|
import {defaultMaxStatements, expressionFunctions, scriptFunctions} from './library.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* The BareScript runtime options
|
|
11
|
-
*
|
|
12
|
-
* @typedef {Object} ExecuteScriptOptions
|
|
13
|
-
* @property {boolean} [debug] - If true, execute in debug mode
|
|
14
|
-
* @property {function} [fetchFn] - The [fetch function]{@link module:lib/runtime~FetchFn}
|
|
15
|
-
* @property {Object} [globals] - The global variables
|
|
16
|
-
* @property {function} [logFn] - The [log function]{@link module:lib/runtime~LogFn}
|
|
17
|
-
* @property {number} [maxStatements] - The maximum number of statements; default is 1e9; 0 for no maximum
|
|
18
|
-
* @property {number} [statementCount] - The current statement count
|
|
19
|
-
* @property {function} [urlFn] - The [URL modifier function]{@link module:lib/runtime~URLFn}
|
|
20
|
-
* @property {string} [systemPrefix] - The system include prefix
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The fetch function
|
|
25
|
-
*
|
|
26
|
-
* @callback FetchFn
|
|
27
|
-
* @param {string} url - The URL to fetch
|
|
28
|
-
* @param {?Object} [options] - The [fetch options]{@link https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters}
|
|
29
|
-
* @returns {Promise} The fetch promise
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The log function
|
|
34
|
-
*
|
|
35
|
-
* @callback LogFn
|
|
36
|
-
* @param {string} text - The log text
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* The URL modifier function
|
|
41
|
-
*
|
|
42
|
-
* @callback URLFn
|
|
43
|
-
* @param {string} url - The URL
|
|
44
|
-
* @returns {string} The modified URL
|
|
45
|
-
*/
|
|
7
|
+
import {valueBoolean, valueCompare, valueString} from './value.js';
|
|
46
8
|
|
|
47
9
|
|
|
48
10
|
/**
|
|
49
11
|
* Execute a BareScript model
|
|
50
12
|
*
|
|
51
|
-
* @param {Object} script - The [BareScript model]
|
|
52
|
-
* @param {Object} [options = {}] - The [script execution options]{@link module:lib/
|
|
13
|
+
* @param {Object} script - The [BareScript model](model/#var.vName='BareScript')
|
|
14
|
+
* @param {Object} [options = {}] - The [script execution options]{@link module:lib/options~ExecuteScriptOptions}
|
|
53
15
|
* @returns The script result
|
|
54
16
|
* @throws [BareScriptRuntimeError]{@link module:lib/runtime.BareScriptRuntimeError}
|
|
55
17
|
*/
|
|
@@ -74,7 +36,7 @@ export function executeScript(script, options = {}) {
|
|
|
74
36
|
}
|
|
75
37
|
|
|
76
38
|
|
|
77
|
-
|
|
39
|
+
function executeScriptHelper(statements, options, locals) {
|
|
78
40
|
const {globals} = options;
|
|
79
41
|
|
|
80
42
|
// Iterate each script statement
|
|
@@ -85,8 +47,9 @@ export function executeScriptHelper(statements, options, locals) {
|
|
|
85
47
|
const [statementKey] = Object.keys(statement);
|
|
86
48
|
|
|
87
49
|
// Increment the statement counter
|
|
50
|
+
options.statementCount += 1;
|
|
88
51
|
const maxStatements = options.maxStatements ?? defaultMaxStatements;
|
|
89
|
-
if (maxStatements > 0 &&
|
|
52
|
+
if (maxStatements > 0 && options.statementCount > maxStatements) {
|
|
90
53
|
throw new BareScriptRuntimeError(`Exceeded maximum script statements (${maxStatements})`);
|
|
91
54
|
}
|
|
92
55
|
|
|
@@ -130,23 +93,7 @@ export function executeScriptHelper(statements, options, locals) {
|
|
|
130
93
|
|
|
131
94
|
// Function?
|
|
132
95
|
} else if (statementKey === 'function') {
|
|
133
|
-
globals[statement.function.name] = (args, fnOptions) =>
|
|
134
|
-
const funcLocals = {};
|
|
135
|
-
if ('args' in statement.function) {
|
|
136
|
-
const argsLength = args.length;
|
|
137
|
-
const funcArgsLength = statement.function.args.length;
|
|
138
|
-
const ixArgLast = (statement.function.lastArgArray ?? null) && (funcArgsLength - 1);
|
|
139
|
-
for (let ixArg = 0; ixArg < funcArgsLength; ixArg++) {
|
|
140
|
-
const argName = statement.function.args[ixArg];
|
|
141
|
-
if (ixArg < argsLength) {
|
|
142
|
-
funcLocals[argName] = (ixArg === ixArgLast ? args.slice(ixArg) : args[ixArg]);
|
|
143
|
-
} else {
|
|
144
|
-
funcLocals[argName] = (ixArg === ixArgLast ? [] : null);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return executeScriptHelper(statement.function.statements, fnOptions, funcLocals);
|
|
149
|
-
};
|
|
96
|
+
globals[statement.function.name] = (args, fnOptions) => scriptFunction(statement.function, args, fnOptions);
|
|
150
97
|
|
|
151
98
|
// Include?
|
|
152
99
|
} else if (statementKey === 'include') {
|
|
@@ -158,14 +105,33 @@ export function executeScriptHelper(statements, options, locals) {
|
|
|
158
105
|
}
|
|
159
106
|
|
|
160
107
|
|
|
108
|
+
// Runtime script function implementation
|
|
109
|
+
export function scriptFunction(function_, args, options) {
|
|
110
|
+
const funcLocals = {};
|
|
111
|
+
if ('args' in function_) {
|
|
112
|
+
const argsLength = args.length;
|
|
113
|
+
const funcArgsLength = function_.args.length;
|
|
114
|
+
const ixArgLast = (function_.lastArgArray ?? null) && (funcArgsLength - 1);
|
|
115
|
+
for (let ixArg = 0; ixArg < funcArgsLength; ixArg++) {
|
|
116
|
+
const argName = function_.args[ixArg];
|
|
117
|
+
if (ixArg < argsLength) {
|
|
118
|
+
funcLocals[argName] = (ixArg === ixArgLast ? args.slice(ixArg) : args[ixArg]);
|
|
119
|
+
} else {
|
|
120
|
+
funcLocals[argName] = (ixArg === ixArgLast ? [] : null);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return executeScriptHelper(function_.statements, options, funcLocals);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
161
128
|
/**
|
|
162
129
|
* Evaluate an expression model
|
|
163
130
|
*
|
|
164
|
-
* @param {Object} expr - The [expression model]
|
|
165
|
-
* @param {?Object} [options = null] - The [script execution options]{@link module:lib/
|
|
131
|
+
* @param {Object} expr - The [expression model](./model/#var.vName='Expression')
|
|
132
|
+
* @param {?Object} [options = null] - The [script execution options]{@link module:lib/options~ExecuteScriptOptions}
|
|
166
133
|
* @param {?Object} [locals = null] - The local variables
|
|
167
|
-
* @param {boolean} [builtins = true] - If true, include the
|
|
168
|
-
* [built-in expression functions]{@link https://craigahobbs.github.io/bare-script/library/expression.html}
|
|
134
|
+
* @param {boolean} [builtins = true] - If true, include the [built-in expression functions](./library/expression.html)
|
|
169
135
|
* @returns The expression result
|
|
170
136
|
* @throws [BareScriptRuntimeError]{@link module:lib/runtime.BareScriptRuntimeError}
|
|
171
137
|
*/
|
|
@@ -257,40 +223,90 @@ export function evaluateExpression(expr, options = null, locals = null, builtins
|
|
|
257
223
|
const binOp = expr.binary.op;
|
|
258
224
|
const leftValue = evaluateExpression(expr.binary.left, options, locals, builtins);
|
|
259
225
|
|
|
260
|
-
// Short-circuiting binary
|
|
226
|
+
// Short-circuiting "and" binary operator
|
|
261
227
|
if (binOp === '&&') {
|
|
262
|
-
|
|
228
|
+
if (!valueBoolean(leftValue)) {
|
|
229
|
+
return leftValue;
|
|
230
|
+
}
|
|
231
|
+
return evaluateExpression(expr.binary.right, options, locals, builtins);
|
|
232
|
+
|
|
233
|
+
// Short-circuiting "or" binary operator
|
|
263
234
|
} else if (binOp === '||') {
|
|
264
|
-
|
|
235
|
+
if (valueBoolean(leftValue)) {
|
|
236
|
+
return leftValue;
|
|
237
|
+
}
|
|
238
|
+
return evaluateExpression(expr.binary.right, options, locals, builtins);
|
|
265
239
|
}
|
|
266
240
|
|
|
267
241
|
// Non-short-circuiting binary operators
|
|
268
242
|
const rightValue = evaluateExpression(expr.binary.right, options, locals, builtins);
|
|
269
|
-
if (binOp === '
|
|
270
|
-
|
|
243
|
+
if (binOp === '+') {
|
|
244
|
+
// number + number
|
|
245
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
246
|
+
return leftValue + rightValue;
|
|
247
|
+
|
|
248
|
+
// string + string
|
|
249
|
+
} else if (typeof leftValue === 'string' && typeof rightValue === 'string') {
|
|
250
|
+
return leftValue + rightValue;
|
|
251
|
+
|
|
252
|
+
// string + <any>
|
|
253
|
+
} else if (typeof leftValue === 'string') {
|
|
254
|
+
return leftValue + valueString(rightValue);
|
|
255
|
+
} else if (typeof rightValue === 'string') {
|
|
256
|
+
return valueString(leftValue) + rightValue;
|
|
257
|
+
|
|
258
|
+
// datetime + number
|
|
259
|
+
} else if (leftValue instanceof Date && typeof rightValue === 'number') {
|
|
260
|
+
return new Date(leftValue.getTime() + rightValue);
|
|
261
|
+
} else if (typeof leftValue === 'number' && rightValue instanceof Date) {
|
|
262
|
+
return new Date(leftValue + rightValue.getTime());
|
|
263
|
+
}
|
|
264
|
+
} else if (binOp === '-') {
|
|
265
|
+
// number - number
|
|
266
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
267
|
+
return leftValue - rightValue;
|
|
268
|
+
|
|
269
|
+
// datetime - datetime
|
|
270
|
+
} else if (leftValue instanceof Date && rightValue instanceof Date) {
|
|
271
|
+
return leftValue - rightValue;
|
|
272
|
+
}
|
|
271
273
|
} else if (binOp === '*') {
|
|
272
|
-
|
|
274
|
+
// number * number
|
|
275
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
276
|
+
return leftValue * rightValue;
|
|
277
|
+
}
|
|
273
278
|
} else if (binOp === '/') {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
279
|
+
// number / number
|
|
280
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
281
|
+
return leftValue / rightValue;
|
|
282
|
+
}
|
|
283
|
+
} else if (binOp === '==') {
|
|
284
|
+
return valueCompare(leftValue, rightValue) === 0;
|
|
285
|
+
} else if (binOp === '!=') {
|
|
286
|
+
return valueCompare(leftValue, rightValue) !== 0;
|
|
281
287
|
} else if (binOp === '<=') {
|
|
282
|
-
return leftValue <=
|
|
288
|
+
return valueCompare(leftValue, rightValue) <= 0;
|
|
283
289
|
} else if (binOp === '<') {
|
|
284
|
-
return leftValue <
|
|
290
|
+
return valueCompare(leftValue, rightValue) < 0;
|
|
285
291
|
} else if (binOp === '>=') {
|
|
286
|
-
return leftValue >=
|
|
292
|
+
return valueCompare(leftValue, rightValue) >= 0;
|
|
287
293
|
} else if (binOp === '>') {
|
|
288
|
-
return leftValue >
|
|
289
|
-
} else if (binOp === '
|
|
290
|
-
|
|
294
|
+
return valueCompare(leftValue, rightValue) > 0;
|
|
295
|
+
} else if (binOp === '%') {
|
|
296
|
+
// number % number
|
|
297
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
298
|
+
return leftValue % rightValue;
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
// binOp === '**'
|
|
302
|
+
// number ** number
|
|
303
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
304
|
+
return leftValue ** rightValue;
|
|
305
|
+
}
|
|
291
306
|
}
|
|
292
|
-
|
|
293
|
-
|
|
307
|
+
|
|
308
|
+
// Invalid operation values
|
|
309
|
+
return null;
|
|
294
310
|
}
|
|
295
311
|
|
|
296
312
|
// Unary expression
|
|
@@ -298,10 +314,13 @@ export function evaluateExpression(expr, options = null, locals = null, builtins
|
|
|
298
314
|
const unaryOp = expr.unary.op;
|
|
299
315
|
const value = evaluateExpression(expr.unary.expr, options, locals, builtins);
|
|
300
316
|
if (unaryOp === '!') {
|
|
301
|
-
return !value;
|
|
317
|
+
return !valueBoolean(value);
|
|
318
|
+
} else if (unaryOp === '-' && typeof value === 'number') {
|
|
319
|
+
return -value;
|
|
302
320
|
}
|
|
303
|
-
|
|
304
|
-
|
|
321
|
+
|
|
322
|
+
// Invalid operation value
|
|
323
|
+
return null;
|
|
305
324
|
}
|
|
306
325
|
|
|
307
326
|
// Expression group
|