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/parser.js CHANGED
@@ -9,14 +9,18 @@
9
9
  *
10
10
  * @param {string|string[]} scriptText - The [script text](./language/)
11
11
  * @param {number} [startLineNumber = 1] - The script's starting line number
12
+ * @param {?string} [scriptName = null] - The script name
12
13
  * @returns {Object} The [BareScript model](./model/#var.vName='BareScript')
13
14
  * @throws [BareScriptParserError]{@link module:lib/parser.BareScriptParserError}
14
15
  */
15
- export function parseScript(scriptText, startLineNumber = 1) {
16
- const script = {'statements': []};
16
+ export function parseScript(scriptText, startLineNumber = 1, scriptName = null) {
17
+ const lines = [];
18
+ const script = {'statements': [], 'scriptLines': lines};
19
+ if (scriptName !== null) {
20
+ script.scriptName = scriptName;
21
+ }
17
22
 
18
23
  // Line-split all script text
19
- const lines = [];
20
24
  if (typeof scriptText === 'string') {
21
25
  lines.push(...scriptText.split(rScriptLineSplit));
22
26
  } else {
@@ -64,6 +68,13 @@ export function parseScript(scriptText, startLineNumber = 1) {
64
68
  line = linePart;
65
69
  }
66
70
 
71
+ // Base statement members
72
+ const lineNumber = ixLine + 1;
73
+ const statementBase = {lineNumber};
74
+ if (ixLine !== ixLinePart) {
75
+ statementBase.lineCount = (ixLinePart - ixLine) + 1;
76
+ }
77
+
67
78
  // Assignment?
68
79
  const matchAssignment = line.match(rScriptAssignment);
69
80
  if (matchAssignment !== null) {
@@ -71,14 +82,15 @@ export function parseScript(scriptText, startLineNumber = 1) {
71
82
  const exprStatement = {
72
83
  'expr': {
73
84
  'name': matchAssignment.groups.name,
74
- 'expr': parseExpression(matchAssignment.groups.expr)
85
+ 'expr': parseExpression(matchAssignment.groups.expr, lineNumber, scriptName, true),
86
+ ...statementBase
75
87
  }
76
88
  };
77
89
  statements.push(exprStatement);
78
90
  continue;
79
91
  } catch (error) {
80
92
  const columnNumber = line.length - matchAssignment.groups.expr.length + error.columnNumber;
81
- throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine);
93
+ throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine, scriptName);
82
94
  }
83
95
  }
84
96
 
@@ -87,7 +99,7 @@ export function parseScript(scriptText, startLineNumber = 1) {
87
99
  if (matchFunctionBegin !== null) {
88
100
  // Nested function definitions are not allowed
89
101
  if (functionDef !== null) {
90
- throw new BareScriptParserError('Nested function definition', line, 1, startLineNumber + ixLine);
102
+ throw new BareScriptParserError('Nested function definition', line, 1, startLineNumber + ixLine, scriptName);
91
103
  }
92
104
 
93
105
  // Add the function definition statement
@@ -95,7 +107,8 @@ export function parseScript(scriptText, startLineNumber = 1) {
95
107
  functionDef = {
96
108
  'function': {
97
109
  'name': matchFunctionBegin.groups.name,
98
- 'statements': []
110
+ 'statements': [],
111
+ ...statementBase
99
112
  }
100
113
  };
101
114
  if (typeof matchFunctionBegin.groups.args !== 'undefined') {
@@ -115,7 +128,7 @@ export function parseScript(scriptText, startLineNumber = 1) {
115
128
  const matchFunctionEnd = line.match(rScriptFunctionEnd);
116
129
  if (matchFunctionEnd !== null) {
117
130
  if (functionDef === null) {
118
- throw new BareScriptParserError('No matching function definition', line, 1, startLineNumber + ixLine);
131
+ throw new BareScriptParserError('No matching function definition', line, 1, startLineNumber + ixLine, scriptName);
119
132
  }
120
133
 
121
134
  // Check for un-matched label definitions
@@ -123,7 +136,7 @@ export function parseScript(scriptText, startLineNumber = 1) {
123
136
  const labelDef = labelDefs.pop();
124
137
  const [defKey] = Object.keys(labelDef);
125
138
  const def = labelDef[defKey];
126
- throw new BareScriptParserError(`Missing end${defKey} statement`, def.line, 1, def.lineNumber);
139
+ throw new BareScriptParserError(`Missing end${defKey} statement`, def.line, 1, def.lineNumber, scriptName);
127
140
  }
128
141
 
129
142
  functionDef = null;
@@ -138,7 +151,8 @@ export function parseScript(scriptText, startLineNumber = 1) {
138
151
  const ifthen = {
139
152
  'jump': {
140
153
  'label': `__bareScriptIf${labelIndex}`,
141
- 'expr': {'unary': {'op': '!', 'expr': parseExpression(matchIfBegin.groups.expr)}}
154
+ 'expr': {'unary': {'op': '!', 'expr': parseExpression(matchIfBegin.groups.expr, lineNumber, scriptName, true)}},
155
+ ...statementBase
142
156
  },
143
157
  'done': `__bareScriptDone${labelIndex}`,
144
158
  'hasElse': false,
@@ -160,26 +174,27 @@ export function parseScript(scriptText, startLineNumber = 1) {
160
174
  const labelDefDepth = (functionDef !== null ? functionLabelDefDepth : 0);
161
175
  const ifthen = (labelDefs.length > labelDefDepth ? (labelDefs[labelDefs.length - 1].if ?? null) : null);
162
176
  if (ifthen === null) {
163
- throw new BareScriptParserError('No matching if statement', line, 1, startLineNumber + ixLine);
177
+ throw new BareScriptParserError('No matching if statement', line, 1, startLineNumber + ixLine, scriptName);
164
178
  }
165
179
 
166
180
  // Cannot come after the else-then statement
167
181
  if (ifthen.hasElse) {
168
- throw new BareScriptParserError('Elif statement following else statement', line, 1, startLineNumber + ixLine);
182
+ throw new BareScriptParserError('Elif statement following else statement', line, 1, startLineNumber + ixLine, scriptName);
169
183
  }
170
184
 
171
185
  // Generate the next if-then jump statement
172
186
  const prevLabel = ifthen.jump.label;
173
187
  ifthen.jump = {
174
188
  'label': `__bareScriptIf${labelIndex}`,
175
- 'expr': {'unary': {'op': '!', 'expr': parseExpression(matchIfElseIf.groups.expr)}}
189
+ 'expr': {'unary': {'op': '!', 'expr': parseExpression(matchIfElseIf.groups.expr, lineNumber, scriptName, true)}},
190
+ ...statementBase
176
191
  };
177
192
  labelIndex += 1;
178
193
 
179
194
  // Add the if-then else statements
180
195
  statements.push(
181
- {'jump': {'label': ifthen.done}},
182
- {'label': prevLabel},
196
+ {'jump': {'label': ifthen.done, ...statementBase}},
197
+ {'label': {'name': prevLabel, ...statementBase}},
183
198
  {'jump': ifthen.jump}
184
199
  );
185
200
  continue;
@@ -192,19 +207,19 @@ export function parseScript(scriptText, startLineNumber = 1) {
192
207
  const labelDefDepth = (functionDef !== null ? functionLabelDefDepth : 0);
193
208
  const ifthen = (labelDefs.length > labelDefDepth ? (labelDefs[labelDefs.length - 1].if ?? null) : null);
194
209
  if (ifthen === null) {
195
- throw new BareScriptParserError('No matching if statement', line, 1, startLineNumber + ixLine);
210
+ throw new BareScriptParserError('No matching if statement', line, 1, startLineNumber + ixLine, scriptName);
196
211
  }
197
212
 
198
213
  // Cannot have multiple else-then statements
199
214
  if (ifthen.hasElse) {
200
- throw new BareScriptParserError('Multiple else statements', line, 1, startLineNumber + ixLine);
215
+ throw new BareScriptParserError('Multiple else statements', line, 1, startLineNumber + ixLine, scriptName);
201
216
  }
202
217
  ifthen.hasElse = true;
203
218
 
204
219
  // Add the if-then else statements
205
220
  statements.push(
206
- {'jump': {'label': ifthen.done}},
207
- {'label': ifthen.jump.label}
221
+ {'jump': {'label': ifthen.done, ...statementBase}},
222
+ {'label': {'name': ifthen.jump.label, ...statementBase}}
208
223
  );
209
224
  continue;
210
225
  }
@@ -216,7 +231,7 @@ export function parseScript(scriptText, startLineNumber = 1) {
216
231
  const labelDefDepth = (functionDef !== null ? functionLabelDefDepth : 0);
217
232
  const ifthen = (labelDefs.length > labelDefDepth ? (labelDefs.pop().if ?? null) : null);
218
233
  if (ifthen === null) {
219
- throw new BareScriptParserError('No matching if statement', line, 1, startLineNumber + ixLine);
234
+ throw new BareScriptParserError('No matching if statement', line, 1, startLineNumber + ixLine, scriptName);
220
235
  }
221
236
 
222
237
  // Update the previous jump statement's label, if necessary
@@ -225,7 +240,7 @@ export function parseScript(scriptText, startLineNumber = 1) {
225
240
  }
226
241
 
227
242
  // Add the if-then footer statement
228
- statements.push({'label': ifthen.done});
243
+ statements.push({'label': {'name': ifthen.done, ...statementBase}});
229
244
  continue;
230
245
  }
231
246
 
@@ -237,7 +252,7 @@ export function parseScript(scriptText, startLineNumber = 1) {
237
252
  'loop': `__bareScriptLoop${labelIndex}`,
238
253
  'continue': `__bareScriptLoop${labelIndex}`,
239
254
  'done': `__bareScriptDone${labelIndex}`,
240
- 'expr': parseExpression(matchWhileBegin.groups.expr),
255
+ 'expr': parseExpression(matchWhileBegin.groups.expr, lineNumber, scriptName, true),
241
256
  line,
242
257
  'lineNumber': startLineNumber + ixLine
243
258
  };
@@ -246,8 +261,8 @@ export function parseScript(scriptText, startLineNumber = 1) {
246
261
 
247
262
  // Add the while-do header statements
248
263
  statements.push(
249
- {'jump': {'label': whiledo.done, 'expr': {'unary': {'op': '!', 'expr': whiledo.expr}}}},
250
- {'label': whiledo.loop}
264
+ {'jump': {'label': whiledo.done, 'expr': {'unary': {'op': '!', 'expr': whiledo.expr}}, ...statementBase}},
265
+ {'label': {'name': whiledo.loop, ...statementBase}}
251
266
  );
252
267
  continue;
253
268
  }
@@ -259,13 +274,13 @@ export function parseScript(scriptText, startLineNumber = 1) {
259
274
  const labelDefDepth = (functionDef !== null ? functionLabelDefDepth : 0);
260
275
  const whiledo = (labelDefs.length > labelDefDepth ? (labelDefs.pop().while ?? null) : null);
261
276
  if (whiledo === null) {
262
- throw new BareScriptParserError('No matching while statement', line, 1, startLineNumber + ixLine);
277
+ throw new BareScriptParserError('No matching while statement', line, 1, startLineNumber + ixLine, scriptName);
263
278
  }
264
279
 
265
280
  // Add the while-do footer statements
266
281
  statements.push(
267
- {'jump': {'label': whiledo.loop, 'expr': whiledo.expr}},
268
- {'label': whiledo.done}
282
+ {'jump': {'label': whiledo.loop, 'expr': whiledo.expr, ...statementBase}},
283
+ {'label': {'name': whiledo.done, ...statementBase}}
269
284
  );
270
285
  continue;
271
286
  }
@@ -290,17 +305,23 @@ export function parseScript(scriptText, startLineNumber = 1) {
290
305
 
291
306
  // Add the for-each header statements
292
307
  statements.push(
293
- {'expr': {'name': foreach.values, 'expr': parseExpression(matchForBegin.groups.values)}},
308
+ {'expr': {
309
+ 'name': foreach.values,
310
+ 'expr': parseExpression(matchForBegin.groups.values, lineNumber, scriptName, true),
311
+ ...statementBase
312
+ }},
294
313
  {'expr': {
295
314
  'name': foreach.length,
296
- 'expr': {'function': {'name': 'arrayLength', 'args': [{'variable': foreach.values}]}}
315
+ 'expr': {'function': {'name': 'arrayLength', 'args': [{'variable': foreach.values}]}},
316
+ ...statementBase
297
317
  }},
298
- {'jump': {'label': foreach.done, 'expr': {'unary': {'op': '!', 'expr': {'variable': foreach.length}}}}},
299
- {'expr': {'name': foreach.index, 'expr': {'number': 0}}},
300
- {'label': foreach.loop},
318
+ {'jump': {'label': foreach.done, 'expr': {'unary': {'op': '!', 'expr': {'variable': foreach.length}}}, ...statementBase}},
319
+ {'expr': {'name': foreach.index, 'expr': {'number': 0}, ...statementBase}},
320
+ {'label': {'name': foreach.loop, ...statementBase}},
301
321
  {'expr': {
302
322
  'name': foreach.value,
303
- 'expr': {'function': {'name': 'arrayGet', 'args': [{'variable': foreach.values}, {'variable': foreach.index}]}}
323
+ 'expr': {'function': {'name': 'arrayGet', 'args': [{'variable': foreach.values}, {'variable': foreach.index}]}},
324
+ ...statementBase
304
325
  }}
305
326
  );
306
327
  continue;
@@ -313,23 +334,25 @@ export function parseScript(scriptText, startLineNumber = 1) {
313
334
  const labelDefDepth = (functionDef !== null ? functionLabelDefDepth : 0);
314
335
  const foreach = (labelDefs.length > labelDefDepth ? (labelDefs.pop().for ?? null) : null);
315
336
  if (foreach === null) {
316
- throw new BareScriptParserError('No matching for statement', line, 1, startLineNumber + ixLine);
337
+ throw new BareScriptParserError('No matching for statement', line, 1, startLineNumber + ixLine, scriptName);
317
338
  }
318
339
 
319
340
  // Add the for-each footer statements
320
341
  if (foreach.hasContinue) {
321
- statements.push({'label': foreach.continue});
342
+ statements.push({'label': {'name': foreach.continue, ...statementBase}});
322
343
  }
323
344
  statements.push(
324
345
  {'expr': {
325
346
  'name': foreach.index,
326
- 'expr': {'binary': {'op': '+', 'left': {'variable': foreach.index}, 'right': {'number': 1}}}
347
+ 'expr': {'binary': {'op': '+', 'left': {'variable': foreach.index}, 'right': {'number': 1}}},
348
+ ...statementBase
327
349
  }},
328
350
  {'jump': {
329
351
  'label': foreach.loop,
330
- 'expr': {'binary': {'op': '<', 'left': {'variable': foreach.index}, 'right': {'variable': foreach.length}}}
352
+ 'expr': {'binary': {'op': '<', 'left': {'variable': foreach.index}, 'right': {'variable': foreach.length}}},
353
+ ...statementBase
331
354
  }},
332
- {'label': foreach.done}
355
+ {'label': {'name': foreach.done, ...statementBase}}
333
356
  );
334
357
  continue;
335
358
  }
@@ -342,13 +365,13 @@ export function parseScript(scriptText, startLineNumber = 1) {
342
365
  const ixLabelDef = labelDefs.findLastIndex((def) => !('if' in def));
343
366
  const labelDef = (ixLabelDef >= labelDefDepth ? labelDefs[ixLabelDef] : null);
344
367
  if (labelDef === null) {
345
- throw new BareScriptParserError('Break statement outside of loop', line, 1, startLineNumber + ixLine);
368
+ throw new BareScriptParserError('Break statement outside of loop', line, 1, startLineNumber + ixLine, scriptName);
346
369
  }
347
370
  const [labelKey] = Object.keys(labelDef);
348
371
  const loopDef = labelDef[labelKey];
349
372
 
350
373
  // Add the break jump statement
351
- statements.push({'jump': {'label': loopDef.done}});
374
+ statements.push({'jump': {'label': loopDef.done, ...statementBase}});
352
375
  continue;
353
376
  }
354
377
 
@@ -360,34 +383,34 @@ export function parseScript(scriptText, startLineNumber = 1) {
360
383
  const ixLabelDef = labelDefs.findLastIndex((def) => !('if' in def));
361
384
  const labelDef = (ixLabelDef >= labelDefDepth ? labelDefs[ixLabelDef] : null);
362
385
  if (labelDef === null) {
363
- throw new BareScriptParserError('Continue statement outside of loop', line, 1, startLineNumber + ixLine);
386
+ throw new BareScriptParserError('Continue statement outside of loop', line, 1, startLineNumber + ixLine, scriptName);
364
387
  }
365
388
  const [labelKey] = Object.keys(labelDef);
366
389
  const loopDef = labelDef[labelKey];
367
390
 
368
391
  // Add the continue jump statement
369
392
  loopDef.hasContinue = true;
370
- statements.push({'jump': {'label': loopDef.continue}});
393
+ statements.push({'jump': {'label': loopDef.continue, ...statementBase}});
371
394
  continue;
372
395
  }
373
396
 
374
397
  // Label definition?
375
398
  const matchLabel = line.match(rScriptLabel);
376
399
  if (matchLabel !== null) {
377
- statements.push({'label': matchLabel.groups.name});
400
+ statements.push({'label': {'name': matchLabel.groups.name, ...statementBase}});
378
401
  continue;
379
402
  }
380
403
 
381
404
  // Jump definition?
382
405
  const matchJump = line.match(rScriptJump);
383
406
  if (matchJump !== null) {
384
- const jumpStatement = {'jump': {'label': matchJump.groups.name}};
407
+ const jumpStatement = {'jump': {'label': matchJump.groups.name, ...statementBase}};
385
408
  if (typeof matchJump.groups.expr !== 'undefined') {
386
409
  try {
387
- jumpStatement.jump.expr = parseExpression(matchJump.groups.expr);
410
+ jumpStatement.jump.expr = parseExpression(matchJump.groups.expr, lineNumber, scriptName, true);
388
411
  } catch (error) {
389
412
  const columnNumber = matchJump.groups.jump.length - matchJump.groups.expr.length - 1 + error.columnNumber;
390
- throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine);
413
+ throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine, scriptName);
391
414
  }
392
415
  }
393
416
  statements.push(jumpStatement);
@@ -397,13 +420,13 @@ export function parseScript(scriptText, startLineNumber = 1) {
397
420
  // Return definition?
398
421
  const matchReturn = line.match(rScriptReturn);
399
422
  if (matchReturn !== null) {
400
- const returnStatement = {'return': {}};
423
+ const returnStatement = {'return': {...statementBase}};
401
424
  if (typeof matchReturn.groups.expr !== 'undefined') {
402
425
  try {
403
- returnStatement.return.expr = parseExpression(matchReturn.groups.expr);
426
+ returnStatement.return.expr = parseExpression(matchReturn.groups.expr, lineNumber, scriptName, true);
404
427
  } catch (error) {
405
428
  const columnNumber = matchReturn.groups.return.length - matchReturn.groups.expr.length + error.columnNumber;
406
- throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine);
429
+ throw new BareScriptParserError(error.error, line, columnNumber, startLineNumber + ixLine, scriptName);
407
430
  }
408
431
  }
409
432
  statements.push(returnStatement);
@@ -414,11 +437,15 @@ export function parseScript(scriptText, startLineNumber = 1) {
414
437
  const matchInclude = line.match(rScriptInclude) || line.match(rScriptIncludeSystem);
415
438
  if (matchInclude !== null) {
416
439
  const {delim} = matchInclude.groups;
417
- const url = (delim === '<' ? matchInclude.groups.url : matchInclude.groups.url.replace(rExprStringEscape, '$1'));
440
+ const url = (
441
+ delim === '<' ? matchInclude.groups.url : matchInclude.groups.url.replace(rExprStringEscapes, replaceStringEscape)
442
+ );
418
443
  let includeStatement = (statements.length ? statements[statements.length - 1] : null);
419
444
  if (includeStatement === null || !('include' in includeStatement)) {
420
- includeStatement = {'include': {'includes': []}};
445
+ includeStatement = {'include': {'includes': [], ...statementBase}};
421
446
  statements.push(includeStatement);
447
+ } else {
448
+ includeStatement.include.lineCount = (ixLinePart - includeStatement.include.lineNumber) + 2;
422
449
  }
423
450
  includeStatement.include.includes.push(delim === '<' ? {url, 'system': true} : {url});
424
451
  continue;
@@ -426,10 +453,10 @@ export function parseScript(scriptText, startLineNumber = 1) {
426
453
 
427
454
  // Expression
428
455
  try {
429
- const exprStatement = {'expr': {'expr': parseExpression(line)}};
456
+ const exprStatement = {'expr': {'expr': parseExpression(line, lineNumber, scriptName, true), ...statementBase}};
430
457
  statements.push(exprStatement);
431
458
  } catch (error) {
432
- throw new BareScriptParserError(error.error, line, error.columnNumber, startLineNumber + ixLine);
459
+ throw new BareScriptParserError(error.error, line, error.columnNumber, startLineNumber + ixLine, scriptName);
433
460
  }
434
461
  }
435
462
 
@@ -438,7 +465,7 @@ export function parseScript(scriptText, startLineNumber = 1) {
438
465
  const labelDef = labelDefs.pop();
439
466
  const [defKey] = Object.keys(labelDef);
440
467
  const def = labelDef[defKey];
441
- throw new BareScriptParserError(`Missing end${defKey} statement`, def.line, 1, def.lineNumber);
468
+ throw new BareScriptParserError(`Missing end${defKey} statement`, def.line, 1, def.lineNumber, scriptName);
442
469
  }
443
470
 
444
471
  return script;
@@ -480,25 +507,28 @@ const rScriptContinue = new RegExp(`^\\s*continue${rPartComment}`);
480
507
  * Parse a BareScript expression
481
508
  *
482
509
  * @param {string} exprText - The [expression text](./language/#expressions)
510
+ * @param {number} [lineNumber = 1] - The script line number
511
+ * @param {?string} [scriptName = null] - The script name
512
+ * @param {boolean} [arrayLiterals = false] - If True, allow parsing of array literals
483
513
  * @returns {Object} The [expression model](./model/#var.vName='Expression')
484
514
  * @throws [BareScriptParserError]{@link module:lib/parser.BareScriptParserError}
485
515
  */
486
- export function parseExpression(exprText) {
516
+ export function parseExpression(exprText, lineNumber = null, scriptName = null, arrayLiterals = false) {
487
517
  try {
488
- const [expr, nextText] = parseBinaryExpression(exprText);
518
+ const [expr, nextText] = parseBinaryExpression(exprText, null, arrayLiterals);
489
519
  if (nextText.trim() !== '') {
490
- throw new BareScriptParserError('Syntax error', nextText);
520
+ throw new BareScriptParserError('Syntax error', nextText, 1, lineNumber, scriptName);
491
521
  }
492
522
  return expr;
493
523
  } catch (error) {
494
524
  const columnNumber = exprText.length - error.line.length + 1;
495
- throw new BareScriptParserError(error.error, exprText, columnNumber);
525
+ throw new BareScriptParserError(error.error, exprText, columnNumber, lineNumber, scriptName);
496
526
  }
497
527
  }
498
528
 
499
529
 
500
530
  // Helper function to parse a binary operator expression chain
501
- function parseBinaryExpression(exprText, binLeftExpr = null) {
531
+ function parseBinaryExpression(exprText, binLeftExpr, arrayLiterals) {
502
532
  // Parse the binary operator's left unary expression if none was passed
503
533
  let leftExpr;
504
534
  let binText;
@@ -506,7 +536,7 @@ function parseBinaryExpression(exprText, binLeftExpr = null) {
506
536
  binText = exprText;
507
537
  leftExpr = binLeftExpr;
508
538
  } else {
509
- [leftExpr, binText] = parseUnaryExpression(exprText);
539
+ [leftExpr, binText] = parseUnaryExpression(exprText, arrayLiterals);
510
540
  }
511
541
 
512
542
  // Match a binary operator - if not found, return the left expression
@@ -523,7 +553,7 @@ function parseBinaryExpression(exprText, binLeftExpr = null) {
523
553
  const rightText = binText.slice(matchBinaryOp[0].length);
524
554
 
525
555
  // Parse the right sub-expression
526
- const [rightExpr, nextText] = parseUnaryExpression(rightText);
556
+ const [rightExpr, nextText] = parseUnaryExpression(rightText, arrayLiterals);
527
557
 
528
558
  // Create the binary expression - re-order for binary operators as necessary
529
559
  let binExpr;
@@ -541,7 +571,7 @@ function parseBinaryExpression(exprText, binLeftExpr = null) {
541
571
  }
542
572
 
543
573
  // Parse the next binary expression in the chain
544
- return parseBinaryExpression(nextText, binExpr);
574
+ return parseBinaryExpression(nextText, binExpr, arrayLiterals);
545
575
  }
546
576
 
547
577
 
@@ -570,15 +600,15 @@ const binaryReorder = {
570
600
 
571
601
 
572
602
  // Helper function to parse a unary expression
573
- function parseUnaryExpression(exprText) {
603
+ function parseUnaryExpression(exprText, arrayLiterals) {
574
604
  // Group open?
575
605
  const matchGroupOpen = exprText.match(rExprGroupOpen);
576
606
  if (matchGroupOpen !== null) {
577
607
  const groupText = exprText.slice(matchGroupOpen[0].length);
578
- const [expr, nextText] = parseBinaryExpression(groupText);
608
+ const [expr, nextText] = parseBinaryExpression(groupText, null, arrayLiterals);
579
609
  const matchGroupClose = nextText.match(rExprGroupClose);
580
610
  if (matchGroupClose === null) {
581
- throw new BareScriptParserError('Unmatched parenthesis', exprText);
611
+ throw new BareScriptParserError('Unmatched parenthesis', exprText, 1, null, null);
582
612
  }
583
613
  return [{'group': expr}, nextText.slice(matchGroupClose[0].length)];
584
614
  }
@@ -595,7 +625,7 @@ function parseUnaryExpression(exprText) {
595
625
  // String?
596
626
  const matchString = exprText.match(rExprString);
597
627
  if (matchString !== null) {
598
- const string = matchString[1].replace(rExprStringEscape, '$1');
628
+ const string = matchString[1].replace(rExprStringEscapes, replaceStringEscape);
599
629
  const expr = {'string': string};
600
630
  return [expr, exprText.slice(matchString[0].length)];
601
631
  }
@@ -603,7 +633,7 @@ function parseUnaryExpression(exprText) {
603
633
  // String (double quotes)?
604
634
  const matchStringDouble = exprText.match(rExprStringDouble);
605
635
  if (matchStringDouble !== null) {
606
- const string = matchStringDouble[1].replace(rExprStringDoubleEscape, '$1');
636
+ const string = matchStringDouble[1].replace(rExprStringEscapes, replaceStringEscape);
607
637
  const expr = {'string': string};
608
638
  return [expr, exprText.slice(matchStringDouble[0].length)];
609
639
  }
@@ -612,7 +642,7 @@ function parseUnaryExpression(exprText) {
612
642
  const matchUnary = exprText.match(rExprUnaryOp);
613
643
  if (matchUnary !== null) {
614
644
  const unaryText = exprText.slice(matchUnary[0].length);
615
- const [expr, nextText] = parseUnaryExpression(unaryText);
645
+ const [expr, nextText] = parseUnaryExpression(unaryText, arrayLiterals);
616
646
  const unaryExpr = {
617
647
  'unary': {
618
648
  'op': matchUnary[1],
@@ -639,15 +669,15 @@ function parseUnaryExpression(exprText) {
639
669
  if (args.length !== 0) {
640
670
  const matchFunctionSeparator = argText.match(rExprFunctionSeparator);
641
671
  if (matchFunctionSeparator === null) {
642
- throw new BareScriptParserError('Syntax error', argText);
672
+ throw new BareScriptParserError('Syntax error', argText, 1, null, null);
643
673
  }
644
674
  argText = argText.slice(matchFunctionSeparator[0].length);
645
675
  }
646
676
 
647
677
  // Get the argument
648
- const [argExpr, nextArgText] = parseBinaryExpression(argText);
649
- args.push(argExpr);
678
+ const [argExpr, nextArgText] = parseBinaryExpression(argText, null, arrayLiterals);
650
679
  argText = nextArgText;
680
+ args.push(argExpr);
651
681
  }
652
682
 
653
683
  const fnExpr = {
@@ -659,6 +689,84 @@ function parseUnaryExpression(exprText) {
659
689
  return [fnExpr, argText];
660
690
  }
661
691
 
692
+ // Object creation?
693
+ const matchObjectOpen = exprText.match(rExprObjectOpen);
694
+ if (matchObjectOpen !== null) {
695
+ let argText = exprText.slice(matchObjectOpen[0].length);
696
+ const args = [];
697
+ while (true) {
698
+ // Object close?
699
+ const matchObjectClose = argText.match(rExprObjectClose);
700
+ if (matchObjectClose !== null) {
701
+ argText = argText.slice(matchObjectClose[0].length);
702
+ break;
703
+ }
704
+
705
+ // Object key/value separator
706
+ if (args.length !== 0) {
707
+ const matchObjectSeparator = argText.match(rExprObjectSeparator2);
708
+ if (matchObjectSeparator === null) {
709
+ throw new BareScriptParserError('Syntax error', argText, 1, null, null);
710
+ }
711
+ argText = argText.slice(matchObjectSeparator[0].length);
712
+ }
713
+
714
+ // Get the key
715
+ const [argKey, nextArgText] = parseBinaryExpression(argText, null, arrayLiterals);
716
+ argText = nextArgText;
717
+ args.push(argKey);
718
+
719
+ // Object key separator
720
+ if (args.length !== 0) {
721
+ const matchObjectSeparator = argText.match(rExprObjectSeparator);
722
+ if (matchObjectSeparator === null) {
723
+ throw new BareScriptParserError('Syntax error', argText, 1, null, null);
724
+ }
725
+ argText = argText.slice(matchObjectSeparator[0].length);
726
+ }
727
+
728
+ // Get the value
729
+ const [argValue, nextArgText2] = parseBinaryExpression(argText, null, arrayLiterals);
730
+ argText = nextArgText2;
731
+ args.push(argValue);
732
+ }
733
+ const fnExpr = {'function': {'name': 'objectNew', 'args': args}};
734
+ return [fnExpr, argText];
735
+ }
736
+
737
+ // Array creation?
738
+ if (arrayLiterals) {
739
+ const matchArrayOpen = exprText.match(rExprArrayOpen);
740
+ if (matchArrayOpen !== null) {
741
+ let argText = exprText.slice(matchArrayOpen[0].length);
742
+ const args = [];
743
+ while (true) {
744
+ // Array close?
745
+ const matchArrayClose = argText.match(rExprArrayClose);
746
+ if (matchArrayClose !== null) {
747
+ argText = argText.slice(matchArrayClose[0].length);
748
+ break;
749
+ }
750
+
751
+ // Array value separator
752
+ if (args.length !== 0) {
753
+ const matchArraySeparator = argText.match(rExprArraySeparator);
754
+ if (matchArraySeparator === null) {
755
+ throw new BareScriptParserError('Syntax error', argText, 1, null, null);
756
+ }
757
+ argText = argText.slice(matchArraySeparator[0].length);
758
+ }
759
+
760
+ // Get the value
761
+ const [argValue, nextArgText2] = parseBinaryExpression(argText, null, arrayLiterals);
762
+ argText = nextArgText2;
763
+ args.push(argValue);
764
+ }
765
+ const fnExpr = {'function': {'name': 'arrayNew', 'args': args}};
766
+ return [fnExpr, argText];
767
+ }
768
+ }
769
+
662
770
  // Variable?
663
771
  const matchVariable = exprText.match(rExprVariable);
664
772
  if (matchVariable !== null) {
@@ -667,14 +775,16 @@ function parseUnaryExpression(exprText) {
667
775
  }
668
776
 
669
777
  // Variable (brackets)?
670
- const matchVariableEx = exprText.match(rExprVariableEx);
671
- if (matchVariableEx !== null) {
672
- const variableName = matchVariableEx[1].replace(rExprVariableExEscape, '$1');
673
- const expr = {'variable': variableName};
674
- return [expr, exprText.slice(matchVariableEx[0].length)];
778
+ if (!arrayLiterals) {
779
+ const matchVariableEx = exprText.match(rExprVariableEx);
780
+ if (matchVariableEx !== null) {
781
+ const variableName = matchVariableEx[1].replace(rExprVariableExEscape, '$1');
782
+ const expr = {'variable': variableName};
783
+ return [expr, exprText.slice(matchVariableEx[0].length)];
784
+ }
675
785
  }
676
786
 
677
- throw new BareScriptParserError('Syntax error', exprText);
787
+ throw new BareScriptParserError('Syntax error', exprText, 1, null, null);
678
788
  }
679
789
 
680
790
 
@@ -688,15 +798,42 @@ const rExprFunctionClose = /^\s*\)/;
688
798
  const rExprGroupOpen = /^\s*\(/;
689
799
  const rExprGroupClose = /^\s*\)/;
690
800
  const rExprNumber = /^\s*(0x[A-Fa-f0-9]+|[+-]?\d+(?:\.\d*)?(?:e[+-]?\d+)?)/;
801
+ const rExprArrayOpen = /^\s*\[/;
802
+ const rExprArraySeparator = /^\s*,/;
803
+ const rExprArrayClose = /^\s*\]/;
804
+ const rExprObjectOpen = /^\s*\{/;
805
+ const rExprObjectSeparator = /^\s*:/;
806
+ const rExprObjectSeparator2 = /^\s*,/;
807
+ const rExprObjectClose = /^\s*\}/;
691
808
  const rExprString = /^\s*'((?:\\\\|\\'|[^'])*)'/;
692
- const rExprStringEscape = /\\([\\'])/g;
693
809
  const rExprStringDouble = /^\s*"((?:\\\\|\\"|[^"])*)"/;
694
- const rExprStringDoubleEscape = /\\([\\"])/g;
695
810
  const rExprVariable = /^\s*([A-Za-z_]\w*)/;
696
811
  const rExprVariableEx = /^\s*\[\s*((?:\\\]|[^\]])+)\s*\]/;
697
812
  const rExprVariableExEscape = /\\([\\\]])/g;
698
813
 
699
814
 
815
+ // String literal escapes
816
+ const rExprStringEscapes = /(?<!\\)\\([nrtbf'"\\]|u[0-9a-fA-F]{4})/g;
817
+
818
+ function replaceStringEscape(unusedMatch, esc) {
819
+ if (esc.startsWith('u')) {
820
+ return String.fromCharCode(parseInt(esc.slice(1), 16));
821
+ }
822
+ return exprStringEscapes[esc];
823
+ }
824
+
825
+ const exprStringEscapes = {
826
+ 'n': '\n',
827
+ 'r': '\r',
828
+ 't': '\t',
829
+ 'b': '\b',
830
+ 'f': '\f',
831
+ "'": "'",
832
+ '"': '"',
833
+ '\\': '\\'
834
+ };
835
+
836
+
700
837
  /**
701
838
  * A BareScript parser error
702
839
  *
@@ -705,6 +842,7 @@ const rExprVariableExEscape = /\\([\\\]])/g;
705
842
  * @property {string} line - The line text
706
843
  * @property {number} columnNumber - The error column number
707
844
  * @property {?number} lineNumber - The error line number
845
+ * @property {?string} scriptName - The error line number
708
846
  */
709
847
  export class BareScriptParserError extends Error {
710
848
  /**
@@ -712,11 +850,11 @@ export class BareScriptParserError extends Error {
712
850
  *
713
851
  * @param {string} error - The error description
714
852
  * @param {string} line - The line text
715
- * @param {number} [columnNumber=1] - The error column number
716
- * @param {?number} [lineNumber=null] - The error line number
717
- * @param {?string} [prefix=null] - The error message prefix line
853
+ * @param {number} [columnNumber] - The error column number
854
+ * @param {?number} [lineNumber] - The error line number
855
+ * @param {?string} [scriptName] - The script name
718
856
  */
719
- constructor(error, line, columnNumber = 1, lineNumber = null, prefix = null) {
857
+ constructor(error, line, columnNumber, lineNumber, scriptName) {
720
858
  // Parser error constants
721
859
  const lineLengthMax = 120;
722
860
  const lineSuffix = ' ...';
@@ -740,8 +878,9 @@ export class BareScriptParserError extends Error {
740
878
  }
741
879
 
742
880
  // Format the message
881
+ const errorPrefix = (lineNumber ? `${scriptName || ''}:${lineNumber}: ` : '');
743
882
  const message = `\
744
- ${prefix !== null ? `${prefix}\n` : ''}${error}${lineNumber !== null ? `, line number ${lineNumber}` : ''}:
883
+ ${errorPrefix}${error}
745
884
  ${lineError}
746
885
  ${' '.repeat(lineColumn - 1)}^
747
886
  `;
@@ -751,5 +890,6 @@ ${' '.repeat(lineColumn - 1)}^
751
890
  this.line = line;
752
891
  this.columnNumber = columnNumber;
753
892
  this.lineNumber = lineNumber;
893
+ this.scriptName = scriptName;
754
894
  }
755
895
  }