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/README.md +19 -19
- 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 +76 -0
- package/lib/model.js +70 -12
- package/lib/options.js +45 -2
- package/lib/parser.js +224 -84
- package/lib/runtime.js +87 -28
- package/lib/runtimeAsync.js +68 -49
- package/package.json +2 -2
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
|
|
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': {
|
|
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 = (
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
|
716
|
-
* @param {?number} [lineNumber
|
|
717
|
-
* @param {?string} [
|
|
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
|
|
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
|
-
${
|
|
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
|
}
|