bare-script 3.5.5 → 3.6.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/lib/bare.js +6 -10
- package/lib/include/args.bare +8 -8
- package/lib/include/dataTable.bare +211 -0
- package/lib/include/diff.bare +1 -1
- package/lib/include/markdownUp.bare +8 -117
- package/lib/include/pager.bare +3 -3
- package/lib/include/unittest.bare +365 -89
- package/lib/library.js +58 -0
- package/lib/model.js +70 -12
- package/lib/options.js +45 -2
- package/lib/parser.js +96 -68
- package/lib/runtime.js +87 -28
- package/lib/runtimeAsync.js +68 -49
- package/package.json +2 -2
package/lib/runtime.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/** @module lib/runtime */
|
|
5
5
|
|
|
6
6
|
import {ValueArgsError, valueBoolean, valueCompare, valueString} from './value.js';
|
|
7
|
-
import {defaultMaxStatements, expressionFunctions, scriptFunctions} from './library.js';
|
|
7
|
+
import {coverageGlobalName, defaultMaxStatements, expressionFunctions, scriptFunctions} from './library.js';
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -32,11 +32,11 @@ export function executeScript(script, options = {}) {
|
|
|
32
32
|
|
|
33
33
|
// Execute the script
|
|
34
34
|
options.statementCount = 0;
|
|
35
|
-
return executeScriptHelper(script.statements, options, null);
|
|
35
|
+
return executeScriptHelper(script, script.statements, options, null);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
function executeScriptHelper(statements, options, locals) {
|
|
39
|
+
function executeScriptHelper(script, statements, options, locals) {
|
|
40
40
|
const {globals} = options;
|
|
41
41
|
|
|
42
42
|
// Iterate each script statement
|
|
@@ -50,12 +50,19 @@ function executeScriptHelper(statements, options, locals) {
|
|
|
50
50
|
options.statementCount = (options.statementCount ?? 0) + 1;
|
|
51
51
|
const maxStatements = options.maxStatements ?? defaultMaxStatements;
|
|
52
52
|
if (maxStatements > 0 && options.statementCount > maxStatements) {
|
|
53
|
-
throw new BareScriptRuntimeError(`Exceeded maximum script statements (${maxStatements})`);
|
|
53
|
+
throw new BareScriptRuntimeError(script, statement, `Exceeded maximum script statements (${maxStatements})`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Record the statement coverage
|
|
57
|
+
const coverageGlobal = globals[coverageGlobalName] ?? null;
|
|
58
|
+
const hasCoverage = coverageGlobal !== null && typeof coverageGlobal === 'object' && coverageGlobal.enabled && !script.system;
|
|
59
|
+
if (hasCoverage) {
|
|
60
|
+
recordStatementCoverage(script, statement, statementKey, coverageGlobal);
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
// Expression?
|
|
57
64
|
if (statementKey === 'expr') {
|
|
58
|
-
const exprValue = evaluateExpression(statement.expr.expr, options, locals, false);
|
|
65
|
+
const exprValue = evaluateExpression(statement.expr.expr, options, locals, false, script, statement);
|
|
59
66
|
if ('name' in statement.expr) {
|
|
60
67
|
if (locals !== null) {
|
|
61
68
|
locals[statement.expr.name] = exprValue;
|
|
@@ -67,14 +74,14 @@ function executeScriptHelper(statements, options, locals) {
|
|
|
67
74
|
// Jump?
|
|
68
75
|
} else if (statementKey === 'jump') {
|
|
69
76
|
// Evaluate the expression (if any)
|
|
70
|
-
if (!('expr' in statement.jump) || evaluateExpression(statement.jump.expr, options, locals, false)) {
|
|
77
|
+
if (!('expr' in statement.jump) || evaluateExpression(statement.jump.expr, options, locals, false, script, statement)) {
|
|
71
78
|
// Find the label
|
|
72
79
|
if (labelIndexes !== null && statement.jump.label in labelIndexes) {
|
|
73
80
|
ixStatement = labelIndexes[statement.jump.label];
|
|
74
81
|
} else {
|
|
75
|
-
const ixLabel = statements.findIndex((stmt) => stmt.label === statement.jump.label);
|
|
82
|
+
const ixLabel = statements.findIndex((stmt) => 'label' in stmt && stmt.label.name === statement.jump.label);
|
|
76
83
|
if (ixLabel === -1) {
|
|
77
|
-
throw new BareScriptRuntimeError(`Unknown jump label "${statement.jump.label}"`);
|
|
84
|
+
throw new BareScriptRuntimeError(script, statement, `Unknown jump label "${statement.jump.label}"`);
|
|
78
85
|
}
|
|
79
86
|
if (labelIndexes === null) {
|
|
80
87
|
labelIndexes = {};
|
|
@@ -82,22 +89,29 @@ function executeScriptHelper(statements, options, locals) {
|
|
|
82
89
|
labelIndexes[statement.jump.label] = ixLabel;
|
|
83
90
|
ixStatement = ixLabel;
|
|
84
91
|
}
|
|
92
|
+
|
|
93
|
+
// Record the label statement coverage
|
|
94
|
+
if (hasCoverage) {
|
|
95
|
+
const labelStatement = statements[ixStatement];
|
|
96
|
+
const [labelStatementKey] = Object.keys(labelStatement);
|
|
97
|
+
recordStatementCoverage(script, labelStatement, labelStatementKey, coverageGlobal);
|
|
98
|
+
}
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
// Return?
|
|
88
102
|
} else if (statementKey === 'return') {
|
|
89
103
|
if ('expr' in statement.return) {
|
|
90
|
-
return evaluateExpression(statement.return.expr, options, locals, false);
|
|
104
|
+
return evaluateExpression(statement.return.expr, options, locals, false, script, statement);
|
|
91
105
|
}
|
|
92
106
|
return null;
|
|
93
107
|
|
|
94
108
|
// Function?
|
|
95
109
|
} else if (statementKey === 'function') {
|
|
96
|
-
globals[statement.function.name] = (args, fnOptions) => scriptFunction(statement.function, args, fnOptions);
|
|
110
|
+
globals[statement.function.name] = (args, fnOptions) => scriptFunction(script, statement.function, args, fnOptions);
|
|
97
111
|
|
|
98
112
|
// Include?
|
|
99
113
|
} else if (statementKey === 'include') {
|
|
100
|
-
throw new BareScriptRuntimeError(`Include of "${statement.include.includes[0].url}" within non-async scope`);
|
|
114
|
+
throw new BareScriptRuntimeError(script, statement, `Include of "${statement.include.includes[0].url}" within non-async scope`);
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
117
|
|
|
@@ -105,8 +119,41 @@ function executeScriptHelper(statements, options, locals) {
|
|
|
105
119
|
}
|
|
106
120
|
|
|
107
121
|
|
|
122
|
+
// Helper function to record statement coverage
|
|
123
|
+
export function recordStatementCoverage(script, statement, statementKey, coverageGlobal) {
|
|
124
|
+
// Get the script name and statement line number
|
|
125
|
+
const scriptName = script.scriptName ?? null;
|
|
126
|
+
const lineno = statement[statementKey].lineNumber ?? null;
|
|
127
|
+
if (scriptName === null || lineno === null) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Record the statement/lineno coverage
|
|
132
|
+
let scripts = coverageGlobal.scripts ?? null;
|
|
133
|
+
if (scripts === null) {
|
|
134
|
+
scripts = {};
|
|
135
|
+
coverageGlobal.scripts = scripts;
|
|
136
|
+
}
|
|
137
|
+
let scriptCoverage = scripts[scriptName] ?? null;
|
|
138
|
+
if (scriptCoverage === null) {
|
|
139
|
+
scriptCoverage = {'script': script, 'covered': {}};
|
|
140
|
+
scripts[scriptName] = scriptCoverage;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Increment the statement coverage count
|
|
144
|
+
const linenoStr = String(lineno);
|
|
145
|
+
const coveredStatements = scriptCoverage.covered;
|
|
146
|
+
let coveredStatement = coveredStatements[linenoStr] ?? null;
|
|
147
|
+
if (coveredStatement === null) {
|
|
148
|
+
coveredStatement = {'statement': statement, 'count': 0};
|
|
149
|
+
coveredStatements[linenoStr] = coveredStatement;
|
|
150
|
+
}
|
|
151
|
+
coveredStatement.count += 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
108
155
|
// Runtime script function implementation
|
|
109
|
-
export function scriptFunction(function_, args, options) {
|
|
156
|
+
export function scriptFunction(script, function_, args, options) {
|
|
110
157
|
const funcLocals = {};
|
|
111
158
|
if ('args' in function_) {
|
|
112
159
|
const argsLength = args.length;
|
|
@@ -121,7 +168,7 @@ export function scriptFunction(function_, args, options) {
|
|
|
121
168
|
}
|
|
122
169
|
}
|
|
123
170
|
}
|
|
124
|
-
return executeScriptHelper(function_.statements, options, funcLocals);
|
|
171
|
+
return executeScriptHelper(script, function_.statements, options, funcLocals);
|
|
125
172
|
}
|
|
126
173
|
|
|
127
174
|
|
|
@@ -135,7 +182,7 @@ export function scriptFunction(function_, args, options) {
|
|
|
135
182
|
* @returns The expression result
|
|
136
183
|
* @throws [BareScriptRuntimeError]{@link module:lib/runtime.BareScriptRuntimeError}
|
|
137
184
|
*/
|
|
138
|
-
export function evaluateExpression(expr, options = null, locals = null, builtins = true) {
|
|
185
|
+
export function evaluateExpression(expr, options = null, locals = null, builtins = true, script = null, statement = null) {
|
|
139
186
|
const [exprKey] = Object.keys(expr);
|
|
140
187
|
const globals = (options !== null ? (options.globals ?? null) : null);
|
|
141
188
|
|
|
@@ -174,14 +221,14 @@ export function evaluateExpression(expr, options = null, locals = null, builtins
|
|
|
174
221
|
const funcName = expr.function.name;
|
|
175
222
|
if (funcName === 'if') {
|
|
176
223
|
const [valueExpr = null, trueExpr = null, falseExpr = null] = expr.function.args ?? [];
|
|
177
|
-
const value = (valueExpr !== null ? evaluateExpression(valueExpr, options, locals, builtins) : false);
|
|
224
|
+
const value = (valueExpr !== null ? evaluateExpression(valueExpr, options, locals, builtins, script, statement) : false);
|
|
178
225
|
const resultExpr = (value ? trueExpr : falseExpr);
|
|
179
|
-
return resultExpr !== null ? evaluateExpression(resultExpr, options, locals, builtins) : null;
|
|
226
|
+
return resultExpr !== null ? evaluateExpression(resultExpr, options, locals, builtins, script, statement) : null;
|
|
180
227
|
}
|
|
181
228
|
|
|
182
229
|
// Compute the function arguments
|
|
183
230
|
const funcArgs = 'args' in expr.function
|
|
184
|
-
? expr.function.args.map((arg) => evaluateExpression(arg, options, locals, builtins))
|
|
231
|
+
? expr.function.args.map((arg) => evaluateExpression(arg, options, locals, builtins, script, statement))
|
|
185
232
|
: null;
|
|
186
233
|
|
|
187
234
|
// Global/local function?
|
|
@@ -195,7 +242,7 @@ export function evaluateExpression(expr, options = null, locals = null, builtins
|
|
|
195
242
|
if (funcValue !== null) {
|
|
196
243
|
// Async function called within non-async execution?
|
|
197
244
|
if (typeof funcValue === 'function' && funcValue.constructor.name === 'AsyncFunction') {
|
|
198
|
-
throw new BareScriptRuntimeError(`Async function "${funcName}" called within non-async scope`);
|
|
245
|
+
throw new BareScriptRuntimeError(script, statement, `Async function "${funcName}" called within non-async scope`);
|
|
199
246
|
}
|
|
200
247
|
|
|
201
248
|
// Call the function
|
|
@@ -209,7 +256,10 @@ export function evaluateExpression(expr, options = null, locals = null, builtins
|
|
|
209
256
|
|
|
210
257
|
// Log and return null
|
|
211
258
|
if (options !== null && 'logFn' in options && options.debug) {
|
|
212
|
-
|
|
259
|
+
const errorMessage = new BareScriptRuntimeError(
|
|
260
|
+
script, statement, `BareScript: Function "${funcName}" failed with error: ${error.message}`
|
|
261
|
+
);
|
|
262
|
+
options.logFn(errorMessage.message);
|
|
213
263
|
}
|
|
214
264
|
if (error instanceof ValueArgsError) {
|
|
215
265
|
return error.returnValue;
|
|
@@ -218,31 +268,31 @@ export function evaluateExpression(expr, options = null, locals = null, builtins
|
|
|
218
268
|
}
|
|
219
269
|
}
|
|
220
270
|
|
|
221
|
-
throw new BareScriptRuntimeError(`Undefined function "${funcName}"`);
|
|
271
|
+
throw new BareScriptRuntimeError(script, statement, `Undefined function "${funcName}"`);
|
|
222
272
|
}
|
|
223
273
|
|
|
224
274
|
// Binary expression
|
|
225
275
|
if (exprKey === 'binary') {
|
|
226
276
|
const binOp = expr.binary.op;
|
|
227
|
-
const leftValue = evaluateExpression(expr.binary.left, options, locals, builtins);
|
|
277
|
+
const leftValue = evaluateExpression(expr.binary.left, options, locals, builtins, script, statement);
|
|
228
278
|
|
|
229
279
|
// Short-circuiting "and" binary operator
|
|
230
280
|
if (binOp === '&&') {
|
|
231
281
|
if (!valueBoolean(leftValue)) {
|
|
232
282
|
return leftValue;
|
|
233
283
|
}
|
|
234
|
-
return evaluateExpression(expr.binary.right, options, locals, builtins);
|
|
284
|
+
return evaluateExpression(expr.binary.right, options, locals, builtins, script, statement);
|
|
235
285
|
|
|
236
286
|
// Short-circuiting "or" binary operator
|
|
237
287
|
} else if (binOp === '||') {
|
|
238
288
|
if (valueBoolean(leftValue)) {
|
|
239
289
|
return leftValue;
|
|
240
290
|
}
|
|
241
|
-
return evaluateExpression(expr.binary.right, options, locals, builtins);
|
|
291
|
+
return evaluateExpression(expr.binary.right, options, locals, builtins, script, statement);
|
|
242
292
|
}
|
|
243
293
|
|
|
244
294
|
// Non-short-circuiting binary operators
|
|
245
|
-
const rightValue = evaluateExpression(expr.binary.right, options, locals, builtins);
|
|
295
|
+
const rightValue = evaluateExpression(expr.binary.right, options, locals, builtins, script, statement);
|
|
246
296
|
if (binOp === '+') {
|
|
247
297
|
// number + number
|
|
248
298
|
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
@@ -340,7 +390,7 @@ export function evaluateExpression(expr, options = null, locals = null, builtins
|
|
|
340
390
|
// Unary expression
|
|
341
391
|
if (exprKey === 'unary') {
|
|
342
392
|
const unaryOp = expr.unary.op;
|
|
343
|
-
const value = evaluateExpression(expr.unary.expr, options, locals, builtins);
|
|
393
|
+
const value = evaluateExpression(expr.unary.expr, options, locals, builtins, script, statement);
|
|
344
394
|
if (unaryOp === '!') {
|
|
345
395
|
return !valueBoolean(value);
|
|
346
396
|
} else if (unaryOp === '-') {
|
|
@@ -360,7 +410,7 @@ export function evaluateExpression(expr, options = null, locals = null, builtins
|
|
|
360
410
|
|
|
361
411
|
// Expression group
|
|
362
412
|
// else if (exprKey === 'group')
|
|
363
|
-
return evaluateExpression(expr.group, options, locals, builtins);
|
|
413
|
+
return evaluateExpression(expr.group, options, locals, builtins, script, statement);
|
|
364
414
|
}
|
|
365
415
|
|
|
366
416
|
|
|
@@ -375,8 +425,17 @@ export class BareScriptRuntimeError extends Error {
|
|
|
375
425
|
*
|
|
376
426
|
* @param {string} message - The runtime error message
|
|
377
427
|
*/
|
|
378
|
-
constructor(message) {
|
|
379
|
-
|
|
428
|
+
constructor(script, statement, message) {
|
|
429
|
+
let messageScript;
|
|
430
|
+
if (script !== null && statement !== null) {
|
|
431
|
+
const [statementKey] = Object.keys(statement);
|
|
432
|
+
const scriptName = script.scriptName ?? '';
|
|
433
|
+
const lineno = statement[statementKey].lineNumber ?? '';
|
|
434
|
+
messageScript = (scriptName && lineno ? `${scriptName}:${lineno}: ${message}` : message);
|
|
435
|
+
} else {
|
|
436
|
+
messageScript = message;
|
|
437
|
+
}
|
|
438
|
+
super(messageScript);
|
|
380
439
|
this.name = this.constructor.name;
|
|
381
440
|
}
|
|
382
441
|
}
|
package/lib/runtimeAsync.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
/** @module lib/runtimeAsync */
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {BareScriptRuntimeError, evaluateExpression, scriptFunction} from './runtime.js';
|
|
6
|
+
import {BareScriptRuntimeError, evaluateExpression, recordStatementCoverage, scriptFunction} from './runtime.js';
|
|
8
7
|
import {ValueArgsError, valueBoolean, valueCompare, valueString} from './value.js';
|
|
9
|
-
import {defaultMaxStatements, expressionFunctions, scriptFunctions} from './library.js';
|
|
8
|
+
import {coverageGlobalName, defaultMaxStatements, expressionFunctions, scriptFunctions} from './library.js';
|
|
10
9
|
import {lintScript} from './model.js';
|
|
10
|
+
import {parseScript} from './parser.js';
|
|
11
11
|
import {urlFileRelative} from './options.js';
|
|
12
12
|
|
|
13
13
|
|
|
@@ -37,11 +37,11 @@ export function executeScriptAsync(script, options = {}) {
|
|
|
37
37
|
|
|
38
38
|
// Execute the script
|
|
39
39
|
options.statementCount = 0;
|
|
40
|
-
return executeScriptHelperAsync(script.statements, options, null);
|
|
40
|
+
return executeScriptHelperAsync(script, script.statements, options, null);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
async function executeScriptHelperAsync(statements, options, locals) {
|
|
44
|
+
async function executeScriptHelperAsync(script, statements, options, locals) {
|
|
45
45
|
const {globals} = options;
|
|
46
46
|
|
|
47
47
|
// Iterate each script statement
|
|
@@ -55,12 +55,19 @@ async function executeScriptHelperAsync(statements, options, locals) {
|
|
|
55
55
|
options.statementCount = (options.statementCount ?? 0) + 1;
|
|
56
56
|
const maxStatements = options.maxStatements ?? defaultMaxStatements;
|
|
57
57
|
if (maxStatements > 0 && options.statementCount > maxStatements) {
|
|
58
|
-
throw new BareScriptRuntimeError(`Exceeded maximum script statements (${maxStatements})`);
|
|
58
|
+
throw new BareScriptRuntimeError(script, statement, `Exceeded maximum script statements (${maxStatements})`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Record the statement coverage
|
|
62
|
+
const coverageGlobal = globals[coverageGlobalName] ?? null;
|
|
63
|
+
const hasCoverage = coverageGlobal !== null && typeof coverageGlobal === 'object' && coverageGlobal.enabled && !script.system;
|
|
64
|
+
if (hasCoverage) {
|
|
65
|
+
recordStatementCoverage(script, statement, statementKey, coverageGlobal);
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
// Expression?
|
|
62
69
|
if (statementKey === 'expr') {
|
|
63
|
-
const exprValue = await evaluateExpressionAsync(statement.expr.expr, options, locals, false);
|
|
70
|
+
const exprValue = await evaluateExpressionAsync(statement.expr.expr, options, locals, false, script, statement);
|
|
64
71
|
if ('name' in statement.expr) {
|
|
65
72
|
if (locals !== null) {
|
|
66
73
|
locals[statement.expr.name] = exprValue;
|
|
@@ -72,14 +79,15 @@ async function executeScriptHelperAsync(statements, options, locals) {
|
|
|
72
79
|
// Jump?
|
|
73
80
|
} else if (statementKey === 'jump') {
|
|
74
81
|
// Evaluate the expression (if any)
|
|
75
|
-
if (!('expr' in statement.jump) ||
|
|
82
|
+
if (!('expr' in statement.jump) ||
|
|
83
|
+
await evaluateExpressionAsync(statement.jump.expr, options, locals, false, script, statement)) {
|
|
76
84
|
// Find the label
|
|
77
85
|
if (labelIndexes !== null && statement.jump.label in labelIndexes) {
|
|
78
86
|
ixStatement = labelIndexes[statement.jump.label];
|
|
79
87
|
} else {
|
|
80
|
-
const ixLabel = statements.findIndex((stmt) => stmt.label === statement.jump.label);
|
|
88
|
+
const ixLabel = statements.findIndex((stmt) => 'label' in stmt && stmt.label.name === statement.jump.label);
|
|
81
89
|
if (ixLabel === -1) {
|
|
82
|
-
throw new BareScriptRuntimeError(`Unknown jump label "${statement.jump.label}"`);
|
|
90
|
+
throw new BareScriptRuntimeError(script, statement, `Unknown jump label "${statement.jump.label}"`);
|
|
83
91
|
}
|
|
84
92
|
if (labelIndexes === null) {
|
|
85
93
|
labelIndexes = {};
|
|
@@ -87,22 +95,30 @@ async function executeScriptHelperAsync(statements, options, locals) {
|
|
|
87
95
|
labelIndexes[statement.jump.label] = ixLabel;
|
|
88
96
|
ixStatement = ixLabel;
|
|
89
97
|
}
|
|
98
|
+
|
|
99
|
+
// Record the label statement coverage
|
|
100
|
+
if (hasCoverage) {
|
|
101
|
+
const labelStatement = statements[ixStatement];
|
|
102
|
+
const [labelStatementKey] = Object.keys(labelStatement);
|
|
103
|
+
recordStatementCoverage(script, labelStatement, labelStatementKey, coverageGlobal);
|
|
104
|
+
}
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
// Return?
|
|
93
108
|
} else if (statementKey === 'return') {
|
|
94
109
|
if ('expr' in statement.return) {
|
|
95
|
-
return evaluateExpressionAsync(statement.return.expr, options, locals, false);
|
|
110
|
+
return evaluateExpressionAsync(statement.return.expr, options, locals, false, script, statement);
|
|
96
111
|
}
|
|
97
112
|
return null;
|
|
98
113
|
|
|
99
114
|
// Function?
|
|
100
115
|
} else if (statementKey === 'function') {
|
|
101
116
|
if (statement.function.async) {
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
globals[statement.function.name] =
|
|
118
|
+
// eslint-disable-next-line require-await
|
|
119
|
+
async (args, fnOptions) => scriptFunctionAsync(script, statement.function, args, fnOptions);
|
|
104
120
|
} else {
|
|
105
|
-
globals[statement.function.name] = (args, fnOptions) => scriptFunction(statement.function, args, fnOptions);
|
|
121
|
+
globals[statement.function.name] = (args, fnOptions) => scriptFunction(script, statement.function, args, fnOptions);
|
|
106
122
|
}
|
|
107
123
|
|
|
108
124
|
// Include?
|
|
@@ -110,48 +126,49 @@ async function executeScriptHelperAsync(statements, options, locals) {
|
|
|
110
126
|
// Fetch the include script text
|
|
111
127
|
const urlFn = options.urlFn ?? null;
|
|
112
128
|
const includeURLs = statement.include.includes.map(({url, system = false}) => {
|
|
129
|
+
let includeURL;
|
|
113
130
|
if (system && 'systemPrefix' in options) {
|
|
114
|
-
|
|
131
|
+
includeURL = urlFileRelative(options.systemPrefix, url);
|
|
132
|
+
} else {
|
|
133
|
+
includeURL = (urlFn !== null ? urlFn(url) : url);
|
|
115
134
|
}
|
|
116
|
-
return
|
|
135
|
+
return {includeURL, 'systemInclude': system};
|
|
117
136
|
});
|
|
118
|
-
const responses = await Promise.all(includeURLs.map(async (
|
|
137
|
+
const responses = await Promise.all(includeURLs.map(async ({includeURL, systemInclude}) => {
|
|
119
138
|
try {
|
|
120
|
-
|
|
139
|
+
const response = ('fetchFn' in options ? await options.fetchFn(includeURL) : null);
|
|
140
|
+
return {response, systemInclude};
|
|
121
141
|
} catch {
|
|
122
|
-
return null;
|
|
142
|
+
return {'response': null, systemInclude};
|
|
123
143
|
}
|
|
124
144
|
}));
|
|
125
|
-
const
|
|
145
|
+
const includeTexts = await Promise.all(responses.map(async ({response, systemInclude}) => {
|
|
126
146
|
try {
|
|
127
|
-
|
|
147
|
+
const includeText = (response !== null && response.ok ? await response.text() : null);
|
|
148
|
+
return {includeText, systemInclude};
|
|
128
149
|
} catch {
|
|
129
|
-
return null;
|
|
150
|
+
return {'includeText': null, systemInclude};
|
|
130
151
|
}
|
|
131
152
|
}));
|
|
132
153
|
|
|
133
154
|
// Parse and execute each script
|
|
134
|
-
for (const [ixScriptText,
|
|
135
|
-
const includeURL = includeURLs[ixScriptText];
|
|
155
|
+
for (const [ixScriptText, {includeText, systemInclude}] of includeTexts.entries()) {
|
|
156
|
+
const {includeURL} = includeURLs[ixScriptText];
|
|
136
157
|
|
|
137
158
|
// Error?
|
|
138
|
-
if (
|
|
139
|
-
throw new BareScriptRuntimeError(`Include of "${includeURL}" failed`);
|
|
159
|
+
if (includeText === null) {
|
|
160
|
+
throw new BareScriptRuntimeError(script, statement, `Include of "${includeURL}" failed`);
|
|
140
161
|
}
|
|
141
162
|
|
|
142
163
|
// Parse the include script
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
} catch (error) {
|
|
147
|
-
throw new BareScriptParserError(
|
|
148
|
-
error.error, error.line, error.columnNumber, error.lineNumber, `Included from "${includeURL}"`
|
|
149
|
-
);
|
|
164
|
+
const includeScript = parseScript(includeText, 1, includeURL);
|
|
165
|
+
if (systemInclude) {
|
|
166
|
+
includeScript.system = true;
|
|
150
167
|
}
|
|
151
168
|
|
|
152
169
|
// Run the bare-script linter?
|
|
153
170
|
if ('logFn' in options && options.debug) {
|
|
154
|
-
const warnings = lintScript(
|
|
171
|
+
const warnings = lintScript(includeScript);
|
|
155
172
|
const warningPrefix = `BareScript: Include "${includeURL}" static analysis...`;
|
|
156
173
|
if (warnings.length) {
|
|
157
174
|
options.logFn(`${warningPrefix} ${warnings.length} warning${warnings.length > 1 ? 's' : ''}:`);
|
|
@@ -164,7 +181,7 @@ async function executeScriptHelperAsync(statements, options, locals) {
|
|
|
164
181
|
// Execute the include script
|
|
165
182
|
const includeOptions = {...options};
|
|
166
183
|
includeOptions.urlFn = (url) => urlFileRelative(includeURL, url);
|
|
167
|
-
await executeScriptHelperAsync(
|
|
184
|
+
await executeScriptHelperAsync(includeScript, includeScript.statements, includeOptions, null);
|
|
168
185
|
}
|
|
169
186
|
}
|
|
170
187
|
}
|
|
@@ -174,7 +191,7 @@ async function executeScriptHelperAsync(statements, options, locals) {
|
|
|
174
191
|
|
|
175
192
|
|
|
176
193
|
// Runtime script async function implementation
|
|
177
|
-
function scriptFunctionAsync(function_, args, options) {
|
|
194
|
+
function scriptFunctionAsync(script, function_, args, options) {
|
|
178
195
|
const funcLocals = {};
|
|
179
196
|
if ('args' in function_) {
|
|
180
197
|
const argsLength = args.length;
|
|
@@ -189,7 +206,7 @@ function scriptFunctionAsync(function_, args, options) {
|
|
|
189
206
|
}
|
|
190
207
|
}
|
|
191
208
|
}
|
|
192
|
-
return executeScriptHelperAsync(function_.statements, options, funcLocals);
|
|
209
|
+
return executeScriptHelperAsync(script, function_.statements, options, funcLocals);
|
|
193
210
|
}
|
|
194
211
|
|
|
195
212
|
|
|
@@ -205,14 +222,14 @@ function scriptFunctionAsync(function_, args, options) {
|
|
|
205
222
|
* @returns The expression result
|
|
206
223
|
* @throws [BareScriptRuntimeError]{@link module:lib/runtime.BareScriptRuntimeError}
|
|
207
224
|
*/
|
|
208
|
-
export async function evaluateExpressionAsync(expr, options = null, locals = null, builtins = true) {
|
|
225
|
+
export async function evaluateExpressionAsync(expr, options = null, locals = null, builtins = true, script = null, statement = null) {
|
|
209
226
|
const [exprKey] = Object.keys(expr);
|
|
210
227
|
const globals = (options !== null ? (options.globals ?? null) : null);
|
|
211
228
|
|
|
212
229
|
// If this expression does not require async then evaluate non-async
|
|
213
230
|
const hasSubExpr = (exprKey !== 'number' && exprKey !== 'string' && exprKey !== 'variable');
|
|
214
231
|
if (hasSubExpr && !isAsyncExpr(expr, globals, locals)) {
|
|
215
|
-
return evaluateExpression(expr, options, locals, builtins);
|
|
232
|
+
return evaluateExpression(expr, options, locals, builtins, script, statement);
|
|
216
233
|
}
|
|
217
234
|
|
|
218
235
|
// Number
|
|
@@ -250,15 +267,17 @@ export async function evaluateExpressionAsync(expr, options = null, locals = nul
|
|
|
250
267
|
const funcName = expr.function.name;
|
|
251
268
|
if (funcName === 'if') {
|
|
252
269
|
const [valueExpr, trueExpr = null, falseExpr = null] = expr.function.args;
|
|
253
|
-
const value = await evaluateExpressionAsync(valueExpr, options, locals, builtins);
|
|
270
|
+
const value = await evaluateExpressionAsync(valueExpr, options, locals, builtins, script, statement);
|
|
254
271
|
const resultExpr = (value ? trueExpr : falseExpr);
|
|
255
|
-
return resultExpr !== null ? evaluateExpressionAsync(resultExpr, options, locals, builtins) : null;
|
|
272
|
+
return resultExpr !== null ? evaluateExpressionAsync(resultExpr, options, locals, builtins, script, statement) : null;
|
|
256
273
|
}
|
|
257
274
|
|
|
258
275
|
// Compute the function arguments
|
|
259
276
|
const funcArgs = 'args' in expr.function
|
|
260
|
-
|
|
261
|
-
|
|
277
|
+
? await Promise.all(expr.function.args.map(
|
|
278
|
+
(arg) => evaluateExpressionAsync(arg, options, locals, builtins, script, statement)
|
|
279
|
+
))
|
|
280
|
+
: null;
|
|
262
281
|
|
|
263
282
|
// Global/local function?
|
|
264
283
|
let funcValue = (locals !== null ? locals[funcName] : undefined);
|
|
@@ -289,20 +308,20 @@ export async function evaluateExpressionAsync(expr, options = null, locals = nul
|
|
|
289
308
|
}
|
|
290
309
|
}
|
|
291
310
|
|
|
292
|
-
throw new BareScriptRuntimeError(`Undefined function "${funcName}"`);
|
|
311
|
+
throw new BareScriptRuntimeError(script, statement, `Undefined function "${funcName}"`);
|
|
293
312
|
}
|
|
294
313
|
|
|
295
314
|
// Binary expression
|
|
296
315
|
if (exprKey === 'binary') {
|
|
297
316
|
const binOp = expr.binary.op;
|
|
298
|
-
const leftValue = await evaluateExpressionAsync(expr.binary.left, options, locals, builtins);
|
|
317
|
+
const leftValue = await evaluateExpressionAsync(expr.binary.left, options, locals, builtins, script, statement);
|
|
299
318
|
|
|
300
319
|
// Short-circuiting "and" binary operator
|
|
301
320
|
if (binOp === '&&') {
|
|
302
321
|
if (!valueBoolean(leftValue)) {
|
|
303
322
|
return leftValue;
|
|
304
323
|
}
|
|
305
|
-
return evaluateExpressionAsync(expr.binary.right, options, locals, builtins);
|
|
324
|
+
return evaluateExpressionAsync(expr.binary.right, options, locals, builtins, script, statement);
|
|
306
325
|
|
|
307
326
|
// Short-circuiting "or" binary operator
|
|
308
327
|
} else if (binOp === '||') {
|
|
@@ -313,7 +332,7 @@ export async function evaluateExpressionAsync(expr, options = null, locals = nul
|
|
|
313
332
|
}
|
|
314
333
|
|
|
315
334
|
// Non-short-circuiting binary operators
|
|
316
|
-
const rightValue = await evaluateExpressionAsync(expr.binary.right, options, locals, builtins);
|
|
335
|
+
const rightValue = await evaluateExpressionAsync(expr.binary.right, options, locals, builtins, script, statement);
|
|
317
336
|
if (binOp === '+') {
|
|
318
337
|
// number + number
|
|
319
338
|
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
@@ -411,7 +430,7 @@ export async function evaluateExpressionAsync(expr, options = null, locals = nul
|
|
|
411
430
|
// Unary expression
|
|
412
431
|
if (exprKey === 'unary') {
|
|
413
432
|
const unaryOp = expr.unary.op;
|
|
414
|
-
const value = await evaluateExpressionAsync(expr.unary.expr, options, locals, builtins);
|
|
433
|
+
const value = await evaluateExpressionAsync(expr.unary.expr, options, locals, builtins, script, statement);
|
|
415
434
|
if (unaryOp === '!') {
|
|
416
435
|
return !valueBoolean(value);
|
|
417
436
|
} else if (unaryOp === '-') {
|
|
@@ -431,7 +450,7 @@ export async function evaluateExpressionAsync(expr, options = null, locals = nul
|
|
|
431
450
|
|
|
432
451
|
// Expression group
|
|
433
452
|
// else if (exprKey === 'group')
|
|
434
|
-
return evaluateExpressionAsync(expr.group, options, locals, builtins);
|
|
453
|
+
return evaluateExpressionAsync(expr.group, options, locals, builtins, script, statement);
|
|
435
454
|
}
|
|
436
455
|
|
|
437
456
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "bare-script",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.6.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": "~10.1",
|
|
34
|
-
"eslint": "~9.
|
|
34
|
+
"eslint": "~9.36",
|
|
35
35
|
"jsdoc": "~4.0"
|
|
36
36
|
}
|
|
37
37
|
}
|