bare-script 3.7.2 → 3.7.3

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 CHANGED
@@ -91,13 +91,13 @@ export async function main(options) {
91
91
  if (args.static || args.debug) {
92
92
  const warnings = lintScript(script);
93
93
  if (warnings.length === 0) {
94
- options.logFn(`BareScript: Static analysis "${scriptName}" ... OK`);
94
+ options.logFn(`BareScript static analysis "${scriptName}" ... OK`);
95
95
  } else {
96
96
  options.logFn(
97
- `BareScript: Static analysis "${scriptName}" ... ${warnings.length} warning${warnings.length > 1 ? 's' : ''}:`
97
+ `BareScript static analysis "${scriptName}" ... ${warnings.length} warning${warnings.length > 1 ? 's' : ''}:`
98
98
  );
99
99
  for (const warning of warnings) {
100
- options.logFn(`BareScript: ${warning}`);
100
+ options.logFn(warning);
101
101
  }
102
102
  if (args.static) {
103
103
  statusCode = 1;
@@ -131,7 +131,7 @@ export async function main(options) {
131
131
  // Log script execution end with timing
132
132
  if (args.debug) {
133
133
  const timeEnd = performance.now();
134
- options.logFn(`BareScript: Script executed in ${(timeEnd - timeBegin).toFixed(1)} milliseconds`);
134
+ options.logFn(`BareScript executed in ${(timeEnd - timeBegin).toFixed(1)} milliseconds`);
135
135
  }
136
136
 
137
137
  // Stop on error status code
package/lib/model.js CHANGED
@@ -324,21 +324,20 @@ export function validateExpression(expr) {
324
324
  */
325
325
  export function lintScript(script) {
326
326
  const warnings = [];
327
+ const {statements} = script;
327
328
 
328
329
  // Empty script?
329
- if (script.statements.length === 0) {
330
- warnings.push('Empty script');
330
+ if (statements.length === 0) {
331
+ lintScriptWarning(warnings, script, null, 'Empty script');
331
332
  }
332
333
 
333
334
  // Variable used before assignment?
334
335
  const varAssigns = {};
335
336
  const varUses = {};
336
- getVariableAssignmentsAndUses(script.statements, varAssigns, varUses);
337
+ getVariableAssignmentsAndUses(statements, varAssigns, varUses);
337
338
  for (const varName of Object.keys(varAssigns)) {
338
339
  if (varName in varUses && varUses[varName] <= varAssigns[varName]) {
339
- warnings.push(
340
- `Global variable "${varName}" used (index ${varUses[varName]}) before assignment (index ${varAssigns[varName]})`
341
- );
340
+ lintScriptWarning(warnings, script, statements[varUses[varName]], `Global variable "${varName}" used before assignment`);
342
341
  }
343
342
  }
344
343
 
@@ -346,14 +345,14 @@ export function lintScript(script) {
346
345
  const functionsDefined = {};
347
346
  const labelsDefined = {};
348
347
  const labelsUsed = {};
349
- for (const [ixStatement, statement] of script.statements.entries()) {
348
+ for (const [ixStatement, statement] of statements.entries()) {
350
349
  const [statementKey] = Object.keys(statement);
351
350
 
352
351
  // Function definition checks
353
352
  if (statementKey === 'function') {
354
353
  // Function redefinition?
355
354
  if (statement.function.name in functionsDefined) {
356
- warnings.push(`Redefinition of function "${statement.function.name}" (index ${ixStatement})`);
355
+ lintScriptWarning(warnings, script, statement, `Redefinition of function "${statement.function.name}"`);
357
356
  } else {
358
357
  functionsDefined[statement.function.name] = ixStatement;
359
358
  }
@@ -369,9 +368,9 @@ export function lintScript(script) {
369
368
  continue;
370
369
  }
371
370
  if (varName in fnVarUses && fnVarUses[varName] <= fnVarAssigns[varName]) {
372
- warnings.push(
373
- `Variable "${varName}" of function "${statement.function.name}" used (index ${fnVarUses[varName]}) ` +
374
- `before assignment (index ${fnVarAssigns[varName]})`
371
+ lintScriptWarning(
372
+ warnings, script, statement,
373
+ `Variable "${varName}" of function "${statement.function.name}" used before assignment`
375
374
  );
376
375
  }
377
376
  }
@@ -379,8 +378,9 @@ export function lintScript(script) {
379
378
  // Unused variables?
380
379
  for (const varName of Object.keys(fnVarAssigns)) {
381
380
  if (!(varName in fnVarUses)) {
382
- warnings.push(
383
- `Unused variable "${varName}" defined in function "${statement.function.name}" (index ${fnVarAssigns[varName]})`
381
+ lintScriptWarning(
382
+ warnings, script, statement,
383
+ `Unused variable "${varName}" defined in function "${statement.function.name}"`
384
384
  );
385
385
  }
386
386
  }
@@ -391,13 +391,17 @@ export function lintScript(script) {
391
391
  for (const arg of args) {
392
392
  // Duplicate argument?
393
393
  if (argsDefined.has(arg)) {
394
- warnings.push(`Duplicate argument "${arg}" of function "${statement.function.name}" (index ${ixStatement})`);
394
+ lintScriptWarning(
395
+ warnings, script, statement, `Duplicate argument "${arg}" of function "${statement.function.name}"`
396
+ );
395
397
  } else {
396
398
  argsDefined.add(arg);
397
399
 
398
400
  // Unused argument?
399
401
  if (!(arg in fnVarUses)) {
400
- warnings.push(`Unused argument "${arg}" of function "${statement.function.name}" (index ${ixStatement})`);
402
+ lintScriptWarning(
403
+ warnings, script, statement, `Unused argument "${arg}" of function "${statement.function.name}"`
404
+ );
401
405
  }
402
406
  }
403
407
  }
@@ -413,7 +417,7 @@ export function lintScript(script) {
413
417
  if (fnStatementKey === 'expr') {
414
418
  // Pointless function expression statement?
415
419
  if (!('name' in fnStatement.expr) && isPointlessExpression(fnStatement.expr.expr)) {
416
- warnings.push(`Pointless statement in function "${statement.function.name}" (index ${ixFnStatement})`);
420
+ lintScriptWarning(warnings, script, statement, `Pointless statement in function "${statement.function.name}"`);
417
421
  }
418
422
 
419
423
  // Function label statement checks
@@ -421,8 +425,9 @@ export function lintScript(script) {
421
425
  // Label redefinition?
422
426
  const fnStatementLabel = fnStatement.label.name;
423
427
  if (fnStatementLabel in fnLabelsDefined) {
424
- warnings.push(
425
- `Redefinition of label "${fnStatementLabel}" in function "${statement.function.name}" (index ${ixFnStatement})`
428
+ lintScriptWarning(
429
+ warnings, script, statement,
430
+ `Redefinition of label "${fnStatementLabel}" in function "${statement.function.name}"`
426
431
  );
427
432
  } else {
428
433
  fnLabelsDefined[fnStatementLabel] = ixFnStatement;
@@ -439,14 +444,14 @@ export function lintScript(script) {
439
444
  // Unused function labels?
440
445
  for (const label of Object.keys(fnLabelsDefined)) {
441
446
  if (!(label in fnLabelsUsed)) {
442
- warnings.push(`Unused label "${label}" in function "${statement.function.name}" (index ${fnLabelsDefined[label]})`);
447
+ lintScriptWarning(warnings, script, statement, `Unused label "${label}" in function "${statement.function.name}"`);
443
448
  }
444
449
  }
445
450
 
446
451
  // Unknown function labels?
447
452
  for (const label of Object.keys(fnLabelsUsed)) {
448
453
  if (!(label in fnLabelsDefined)) {
449
- warnings.push(`Unknown label "${label}" in function "${statement.function.name}" (index ${fnLabelsUsed[label]})`);
454
+ lintScriptWarning(warnings, script, statement, `Unknown label "${label}" in function "${statement.function.name}"`);
450
455
  }
451
456
  }
452
457
 
@@ -454,7 +459,7 @@ export function lintScript(script) {
454
459
  } else if (statementKey === 'expr') {
455
460
  // Pointless global expression statement?
456
461
  if (!('name' in statement.expr) && isPointlessExpression(statement.expr.expr)) {
457
- warnings.push(`Pointless global statement (index ${ixStatement})`);
462
+ lintScriptWarning(warnings, script, statement, 'Pointless global statement');
458
463
  }
459
464
 
460
465
  // Global label statement checks
@@ -462,7 +467,7 @@ export function lintScript(script) {
462
467
  // Label redefinition?
463
468
  const statementLabel = statement.label.name;
464
469
  if (statementLabel in labelsDefined) {
465
- warnings.push(`Redefinition of global label "${statementLabel}" (index ${ixStatement})`);
470
+ lintScriptWarning(warnings, script, statement, `Redefinition of global label "${statementLabel}"`);
466
471
  } else {
467
472
  labelsDefined[statementLabel] = ixStatement;
468
473
  }
@@ -478,14 +483,14 @@ export function lintScript(script) {
478
483
  // Unused global labels?
479
484
  for (const label of Object.keys(labelsDefined)) {
480
485
  if (!(label in labelsUsed)) {
481
- warnings.push(`Unused global label "${label}" (index ${labelsDefined[label]})`);
486
+ lintScriptWarning(warnings, script, statements[labelsDefined[label]], `Unused global label "${label}"`);
482
487
  }
483
488
  }
484
489
 
485
490
  // Unknown global labels?
486
491
  for (const label of Object.keys(labelsUsed)) {
487
492
  if (!(label in labelsDefined)) {
488
- warnings.push(`Unknown global label "${label}" (index ${labelsUsed[label]})`);
493
+ lintScriptWarning(warnings, script, statements[labelsUsed[label]], `Unknown global label "${label}"`);
489
494
  }
490
495
  }
491
496
 
@@ -493,6 +498,21 @@ export function lintScript(script) {
493
498
  }
494
499
 
495
500
 
501
+ // Helper to format static analysis warnings
502
+ function lintScriptWarning(warnings, script, statement, message) {
503
+ let warning;
504
+ if (script && statement) {
505
+ const [statementKey] = Object.keys(statement);
506
+ const scriptName = script.scriptName ?? '';
507
+ const lineno = statement[statementKey].lineNumber ?? '';
508
+ warning = (scriptName || lineno) ? `${scriptName}:${lineno}: ${message}` : message;
509
+ } else {
510
+ warning = message;
511
+ }
512
+ warnings.push(warning);
513
+ }
514
+
515
+
496
516
  // Helper function to determine if an expression statement's expression is pointless
497
517
  function isPointlessExpression(expr) {
498
518
  const [exprKey] = Object.keys(expr);
package/lib/parser.js CHANGED
@@ -78,20 +78,25 @@ export function parseScript(scriptText, startLineNumber = 1, scriptName = null)
78
78
  // Assignment?
79
79
  const matchAssignment = line.match(rScriptAssignment);
80
80
  if (matchAssignment !== null) {
81
+ // Parse the expression
82
+ let assignmentExpr;
81
83
  try {
82
- const exprStatement = {
83
- 'expr': {
84
- 'name': matchAssignment.groups.name,
85
- 'expr': parseExpression(matchAssignment.groups.expr, lineNumber, scriptName, true),
86
- ...statementBase
87
- }
88
- };
89
- statements.push(exprStatement);
90
- continue;
84
+ assignmentExpr = parseExpression(matchAssignment.groups.expr, lineNumber, scriptName, true);
91
85
  } catch (error) {
92
86
  const columnNumber = line.length - matchAssignment.groups.expr.length + error.columnNumber;
93
87
  throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine, scriptName);
94
88
  }
89
+
90
+ // Add the expression statement
91
+ const exprStatement = {
92
+ 'expr': {
93
+ 'name': matchAssignment.groups.name,
94
+ 'expr': assignmentExpr,
95
+ ...statementBase
96
+ }
97
+ };
98
+ statements.push(exprStatement);
99
+ continue;
95
100
  }
96
101
 
97
102
  // Function definition begin?
@@ -147,11 +152,20 @@ export function parseScript(scriptText, startLineNumber = 1, scriptName = null)
147
152
  // If-then begin?
148
153
  const matchIfBegin = line.match(rScriptIfBegin);
149
154
  if (matchIfBegin !== null) {
155
+ // Parse the if-then expression
156
+ let ifthenExpr;
157
+ try {
158
+ ifthenExpr = parseExpression(matchIfBegin.groups.expr, lineNumber, scriptName, true);
159
+ } catch (error) {
160
+ const columnNumber = matchIfBegin.groups.if.length + error.columnNumber;
161
+ throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine, scriptName);
162
+ }
163
+
150
164
  // Add the if-then label definition
151
165
  const ifthen = {
152
166
  'jump': {
153
167
  'label': `__bareScriptIf${labelIndex}`,
154
- 'expr': {'unary': {'op': '!', 'expr': parseExpression(matchIfBegin.groups.expr, lineNumber, scriptName, true)}},
168
+ 'expr': {'unary': {'op': '!', 'expr': ifthenExpr}},
155
169
  ...statementBase
156
170
  },
157
171
  'done': `__bareScriptDone${labelIndex}`,
@@ -170,7 +184,7 @@ export function parseScript(scriptText, startLineNumber = 1, scriptName = null)
170
184
  // Else-if-then?
171
185
  const matchIfElseIf = line.match(rScriptIfElseIf);
172
186
  if (matchIfElseIf !== null) {
173
- // Get the if-then definition
187
+ // Get the else-if-then definition
174
188
  const labelDefDepth = (functionDef !== null ? functionLabelDefDepth : 0);
175
189
  const ifthen = (labelDefs.length > labelDefDepth ? (labelDefs[labelDefs.length - 1].if ?? null) : null);
176
190
  if (ifthen === null) {
@@ -182,11 +196,20 @@ export function parseScript(scriptText, startLineNumber = 1, scriptName = null)
182
196
  throw new BareScriptParserError('Elif statement following else statement', line, 1, startLineNumber + ixLine, scriptName);
183
197
  }
184
198
 
199
+ // Parse the else-if-then expression
200
+ let ifElseIfExpr;
201
+ try {
202
+ ifElseIfExpr = parseExpression(matchIfElseIf.groups.expr, lineNumber, scriptName, true);
203
+ } catch (error) {
204
+ const columnNumber = matchIfElseIf.groups.elif.length + error.columnNumber;
205
+ throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine, scriptName);
206
+ }
207
+
185
208
  // Generate the next if-then jump statement
186
209
  const prevLabel = ifthen.jump.label;
187
210
  ifthen.jump = {
188
211
  'label': `__bareScriptIf${labelIndex}`,
189
- 'expr': {'unary': {'op': '!', 'expr': parseExpression(matchIfElseIf.groups.expr, lineNumber, scriptName, true)}},
212
+ 'expr': {'unary': {'op': '!', 'expr': ifElseIfExpr}},
190
213
  ...statementBase
191
214
  };
192
215
  labelIndex += 1;
@@ -247,12 +270,21 @@ export function parseScript(scriptText, startLineNumber = 1, scriptName = null)
247
270
  // While-do begin?
248
271
  const matchWhileBegin = line.match(rScriptWhileBegin);
249
272
  if (matchWhileBegin !== null) {
273
+ // Parse the while-do expression
274
+ let whileBeginExpr;
275
+ try {
276
+ whileBeginExpr = parseExpression(matchWhileBegin.groups.expr, lineNumber, scriptName, true);
277
+ } catch (error) {
278
+ const columnNumber = matchWhileBegin.groups.while.length + error.columnNumber;
279
+ throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine, scriptName);
280
+ }
281
+
250
282
  // Add the while-do label
251
283
  const whiledo = {
252
284
  'loop': `__bareScriptLoop${labelIndex}`,
253
285
  'continue': `__bareScriptLoop${labelIndex}`,
254
286
  'done': `__bareScriptDone${labelIndex}`,
255
- 'expr': parseExpression(matchWhileBegin.groups.expr, lineNumber, scriptName, true),
287
+ 'expr': whileBeginExpr,
256
288
  line,
257
289
  'lineNumber': startLineNumber + ixLine
258
290
  };
@@ -303,11 +335,20 @@ export function parseScript(scriptText, startLineNumber = 1, scriptName = null)
303
335
  labelDefs.push({'for': foreach});
304
336
  labelIndex += 1;
305
337
 
338
+ // Parse the for-each expression
339
+ let forBeginExpr;
340
+ try {
341
+ forBeginExpr = parseExpression(matchForBegin.groups.values, lineNumber, scriptName, true);
342
+ } catch (error) {
343
+ const columnNumber = matchForBegin.groups.for.length + error.columnNumber;
344
+ throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine, scriptName);
345
+ }
346
+
306
347
  // Add the for-each header statements
307
348
  statements.push(
308
349
  {'expr': {
309
350
  'name': foreach.values,
310
- 'expr': parseExpression(matchForBegin.groups.values, lineNumber, scriptName, true),
351
+ 'expr': forBeginExpr,
311
352
  ...statementBase
312
353
  }},
313
354
  {'expr': {
@@ -489,15 +530,15 @@ const rScriptJump = new RegExp(`^(?<jump>\\s*(?:jump|jumpif\\s*\\((?<expr>.+)\\)
489
530
  const rScriptReturn = new RegExp(`^(?<return>\\s*return(?:\\s+(?<expr>[^#\\s].*))?)${rPartComment}`);
490
531
  const rScriptInclude = new RegExp(`^\\s*include\\s+(?<delim>')(?<url>(?:\\'|[^'])*)'${rPartComment}`);
491
532
  const rScriptIncludeSystem = new RegExp(`^\\s*include\\s+(?<delim><)(?<url>[^>]*)>${rPartComment}`);
492
- const rScriptIfBegin = new RegExp(`^\\s*if\\s+(?<expr>.+)\\s*:${rPartComment}`);
493
- const rScriptIfElseIf = new RegExp(`^\\s*elif\\s+(?<expr>.+)\\s*:${rPartComment}`);
533
+ const rScriptIfBegin = new RegExp(`^(?<if>\\s*if\\s+)(?<expr>.+)\\s*:${rPartComment}`);
534
+ const rScriptIfElseIf = new RegExp(`^(?<elif>\\s*elif\\s+)(?<expr>.+)\\s*:${rPartComment}`);
494
535
  const rScriptIfElse = new RegExp(`^\\s*else\\s*:${rPartComment}`);
495
536
  const rScriptIfEnd = new RegExp(`^\\s*endif${rPartComment}`);
496
537
  const rScriptForBegin = new RegExp(
497
- `^\\s*for\\s+(?<value>[A-Za-z_]\\w*)(?:\\s*,\\s*(?<index>[A-Za-z_]\\w*))?\\s+in\\s+(?<values>.+)\\s*:${rPartComment}`
538
+ `^(?<for>\\s*for\\s+(?<value>[A-Za-z_]\\w*)(?:\\s*,\\s*(?<index>[A-Za-z_]\\w*))?\\s+in\\s+)(?<values>.+)\\s*:${rPartComment}`
498
539
  );
499
540
  const rScriptForEnd = new RegExp(`^\\s*endfor${rPartComment}`);
500
- const rScriptWhileBegin = new RegExp(`^\\s*while\\s+(?<expr>.+)\\s*:${rPartComment}`);
541
+ const rScriptWhileBegin = new RegExp(`^(?<while>\\s*while\\s+)(?<expr>.+)\\s*:${rPartComment}`);
501
542
  const rScriptWhileEnd = new RegExp(`^\\s*endwhile${rPartComment}`);
502
543
  const rScriptBreak = new RegExp(`^\\s*break${rPartComment}`);
503
544
  const rScriptContinue = new RegExp(`^\\s*continue${rPartComment}`);
@@ -702,9 +743,9 @@ function parseUnaryExpression(exprText, arrayLiterals) {
702
743
  break;
703
744
  }
704
745
 
705
- // Object key/value separator
746
+ // Key/value pair separator
706
747
  if (args.length !== 0) {
707
- const matchObjectSeparator = argText.match(rExprObjectSeparator2);
748
+ const matchObjectSeparator = argText.match(rExprObjectSeparator);
708
749
  if (matchObjectSeparator === null) {
709
750
  throw new BareScriptParserError('Syntax error', argText, 1, null, null);
710
751
  }
@@ -716,13 +757,13 @@ function parseUnaryExpression(exprText, arrayLiterals) {
716
757
  argText = nextArgText;
717
758
  args.push(argKey);
718
759
 
719
- // Object key separator
760
+ // Key/value separator
720
761
  if (args.length !== 0) {
721
- const matchObjectSeparator = argText.match(rExprObjectSeparator);
722
- if (matchObjectSeparator === null) {
762
+ const matchObjectSeparatorKey = argText.match(rExprObjectSeparatorKey);
763
+ if (matchObjectSeparatorKey === null) {
723
764
  throw new BareScriptParserError('Syntax error', argText, 1, null, null);
724
765
  }
725
- argText = argText.slice(matchObjectSeparator[0].length);
766
+ argText = argText.slice(matchObjectSeparatorKey[0].length);
726
767
  }
727
768
 
728
769
  // Get the value
@@ -802,8 +843,8 @@ const rExprArrayOpen = /^\s*\[/;
802
843
  const rExprArraySeparator = /^\s*,/;
803
844
  const rExprArrayClose = /^\s*\]/;
804
845
  const rExprObjectOpen = /^\s*\{/;
805
- const rExprObjectSeparator = /^\s*:/;
806
- const rExprObjectSeparator2 = /^\s*,/;
846
+ const rExprObjectSeparatorKey = /^\s*:/;
847
+ const rExprObjectSeparator = /^\s*,/;
807
848
  const rExprObjectClose = /^\s*\}/;
808
849
  const rExprString = /^\s*'((?:\\\\|\\'|[^'])*)'/;
809
850
  const rExprStringDouble = /^\s*"((?:\\\\|\\"|[^"])*)"/;
@@ -842,7 +883,7 @@ const exprStringEscapes = {
842
883
  * @property {string} line - The line text
843
884
  * @property {number} columnNumber - The error column number
844
885
  * @property {?number} lineNumber - The error line number
845
- * @property {?string} scriptName - The error line number
886
+ * @property {?string} scriptName - The script name
846
887
  */
847
888
  export class BareScriptParserError extends Error {
848
889
  /**
@@ -173,7 +173,7 @@ async function executeScriptHelperAsync(script, statements, options, locals) {
173
173
  if (warnings.length) {
174
174
  options.logFn(`${warningPrefix} ${warnings.length} warning${warnings.length > 1 ? 's' : ''}:`);
175
175
  for (const warning of warnings) {
176
- options.logFn(`BareScript: ${warning}`);
176
+ options.logFn(`BareScript: ${warning}`);
177
177
  }
178
178
  }
179
179
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "bare-script",
4
- "version": "3.7.2",
4
+ "version": "3.7.3",
5
5
  "description": "BareScript; a lightweight scripting and expression language",
6
6
  "keywords": [
7
7
  "expression",