bare-script 3.5.5 → 3.7.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/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
- options.logFn(`BareScript: Function "${funcName}" failed with error: ${error.message}`);
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
- super(message);
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
  }
@@ -3,11 +3,11 @@
3
3
 
4
4
  /** @module lib/runtimeAsync */
5
5
 
6
- import {BareScriptParserError, parseScript} from './parser.js';
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) || await evaluateExpressionAsync(statement.jump.expr, options, locals, false)) {
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
- // eslint-disable-next-line require-await
103
- globals[statement.function.name] = async (args, fnOptions) => scriptFunctionAsync(statement.function, args, fnOptions);
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
- return urlFileRelative(options.systemPrefix, url);
131
+ includeURL = urlFileRelative(options.systemPrefix, url);
132
+ } else {
133
+ includeURL = (urlFn !== null ? urlFn(url) : url);
115
134
  }
116
- return urlFn !== null ? urlFn(url) : url;
135
+ return {includeURL, 'systemInclude': system};
117
136
  });
118
- const responses = await Promise.all(includeURLs.map(async (url) => {
137
+ const responses = await Promise.all(includeURLs.map(async ({includeURL, systemInclude}) => {
119
138
  try {
120
- return 'fetchFn' in options ? await options.fetchFn(url) : null;
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 scriptTexts = await Promise.all(responses.map(async (response) => {
145
+ const includeTexts = await Promise.all(responses.map(async ({response, systemInclude}) => {
126
146
  try {
127
- return response !== null && response.ok ? await response.text() : null;
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, scriptText] of scriptTexts.entries()) {
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 (scriptText === null) {
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
- let scriptModel;
144
- try {
145
- scriptModel = parseScript(scriptText);
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(scriptModel);
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(scriptModel.statements, includeOptions, null);
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
- ? await Promise.all(expr.function.args.map((arg) => evaluateExpressionAsync(arg, options, locals, builtins)))
261
- : null;
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.5.5",
4
+ "version": "3.7.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.35",
34
+ "eslint": "~9.36",
35
35
  "jsdoc": "~4.0"
36
36
  }
37
37
  }